diff --git a/.Rbuildignore b/.Rbuildignore index bce7af1..7c6f503 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -3,8 +3,14 @@ ^.*\.Rcheck$ ^\.vscode$ ^experiments$ +^tests$ +^docs$ ^.*.zip ^.*.tar.gz ^\.lintr$ ^CITATIONS.bib$ ^\.github$ +^_pkgdown\.yml$ +^pkgdown$ +^doc$ +^pdfs$ diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..12ff12b --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,46 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + # pull_request: + # branches: [main, master] + # release: + # types: [published] + workflow_dispatch: + +name: docs + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.4.1 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/r_check.yml b/.github/workflows/r_check.yml index 01820a1..20f613f 100644 --- a/.github/workflows/r_check.yml +++ b/.github/workflows/r_check.yml @@ -1,10 +1,15 @@ # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: + push: + branches: [main, master] + paths: ['R/*.R', 'tests/*.R', 'tests/testthat/*.R', 'src/*.cpp', 'src/*.h'] pull_request: branches: [main, master] + paths: ['R/*.R', 'tests/*.R', 'tests/testthat/*.R', 'src/*.cpp', 'src/*.h'] + workflow_dispatch: -name: R-CMD-check +name: tests jobs: R-CMD-check: diff --git a/.gitignore b/.gitignore index e27745a..2c54eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ __pycache__ *ACLiC* .Rproj.user *Rproj -r/*.html *.RData *.Rcheck *.aux @@ -21,15 +20,13 @@ r/*.html *.fdb_latexmk *.pdf *.synctex.* -*.nb.html +*.nb.* .Rhistory .Rapp.history .RData *-Ex.R /*.tar.gz /*.Rcheck/ -vignettes/*.html -vignettes/*.pdf .httr-oauth /*_cache/ /cache/ @@ -37,8 +34,6 @@ vignettes/*.pdf *.knit.md rsconnect/ experiments/results/* -!experiments/results/*.png -!experiments/results/*.jpg experiments/data/* !experiments/data/*.r !experiments/data/*.R @@ -49,13 +44,11 @@ tmp *.html *.zip *.tar.gz - *~ *.~ todo.md *.dll data/* log*.txt -!vignettes/paper.pdf -!vignettes/presentation.pdf -!vignettes/poster.pdf \ No newline at end of file +docs +!pdfs/*.pdf diff --git a/DESCRIPTION b/DESCRIPTION index b4fb5ce..873574e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: slise Title: Sparse Linear Subset Explanations -Version: 2.0.0 +Version: 2.1.0 Authors@R: c( person("Anton", "Björklund", email = "anton.bjorklund@helsinki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-7749-2918")), person("Andreas", "Henelius", role = "aut", comment = c(ORCID = "0000-0002-4040-6967")), @@ -11,8 +11,7 @@ License: MIT + file LICENSE Encoding: UTF-8 LazyData: true Imports: lbfgs, stats, utils, methods, graphics, base -Suggests: ggplot2, grid, gridExtra, reshape2, wordcloud, testthat, numDeriv, R.rsp +Suggests: ggplot2, grid, gridExtra, reshape2, wordcloud, testthat, numDeriv URL: https://github.com/edahelsinki/slise LinkingTo: Rcpp, RcppArmadillo RoxygenNote: 7.1.1 -VignetteBuilder: R.rsp diff --git a/NAMESPACE b/NAMESPACE index c6d4a06..40e4649 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ S3method(plot,slise) S3method(predict,slise) S3method(print,slise) +export(auto_named_list) export(graduated_optimisation) export(scale_robust) export(simple_pca) @@ -10,6 +11,7 @@ export(slise.explain) export(slise.explain_comb) export(slise.explain_find) export(slise.fit) +export(slise.formula) export(slise_initialisation_candidates) export(slise_initialisation_candidates2) export(slise_initialisation_lasso) @@ -21,9 +23,13 @@ importFrom(stats,.lm.fit) importFrom(stats,lm.wfit) importFrom(stats,mad) importFrom(stats,median) +importFrom(stats,model.frame) +importFrom(stats,model.matrix) +importFrom(stats,model.response) importFrom(stats,predict) importFrom(stats,quantile) importFrom(stats,sd) +importFrom(stats,setNames) importFrom(stats,uniroot) importFrom(utils,combn) useDynLib(slise) diff --git a/R/RcppExports.R b/R/RcppExports.R index 8a054e3..af02062 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,43 +1,43 @@ -# Generated by using Rcpp::compileAttributes() -> do not edit by hand -# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 - -sigmoidc <- function(x) { - .Call('_slise_sigmoidc', PACKAGE = 'slise', x) -} - -log_sigmoidc <- function(x) { - .Call('_slise_log_sigmoidc', PACKAGE = 'slise', x) -} - -loss_smooth_c <- function(alpha, data, response, epsilon, beta, lambda1, lambda2, weight) { - .Call('_slise_loss_smooth_c', PACKAGE = 'slise', alpha, data, response, epsilon, beta, lambda1, lambda2, weight) -} - -loss_smooth_c_dc <- function(xs, dcptr) { - .Call('_slise_loss_smooth_c_dc', PACKAGE = 'slise', xs, dcptr) -} - -loss_smooth_grad_c <- function(alpha, data, response, epsilon, beta, lambda1, lambda2, weight) { - .Call('_slise_loss_smooth_grad_c', PACKAGE = 'slise', alpha, data, response, epsilon, beta, lambda1, lambda2, weight) -} - -loss_smooth_grad_c_dc <- function(xs, dcptr) { - .Call('_slise_loss_smooth_grad_c_dc', PACKAGE = 'slise', xs, dcptr) -} - -lg_combined_smooth_c_dc <- function(xs, dcptr) { - .Call('_slise_lg_combined_smooth_c_dc', PACKAGE = 'slise', xs, dcptr) -} - -lg_getgrad_c_dc <- function(xs, dcptr) { - .Call('_slise_lg_getgrad_c_dc', PACKAGE = 'slise', xs, dcptr) -} - -loss_smooth_c_ptr <- function() { - .Call('_slise_loss_smooth_c_ptr', PACKAGE = 'slise') -} - -loss_smooth_grad_c_ptr <- function() { - .Call('_slise_loss_smooth_grad_c_ptr', PACKAGE = 'slise') -} - +# Generated by using Rcpp::compileAttributes() -> do not edit by hand +# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +sigmoidc <- function(x) { + .Call('_slise_sigmoidc', PACKAGE = 'slise', x) +} + +log_sigmoidc <- function(x) { + .Call('_slise_log_sigmoidc', PACKAGE = 'slise', x) +} + +loss_smooth_c <- function(alpha, data, response, epsilon, beta, lambda1, lambda2, weight) { + .Call('_slise_loss_smooth_c', PACKAGE = 'slise', alpha, data, response, epsilon, beta, lambda1, lambda2, weight) +} + +loss_smooth_c_dc <- function(xs, dcptr) { + .Call('_slise_loss_smooth_c_dc', PACKAGE = 'slise', xs, dcptr) +} + +loss_smooth_grad_c <- function(alpha, data, response, epsilon, beta, lambda1, lambda2, weight) { + .Call('_slise_loss_smooth_grad_c', PACKAGE = 'slise', alpha, data, response, epsilon, beta, lambda1, lambda2, weight) +} + +loss_smooth_grad_c_dc <- function(xs, dcptr) { + .Call('_slise_loss_smooth_grad_c_dc', PACKAGE = 'slise', xs, dcptr) +} + +lg_combined_smooth_c_dc <- function(xs, dcptr) { + .Call('_slise_lg_combined_smooth_c_dc', PACKAGE = 'slise', xs, dcptr) +} + +lg_getgrad_c_dc <- function(xs, dcptr) { + .Call('_slise_lg_getgrad_c_dc', PACKAGE = 'slise', xs, dcptr) +} + +loss_smooth_c_ptr <- function() { + .Call('_slise_loss_smooth_c_ptr', PACKAGE = 'slise') +} + +loss_smooth_grad_c_ptr <- function() { + .Call('_slise_loss_smooth_grad_c_ptr', PACKAGE = 'slise') +} + diff --git a/R/data.R b/R/data.R index c29c47e..bb9ff05 100644 --- a/R/data.R +++ b/R/data.R @@ -13,7 +13,7 @@ add_intercept_column <- function(x) { } remove_intercept_column <- function(x) { - x2 <- x[, -1] + x2 <- x[, -1, drop = FALSE] attr(x2, "scaled:center") <- attr(x, "scaled:center") attr(x2, "scaled:scale") <- attr(x, "scaled:scale") attr(x2, "constant_columns") <- attr(x, "constant_columns") @@ -47,7 +47,7 @@ add_constant_columns <- function(x, columns) { } } -unscale_alpha <- function(alpha, x_center, x_scale, y_center=NULL, y_scale=NULL) { +unscale_alpha <- function(alpha, x_center, x_scale, y_center = NULL, y_scale = NULL) { if (is.null(y_center) && is.null(y_scale)) { if (!hasattr(x_scale, "scaled:scale") && !hasattr(x_center, "scaled:scale")) { stop("X and Y must have the scaled attributes") @@ -100,6 +100,30 @@ scale_robust <- function(x, th = .Machine$double.eps) { x } +#' Unscale a scaled matrix / vector +#' +#' @param x the matrix / vector to unscale +#' @param scaled optional object with "scaled:..." attributes +#' +#' @return x unscaled +#' +unscale <- function(x, scaled = NULL) { + if (is.null(scaled)) { + scaled <- x + } + center <- attr(scaled, "scaled:center") + scale <- attr(scaled, "scaled:scale") + if (is.null(dim(x))) { # Vector + x <- x * scale + center + } else { # Matrix + x <- sweep(x, 2, scale, `*`) + x <- sweep(x, 2, center, `+`) + } + attr(x, "scaled:center") <- NULL + attr(x, "scaled:scale") <- NULL + x +} + #' A variant of `scale` that only adds the attributes #' @@ -113,7 +137,7 @@ scale_identity <- function(x) { x } -scale_same <- function(x, center=NULL, scale=NULL, constant_columns=NULL) { +scale_same <- function(x, center = NULL, scale = NULL, constant_columns = NULL) { if (is.null(center)) { return(x) } @@ -163,4 +187,4 @@ simple_pca <- function(X, dimensions, tolerance = 1e-10) { } # Return the rotation matrix (only) pca_rotation -} +} \ No newline at end of file diff --git a/R/plot.R b/R/plot.R index 93fa390..6880aca 100644 --- a/R/plot.R +++ b/R/plot.R @@ -11,6 +11,7 @@ SLISE_DARKPURPLE <- "#5e3c99" #' #' @param x The slise object #' @param num_vars Minimum number of variables to show without filtering (default: 10) +#' @param labels Name of y or class labels #' @param ... Ignored additional parameters #' #' @return invisible(x) @@ -20,7 +21,7 @@ SLISE_DARKPURPLE <- "#5e3c99" #' X <- matrix(rnorm(30), 15, 2) #' Y <- runif(15, 0, 1) #' print(slise.fit(X, Y, epsilon = 0.1)) -print.slise <- function(x, num_vars = 10, ...) { +print.slise <- function(x, num_vars = 10, labels = NULL, ...) { check_package("stringr") slise <- x if (is.null(slise$x)) { @@ -50,7 +51,11 @@ print.slise <- function(x, num_vars = 10, ...) { # Other Values cat(sprintf("Subset size: %7.2f\n", mean(slise$subset))) cat(sprintf("Loss: %7.2f\n", slise$value)) - cat(sprintf("Epsilon: %7.2f\n", slise$epsilon)) + if (is.null(slise$normalised_epsilon) || slise$normalised_epsilon == slise$epsilon) { + cat(sprintf("Epsilon: %7.2f\n", slise$epsilon)) + } else { + cat(sprintf("Epsilon: %7.2f (Normalised: %7.2f)\n", slise$epsilon, slise$normalised_epsilon)) + } if (slise$lambda1 > 0) { cat(sprintf("Lambda1: %7.2f\n", slise$lambda1)) } @@ -64,8 +69,23 @@ print.slise <- function(x, num_vars = 10, ...) { length(slise$coefficients) )) } + cat(sprintf("Predicted: %s\n", string_prediction(slise$y, slise$logit, labels))) if (slise$logit) { - cat(sprintf("Class Balance: %.1f%% <> %.1f%%\n", mean(slise$Y[slise$subset] > 0) * 100, mean(slise$Y[slise$subset] < 0) * 100)) + if (length(labels) > 1) { + cat(sprintf( + "Class Balance: %.1f%% %s <> %.1f%% %s\n", + mean(slise$Y[slise$subset] < 0) * 100, + labels[1], + mean(slise$Y[slise$subset] > 0) * 100, + labels[2] + )) + } else { + cat(sprintf( + "Class Balance: %.1f%% <> %.1f%%\n", + mean(slise$Y[slise$subset] < 0) * 100, + mean(slise$Y[slise$subset] > 0) * 100 + )) + } } invisible(slise) } @@ -87,13 +107,43 @@ order_coefficients <- function(slise, hightolow = FALSE, minimum = .Machine$doub ord } +string_prediction <- function(y, logit = FALSE, labels = NULL) { + if (logit) { + if (!is.null(labels)) { + if (y < 0.0 && length(labels) > 1) { + sprintf("%.1f%% %s", (1 - sigmoid(y)) * 100, labels[1]) + } else { + sprintf("%.1f%% %s", sigmoid(y) * 100, labels[2]) + } + } else { + sprintf("%.1f%%", sigmoid(y) * 100) + } + } else { + if (!is.null(labels)) { + sprintf("%g %s", y, labels[1]) + } else { + sprintf("%g", y) + } + } +} + +string_title <- function(title = NULL, y = NULL, logit = FALSE, labels = NULL) { + if (title == "") { + NULL + } else if (!is.null(y) && grepl("%s", title)) { + sprintf(title, string_prediction(y, logit, labels)) + } else { + title + } +} + summary.slise <- print.slise #' Plot the robust regression or explanation from slise #' #' @param x The slise object #' @param type The type of plot ("2D", "bar", "distribution", "mnist", "prediction", "wordcloud") -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param ... Other parameters to the plotting functions #' @inheritDotParams plot.slise_2d labels partial size #' @inheritDotParams plot.slise_bar labels partial size @@ -154,7 +204,7 @@ plot.slise <- function(x, #' Plot the robust regression or explanation from slise in 2D #' #' @param slise The slise object -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param labels The axis labels (default: c("X", "Y") or c("x", "f(x)")) #' @param partial Should the raw ggplot2 objects be returned instead of directly plotting (default: FALSE) #' @param size The size of the plot elements (default: 2) @@ -163,7 +213,7 @@ plot.slise <- function(x, #' @return ggplot object or plot #' plot.slise_2d <- function(slise, - title, + title = NULL, labels = NULL, partial = FALSE, size = 2, @@ -193,9 +243,8 @@ plot.slise_2d <- function(slise, labels <- c("x", "f(x)") } } - gg <- ggplot2::ggplot() + - ggplot2::ggtitle(if (is.null(title) || title == "") NULL else title) + + ggplot2::ggtitle(string_title(title, slise$y, slise$logit)) + ggplot2::theme_bw() + ggplot2::xlab(labels[1]) + ggplot2::ylab(labels[2]) + @@ -231,7 +280,7 @@ plot.slise_2d <- function(slise, shape = "Explained Point" ), size = size * 2) } else { - gg <- gg + ggplot2::guides(shape = "none", color = "none", linetype = "none") + gg <- gg + ggplot2::guides(shape = FALSE, color = FALSE, linetype = FALSE) } if (partial) { gg @@ -243,7 +292,7 @@ plot.slise_2d <- function(slise, #' Plot the robust regression or explanation from slise with distributions #' #' @param slise The slise object -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param labels The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High")) #' @param partial Should the raw ggplot2 objects be returned instead of directly plotting (default: FALSE) #' @param signif The number of significant digits to display (default: 3) @@ -254,7 +303,7 @@ plot.slise_2d <- function(slise, #' @importFrom stats predict #' plot.slise_distribution <- function(slise, - title, + title = NULL, labels = c("Low", "High"), partial = FALSE, signif = 3, @@ -422,19 +471,24 @@ plot.slise_distribution <- function(slise, } else { check_package("grid") check_package("gridExtra") - gridExtra::grid.arrange( - gg1, - gg2, - ncol = 2, - top = grid::textGrob(title, gp = grid::gpar(cex = 1.2)) - ) + title <- string_title(title, slise$y, slise$logit, labels) + if (is.null(title)) { + gridExtra::grid.arrange(gg1, gg2, ncol = 2) + } else { + gridExtra::grid.arrange( + gg1, + gg2, + ncol = 2, + top = grid::textGrob(title, gp = grid::gpar(cex = 1.2)) + ) + } } } #' Plot the robust regression or explanation from slise based on predictions #' #' @param slise The slise object -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param labels The axis labels (default: c("Response", "Count")) #' @param partial Should the raw ggplot2 objects be returned instead of directly plotting (default: FALSE) #' @param approximation Should the approximation density be added (default: TRUE) @@ -446,7 +500,7 @@ plot.slise_distribution <- function(slise, #' @importFrom stats predict #' plot.slise_prediction <- function(slise, - title, + title = NULL, labels = c("Response", "Count"), partial = FALSE, approximation = TRUE, @@ -488,7 +542,7 @@ plot.slise_prediction <- function(slise, limits = c("Dataset", "Subset", if (approximation) "Approximation" else NULL), name = NULL ) + - ggplot2::ggtitle(if (is.null(title) || title == "") NULL else title) + + ggplot2::ggtitle(string_title(title, slise$y, slise$logit)) + ggplot2::scale_x_continuous(labels = function(x) { if (slise$logit) base::signif(sigmoid(x), signif) else base::signif(x, signif) }) @@ -515,7 +569,7 @@ plot.slise_prediction <- function(slise, #' Plot the robust regression or explanation from slise as bar plots #' #' @param slise The slise object -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param labels The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High")) #' @param partial Should the raw ggplot2 objects be returned instead of directly plotting (default: FALSE) #' @param size The size of the segments (default: 8) @@ -526,7 +580,7 @@ plot.slise_prediction <- function(slise, #' @importFrom stats quantile #' plot.slise_bar <- function(slise, - title, + title = NULL, labels = c("Low", "High"), partial = FALSE, size = 8, @@ -675,7 +729,8 @@ plot.slise_bar <- function(slise, check_package("grid") check_package("gridExtra") out$ncol <- length(out) - out$top <- grid::textGrob(title, gp = grid::gpar(cex = 1.2)) + title <- string_title(title, slise$y, slise$logit, labels) + if (!is.null(title)) out$top <- grid::textGrob(title, gp = grid::gpar(cex = 1.2)) do.call(gridExtra::grid.arrange, out) } } @@ -683,7 +738,7 @@ plot.slise_bar <- function(slise, #' Plot the robust regression or explanation from slise as an image #' #' @param slise The slise object -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param labels The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High")) #' @param partial Should the raw ggplot2 objects be returned instead of directly plotting (default: FALSE) #' @param width The width of the image (width * height == ncol(X)) @@ -691,30 +746,34 @@ plot.slise_bar <- function(slise, #' @param plots The number of plots to split the explanation into (default: 1) #' @param enhance_colours Increse the saturation of the explanation (default: TRUE) #' @param ... Ignored parameters +#' @param breaks Breaks for the countours, see `ggplot2::stat_contour` (default: NULL) #' #' @return ggplot object(s) or plot #' plot.slise_mnist <- function(slise, - title, + title = NULL, labels = c("Low", "High"), partial = FALSE, width = floor(sqrt(ncol(slise$X))), height = width, plots = 1, enhance_colours = TRUE, - ...) { + ..., + breaks = NULL) { check_package("ggplot2") if (is.null(slise$x)) { plots <- 1 } + title <- string_title(title, slise$y, slise$logit, labels) if (plots == 1) { gg <- plot_mnist( matrix(slise$coefficients[-1], height, width), if (is.null(slise$x)) NULL else matrix(slise$x, height, width), labels, - enhance_colours = enhance_colours + enhance_colours = enhance_colours, + breaks = breaks ) + - ggplot2::ggtitle(if (is.null(title) || title == "") NULL else title) + ggplot2::ggtitle(title) if (partial) { gg } else { @@ -732,7 +791,8 @@ plot.slise_mnist <- function(slise, matrix(slise$coefficients[-1], height, width), matrix(slise$x, height, width), labels, - enhance_colours = enhance_colours + enhance_colours = enhance_colours, + breaks = breaks ) + ggplot2::ggtitle("Explanation") + ggplot2::theme(legend.position = "right") @@ -741,12 +801,16 @@ plot.slise_mnist <- function(slise, } else { check_package("grid") check_package("gridExtra") - gridExtra::grid.arrange( - gg1, - gg2, - ncol = 2, - top = grid::textGrob(title, gp = grid::gpar(cex = 1.2)) - ) + if (is.null(title)) { + gridExtra::grid.arrange(gg1, gg2, ncol = 2) + } else { + gridExtra::grid.arrange( + gg1, + gg2, + ncol = 2, + top = grid::textGrob(title, gp = grid::gpar(cex = 1.2)) + ) + } } } else if (plots == 3) { gg1 <- plot_mnist( @@ -763,7 +827,8 @@ plot.slise_mnist <- function(slise, matrix(pmin(slise$coefficients[-1], 0), height, width), matrix(slise$x, height, width), labels, - enhance_colours = enhance_colours + enhance_colours = enhance_colours, + breaks = breaks ) + ggplot2::ggtitle(paste("Towards", labels[1])) + ggplot2::theme( @@ -774,7 +839,8 @@ plot.slise_mnist <- function(slise, matrix(pmax(slise$coefficients[-1], 0), height, width), matrix(slise$x, height, width), labels, - enhance_colours = enhance_colours + enhance_colours = enhance_colours, + breaks = breaks ) + ggplot2::ggtitle(paste("Towards", labels[2])) + ggplot2::theme( @@ -786,13 +852,17 @@ plot.slise_mnist <- function(slise, } else { check_package("grid") check_package("gridExtra") - gridExtra::grid.arrange( - gg1, - gg3, - gg2, - ncol = 3, - top = grid::textGrob(title, gp = grid::gpar(cex = 1.2)) - ) + if (is.null(title)) { + gridExtra::grid.arrange(gg1, gg3, gg2, ncol = 3) + } else { + gridExtra::grid.arrange( + gg1, + gg3, + gg2, + ncol = 3, + top = grid::textGrob(title, gp = grid::gpar(cex = 1.2)) + ) + } } } else { stop("Unimplemented number of plots") @@ -804,7 +874,8 @@ plot_mnist <- function(image, contour = NULL, labels = NULL, colours = c(SLISE_DARKORANGE, "white", SLISE_DARKPURPLE), - enhance_colours = TRUE) { + enhance_colours = TRUE, + breaks = NULL) { check_package("reshape2") check_package("ggplot2") shape <- dim(image) @@ -866,7 +937,8 @@ plot_mnist <- function(image, }, data = reshape2::melt(contour), col = "black", - bins = 2 + bins = 2, + breaks = breaks ) } gg @@ -875,7 +947,7 @@ plot_mnist <- function(image, #' Plot the robust regression or explanation from slise as a wordcloud #' #' @param slise The slise object -#' @param title The title of the plot +#' @param title The title of the plot (may include a `\%s`, which will be replaced by the prediction) #' @param labels The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High")) #' @param treshold Treshold for ignored value (default: 1e-8) #' @param local Only display the words relevant for the explained item (default: TRUE) @@ -886,7 +958,7 @@ plot_mnist <- function(image, #' @importFrom graphics legend #' plot.slise_wordcloud <- function(slise, - title, + title = NULL, labels = c("Low", "High"), treshold = 1e-8, local = TRUE, @@ -920,5 +992,5 @@ plot.slise_wordcloud <- function(slise, adj = 0.5, x.intersp = 2 ) - title(title) -} + title(string_title(title, slise$y, slise$logit, labels)) +} \ No newline at end of file diff --git a/R/slise.R b/R/slise.R index f4cbcc5..e45b402 100644 --- a/R/slise.R +++ b/R/slise.R @@ -1,8 +1,7 @@ # This script contains the SLISE functions (slise.fit and slise.explain) -#' SLISE Regression -#' Use SLISE for robust regression. +#' SLISE for robust regression. #' #' It is highly recommended that you normalise the data, #' either before using SLISE or by setting normalise = TRUE. @@ -20,12 +19,10 @@ #' @inheritDotParams graduated_optimisation max_approx beta_max max_iterations debug #' @inheritDotParams slise_initialisation_candidates num_init beta_max_init pca_treshold #' -#' @return slise object (coefficients, subset, value, X, Y, lambda1, lambda2, epsilon, scaled, alpha) +#' @return slise.object #' @export #' #' @examples -#' # Assuming data is a data.frame with the first column containing the response -#' # Further assuming newdata is a similar data.frame with the response missing #' X <- matrix(rnorm(32), 8, 4) #' Y <- rnorm(8) #' model <- slise.fit(X, Y, (max(Y) - min(Y)) * 0.1) @@ -42,40 +39,19 @@ slise.fit <- function(X, ...) { # Setup matprod_default <- options(matprod = "blas") # Use faster math - X <- as.matrix(X) - Y <- c(Y) - X_orig <- X - Y_orig <- Y - if (any(weight < 0)) { - stop("Weights must not be negative!") - } - stopifnot(epsilon > 0) - stopifnot(lambda1 >= 0) - stopifnot(lambda2 >= 0) - # Preprocessing - if (normalise) { - X <- remove_constant_columns(X) - X <- scale_robust(X) - Y <- scale_robust(Y) - if (!intercept) { - stop("Normalisation requires intercept!") - } - } - if (intercept) { - X <- add_intercept_column(X) - } + data <- slise.preprocess(X, Y, epsilon, NULL, NULL, lambda1, lambda2, weight, intercept, normalise, FALSE) # Initialisation if (is.list(initialisation)) { init <- initialisation names(init) <- c("alpha", "beta") } else { - init <- initialisation(X, Y, epsilon = epsilon, weight = weight, ...) + init <- initialisation(data$X, data$Y, epsilon = epsilon, weight = weight, ...) } # Optimisation alpha <- graduated_optimisation( init$alpha, - X, - Y, + data$X, + data$Y, epsilon = epsilon, beta = init$beta, lambda1 = lambda1, @@ -84,31 +60,72 @@ slise.fit <- function(X, ... )$par # Output + out <- slise.object( + alpha = alpha, + X = data$X, + Y = data$Y, + epsilon = epsilon, + lambda1 = lambda1, + lambda2 = lambda2, + weight = weight, + intercept = intercept + ) if (normalise) { - alpha2 <- unscale_alpha(alpha, X, Y) - alpha2 <- add_constant_columns(alpha2, attr(X, "constant_columns") + 1) - alpha <- add_constant_columns(alpha, attr(X, "constant_columns") + 1) - out <- slise.object( - alpha2, - X_orig, - Y_orig, - epsilon * attr(Y, "scaled:scale"), - lambda1, - lambda2, - weight, - intercept, - normalised = alpha - ) - } else { - out <- slise.object(alpha, X_orig, Y, epsilon, lambda1, lambda2, weight, intercept) + out <- slise.object_unnormalise(out, data$X_orig, data$Y_orig) } options(matprod_default) # Reset options out } +#' SLISE for robust regression (using a formula). +#' +#' It is highly recommended that you normalise the data, +#' either before using SLISE or by setting normalise = TRUE. +#' +#' @param formula formula +#' @param data data for the formula +#' @param epsilon Error tolerance +#' @param lambda1 L1 regularisation coefficient (default: 0) +#' @param lambda2 L2 regularisation coefficient (default: 0) +#' @param weight Optional weight vector (default: NULL) +#' @param normalise Preprocess X and Y by scaling, note that epsilon is not scaled (default: FALSE) +#' @param initialisation Function that gives the initial alpha and beta, or a list containing the initial alpha and beta (default: slise_initialisation_candidates) +#' @param ... Other parameters to the optimiser and initialiser +#' @inheritDotParams graduated_optimisation max_approx beta_max max_iterations debug +#' @inheritDotParams slise_initialisation_candidates num_init beta_max_init pca_treshold +#' +#' @return slise.object +#' @export +#' +#' @importFrom stats model.frame +#' @importFrom stats model.response +#' @importFrom stats model.matrix +#' +#' @examples +#' data <- data.frame(y = rnorm(8), a = rnorm(8), b = rnorm(8)) +#' model <- slise.formula(y ~ a * b + abs(a), data, 0.1, normalise = TRUE) +slise.formula <- function(formula, + data, + epsilon, + lambda1 = 0, + lambda2 = 0, + weight = NULL, + normalise = FALSE, + initialisation = slise_initialisation_candidates, + ...) { + mf <- model.frame(formula, data) + Y <- model.response(mf) + X <- model.matrix(mf, data) + if (colnames(X)[[1]] == "(Intercept)") { + intercept <- TRUE + X <- X[, -1, drop = FALSE] + } else { + intercept <- FALSE + } + slise.fit(X, Y, epsilon, lambda1, lambda2, weight, intercept, normalise, initialisation, ...) +} -#' SLISE Black Box Explainer -#' Use SLISE for explaining predictions made by a black box. +#' SLISE for explaining Black box models. #' #' It is highly recommended that you normalise the data, #' either before using SLISE or by setting normalise = TRUE. @@ -128,7 +145,7 @@ slise.fit <- function(X, #' @inheritDotParams graduated_optimisation max_approx beta_max max_iterations debug #' @inheritDotParams slise_initialisation_candidates num_init beta_max_init pca_treshold #' -#' @return slise object (coefficients, subset, value, X, Y, lambda1, lambda2, epsilon, scaled, alpha, x, y) +#' @return slise.object #' @export #' #' @examples @@ -150,55 +167,19 @@ slise.explain <- function(X, ...) { # Setup matprod_default <- options(matprod = "blas") # Use faster math - X <- as.matrix(X) - Y <- c(Y) - if (logit) { - Y <- limited_logit(Y) - } - if (any(weight < 0)) { - stop("Weights must not be negative!") - } - stopifnot(epsilon > 0) - stopifnot(lambda1 >= 0) - stopifnot(lambda2 >= 0) - X_orig <- X - Y_orig <- Y - if (is.null(y)) { - # x is an index - y <- Y[[x]] - x <- X[x, ] - } else if (logit) { - y <- limited_logit(y) - } - x_orig <- x - y_orig <- y - # Preprocessing - if (normalise) { - X <- remove_constant_columns(X) - X <- scale_robust(X) - Y <- if (!logit) { - scale_robust(Y) - } else { - scale_identity(Y) - } - x <- scale_same(x, X) - y <- scale_same(y, Y) - } - # Localise - X <- sweep(X, 2, x) - Y <- Y - y + data <- slise.preprocess(X, Y, epsilon, x, y, lambda1, lambda2, weight, FALSE, normalise, logit) # Initialisation if (is.list(initialisation)) { init <- initialisation names(init) <- c("alpha", "beta") } else { - init <- initialisation(X, Y, epsilon = epsilon, weight = weight, ...) + init <- initialisation(data$X_local, data$Y_local, epsilon = epsilon, weight = weight, ...) } # Optimisation alpha <- graduated_optimisation( init$alpha, - X, - Y, + data$X_local, + data$Y_local, epsilon = epsilon, beta = init$beta, lambda1 = lambda1, @@ -207,47 +188,20 @@ slise.explain <- function(X, ... )$par # Output + out <- slise.object( + alpha = alpha, + X = data$X, + Y = data$Y, + epsilon = epsilon, + lambda1 = lambda1, + lambda2 = lambda2, + weight = weight, + logit = logit, + x = data$x, + y = data$y + ) if (normalise) { - alpha2 <- unscale_alpha(alpha, X, Y) - alpha2 <- add_constant_columns(alpha2[-1], attr(X, "constant_columns")) - alpha2 <- c(y_orig - sum(x_orig * alpha2), alpha2) - alpha <- add_constant_columns(alpha, attr(X, "constant_columns")) - x <- add_constant_columns(x, attr(X, "constant_columns")) - alpha <- c(y - sum(x * alpha), alpha) - out <- slise.object( - alpha2, - X_orig, - Y_orig, - epsilon * attr(Y, "scaled:scale"), - lambda1, - lambda2, - weight, - TRUE, - x = x_orig, - y = y_orig, - impact = c(1, x_orig) * alpha2, - logit = logit, - normalised = alpha, - normalised_x = x, - normalised_y = y, - normalised_impact = c(1, x) * alpha - ) - } else { - alpha <- c(y_orig - sum(x_orig * alpha), alpha) - out <- slise.object( - alpha, - X_orig, - Y_orig, - epsilon, - lambda1, - lambda2, - weight, - TRUE, - x = x_orig, - y = y_orig, - impact = c(1, x_orig) * alpha, - logit = logit - ) + out <- slise.object_unnormalise(out, data$X_orig, data$Y_orig, data$x_orig, data$y_orig) } options(matprod_default) # Reset options out @@ -257,6 +211,8 @@ slise.explain <- function(X, #' Use SLISE for explaining predictions made by a black box. #' BUT with a binary search for sparsity! #' +#' DEPRECATED: This is a simple binary search, no need for a separate function +#' #' @param ... parameters to slise.explain #' @inheritDotParams slise.explain -lambda1 #' @param lambda1 the starting value of the search @@ -268,6 +224,7 @@ slise.explain <- function(X, #' @export #' slise.explain_find <- function(..., lambda1 = 5, variables = 4, iters = 10, treshold = 1e-4) { + .Deprecated("slise.explain_comb") lower <- 0 upper <- -1 upper_best <- NULL @@ -339,6 +296,85 @@ slise.explain_comb <- function(X, Y, epsilon, x, y = NULL, ..., variables = 4) { expl } +#' Preprocess the data as necessary before running SLISE +#' +#' @param X Matrix of independent variables +#' @param Y Vector of the target variable +#' @param epsilon Error tolerance +#' @param x The sample to be explained (or index if y is null) +#' @param y The prediction to be explained (default: NULL) +#' @param lambda1 L1 regularisation coefficient (default: 0) +#' @param lambda2 L2 regularisation coefficient (default: 0) +#' @param weight Optional weight vector (default: NULL) +#' @param intercept Should an intercept be added (default: TRUE) +#' @param normalise Preprocess X and Y by scaling, note that epsilon is not scaled (default: FALSE) +#' @param logit Logit transform Y from probabilities to real values (default: FALSE) +#' +#' @return list(X_orig, Y_orig, X, Y, x_orig, y_orig, x, y) +slise.preprocess <- function(X, + Y, + epsilon, + x = NULL, + y = NULL, + lambda1 = 0, + lambda2 = 0, + weight = NULL, + intercept = FALSE, + normalise = FALSE, + logit = FALSE) { + # Checks + stopifnot(epsilon > 0) + stopifnot(lambda1 >= 0) + stopifnot(lambda2 >= 0) + if (any(weight < 0)) stop("Weights must not be negative!") + # Original data as matrix + X <- as.matrix(X) + Y <- c(Y) + if (logit) { + Y <- limited_logit(Y) + } + X_orig <- X + Y_orig <- Y + # Preprocessing + if (normalise) { + X <- remove_constant_columns(X) + X <- scale_robust(X) + Y <- if (logit) { + scale_identity(Y) + } else { + scale_robust(Y) + } + } + if (intercept) { + X <- add_intercept_column(X) + } + # Explanations + if (!is.null(x)) { + if (intercept) stop("Explanations cannot have intercepts") + if (is.null(y)) { + # x is an index + y_orig <- Y_orig[[x]] + x_orig <- X_orig[x, ] + y <- Y[[x]] + x <- X[x, ] + } else { + if (logit) y <- limited_logit(y) + x_orig <- x + y_orig <- y + if (normalise) { + x <- scale_same(x, X) + y <- scale_same(y, Y) + } + } + # Localise + X_local <- sweep(X, 2, x) + Y_local <- Y - y + } else { + x_orig <- y_orig <- x <- y <- X_local <- Y_local <- NULL + } + auto_named_list(X_orig, Y_orig, X, Y, x_orig, y_orig, x, y, X_local, Y_local) +} + #' Create a result object for SLISE that is similar to other regression method results #' #' @param alpha linear model @@ -352,9 +388,8 @@ slise.explain_comb <- function(X, Y, epsilon, x, y = NULL, ..., variables = 4) { #' @param logit has the target been logit-transformed (default: FALSE) #' @param x explained item x (default: NULL) #' @param y explained item y (default: NULL) -#' @param ... other variables to add to the SLISE object #' -#' @return list(coefficients=unscale(alpha), X, Y, scaled=data, lambda1, lambda2, alpha, subset=[r_i= 0, - log(1 + exp(-x)), x - log(1 + exp(x))) +log_sigmoid <- function(x) ifelse(x >= 0, -log(1 + exp(-x)), x - log(1 + exp(x))) #' derivative of log-sigmoid function #' @@ -115,3 +113,21 @@ check_package <- function(pack) { stop(paste0("Package \"", pack, "\" needed for the function to work. Please install it."), call. = FALSE) } } + +#' Creates a named list where the names are taken from the input variables +#' +#' NOTE: only supports arguments, not keyword arguments +#' +#' @param ... list elements +#' +#' @return named list of elements +#' +#' @export +#' @importFrom stats setNames +#' +#' @examples +#' a <- 1 +#' b <- 2 +#' auto_named_list(a, b)$a == 1 +#' auto_named_list(a, b)$b == 2 +auto_named_list <- function(...) setNames(list(...), substitute(alist(...))) \ No newline at end of file diff --git a/README.md b/README.md index b70aa73..63b3feb 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,108 @@ -# SLISE - Sparse Linear Subset Explanations - -R implementation of the SLISE algorithm. The SLISE algorithm can be used for both robust regression and to explain outcomes from black box models. For more details see [the original paper](https://rdcu.be/bVbda) or the [robust regression paper](https://rdcu.be/cFRHD). Alternatively for a more informal overview see [the presentation](https://github.com/edahelsinki/slise/raw/master/vignettes/presentation.pdf), or [the poster](https://github.com/edahelsinki/slise/raw/master/vignettes/poster.pdf). - -> *Björklund A., Henelius A., Oikarinen E., Kallonen K., Puolamäki K.* (2019) -> **Sparse Robust Regression for Explaining Classifiers.** -> Discovery Science (DS 2019). -> Lecture Notes in Computer Science, vol 11828, Springer. -> https://doi.org/10.1007/978-3-030-33778-0_27 - -> *Björklund A., Henelius A., Oikarinen E., Kallonen K., Puolamäki K.* (2022). -> **Robust regression via error tolerance.** -> Data Mining and Knowledge Discovery. -> https://doi.org/10.1007/s10618-022-00819-2 - - -## The idea - -In robust regression we fit regression models that can handle data that contains outliers (see the example below for why outliers are problematic for normal regression). SLISE accomplishes this by fitting a model such that the largest possible subset of the data items have an error less than a given value. All items with an error larger than that are considered potential outliers and do not affect the resulting model. - -SLISE can also be used to provide *local model-agnostic explanations* for outcomes from black box models. To do this we replace the ground truth response vector with the predictions from the complex model. Furthermore, we force the model to fit a selected item (making the explanation local). This gives us a local approximation of the complex model with a simpler linear model (this is similar to, e.g., [LIME](https://github.com/marcotcr/lime) and [SHAP](https://github.com/slundberg/shap)). In contrast to other methods SLISE creates explanations using real data (not some discretised and randomly sampled data) so we can be sure that all inputs are valid (i.e. in the correct data manifold, and follows the constraints used to generate the data, e.g., the laws of physics). - - -## Installation - -First install the `devtools`-package: - -```R -install.packages("devtools") -``` - -Then install the `slise` package: - -```R -devtools::install_github("edahelsinki/slise") -``` - -After installation, load the package using: - -```R -library(slise) -``` - - -## Other Languages - -The official Python version can be found [here](https://github.com/edahelsinki/pyslise). - - -## Example - -In order to use SLISE you need to have your data in a numerical matrix (or something that can be cast to a matrix), and the response as a numerical vector. Below is an example of SLISE being used for robust regression: - -```R -library(ggplot2) -source("experiments/regression/utils.R") -set.seed(42) - -x <- seq(-1, 1, length.out = 50) -y <- -x + rnorm(50, 0, 0.15) -x <- c(x, rep(seq(1.6, 1.8, 0.1), 2)) -y <- c(y, rep(c(1.8, 1.95), each = 3)) - -ols <- lm(y ~ x)$coefficients -slise <- slise.fit(x, y, epsilon = 0.5) - -plot(slise, title = "", partial = TRUE, size = 2) + - geom_abline(aes(intercept = ols[1], slope = ols[2], color = "OLS", linetype = "OLS"), size = 2) + - scale_color_manual(values = c("#1b9e77", SLISE_ORANGE), name = NULL) + - scale_linetype_manual(values = 2:1, name = NULL) + - theme(axis.title.y = element_text(angle = 0, vjust = 0.5), legend.key.size = grid::unit(2, "line")) + - guides(shape = FALSE, color = "legend", linetype = "legend") -``` -![Robust Regression Example Plot](experiments/results/ex1.png) - - -SLISE can also be used to explain predictions from black box models such as convolutional neural networks: - -```R -devtools::load_all() -source("experiments/regression/data.R") -set.seed(42) - -emnist <- data_emnist(10000, digit=2, th = -1) -slise <- slise.explain(emnist$X, emnist$Y, 0.5, emnist$X[17,], emnist$Y[17], 3, 6) - -plot(slise, "image", "", c("not 2", "is 2"), plots = 1) -``` -![Explanation Example Plot](experiments/results/ex2.png) - - -## Dependencies - -SLISE depends on the following R-packages: - -- Rcpp -- RcppArmadillo -- lbfgs - -The following R-packages are optional, but needed for *some* of the built-in visualisations: - -- ggplot2 -- grid -- gridExtra -- reshape2 -- crayon -- wordcloud +# SLISE - Sparse Linear Subset Explanations + +[![Version](https://img.shields.io/github/r-package/v/edahelsinki/slise?label=version)](https://edahelsinki.github.io/slise) +[![Documentation](https://github.com/edahelsinki/slise/actions/workflows/pkgdown.yml/badge.svg)](https://edahelsinki.github.io/slise) +[![Tests](https://github.com/edahelsinki/slise/actions/workflows/r_check.yml/badge.svg)](https://github.com/edahelsinki/slise/actions/workflows/r_check.yml) +[![License: MIT](https://img.shields.io/github/license/edahelsinki/slise)](https://github.com/edahelsinki/slise/blob/master/LICENSE) + +R implementation of the SLISE algorithm. The SLISE algorithm can be used for both robust regression and to explain outcomes from black box models. +For more details see the [original paper](https://rdcu.be/bVbda) or the [robust regression paper](https://rdcu.be/cFRHD). +Alternatively for a more informal overview see the [presentation](https://github.com/edahelsinki/slise/raw/master/pdfs/presentation.pdf), or the [poster](https://github.com/edahelsinki/slise/raw/master/pdfs/poster.pdf). +Finally, there is also the [documentation](https://edahelsinki.github.io/slise). + +> *Björklund A., Henelius A., Oikarinen E., Kallonen K., Puolamäki K.* (2019) +> **Sparse Robust Regression for Explaining Classifiers.** +> Discovery Science (DS 2019). +> Lecture Notes in Computer Science, vol 11828, Springer. +> https://doi.org/10.1007/978-3-030-33778-0_27 + +> *Björklund A., Henelius A., Oikarinen E., Kallonen K., Puolamäki K.* (2022). +> **Robust regression via error tolerance.** +> Data Mining and Knowledge Discovery. +> https://doi.org/10.1007/s10618-022-00819-2 + + +## The idea + +In robust regression we fit regression models that can handle data that contains outliers (see the example below for why outliers are problematic for normal regression). SLISE accomplishes this by fitting a model such that the largest possible subset of the data items have an error less than a given value. All items with an error larger than that are considered potential outliers and do not affect the resulting model. + +SLISE can also be used to provide *local model-agnostic explanations* for outcomes from black box models. To do this we replace the ground truth response vector with the predictions from the complex model. Furthermore, we force the model to fit a selected item (making the explanation local). This gives us a local approximation of the complex model with a simpler linear model (this is similar to, e.g., [LIME](https://github.com/marcotcr/lime) and [SHAP](https://github.com/slundberg/shap)). In contrast to other methods SLISE creates explanations using real data (not some discretised and randomly sampled data) so we can be sure that all inputs are valid (follows the same constraints as when the data was generated, e.g., the laws of physics). + + +## Installation + +Using the `devtools`-package (`install.packages("devtools")`) install the `slise` package: + +```R +devtools::install_github("edahelsinki/slise") +``` + +After installation, load the package using: + +```R +library(slise) +``` + + +## Other Languages + +The official __Python__ version can be found [here](https://github.com/edahelsinki/pyslise). + + +## Example + +In order to use SLISE you need to have your data in a numerical matrix (or something that can be cast to a matrix), and the response as a numerical vector. Below is an example of SLISE being used for robust regression: + +```R +library(slise) +library(ggplot2) +set.seed(42) + +x <- seq(-1, 1, length.out = 50) +y <- -x + rnorm(50, 0, 0.15) +x <- c(x, rep(seq(1.6, 1.8, 0.1), 2)) +y <- c(y, rep(c(1.8, 1.95), each = 3)) + +ols <- lm(y ~ x)$coefficients +slise <- slise.fit(x, y, epsilon = 0.5) + +plot(slise, title = "", partial = TRUE, size = 2) + + geom_abline(aes(intercept = ols[1], slope = ols[2], color = "OLS", linetype = "OLS"), size = 2) + + scale_color_manual(values = c("#1b9e77", "#fda411"), name = NULL) + + scale_linetype_manual(values = 2:1, name = NULL) + + theme(axis.title.y = element_text(angle = 0, vjust = 0.5), legend.key.size = grid::unit(2, "line")) + + guides(shape = FALSE, color = "legend", linetype = "legend") +``` +![Robust Regression Example Plot](man/figures/ex1.png) + + +SLISE can also be used to explain predictions from black box models such as convolutional neural networks: + +```R +library(slise) +set.seed(42) + +source("experiments/explanations/data.R") +emnist <- data_emnist(digit=2) + +slise <- slise.explain(emnist$X, emnist$Y, 0.5, emnist$X[17,], emnist$Y[17], logit=TRUE, lambda1=3, lambda2=6) +plot(slise, "image", "", c("not 2", "is 2"), plots = 1) +``` +![Explanation Example Plot](man/figures/ex2.png) + + +## Dependencies + +SLISE depends on the following R-packages: + +- Rcpp +- RcppArmadillo +- lbfgs + +The following R-packages are optional, but needed for *some* of the built-in visualisations: + +- ggplot2 +- grid +- gridExtra +- reshape2 +- wordcloud diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..a1f5f7c --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,29 @@ +url: https://edahelsinki.github.io/slise +template: + bootstrap: 5 + bootswatch: pulse + theme: breeze-light + bslib: + pkgdown-nav-height: 100px +navbar: + bg: primary + structure: + left: [reference] + right: [search, github] +home: + name: SLISE + sidebar: + structure: [links, license, community, citation, authors, dev] + links: + - text: Original paper + href: https://rdcu.be/bVbda + - text: Robust regression paper + href: https://rdcu.be/cFRHD + - text: Poster + href: https://github.com/edahelsinki/slise/raw/master/pdfs/poster.pdf + - text: Presentation + href: https://github.com/edahelsinki/slise/raw/master/pdfs/presentation.pdf + - text: Code reference + href: reference/index.html + - text: Python implementation + href: https://edahelsinki.github.io/pyslise diff --git a/experiments/README.md b/experiments/README.md index a9ae94f..a69147f 100644 --- a/experiments/README.md +++ b/experiments/README.md @@ -1,9 +1,9 @@ -# Experiments - -This directory contains experiments used in the papers. - -- data: Contains scripts for pre-processing data (training classifiers). -- conference: Contains out-of-date experiments for the conference paper. -- regression: Contains experiments for the robust regression paper. -- explanations: Contains experiments for the explanation paper. - +# Experiments + +This directory contains experiments used in the papers. + +- data: Contains scripts for pre-processing data (training classifiers). +- conference: Contains out-of-date experiments for the conference paper. +- regression: Contains experiments for the robust regression paper. +- explanations: Contains experiments for the explanation paper. + diff --git a/experiments/conference/README.md b/experiments/conference/README.md index 2335fe5..e41e3bb 100644 --- a/experiments/conference/README.md +++ b/experiments/conference/README.md @@ -1,52 +1,52 @@ -# Experiments - -This directory contains the experiments used in the conference paper. - -> These files are out-of-date due to a large refactoring of the SLISE code. - -## Files - - - Helper functions - - utils.R - - lime.R - - collect_results.R - - Data downloading and preparation - - data/retrieve_*.R - - Experiments (some are designed to run on a cluster) - - exp_*.R - - Plotting results for the paper - - plot_*.R - - -## Dependencies - -For running all the experiments and gathering all the data the following -packages are needed, note that not all packages are needed individual -experiments: - - - MASS - - MTE - - robustHD - - sparseLTSEigen - - robustbase - - glmnet - - R.utils - - ggplot2 - - dplyr - - scales - - abind - - magick - - lime - - xtable - - plyr - - keras - - R.matlab - - tm - - qdap - - Matrix - - SnowballC - - e1071 - - randomForest - - elmNNRcpp - - latex2exp - - pense +# Experiments + +This directory contains the experiments used in the conference paper. + +> These files are out-of-date due to a large refactoring of the SLISE code. + +## Files + + - Helper functions + - utils.R + - lime.R + - collect_results.R + - Data downloading and preparation + - data/retrieve_*.R + - Experiments (some are designed to run on a cluster) + - exp_*.R + - Plotting results for the paper + - plot_*.R + + +## Dependencies + +For running all the experiments and gathering all the data the following +packages are needed, note that not all packages are needed individual +experiments: + + - MASS + - MTE + - robustHD + - sparseLTSEigen + - robustbase + - glmnet + - R.utils + - ggplot2 + - dplyr + - scales + - abind + - magick + - lime + - xtable + - plyr + - keras + - R.matlab + - tm + - qdap + - Matrix + - SnowballC + - e1071 + - randomForest + - elmNNRcpp + - latex2exp + - pense diff --git a/experiments/explanations/README.md b/experiments/explanations/README.md index 32d5d70..6a332ef 100644 --- a/experiments/explanations/README.md +++ b/experiments/explanations/README.md @@ -1,31 +1,31 @@ -# Experiments - -This directory contains explanation experiments. - -## Dependencies - -For running all the experiments and gathering all the data the following -packages are needed, note that not all packages are needed for individual -experiments: - -- ggplot2 -- xtable -- R.matlab -- tm -- Matrix -- e1071 -- randomForest -- elmNNRcpp -- datasets -- reticulate -- keras -- tensorflow - -### Reticulate dependencies - -These are python packages that are accessed through reticulate: - -- `keras::install_keras()` -- `tensorflow::install_tensorflow()` -- `reticulate::py_install("lime")` -- `reticulate::py_install("shap")` +# Experiments + +This directory contains explanation experiments. + +## Dependencies + +For running all the experiments and gathering all the data the following +packages are needed, note that not all packages are needed for individual +experiments: + +- ggplot2 +- xtable +- R.matlab +- tm +- Matrix +- e1071 +- randomForest +- elmNNRcpp +- datasets +- reticulate +- keras +- tensorflow + +### Reticulate dependencies + +These are python packages that are accessed through reticulate: + +- `keras::install_keras()` +- `tensorflow::install_tensorflow()` +- `reticulate::py_install("lime")` +- `reticulate::py_install("shap")` diff --git a/man/auto_named_list.Rd b/man/auto_named_list.Rd new file mode 100644 index 0000000..f05f501 --- /dev/null +++ b/man/auto_named_list.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{auto_named_list} +\alias{auto_named_list} +\title{Creates a named list where the names are taken from the input variables} +\usage{ +auto_named_list(...) +} +\arguments{ +\item{...}{list elements} +} +\value{ +named list of elements +} +\description{ +NOTE: only supports arguments, not keyword arguments +} +\examples{ +a <- 1 +b <- 2 +auto_named_list(a, b)$a == 1 +auto_named_list(a, b)$b == 2 +} diff --git a/experiments/results/ex1.png b/man/figures/ex1.png similarity index 100% rename from experiments/results/ex1.png rename to man/figures/ex1.png diff --git a/experiments/results/ex2.png b/man/figures/ex2.png similarity index 100% rename from experiments/results/ex2.png rename to man/figures/ex2.png diff --git a/man/plot.slise.Rd b/man/plot.slise.Rd index 68c9dec..4e058b0 100644 --- a/man/plot.slise.Rd +++ b/man/plot.slise.Rd @@ -11,7 +11,7 @@ \item{type}{The type of plot ("2D", "bar", "distribution", "mnist", "prediction", "wordcloud")} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{...}{ Arguments passed on to \code{\link[=plot.slise_2d]{plot.slise_2d}}, \code{\link[=plot.slise_bar]{plot.slise_bar}}, \code{\link[=plot.slise_distribution]{plot.slise_distribution}}, \code{\link[=plot.slise_mnist]{plot.slise_mnist}}, \code{\link[=plot.slise_prediction]{plot.slise_prediction}}, \code{\link[=plot.slise_wordcloud]{plot.slise_wordcloud}} diff --git a/man/plot.slise_2d.Rd b/man/plot.slise_2d.Rd index 87d1554..12e954e 100644 --- a/man/plot.slise_2d.Rd +++ b/man/plot.slise_2d.Rd @@ -4,12 +4,12 @@ \alias{plot.slise_2d} \title{Plot the robust regression or explanation from slise in 2D} \usage{ -\method{plot}{slise_2d}(slise, title, labels = NULL, partial = FALSE, size = 2, ...) +\method{plot}{slise_2d}(slise, title = NULL, labels = NULL, partial = FALSE, size = 2, ...) } \arguments{ \item{slise}{The slise object} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{labels}{The axis labels (default: c("X", "Y") or c("x", "f(x)"))} diff --git a/man/plot.slise_bar.Rd b/man/plot.slise_bar.Rd index 960ea4b..58dfa61 100644 --- a/man/plot.slise_bar.Rd +++ b/man/plot.slise_bar.Rd @@ -4,12 +4,19 @@ \alias{plot.slise_bar} \title{Plot the robust regression or explanation from slise as bar plots} \usage{ -\method{plot}{slise_bar}(slise, title, labels = c("Low", "High"), partial = FALSE, size = 8, ...) +\method{plot}{slise_bar}( + slise, + title = NULL, + labels = c("Low", "High"), + partial = FALSE, + size = 8, + ... +) } \arguments{ \item{slise}{The slise object} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{labels}{The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High"))} diff --git a/man/plot.slise_distribution.Rd b/man/plot.slise_distribution.Rd index fea1405..b4913bd 100644 --- a/man/plot.slise_distribution.Rd +++ b/man/plot.slise_distribution.Rd @@ -4,12 +4,19 @@ \alias{plot.slise_distribution} \title{Plot the robust regression or explanation from slise with distributions} \usage{ -\method{plot}{slise_distribution}(slise, title, labels = c("Low", "High"), partial = FALSE, signif = 3, ...) +\method{plot}{slise_distribution}( + slise, + title = NULL, + labels = c("Low", "High"), + partial = FALSE, + signif = 3, + ... +) } \arguments{ \item{slise}{The slise object} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{labels}{The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High"))} diff --git a/man/plot.slise_mnist.Rd b/man/plot.slise_mnist.Rd index a592305..fead2ab 100644 --- a/man/plot.slise_mnist.Rd +++ b/man/plot.slise_mnist.Rd @@ -6,20 +6,21 @@ \usage{ \method{plot}{slise_mnist}( slise, - title, + title = NULL, labels = c("Low", "High"), partial = FALSE, width = floor(sqrt(ncol(slise$X))), height = width, plots = 1, enhance_colours = TRUE, - ... + ..., + breaks = NULL ) } \arguments{ \item{slise}{The slise object} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{labels}{The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High"))} @@ -34,6 +35,8 @@ \item{enhance_colours}{Increse the saturation of the explanation (default: TRUE)} \item{...}{Ignored parameters} + +\item{breaks}{Breaks for the countours, see `ggplot2::stat_contour` (default: NULL)} } \value{ ggplot object(s) or plot diff --git a/man/plot.slise_prediction.Rd b/man/plot.slise_prediction.Rd index 7ed7089..40d6bb6 100644 --- a/man/plot.slise_prediction.Rd +++ b/man/plot.slise_prediction.Rd @@ -6,7 +6,7 @@ \usage{ \method{plot}{slise_prediction}( slise, - title, + title = NULL, labels = c("Response", "Count"), partial = FALSE, approximation = TRUE, @@ -17,7 +17,7 @@ \arguments{ \item{slise}{The slise object} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{labels}{The axis labels (default: c("Response", "Count"))} diff --git a/man/plot.slise_wordcloud.Rd b/man/plot.slise_wordcloud.Rd index aadc729..5e254b0 100644 --- a/man/plot.slise_wordcloud.Rd +++ b/man/plot.slise_wordcloud.Rd @@ -6,7 +6,7 @@ \usage{ \method{plot}{slise_wordcloud}( slise, - title, + title = NULL, labels = c("Low", "High"), treshold = 1e-08, local = TRUE, @@ -16,7 +16,7 @@ \arguments{ \item{slise}{The slise object} -\item{title}{The title of the plot} +\item{title}{The title of the plot (may include a `\%s`, which will be replaced by the prediction)} \item{labels}{The class labels (vector with two strings: c(y_low, y_high), default: c("Low", "High"))} diff --git a/man/print.slise.Rd b/man/print.slise.Rd index d39698a..c0826c1 100644 --- a/man/print.slise.Rd +++ b/man/print.slise.Rd @@ -4,13 +4,15 @@ \alias{print.slise} \title{Print the robust regression or explanation from slise} \usage{ -\method{print}{slise}(x, num_vars = 10, ...) +\method{print}{slise}(x, num_vars = 10, labels = NULL, ...) } \arguments{ \item{x}{The slise object} \item{num_vars}{Minimum number of variables to show without filtering (default: 10)} +\item{labels}{Name of y or class labels} + \item{...}{Ignored additional parameters} } \value{ diff --git a/man/slise.explain.Rd b/man/slise.explain.Rd index 5be6ff2..8c3b510 100644 --- a/man/slise.explain.Rd +++ b/man/slise.explain.Rd @@ -2,8 +2,7 @@ % Please edit documentation in R/slise.R \name{slise.explain} \alias{slise.explain} -\title{SLISE Black Box Explainer -Use SLISE for explaining predictions made by a black box.} +\title{SLISE for explaining Black box models.} \usage{ slise.explain( X, @@ -56,7 +55,7 @@ slise.explain( }} } \value{ -slise object (coefficients, subset, value, X, Y, lambda1, lambda2, epsilon, scaled, alpha, x, y) +slise.object } \description{ It is highly recommended that you normalise the data, diff --git a/man/slise.explain_find.Rd b/man/slise.explain_find.Rd index 2042557..3741e81 100644 --- a/man/slise.explain_find.Rd +++ b/man/slise.explain_find.Rd @@ -42,7 +42,5 @@ slise.explain_find( SLISE object } \description{ -SLISE Black Box Explainer -Use SLISE for explaining predictions made by a black box. -BUT with a binary search for sparsity! +DEPRECATED: This is a simple binary search, no need for a separate function } diff --git a/man/slise.fit.Rd b/man/slise.fit.Rd index 6d03a82..f9847b0 100644 --- a/man/slise.fit.Rd +++ b/man/slise.fit.Rd @@ -2,8 +2,7 @@ % Please edit documentation in R/slise.R \name{slise.fit} \alias{slise.fit} -\title{SLISE Regression -Use SLISE for robust regression.} +\title{SLISE for robust regression.} \usage{ slise.fit( X, @@ -50,15 +49,13 @@ slise.fit( }} } \value{ -slise object (coefficients, subset, value, X, Y, lambda1, lambda2, epsilon, scaled, alpha) +slise.object } \description{ It is highly recommended that you normalise the data, either before using SLISE or by setting normalise = TRUE. } \examples{ -# Assuming data is a data.frame with the first column containing the response -# Further assuming newdata is a similar data.frame with the response missing X <- matrix(rnorm(32), 8, 4) Y <- rnorm(8) model <- slise.fit(X, Y, (max(Y) - min(Y)) * 0.1) diff --git a/man/slise.formula.Rd b/man/slise.formula.Rd new file mode 100644 index 0000000..f75aa88 --- /dev/null +++ b/man/slise.formula.Rd @@ -0,0 +1,58 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/slise.R +\name{slise.formula} +\alias{slise.formula} +\title{SLISE for robust regression (using a formula).} +\usage{ +slise.formula( + formula, + data, + epsilon, + lambda1 = 0, + lambda2 = 0, + weight = NULL, + normalise = FALSE, + initialisation = slise_initialisation_candidates, + ... +) +} +\arguments{ +\item{formula}{formula} + +\item{data}{data for the formula} + +\item{epsilon}{Error tolerance} + +\item{lambda1}{L1 regularisation coefficient (default: 0)} + +\item{lambda2}{L2 regularisation coefficient (default: 0)} + +\item{weight}{Optional weight vector (default: NULL)} + +\item{normalise}{Preprocess X and Y by scaling, note that epsilon is not scaled (default: FALSE)} + +\item{initialisation}{Function that gives the initial alpha and beta, or a list containing the initial alpha and beta (default: slise_initialisation_candidates)} + +\item{...}{ + Arguments passed on to \code{\link[=graduated_optimisation]{graduated_optimisation}}, \code{\link[=slise_initialisation_candidates]{slise_initialisation_candidates}} + \describe{ + \item{\code{beta_max}}{Stopping sigmoid steepness (default: 20 / epsilon^2)} + \item{\code{max_approx}}{Approximation ratio when selecting the next beta (default: 1.15)} + \item{\code{max_iterations}}{Maximum number of OWL-QN iterations (default: 300)} + \item{\code{debug}}{Should debug statement be printed each iteration (default: FALSE)} + \item{\code{num_init}}{the number of initial subsets to generate (default: 500)} + \item{\code{beta_max_init}}{the maximum sigmoid steepness in the initialisation} + \item{\code{pca_treshold}}{the maximum number of columns without using PCA (default: 10)} + }} +} +\value{ +slise.object +} +\description{ +It is highly recommended that you normalise the data, +either before using SLISE or by setting normalise = TRUE. +} +\examples{ +data <- data.frame(y = rnorm(8), a = rnorm(8), b = rnorm(8)) +model <- slise.formula(y ~ a * b + abs(a), data, 0.1, normalise = TRUE) +} diff --git a/man/slise.object.Rd b/man/slise.object.Rd index 0c2da87..202adea 100644 --- a/man/slise.object.Rd +++ b/man/slise.object.Rd @@ -15,8 +15,7 @@ slise.object( intercept = FALSE, logit = FALSE, x = NULL, - y = NULL, - ... + y = NULL ) } \arguments{ @@ -41,11 +40,9 @@ slise.object( \item{x}{explained item x (default: NULL)} \item{y}{explained item y (default: NULL)} - -\item{...}{other variables to add to the SLISE object} } \value{ -list(coefficients=unscale(alpha), X, Y, scaled=data, lambda1, lambda2, alpha, subset=[r_i do not edit by hand -// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 - -#include "slise_types.h" -#include -#include - -using namespace Rcpp; - -// sigmoidc -arma::vec sigmoidc(const arma::vec& x); -RcppExport SEXP _slise_sigmoidc(SEXP xSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const arma::vec& >::type x(xSEXP); - rcpp_result_gen = Rcpp::wrap(sigmoidc(x)); - return rcpp_result_gen; -END_RCPP -} -// log_sigmoidc -arma::vec log_sigmoidc(const arma::vec& x); -RcppExport SEXP _slise_log_sigmoidc(SEXP xSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const arma::vec& >::type x(xSEXP); - rcpp_result_gen = Rcpp::wrap(log_sigmoidc(x)); - return rcpp_result_gen; -END_RCPP -} -// loss_smooth_c -double loss_smooth_c(const arma::vec& alpha, const arma::mat& data, const arma::vec& response, const double& epsilon, const double& beta, const double& lambda1, const double& lambda2, const arma::vec& weight); -RcppExport SEXP _slise_loss_smooth_c(SEXP alphaSEXP, SEXP dataSEXP, SEXP responseSEXP, SEXP epsilonSEXP, SEXP betaSEXP, SEXP lambda1SEXP, SEXP lambda2SEXP, SEXP weightSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const arma::vec& >::type alpha(alphaSEXP); - Rcpp::traits::input_parameter< const arma::mat& >::type data(dataSEXP); - Rcpp::traits::input_parameter< const arma::vec& >::type response(responseSEXP); - Rcpp::traits::input_parameter< const double& >::type epsilon(epsilonSEXP); - Rcpp::traits::input_parameter< const double& >::type beta(betaSEXP); - Rcpp::traits::input_parameter< const double& >::type lambda1(lambda1SEXP); - Rcpp::traits::input_parameter< const double& >::type lambda2(lambda2SEXP); - Rcpp::traits::input_parameter< const arma::vec& >::type weight(weightSEXP); - rcpp_result_gen = Rcpp::wrap(loss_smooth_c(alpha, data, response, epsilon, beta, lambda1, lambda2, weight)); - return rcpp_result_gen; -END_RCPP -} -// loss_smooth_c_dc -Rcpp::NumericVector loss_smooth_c_dc(const SEXP xs, const SEXP dcptr); -RcppExport SEXP _slise_loss_smooth_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const SEXP >::type xs(xsSEXP); - Rcpp::traits::input_parameter< const SEXP >::type dcptr(dcptrSEXP); - rcpp_result_gen = Rcpp::wrap(loss_smooth_c_dc(xs, dcptr)); - return rcpp_result_gen; -END_RCPP -} -// loss_smooth_grad_c -Rcpp::NumericVector loss_smooth_grad_c(const arma::vec& alpha, const arma::mat& data, const arma::vec& response, const double& epsilon, const double& beta, const double& lambda1, const double& lambda2, const arma::vec& weight); -RcppExport SEXP _slise_loss_smooth_grad_c(SEXP alphaSEXP, SEXP dataSEXP, SEXP responseSEXP, SEXP epsilonSEXP, SEXP betaSEXP, SEXP lambda1SEXP, SEXP lambda2SEXP, SEXP weightSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const arma::vec& >::type alpha(alphaSEXP); - Rcpp::traits::input_parameter< const arma::mat& >::type data(dataSEXP); - Rcpp::traits::input_parameter< const arma::vec& >::type response(responseSEXP); - Rcpp::traits::input_parameter< const double& >::type epsilon(epsilonSEXP); - Rcpp::traits::input_parameter< const double& >::type beta(betaSEXP); - Rcpp::traits::input_parameter< const double& >::type lambda1(lambda1SEXP); - Rcpp::traits::input_parameter< const double& >::type lambda2(lambda2SEXP); - Rcpp::traits::input_parameter< const arma::vec& >::type weight(weightSEXP); - rcpp_result_gen = Rcpp::wrap(loss_smooth_grad_c(alpha, data, response, epsilon, beta, lambda1, lambda2, weight)); - return rcpp_result_gen; -END_RCPP -} -// loss_smooth_grad_c_dc -Rcpp::NumericVector loss_smooth_grad_c_dc(const SEXP xs, const SEXP dcptr); -RcppExport SEXP _slise_loss_smooth_grad_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const SEXP >::type xs(xsSEXP); - Rcpp::traits::input_parameter< const SEXP >::type dcptr(dcptrSEXP); - rcpp_result_gen = Rcpp::wrap(loss_smooth_grad_c_dc(xs, dcptr)); - return rcpp_result_gen; -END_RCPP -} -// lg_combined_smooth_c_dc -Rcpp::NumericVector lg_combined_smooth_c_dc(SEXP xs, SEXP dcptr); -RcppExport SEXP _slise_lg_combined_smooth_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< SEXP >::type xs(xsSEXP); - Rcpp::traits::input_parameter< SEXP >::type dcptr(dcptrSEXP); - rcpp_result_gen = Rcpp::wrap(lg_combined_smooth_c_dc(xs, dcptr)); - return rcpp_result_gen; -END_RCPP -} -// lg_getgrad_c_dc -Rcpp::NumericVector lg_getgrad_c_dc(SEXP xs, SEXP dcptr); -RcppExport SEXP _slise_lg_getgrad_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< SEXP >::type xs(xsSEXP); - Rcpp::traits::input_parameter< SEXP >::type dcptr(dcptrSEXP); - rcpp_result_gen = Rcpp::wrap(lg_getgrad_c_dc(xs, dcptr)); - return rcpp_result_gen; -END_RCPP -} -// loss_smooth_c_ptr -Rcpp::XPtr loss_smooth_c_ptr(); -RcppExport SEXP _slise_loss_smooth_c_ptr() { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - rcpp_result_gen = Rcpp::wrap(loss_smooth_c_ptr()); - return rcpp_result_gen; -END_RCPP -} -// loss_smooth_grad_c_ptr -Rcpp::XPtr loss_smooth_grad_c_ptr(); -RcppExport SEXP _slise_loss_smooth_grad_c_ptr() { -BEGIN_RCPP - Rcpp::RObject rcpp_result_gen; - Rcpp::RNGScope rcpp_rngScope_gen; - rcpp_result_gen = Rcpp::wrap(loss_smooth_grad_c_ptr()); - return rcpp_result_gen; -END_RCPP -} - -RcppExport SEXP _rcpp_module_boot_slise_mod(); - -static const R_CallMethodDef CallEntries[] = { - {"_slise_sigmoidc", (DL_FUNC) &_slise_sigmoidc, 1}, - {"_slise_log_sigmoidc", (DL_FUNC) &_slise_log_sigmoidc, 1}, - {"_slise_loss_smooth_c", (DL_FUNC) &_slise_loss_smooth_c, 8}, - {"_slise_loss_smooth_c_dc", (DL_FUNC) &_slise_loss_smooth_c_dc, 2}, - {"_slise_loss_smooth_grad_c", (DL_FUNC) &_slise_loss_smooth_grad_c, 8}, - {"_slise_loss_smooth_grad_c_dc", (DL_FUNC) &_slise_loss_smooth_grad_c_dc, 2}, - {"_slise_lg_combined_smooth_c_dc", (DL_FUNC) &_slise_lg_combined_smooth_c_dc, 2}, - {"_slise_lg_getgrad_c_dc", (DL_FUNC) &_slise_lg_getgrad_c_dc, 2}, - {"_slise_loss_smooth_c_ptr", (DL_FUNC) &_slise_loss_smooth_c_ptr, 0}, - {"_slise_loss_smooth_grad_c_ptr", (DL_FUNC) &_slise_loss_smooth_grad_c_ptr, 0}, - {"_rcpp_module_boot_slise_mod", (DL_FUNC) &_rcpp_module_boot_slise_mod, 0}, - {NULL, NULL, 0} -}; - -RcppExport void R_init_slise(DllInfo *dll) { - R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); - R_useDynamicSymbols(dll, FALSE); -} +// Generated by using Rcpp::compileAttributes() -> do not edit by hand +// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +#include "slise_types.h" +#include +#include + +using namespace Rcpp; + +#ifdef RCPP_USE_GLOBAL_ROSTREAM +Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); +Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); +#endif + +// sigmoidc +arma::vec sigmoidc(const arma::vec& x); +RcppExport SEXP _slise_sigmoidc(SEXP xSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const arma::vec& >::type x(xSEXP); + rcpp_result_gen = Rcpp::wrap(sigmoidc(x)); + return rcpp_result_gen; +END_RCPP +} +// log_sigmoidc +arma::vec log_sigmoidc(const arma::vec& x); +RcppExport SEXP _slise_log_sigmoidc(SEXP xSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const arma::vec& >::type x(xSEXP); + rcpp_result_gen = Rcpp::wrap(log_sigmoidc(x)); + return rcpp_result_gen; +END_RCPP +} +// loss_smooth_c +double loss_smooth_c(const arma::vec& alpha, const arma::mat& data, const arma::vec& response, const double& epsilon, const double& beta, const double& lambda1, const double& lambda2, const arma::vec& weight); +RcppExport SEXP _slise_loss_smooth_c(SEXP alphaSEXP, SEXP dataSEXP, SEXP responseSEXP, SEXP epsilonSEXP, SEXP betaSEXP, SEXP lambda1SEXP, SEXP lambda2SEXP, SEXP weightSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const arma::vec& >::type alpha(alphaSEXP); + Rcpp::traits::input_parameter< const arma::mat& >::type data(dataSEXP); + Rcpp::traits::input_parameter< const arma::vec& >::type response(responseSEXP); + Rcpp::traits::input_parameter< const double& >::type epsilon(epsilonSEXP); + Rcpp::traits::input_parameter< const double& >::type beta(betaSEXP); + Rcpp::traits::input_parameter< const double& >::type lambda1(lambda1SEXP); + Rcpp::traits::input_parameter< const double& >::type lambda2(lambda2SEXP); + Rcpp::traits::input_parameter< const arma::vec& >::type weight(weightSEXP); + rcpp_result_gen = Rcpp::wrap(loss_smooth_c(alpha, data, response, epsilon, beta, lambda1, lambda2, weight)); + return rcpp_result_gen; +END_RCPP +} +// loss_smooth_c_dc +Rcpp::NumericVector loss_smooth_c_dc(const SEXP xs, const SEXP dcptr); +RcppExport SEXP _slise_loss_smooth_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const SEXP >::type xs(xsSEXP); + Rcpp::traits::input_parameter< const SEXP >::type dcptr(dcptrSEXP); + rcpp_result_gen = Rcpp::wrap(loss_smooth_c_dc(xs, dcptr)); + return rcpp_result_gen; +END_RCPP +} +// loss_smooth_grad_c +Rcpp::NumericVector loss_smooth_grad_c(const arma::vec& alpha, const arma::mat& data, const arma::vec& response, const double& epsilon, const double& beta, const double& lambda1, const double& lambda2, const arma::vec& weight); +RcppExport SEXP _slise_loss_smooth_grad_c(SEXP alphaSEXP, SEXP dataSEXP, SEXP responseSEXP, SEXP epsilonSEXP, SEXP betaSEXP, SEXP lambda1SEXP, SEXP lambda2SEXP, SEXP weightSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const arma::vec& >::type alpha(alphaSEXP); + Rcpp::traits::input_parameter< const arma::mat& >::type data(dataSEXP); + Rcpp::traits::input_parameter< const arma::vec& >::type response(responseSEXP); + Rcpp::traits::input_parameter< const double& >::type epsilon(epsilonSEXP); + Rcpp::traits::input_parameter< const double& >::type beta(betaSEXP); + Rcpp::traits::input_parameter< const double& >::type lambda1(lambda1SEXP); + Rcpp::traits::input_parameter< const double& >::type lambda2(lambda2SEXP); + Rcpp::traits::input_parameter< const arma::vec& >::type weight(weightSEXP); + rcpp_result_gen = Rcpp::wrap(loss_smooth_grad_c(alpha, data, response, epsilon, beta, lambda1, lambda2, weight)); + return rcpp_result_gen; +END_RCPP +} +// loss_smooth_grad_c_dc +Rcpp::NumericVector loss_smooth_grad_c_dc(const SEXP xs, const SEXP dcptr); +RcppExport SEXP _slise_loss_smooth_grad_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const SEXP >::type xs(xsSEXP); + Rcpp::traits::input_parameter< const SEXP >::type dcptr(dcptrSEXP); + rcpp_result_gen = Rcpp::wrap(loss_smooth_grad_c_dc(xs, dcptr)); + return rcpp_result_gen; +END_RCPP +} +// lg_combined_smooth_c_dc +Rcpp::NumericVector lg_combined_smooth_c_dc(SEXP xs, SEXP dcptr); +RcppExport SEXP _slise_lg_combined_smooth_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< SEXP >::type xs(xsSEXP); + Rcpp::traits::input_parameter< SEXP >::type dcptr(dcptrSEXP); + rcpp_result_gen = Rcpp::wrap(lg_combined_smooth_c_dc(xs, dcptr)); + return rcpp_result_gen; +END_RCPP +} +// lg_getgrad_c_dc +Rcpp::NumericVector lg_getgrad_c_dc(SEXP xs, SEXP dcptr); +RcppExport SEXP _slise_lg_getgrad_c_dc(SEXP xsSEXP, SEXP dcptrSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< SEXP >::type xs(xsSEXP); + Rcpp::traits::input_parameter< SEXP >::type dcptr(dcptrSEXP); + rcpp_result_gen = Rcpp::wrap(lg_getgrad_c_dc(xs, dcptr)); + return rcpp_result_gen; +END_RCPP +} +// loss_smooth_c_ptr +Rcpp::XPtr loss_smooth_c_ptr(); +RcppExport SEXP _slise_loss_smooth_c_ptr() { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + rcpp_result_gen = Rcpp::wrap(loss_smooth_c_ptr()); + return rcpp_result_gen; +END_RCPP +} +// loss_smooth_grad_c_ptr +Rcpp::XPtr loss_smooth_grad_c_ptr(); +RcppExport SEXP _slise_loss_smooth_grad_c_ptr() { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + rcpp_result_gen = Rcpp::wrap(loss_smooth_grad_c_ptr()); + return rcpp_result_gen; +END_RCPP +} + +RcppExport SEXP _rcpp_module_boot_slise_mod(); + +static const R_CallMethodDef CallEntries[] = { + {"_slise_sigmoidc", (DL_FUNC) &_slise_sigmoidc, 1}, + {"_slise_log_sigmoidc", (DL_FUNC) &_slise_log_sigmoidc, 1}, + {"_slise_loss_smooth_c", (DL_FUNC) &_slise_loss_smooth_c, 8}, + {"_slise_loss_smooth_c_dc", (DL_FUNC) &_slise_loss_smooth_c_dc, 2}, + {"_slise_loss_smooth_grad_c", (DL_FUNC) &_slise_loss_smooth_grad_c, 8}, + {"_slise_loss_smooth_grad_c_dc", (DL_FUNC) &_slise_loss_smooth_grad_c_dc, 2}, + {"_slise_lg_combined_smooth_c_dc", (DL_FUNC) &_slise_lg_combined_smooth_c_dc, 2}, + {"_slise_lg_getgrad_c_dc", (DL_FUNC) &_slise_lg_getgrad_c_dc, 2}, + {"_slise_loss_smooth_c_ptr", (DL_FUNC) &_slise_loss_smooth_c_ptr, 0}, + {"_slise_loss_smooth_grad_c_ptr", (DL_FUNC) &_slise_loss_smooth_grad_c_ptr, 0}, + {"_rcpp_module_boot_slise_mod", (DL_FUNC) &_rcpp_module_boot_slise_mod, 0}, + {NULL, NULL, 0} +}; + +RcppExport void R_init_slise(DllInfo *dll) { + R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); + R_useDynamicSymbols(dll, FALSE); +} diff --git a/src/loss_functions.cpp b/src/loss_functions.cpp index 708a25c..d0be6ee 100644 --- a/src/loss_functions.cpp +++ b/src/loss_functions.cpp @@ -166,7 +166,7 @@ arma::vec sigmoidc(const arma::vec &x) return (1 / (1 + arma::exp(-x))); } -arma::vec i_sigmoidc(const arma::vec &x) +inline arma::vec i_sigmoidc(const arma::vec &x) { return (1 / (1 + arma::exp(-x))); } @@ -183,7 +183,7 @@ arma::vec log_sigmoidc(const arma::vec &x) return res; } -arma::vec pmin(const arma::vec &a, const double b) +inline arma::vec pmin(const arma::vec &a, const double b) { arma::vec res(a.size()); for (arma::uword i = 0; i < a.size(); i++) @@ -194,7 +194,7 @@ arma::vec pmin(const arma::vec &a, const double b) return res; } -arma::vec pmin_other(const arma::vec &a, const arma::vec &b, const double c) +inline arma::vec pmin_other(const arma::vec &a, const arma::vec &b, const double c) { arma::vec res(a.size()); for (arma::uword i = 0; i < a.size(); i++) diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index 4b711b0..1876d51 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -1,12 +1,12 @@ - -library(slise) +library(devtools) +devtools::load_all() #' Create Synthetic data #' #' @param n number of items #' @param d number of columns #' @param num_zero number of irrelevant features -#' @param epsilon +#' @param epsilon #' @param rnd_unif fraction of uniform noise #' @param rnd_adver fraction of adversarial noise models (list) #' @@ -35,4 +35,4 @@ data_create <- function(n, d, num_zero = floor(d * 0.3), epsilon = 0.1, rnd_unif Y[(start + 1):(start + size)] <- runif(size, min(Y), max(Y)) } list(X = X, Y = c(Y), alpha = alpha, clean = c(clean)) -} +} \ No newline at end of file diff --git a/tests/testthat/test_data.R b/tests/testthat/test_data.R index 924da02..662dfa5 100644 --- a/tests/testthat/test_data.R +++ b/tests/testthat/test_data.R @@ -1,44 +1,42 @@ -context("Tests for the data functions") -source("setup.R") - -test_that("Check data preprocessing", { - for (i in c(rep(c(4, 8), 2))) { - data <- data_create(i * 30, i, floor(i * 0.5), 0.03, 0.3, 0.3) - for(i in 0:7) { - expect_equal( - c(data$X), - c(remove_intercept_column(add_intercept_column(data$X))) - ) - X <- data$X - X[, 3] <- 0 - X2 <- remove_constant_columns(X) - expect_equal(c(X[, -3]), c(X2)) - expect_equal(attr(X2, "constant_columns"), 3) - X3 <- scale_robust(X2) - expect_equal( - c(X2), - c(sweep(sweep(X3, 2, attr(X3, "scaled:scale"), `*`), 2, attr(X3, "scaled:center"), `+`)) - ) - expect_equal(X3, scale_same(data$X, X3)) - expect_equal(c(scale_same(X[4, ], X3)), c(X3[4, ])) - expect_equal(c(scale_same(X[1:3, ], X3)), c(X3[1:3, ])) - Y2 <- scale_robust(data$Y) - ols1 <- .lm.fit(add_intercept_column(X3), Y2)$coefficients - ols2 <- .lm.fit(add_intercept_column(X2), data$Y)$coefficients - ols3 <- unscale_alpha(ols1, X3, Y2) - expect_equal(ols2, ols3) - } - } -}) - -test_that("Check simple_pca", { - for (i in c(rep(c(4, 8), 2))) { - data <- data_create(i * 30, i, floor(i * 0.5), 0.03, 0.3, 0.3) - pca1 <- simple_pca(data$X, ceiling(i * 0.3)) - pca2 <- prcomp(data$X, center = FALSE, scale = FALSE, rank = ceiling(i * 0.3))$rotation - expect_equal(c(pca1), c(pca2)) - pca3 <- simple_pca(data$X, i) - X2 <- (data$X %*% pca3) %*% t(pca3) - expect_equal(X2, data$X) - } -}) +context("Tests for the data functions") +source("setup.R") + +test_that("Check data preprocessing", { + for (i in c(rep(c(4, 8), 2))) { + data <- data_create(i * 30, i, floor(i * 0.5), 0.03, 0.3, 0.3) + for (i in 0:7) { + expect_equal( + c(data$X), + c(remove_intercept_column(add_intercept_column(data$X))) + ) + X <- data$X + X[, 3] <- 0 + X2 <- remove_constant_columns(X) + expect_equal(c(X[, -3]), c(X2)) + expect_equal(attr(X2, "constant_columns"), 3) + X3 <- scale_robust(X2) + expect_equal(X2, unscale(X3)) + expect_equal(X3, scale_same(data$X, X3)) + expect_equal(c(scale_same(X[4, ], X3)), c(X3[4, ])) + expect_equal(c(scale_same(X[1:3, ], X3)), c(X3[1:3, ])) + Y2 <- scale_robust(data$Y) + expect_equal(data$Y, unscale(Y2)) + ols1 <- .lm.fit(add_intercept_column(X3), Y2)$coefficients + ols2 <- .lm.fit(add_intercept_column(X2), data$Y)$coefficients + ols3 <- unscale_alpha(ols1, X3, Y2) + expect_equal(ols2, ols3) + } + } +}) + +test_that("Check simple_pca", { + for (i in c(rep(c(4, 8), 2))) { + data <- data_create(i * 30, i, floor(i * 0.5), 0.03, 0.3, 0.3) + pca1 <- simple_pca(data$X, ceiling(i * 0.3)) + pca2 <- prcomp(data$X, center = FALSE, scale = FALSE, rank = ceiling(i * 0.3))$rotation + expect_equal(c(pca1), c(pca2)) + pca3 <- simple_pca(data$X, i) + X2 <- (data$X %*% pca3) %*% t(pca3) + expect_equal(X2, data$X) + } +}) \ No newline at end of file diff --git a/tests/testthat/test_plot.R b/tests/testthat/test_plot.R index d28bd39..3c5ef1e 100644 --- a/tests/testthat/test_plot.R +++ b/tests/testthat/test_plot.R @@ -19,6 +19,9 @@ test_that("Check print", { expl <- slise.explain(data$X, data$Y, 0.2, 3) cap <- capture.output(print(expl))[[1]] expect_equal(cap, "SLISE Explanation:") + expl <- slise.explain(data$X, data$Y, 0.2, 3, normalise = TRUE) + cap <- capture.output(print(expl))[[1]] + expect_equal(cap, "SLISE Explanation:") } }) diff --git a/tests/testthat/test_slise.R b/tests/testthat/test_slise.R index b37ce30..c27f007 100644 --- a/tests/testthat/test_slise.R +++ b/tests/testthat/test_slise.R @@ -22,16 +22,22 @@ test_that("Check SLISE", { } }) -test_that("Check SLISE find", { - for (i in c(rep(c(4, 8), 2))) { - data <- data_create(i * 30, i, floor(i * 0.5), 0.03, 0.3, 0.3) - x <- data$X[1, ] - y <- data$clean[1] - expl1_alpha <- slise.explain_find(data$X, data$Y, 0.03, x, y, lambda1 = 0, variables = i / 2)$coefficients - expl2_alpha <- slise.explain_find(data$X, data$Y, 0.03, x, y, lambda1 = 0.1, variables = i / 2)$coefficients - expect_equal(sum(expl1_alpha[-1] * x) + expl1_alpha[[1]], y) - expect_equal(sum(expl2_alpha[-1] * x) + expl2_alpha[[1]], y) - } +test_that("Check SLISE formula", { + data <- data.frame(y = rnorm(8), a = rnorm(8), b = rnorm(8)) + model <- slise.formula(y ~ a * b + abs(a), data, 0.1, normalise = TRUE) + expect_equal(length(model$coefficients), 5) + expect_equal(length(model$normalised_coefficients), 5) + expect_true(model$intercept) + model <- slise.formula(y ~ a * b + abs(a) + 0, data, 0.1, normalise = TRUE) + expect_equal(length(model$coefficients), 5) + expect_equal(length(model$normalised_coefficients), 4) + expect_true(model$intercept) + model <- slise.formula(y ~ a * b + abs(a) + 0, data, 0.1, normalise = FALSE, lambda1 = 0.1) + expect_equal(length(model$coefficients), 4) + expect_false(model$intercept) + model <- slise.formula(y ~ poly(a, b, degree = 2), data, 0.1, normalise = FALSE, lambda1 = 0.1) + expect_equal(length(model$coefficients), 6) + expect_true(model$intercept) }) test_that("Check SLISE comb", { @@ -63,14 +69,21 @@ test_that("Check SLISE predict", { Y3 <- predict(fit1, data$X) expect_equal(Y1[1], data$clean[1]) expect_equal(Y2[2], c(Y2c)) + expect_equal(expl1$subset, c(abs(predict(expl1, data$X) - data$Y) < expl1$epsilon)) + expect_equal(expl2$subset, c(abs(predict(expl2, data$X, logit = TRUE) - expl2$Y) < expl2$epsilon)) + expect_equal(expl3$subset, c(abs(predict(expl3, data$X, logit = TRUE) - expl3$Y) < expl3$epsilon)) + expect_equal(fit1$subset, c(abs(predict(fit1, data$X) - data$Y) < fit1$epsilon)) } }) test_that("Check SLISE unscale", { for (i in c(rep(c(4, 8, 16), 2))) { - data <- data_create(i * 30, i, floor(i * 0.5), 0.03, 0.3, 0.3) - slise1 <- slise.fit(data$X, data$Y, epsilon = 0.1, lambda1 = 0, normalise = TRUE) - slise2 <- slise.fit(data$X, data$Y, epsilon = slise1$epsilon, lambda1 = 0) - expect_equal(slise1$coefficients, slise2$coefficients, 0.3) + data <- data_create(i * 30, i, floor(i * 0.3), 0.03, 0.3, 0.3) + slise1 <- slise.fit(data$X, data$Y, epsilon = 0.1, normalise = TRUE) + data2 <- slise.preprocess(data$X, data$Y, 0.1, intercept = TRUE, normalise = TRUE) + subset1 <- mean(abs(predict(slise1, data$X) - data$Y) <= slise1$epsilon) + subset2 <- mean(abs(data2$X %*% slise1$normalised_alpha - data2$Y) < slise1$normalised_epsilon) + expect_equal(subset1, mean(slise1$subset)) + expect_equal(subset2, mean(slise1$subset)) } }) \ No newline at end of file diff --git a/vignettes/bjorklund2019sparse.pdf b/vignettes/bjorklund2019sparse.pdf deleted file mode 100644 index 9dc3fde..0000000 Binary files a/vignettes/bjorklund2019sparse.pdf and /dev/null differ diff --git a/vignettes/bjorklund2019sparse.pdf.asis b/vignettes/bjorklund2019sparse.pdf.asis deleted file mode 100644 index 7093529..0000000 --- a/vignettes/bjorklund2019sparse.pdf.asis +++ /dev/null @@ -1,3 +0,0 @@ -%\VignetteIndexEntry{Paper: Sparse Robust Regression for Explaining Outliers} -%\VignetteEngine{R.rsp::asis} -%\VignetteKeyword{paper} diff --git a/vignettes/bjorklund2022robust.pdf b/vignettes/bjorklund2022robust.pdf deleted file mode 100644 index b5fbbf6..0000000 Binary files a/vignettes/bjorklund2022robust.pdf and /dev/null differ diff --git a/vignettes/bjorklund2022robust.pdf.asis b/vignettes/bjorklund2022robust.pdf.asis deleted file mode 100644 index 0bb3270..0000000 --- a/vignettes/bjorklund2022robust.pdf.asis +++ /dev/null @@ -1,3 +0,0 @@ -%\VignetteIndexEntry{Paper: Robust regression via error tolerance} -%\VignetteEngine{R.rsp::asis} -%\VignetteKeyword{paper} diff --git a/vignettes/poster.pdf.asis b/vignettes/poster.pdf.asis deleted file mode 100644 index 05d8317..0000000 --- a/vignettes/poster.pdf.asis +++ /dev/null @@ -1,3 +0,0 @@ -%\VignetteIndexEntry{Poster} -%\VignetteEngine{R.rsp::asis} -%\VignetteKeyword{poster} diff --git a/vignettes/presentation.pdf b/vignettes/presentation.pdf deleted file mode 100644 index bb6c986..0000000 Binary files a/vignettes/presentation.pdf and /dev/null differ diff --git a/vignettes/presentation.pdf.asis b/vignettes/presentation.pdf.asis deleted file mode 100644 index 4c576d2..0000000 --- a/vignettes/presentation.pdf.asis +++ /dev/null @@ -1,3 +0,0 @@ -%\VignetteIndexEntry{Presentation} -%\VignetteEngine{R.rsp::asis} -%\VignetteKeyword{presentation}