├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── NAMESPACE ├── NEWS.md ├── R ├── helper_functions.R ├── kernels.R ├── localModel.R ├── local_surrogate.R ├── neighbourhood.R ├── plot_print.R └── tree_discretizer.R ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── contributing.md ├── docs ├── 404.html ├── MI2logo.jpg ├── articles │ ├── classification_example.html │ ├── classification_example_files │ │ ├── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ │ ├── figure-html │ │ │ ├── explanation-1.png │ │ │ └── explanation-2.png │ │ └── header-attrs-2.3 │ │ │ └── header-attrs.js │ ├── index.html │ ├── localModel_methodology.html │ ├── localModel_methodology_files │ │ ├── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ │ └── header-attrs-2.3 │ │ │ └── header-attrs.js │ ├── regression_example.html │ └── regression_example_files │ │ ├── accessible-code-block-0.0.1 │ │ └── empty-anchor.js │ │ ├── figure-html │ │ └── plot-1.png │ │ └── header-attrs-2.3 │ │ └── header-attrs.js ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── contributing.html ├── dalexverse-2.css ├── dalexverse.css ├── docsearch.css ├── docsearch.js ├── drwhy_znak_violet.jpg ├── drwhy_znak_violet_small.jpg ├── drwhylogo.jpg ├── index.html ├── jquery.sticky-kit.min.js ├── link.svg ├── news │ └── index.html ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── gaussian_kernel-1.png │ ├── gaussian_kernel.html │ ├── grapes-colon-colon-colon-grapes.html │ ├── identity_kernel-1.png │ ├── identity_kernel.html │ ├── index.html │ ├── individual_surrogate_model-1.png │ ├── individual_surrogate_model.html │ ├── localModel.html │ ├── model_type.html │ ├── plot.local_surrogate_explainer-1.png │ ├── plot.local_surrogate_explainer.html │ ├── plot_interpretable_feature.html │ ├── predict_model.html │ ├── predict_surrogate.html │ ├── print.local_surrogate_explainer-1.png │ └── print.local_surrogate_explainer.html └── tocBullet.svg ├── localModel.Rproj ├── man ├── gaussian_kernel.Rd ├── identity_kernel.Rd ├── individual_surrogate_model.Rd ├── localModel.Rd ├── plot.local_surrogate_explainer.Rd ├── plot_interpretable_feature.Rd └── print.local_surrogate_explainer.Rd ├── tests ├── testthat.R └── testthat │ ├── setup-xyz.R │ ├── teardown-xyz.R │ ├── test_generic.R │ ├── test_individual_surrogate_model.R │ ├── test_interpretable_inputs.R │ ├── test_main.R │ └── test_transform_to_interpretable.R └── vignettes ├── classification_example.Rmd ├── localModel_methodology.Rmd └── regression_example.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^codecov\.yml$ 5 | ^\.pkgdown\.yml$ 6 | ^\docs$ 7 | ^docs$ 8 | ^_pkgdown\.yml$ 9 | ^paper/* 10 | ^contributing\.md 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | sudo: false 5 | cache: packages 6 | warnings_are_errors: false 7 | 8 | r_github_packages: 9 | - ModelOriented/DALEX2 10 | - ModelOriented/ceterisParibus2 11 | 12 | after_success: 13 | - Rscript -e 'covr::codecov()' 14 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: localModel 2 | Title: LIME-Based Explanations with Interpretable Inputs Based on Ceteris Paribus Profiles 3 | Version: 0.5 4 | Authors@R: c(person("Przemyslaw", "Biecek", email = "przemyslaw.biecek@gmail.com", role = c("aut", "cre")), 5 | person("Mateusz", "Staniak", role = "aut"), 6 | person("Krystian", "Igras", role = "ctb"), 7 | person("Alicja", "Gosiewska", role = "ctb"), 8 | person("Harel", "Lustiger", role = "ctb"), 9 | person("Willy", "Tadema", role = "ctb") 10 | ) 11 | Maintainer: Przemyslaw Biecek 12 | Description: Local explanations of machine learning models describe, how features contributed to a single prediction. 13 | This package implements an explanation method based on LIME 14 | (Local Interpretable Model-agnostic Explanations, 15 | see Tulio Ribeiro, Singh, Guestrin (2016) ) in which interpretable 16 | inputs are created based on local rather than global behaviour of each original feature. 17 | URL: https://github.com/ModelOriented/localModel 18 | BugReports: https://github.com/ModelOriented/localModel/issues 19 | Depends: R (>= 3.5) 20 | License: GPL 21 | Encoding: UTF-8 22 | LazyData: true 23 | Imports: glmnet, 24 | DALEX, 25 | ggplot2, 26 | partykit, 27 | ingredients 28 | RoxygenNote: 7.1.1 29 | Suggests: covr, 30 | knitr, 31 | rmarkdown, 32 | randomForest, 33 | testthat 34 | VignetteBuilder: knitr 35 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(plot,local_surrogate_explainer) 4 | S3method(print,local_surrogate_explainer) 5 | export(gaussian_kernel) 6 | export(identity_kernel) 7 | export(individual_surrogate_model) 8 | export(plot_interpretable_feature) 9 | import(ggplot2) 10 | importFrom(DALEX,colors_breakdown_drwhy) 11 | importFrom(DALEX,theme_drwhy) 12 | importFrom(ggplot2,aes) 13 | importFrom(ggplot2,geom_line) 14 | importFrom(ggplot2,geom_point) 15 | importFrom(ggplot2,ggplot) 16 | importFrom(stats,as.formula) 17 | importFrom(stats,coef) 18 | importFrom(stats,model.matrix) 19 | importFrom(stats,predict) 20 | importFrom(stats,reorder) 21 | importFrom(utils,head) 22 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # localModel 0.5 2 | 3 | * `predict_surrogate` function is added to provide easier interface of accessing lime/iml/localModel implementations of the LIME method. (this function wes moved to `DALEXtra`) 4 | 5 | # localModel 0.4.1 6 | 7 | * Fixes the bug in handling the case where all extracted variables are constant 8 | * Changed default theme for plots, more consistent with other DrWhy packages 9 | 10 | # localModel 0.4.0 11 | 12 | * Add `plot_interpretable_feature` function to plot discretizations. 13 | * Fixes bugs related to feature extraction. 14 | * Changes the method of sampling new observations (features to permute are chosen from Beroulli distribution). 15 | 16 | # localModel 0.3.11 17 | 18 | * Changes dependencies: `DALEX2` to `DALEX`, `ceterisParibus2` to `ingredients`. 19 | 20 | # localModel 0.3.10 21 | 22 | * Vignettes updated. In particular, seed is now set before using randomForest. 23 | * Plot function now shows a message and returns and empty plot if all effects 24 | are equal to 0. 25 | * dev.ratio from glmnet is now returned as one of the columns of surrogate_model_explainer. 26 | 27 | # localModel 0.3.9 28 | 29 | * Explanation models is now fitted to raw predictions, not difference between predictions and model mean. 30 | 31 | # localModel 0.3.8 32 | 33 | * Fixed bug related to one-colum model matrix in glmnet. 34 | 35 | # localModel 0.3.7 36 | 37 | * Bug fixed in example. 38 | 39 | # localModel 0.3.6 40 | 41 | * Improved examples. 42 | 43 | # localModel 0.3.5 44 | 45 | * Fixed issues with examples. 46 | * Improved documentation. 47 | 48 | # localModel 0.3.4 49 | 50 | * Fixed a bug related to interpretable numerical inputs (missing as.numeric) 51 | * Added tests for 100% coverage. 52 | 53 | # localModel 0.3.3 54 | 55 | * Set.seed before fitting cv.glmnet. 56 | * New vignette describing methodology. 57 | 58 | # localModel 0.3.2 59 | 60 | * Weighting option added. 61 | * Improved documentation. 62 | 63 | # localModel 0.3.1 64 | 65 | * Improvements to plots 66 | 67 | # localModel 0.3.0 68 | 69 | * Multi-model plot option added. 70 | * `stringr` dependency removed. 71 | 72 | # localModel 0.2.9 73 | 74 | * Same interpretable features are used for every response level. 75 | * `rpart` dependency replaced with `partykit`. 76 | 77 | # localModel 0.2.8 78 | 79 | * Print method added. 80 | 81 | # localModel 0.2.7 82 | 83 | * Updates link in README. 84 | 85 | # localModel 0.2.6 86 | 87 | * Adds help for the package. 88 | 89 | # localModel 0.2.5 90 | 91 | * Fixes bug related to number of levels when sampling new observation. 92 | 93 | # localModel 0.2.4 94 | 95 | * Sets seed in tests. 96 | 97 | # localModel 0.2.3 98 | 99 | * Minor updates to documentation (including paper/paper.md) and unit tests. 100 | 101 | # localModel 0.2.2 102 | 103 | * Fixed bugs related to column names in case when response in is new_observation. 104 | * Unit tests added. 105 | 106 | # localModel 0.2.1 107 | 108 | * Vignettes added. 109 | 110 | # localModel 0.2.0 111 | 112 | * Major changes: interpretable features are created using decision trees-based discretization. 113 | * Now-unnecessary dependencies dropped. 114 | * Plots are now drawn around intercept. 115 | 116 | # localModel 0.1.2 117 | 118 | * Fixes bug in observation enconding. 119 | * Improves and simplifies encoding. 120 | * Improves plot. 121 | 122 | # localModel 0.1.1 123 | 124 | * Improved plot labels. 125 | 126 | # localModel 0.1 127 | 128 | * Added possibility of coding variables as (baseline / lower or higher) or ( baseline / lower / higher). 129 | * Added option to skip loess smoothing. 130 | * Removed option of setting `family` argument in glmnet. 131 | 132 | # localModel 0.0.0.9000 133 | 134 | * Added a `NEWS.md` file to track changes to the package. 135 | -------------------------------------------------------------------------------- /R/helper_functions.R: -------------------------------------------------------------------------------- 1 | calculate_weights <- function(simulated_dataset, new_observation, kernel) { 2 | for_weights_x <- rbind(simulated_dataset, new_observation) 3 | for_weights <- cbind(y = 1, for_weights_x) 4 | 5 | model_matrix <- stats::model.matrix(stats::lm(y ~., data = for_weights)) 6 | new_observation_coords <- model_matrix[nrow(model_matrix), ] 7 | sapply(as.data.frame(t(model_matrix[1:(nrow(model_matrix) - 1), ])), 8 | function(x) kernel(new_observation_coords, x)) 9 | } 10 | 11 | 12 | #' @importFrom utils head 13 | assign_target_names <- function(x) { 14 | try_predict <- x$predict_function(x$model, head(x$data)) 15 | predicted_names <- colnames(try_predict) 16 | if(is.null(predicted_names)) 17 | predicted_names <- "yhat" 18 | predicted_names 19 | } 20 | 21 | remove_redundant_columns <- function(simulated_data) { 22 | simulated_data[, vapply(simulated_data, 23 | function(col) length(unique(col)) > 1, 24 | logical(1)), 25 | drop = FALSE] 26 | } 27 | 28 | set_explainer_attributes <- function(explainer, x, new_observation, interpretable_features) { 29 | attr(explainer, "new_observation") <- new_observation 30 | attr(explainer, "interpretable_features") <- interpretable_features 31 | attr(explainer, "prediction") <- predict(x, new_observation) 32 | explainer$model <- x$label 33 | class(explainer) <- c("local_surrogate_explainer", class(explainer)) 34 | explainer 35 | } 36 | 37 | 38 | prepare_model_matrix <- function(x, simulated_data) { 39 | simulated_data[["y"]] <- 1 40 | model_mean <- mean(x$predict_function(x$model, x$data)) 41 | 42 | model_matrix <- model.matrix(y ~ ., 43 | data = simulated_data)[, -1, drop = FALSE] 44 | if(ncol(model_matrix) == 1) { 45 | model_matrix <- cbind(zero = 0, model_matrix) 46 | } 47 | model_matrix 48 | } 49 | -------------------------------------------------------------------------------- /R/kernels.R: -------------------------------------------------------------------------------- 1 | #' LIME kernel that treats all observations as equally similar to the observation of interest. 2 | #' 3 | #' Kernels are meant to be used as an argument to individual_surrogate_model function. 4 | #' Other custom functions can be used. Such functions take two vectors and 5 | #' return a single number. 6 | #' 7 | #' @param explained_instance explained instance 8 | #' @param simulated_instance new observation 9 | #' 10 | #' @return numeric 11 | #' 12 | #' @export 13 | #' 14 | #' @examples 15 | #' library(DALEX) 16 | #' library(randomForest) 17 | #' library(localModel) 18 | #' data('apartments') 19 | #' mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 20 | #' explainer <- explain(model = mrf, 21 | #' data = apartments[, -1]) 22 | #' model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 23 | #' size = 500, seed = 17, 24 | #' kernel = identity_kernel) 25 | #' # In this case each simulated observation has equal weight 26 | #' # when explanation model (LASSO) is fitted. 27 | #' model_lok 28 | #' plot(model_lok) 29 | #' 30 | 31 | identity_kernel <- function(explained_instance, simulated_instance) { 32 | 1 33 | } 34 | 35 | 36 | #' LIME kernel from the original article with sigma = 1. 37 | #' 38 | #' Since only binary features are used, the weight associated with an observation 39 | #' is simply exp(-\{number of features that were changed compared to the original observation\}). 40 | #' Kernels are meant to be used as an argument to individual_surrogate_model function. 41 | #' Other custom functions can be used. Such functions take two vectors and 42 | #' return a single number. 43 | #' 44 | #' @param explained_instance explained instance 45 | #' @param simulated_instance new observation 46 | #' 47 | #' @return numeric 48 | #' 49 | #' @export 50 | #' 51 | #' @examples 52 | #' library(DALEX) 53 | #' library(randomForest) 54 | #' library(localModel) 55 | #' data('apartments') 56 | #' mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 57 | #' explainer <- explain(model = mrf, 58 | #' data = apartments[, -1]) 59 | #' model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 60 | #' size = 500, seed = 17, 61 | #' kernel = gaussian_kernel) 62 | #' # In this case each simulated observation has weight 63 | #' # that is small when the distance from original observation is large, 64 | #' # so closer observation have more weight. 65 | #' model_lok 66 | #' plot(model_lok) 67 | #' 68 | 69 | gaussian_kernel <- function(explained_instance, simulated_instance) { 70 | exp(-sum((explained_instance - simulated_instance)^2)) 71 | } 72 | -------------------------------------------------------------------------------- /R/localModel.R: -------------------------------------------------------------------------------- 1 | #' localModel: LIME-like explanations with interpretable features based on Ceteris Paribus profiles 2 | #' 3 | #' This package implements LIME-like explanation method 4 | #' (see Tulio Ribeiro, Singh, Guestrin (2016) ) in which interpretable 5 | #' inputs are created based on local rather than global behaviour of each original feature.#' 6 | #' 7 | #' @section Important functions: 8 | #' \code{\link{individual_surrogate_model}} generates an explanation for a single prediction with 9 | #' interpretable features based on Ceteris Paribus profiles. 10 | #' \code{\link{plot.local_surrogate_explainer}} plots the explanation. 11 | #' 12 | #' @docType package 13 | #' @name localModel 14 | NULL 15 | -------------------------------------------------------------------------------- /R/local_surrogate.R: -------------------------------------------------------------------------------- 1 | #' @importFrom stats as.formula coef model.matrix 2 | single_column_surrogate <- function(x, new_observation, simulated_data, 3 | to_predict, size, seed, weights, 4 | sampling = "uniform") { 5 | 6 | predicted_scores <- x$predict_function(x$model, to_predict) 7 | 8 | model_matrix <- prepare_model_matrix(x, simulated_data) 9 | 10 | if(!is.null(seed)) set.seed(seed) 11 | fitted_model <- glmnet::cv.glmnet(model_matrix, 12 | predicted_scores, 13 | alpha = 1, weights = weights) 14 | result <- as.data.frame(as.matrix(coef(fitted_model, lambda = "lambda.min"))) 15 | result$variable <- rownames(result) 16 | rownames(result) <- NULL 17 | result <- result[result$variable != "zero", ] 18 | colnames(result)[1] <- "estimated" 19 | 20 | for(row_number in 2:nrow(result)) { 21 | result[row_number, "variable"] <- substr(result[row_number, "variable"], 22 | nchar(colnames(simulated_data)[row_number - 1]) + 1, 23 | nchar(result[row_number, "variable"])) 24 | } 25 | 26 | result <- rbind( 27 | data.frame(estimated = mean(x$predict_function(x$model, x$data)), 28 | variable = "(Model mean)"), 29 | result 30 | ) 31 | result$original_variable <- "" 32 | for(i in 3:nrow(result)) { 33 | result[i, "original_variable"] <- colnames(new_observation)[ 34 | sapply(colnames(new_observation), function(c) grepl(c, result[i, "variable"]))] 35 | } 36 | 37 | correct_lambda <- which(fitted_model$glmnet.fit$lambda == fitted_model$lambda.min) 38 | result$dev_ratio <- fitted_model$glmnet.fit$dev.ratio[correct_lambda] 39 | result 40 | } 41 | 42 | 43 | #' LIME-like explanations based on Ceteris Paribus curves 44 | #' 45 | #' This function fits a LIME-type explanation of a single prediction. 46 | #' Interpretable binary features that describe the local impact of features on 47 | #' the prediction are created based on Ceteris Paribus Profiles. 48 | #' Thend, a new dataset of similar observations is created and black box model 49 | #' predictions (scores in case of classification) are calculated for this dataset 50 | #' and LASSO regression model is fitted to them. 51 | #' This way, explanations are simplified and include only the most important features. 52 | #' More details about the methodology can be found in the vignettes. 53 | #' 54 | #' @param x an explainer created with the function DALEX::explain(). 55 | #' @param new_observation an observation to be explained. Columns in should correspond to columns in the data argument to x. 56 | #' @param size number of similar observation to be sampled. 57 | #' @param seed If not NULL, seed will be set to this value for reproducibility. 58 | #' @param kernel Kernel function which will be used to weight simulated observations. 59 | #' @param sampling Parameter that controls sampling while creating new observations. 60 | #' @param ... Additional arguments that will be passed to ingredients::ceteris_paribus. 61 | #' 62 | #' @return data.frame of class local_surrogate_explainer 63 | #' 64 | #' @export 65 | #' 66 | #' @examples 67 | #' # Example based on apartments data from DALEX package. 68 | #' library(DALEX) 69 | #' library(randomForest) 70 | #' library(localModel) 71 | #' data('apartments') 72 | #' mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 73 | #' explainer <- explain(model = mrf, 74 | #' data = apartments[, -1]) 75 | #' model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 76 | #' size = 500, seed = 17) 77 | #' model_lok 78 | #' plot(model_lok) 79 | #' 80 | 81 | individual_surrogate_model <- function(x, new_observation, size, seed = NULL, 82 | kernel = identity_kernel, 83 | sampling = "uniform", ...) { 84 | 85 | # Prepare the data 86 | x$data <- x$data[, intersect(colnames(x$data), colnames(new_observation)), drop = F] 87 | predicted_names <- assign_target_names(x) 88 | 89 | # Create interpretable features 90 | feature_representations_full <- get_feature_representations(x, new_observation, 91 | predicted_names, seed, ...) 92 | discretizations <- lapply(feature_representations_full, function(x) x[[2]]) 93 | names(discretizations) <- colnames(x$data) 94 | encoded_data <- transform_to_interpretable(x, new_observation, 95 | feature_representations_full) 96 | 97 | # Generate similar observations 98 | simulated_data <- create_neighbourhood(encoded_data, size, sampling, seed) 99 | 100 | # Transform back to feature space so the predictions can be obtained 101 | to_predict <- transform_from_interpretable(x, new_observation, 102 | simulated_data, encoded_data, 103 | size, seed) 104 | 105 | # Prepare to fit linear model 106 | simulated_data <- remove_redundant_columns(simulated_data) 107 | if(ncol(simulated_data) == 0) { 108 | explainer <- data.frame( 109 | estimated = NA, 110 | variable = NA, 111 | original_variable = NA, 112 | dev_ratio = NA, 113 | response = NA, 114 | predicted_value = NA, 115 | model = NA 116 | ) 117 | } else { 118 | # Fit linear model to each target dimension, combine the results 119 | instance <- data.frame(lapply(simulated_data, function(c) levels(c)[2])) 120 | weights <- calculate_weights(simulated_data, instance, kernel) 121 | explainer <- combine_explanations(x, new_observation, simulated_data, 122 | to_predict, size, seed, weights, sampling) 123 | } 124 | 125 | set_explainer_attributes(explainer, x, new_observation, discretizations) 126 | } 127 | 128 | 129 | -------------------------------------------------------------------------------- /R/neighbourhood.R: -------------------------------------------------------------------------------- 1 | create_neighbourhood <- function(encoded_data, size, sampling, seed) { 2 | p <- ncol(encoded_data) 3 | simulated_data <- as.data.frame( 4 | lapply(encoded_data, 5 | function(column) { 6 | as.character(rep(levels(column)[max(1, length(levels(column)))], size)) 7 | }), stringsAsFactors = FALSE) 8 | 9 | probs <- lapply(encoded_data, 10 | function(column) { 11 | as.data.frame(prop.table(table(column)))$Freq 12 | }) 13 | 14 | if(!is.null(seed)) set.seed(seed) 15 | for(row_number in 1:size) { 16 | if(sampling == "uniform") { 17 | change <- which(sample(c(TRUE, FALSE), size = p, replace = TRUE, prob = rep(0.5, 2))) 18 | if(length(change) == 0) { 19 | change <- sample(1:p, 1) 20 | } 21 | simulated_data[row_number, change] <- "baseline" 22 | } else { 23 | change <- which(sample(c(TRUE, FALSE), size = p, replace = TRUE, prob = rep(0.5, 2))) 24 | for(index in change) { 25 | simulated_data[row_number, index] <- sample( 26 | levels(encoded_data[, index]), 27 | size = 1, 28 | prob = probs[[index]]) 29 | } 30 | } 31 | } 32 | 33 | # TODO: wydzielić z tego funkcję, żeby przetestować te poziomy 34 | for(col_number in 1:p) { 35 | new_levels <- levels(encoded_data[, col_number]) 36 | new_levels <- new_levels[c(which(new_levels == "baseline"), 37 | which(new_levels != "baseline"))] 38 | simulated_data[, col_number] <- factor( 39 | simulated_data[, col_number], 40 | levels = new_levels 41 | ) 42 | } 43 | 44 | simulated_data 45 | } 46 | 47 | combine_explanations <- function(x, new_observation, simulated_data, 48 | to_predict, size, seed, weights, sampling) { 49 | try_predict <- x$predict_function(x$model, head(x$data)) 50 | 51 | if(!is.null(ncol(try_predict))) { 52 | explainer <- lapply(unique(colnames(try_predict)), function(unique_level) { 53 | internal_explainer <- x 54 | internal_explainer$predict_function <- function(model, newdata) { 55 | x$predict_function(model, newdata)[, unique_level] 56 | 57 | } 58 | result <- single_column_surrogate(internal_explainer, 59 | new_observation, 60 | simulated_data, to_predict, 61 | size, seed, 62 | weights, sampling) 63 | result[, "response"] <- unique_level 64 | result[, "predicted_value"] <- internal_explainer$predict_function( 65 | internal_explainer$model, 66 | new_observation 67 | ) 68 | result 69 | }) 70 | explainer <- do.call("rbind", explainer) 71 | } else { 72 | explainer <- single_column_surrogate( 73 | x, new_observation, 74 | simulated_data, to_predict, 75 | size, seed, weights, sampling 76 | ) 77 | explainer[["response"]] <- "" 78 | explainer[["predicted_value"]] <- x$predict_function( 79 | x$model, 80 | new_observation 81 | ) 82 | } 83 | explainer 84 | } 85 | 86 | get_feature_representations <- function(x, new_observation, 87 | predicted_names, seed, ...) { 88 | if(!is.null(seed)) set.seed(seed) 89 | lapply(colnames(x$data), 90 | function(column) { 91 | feature_representation(x, 92 | new_observation, 93 | column, 94 | predicted_names, 95 | ...) 96 | } 97 | ) 98 | } 99 | 100 | transform_to_interpretable <- function(x, new_observation, 101 | feature_representations) { 102 | encoded_data <- as.data.frame(lapply(feature_representations, 103 | function(x) x[[1]])) 104 | colnames(encoded_data) <- intersect(colnames(x$data), 105 | colnames(new_observation)) 106 | encoded_data 107 | } 108 | 109 | transform_from_interpretable <- function(x, new_observation, 110 | simulated_data, 111 | encoded_data, size, seed) { 112 | n_rows <- nrow(encoded_data) 113 | if(!is.null(seed)) set.seed(seed) 114 | to_predict <- data.frame( 115 | lapply(colnames(simulated_data), 116 | function(column) { 117 | how_many_baselines <- sum(simulated_data[, column] == "baseline") 118 | baseline_indices <- which(encoded_data[, column] == "baseline") 119 | if(is.numeric(x$data[, column])) { 120 | ifelse(simulated_data[, column] == "baseline", 121 | sample(x$data[baseline_indices, column], 122 | how_many_baselines, 123 | replace = TRUE), 124 | rep(new_observation[, column], size - how_many_baselines) 125 | ) 126 | } else { 127 | ifelse(simulated_data[, column] == "baseline", 128 | as.character(sample(x$data[baseline_indices, column], 129 | how_many_baselines, 130 | replace = TRUE)), 131 | as.character(rep(new_observation[, column], 132 | size - how_many_baselines)) 133 | ) 134 | } 135 | })) 136 | colnames(to_predict) <- colnames(simulated_data) 137 | for(colname in colnames(simulated_data)) { 138 | if(is.numeric(x$data[, colname])) { 139 | to_predict[, colname] <- as.numeric(to_predict[, colname]) 140 | } else { 141 | to_predict[, colname] <- factor(to_predict[, colname], 142 | levels = levels(x$data[, colname])) 143 | } 144 | } 145 | to_predict 146 | } 147 | -------------------------------------------------------------------------------- /R/plot_print.R: -------------------------------------------------------------------------------- 1 | #' Generic plot function for local surrogate explainers 2 | #' 3 | #' @param x object of class local_surrogate_explainer 4 | #' @param ... other objects of class local_surrogate_explainer. 5 | #' If provided, models will be plotted in rows, response levels in columns. 6 | #' @param geom If "point", lines with points at the end will be plotted, 7 | #' if "bar", bars will be plotted and if "arrow", arrows. 8 | #' 9 | #' @import ggplot2 10 | #' 11 | #' @importFrom stats reorder 12 | #' @importFrom DALEX colors_breakdown_drwhy theme_drwhy 13 | #' 14 | #' @export 15 | #' 16 | #' @examples 17 | #' # Example based on apartments data from DALEX package. 18 | #' library(DALEX) 19 | #' library(randomForest) 20 | #' library(localModel) 21 | #' data('apartments') 22 | #' mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 23 | #' explainer <- explain(model = mrf, 24 | #' data = apartments[, -1]) 25 | #' model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 26 | #' size = 500, seed = 17) 27 | #' model_lok 28 | #' plot(model_lok) 29 | #' 30 | 31 | plot.local_surrogate_explainer <- function(x, ..., geom = "bar") { 32 | variable <- estimated <- intercept <- NULL 33 | 34 | models <- do.call("rbind", c(list(x), list(...))) 35 | 36 | if (all(models$estimated[models$original_variable != ""] == 0)) { 37 | message("All estimated feature effects are equal to 0.") 38 | return(ggplot()) 39 | } 40 | 41 | models$labeller <- paste( 42 | models$model, 43 | "prediction: ", 44 | round(models$predicted_value, 2) 45 | ) 46 | 47 | models <- do.call("rbind", by( 48 | models, 49 | models$response, 50 | function(y) { 51 | y$labeller <- paste( 52 | unique(y$response), 53 | paste(unique(y$labeller), 54 | sep = "\n ", collapse = "\n "), 55 | sep = "\n ") 56 | y 57 | } 58 | )) 59 | 60 | models$sign <- as.factor(as.character(sign(models$estimated))) 61 | models <- models[models$variable != "(Intercept)", ] 62 | 63 | models <- do.call("rbind", by( 64 | models, 65 | list(models$model, models$response), 66 | function(y) { 67 | y$intercept <- y$estimated[y$variable == "(Model mean)"] 68 | y 69 | } 70 | )) 71 | 72 | models <- do.call("rbind", by( 73 | models, 74 | list(models$model, models$response), 75 | function(y) { 76 | y$estimated = ifelse( 77 | y$variable == "(Model mean)", 78 | y$estimated, 79 | y$estimated + y$intercept 80 | ) 81 | y 82 | } 83 | )) 84 | 85 | models <- do.call("rbind", by( 86 | models, 87 | list(models$original_variable), 88 | function(y) { 89 | y$all_zero <- all(y$sign == 0) 90 | y 91 | } 92 | )) 93 | 94 | models <- models[!models$all_zero, ] 95 | models <- models[models$variable != "(Model mean)", ] 96 | 97 | if(geom == "point") { 98 | final_geom <- geom_pointrange(aes(ymin = intercept, ymax = estimated), 99 | size = 2) 100 | } else if(geom == "bar") { 101 | final_geom <- geom_segment(aes(xend = variable, yend = intercept, y = estimated), 102 | size = 10, lineend = "butt") 103 | } else { 104 | final_geom <- geom_segment(aes(xend = variable, yend = intercept, y = estimated), 105 | size = 2, 106 | arrow = arrow(length=unit(0.20,"cm"), 107 | ends="first", type = "closed")) 108 | } 109 | 110 | ggplot(models, aes(x = reorder(variable, -abs(estimated)), 111 | y = estimated, 112 | color = sign)) + 113 | theme_bw() + 114 | geom_hline(aes(yintercept = intercept), 115 | size = 1) + 116 | facet_grid(model~labeller, scales = "free_y") + 117 | final_geom + 118 | coord_flip() + 119 | ylab("Feature influence") + 120 | xlab("") + 121 | scale_color_manual(values = DALEX::colors_breakdown_drwhy()) + 122 | guides(color = "none") + 123 | DALEX::theme_drwhy() 124 | } 125 | 126 | 127 | #' Generic print function for local surrogate explainers 128 | #' 129 | #' @param x object of class local_surrogate_explainer 130 | #' @param ... currently ignored 131 | #' 132 | #' @export 133 | #' 134 | #' @importFrom utils head 135 | #' 136 | #' @examples 137 | #' # Example based on apartments data from DALEX package. 138 | #' library(DALEX) 139 | #' library(randomForest) 140 | #' library(localModel) 141 | #' data('apartments') 142 | #' mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 143 | #' explainer <- explain(model = mrf, 144 | #' data = apartments[, -1]) 145 | #' model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 146 | #' size = 500, seed = 17) 147 | #' plot(model_lok) 148 | #' model_lok 149 | #' 150 | 151 | print.local_surrogate_explainer <- function(x, ...) { 152 | print(head(as.data.frame(x))) 153 | } 154 | 155 | #' Plot Ceteris Paribus Profile and discretization 156 | #' 157 | #' @param x local_surrogate_explainer object 158 | #' @param variable chr, name of the variable to be plotted 159 | #' 160 | #' @return ggplot2 object 161 | #' 162 | #' @importFrom DALEX theme_drwhy 163 | #' @importFrom ggplot2 ggplot geom_line aes geom_point 164 | #' 165 | #' @export 166 | #' 167 | 168 | plot_interpretable_feature <- function(x, variable) { 169 | value <- output <- output_disc <- NULL 170 | 171 | observation <- attr(x, "new_observation") 172 | prediction <- attr(x, "prediction") 173 | true_point <- data.frame(observation[[variable]], prediction) 174 | colnames(true_point) <- c("variable", "value") 175 | df <- attr(x, "interpretable_features")[[variable]] 176 | df_no_disc <- df[!grepl("discretization", df$output), ] 177 | df_disc <- df[grepl("discretization", df$output), ] 178 | df_disc$output_disc <- paste0(df_disc$output, df_disc$value) 179 | 180 | if (is.numeric(df$variable)) { 181 | pl <- ggplot(df_no_disc, 182 | aes(x = variable, y = value, color = output, 183 | group = output)) + 184 | geom_line(size = 1.5) + 185 | geom_line(data = df_disc, 186 | aes(x = variable, y = value, 187 | color = output, group = output_disc), 188 | inherit.aes = FALSE, 189 | size = 1.5) + 190 | geom_point(data = true_point, aes(x = variable, y = value), 191 | inherit.aes = FALSE, size = 2) 192 | } else { 193 | pl <- ggplot(df_no_disc, 194 | aes(x = variable, y = mean(value), color = output, 195 | group = output)) + 196 | geom_point(size = 1.5) + 197 | geom_line(data = df_disc, 198 | aes(x = variable, y = value, 199 | color = output, group = output_disc), 200 | inherit.aes = FALSE, 201 | size = 1.5) + 202 | geom_point(data = true_point, aes(x = variable, y = value), 203 | inherit.aes = FALSE, size = 2) 204 | } 205 | 206 | pl + DALEX::theme_drwhy() 207 | } 208 | 209 | -------------------------------------------------------------------------------- /R/tree_discretizer.R: -------------------------------------------------------------------------------- 1 | extract_numerical_feature <- function(rules, true_value) { 2 | rules_df <- as.data.frame(do.call("rbind", strsplit(rules, " ")), 3 | stringsAsFactors = FALSE) 4 | if(ncol(rules_df) == 7) { 5 | rules_df[, 3] <- as.numeric(rules_df[, 3]) 6 | rules_df[, 7] <- as.numeric(rules_df[, 7]) 7 | for(row_number in 1:nrow(rules_df)) { 8 | if(rules_df[row_number, 2] != rules_df[row_number, 6]) { 9 | lower_limit <- min(rules_df[row_number, 3], rules_df[row_number, 7]) 10 | upper_limit <- max(rules_df[row_number, 3], rules_df[row_number, 7]) 11 | if(lower_limit < true_value & true_value <= upper_limit) { 12 | label <- paste(c(round(lower_limit, 2), 13 | "<", rules_df[1, 1], 14 | "<=", round(upper_limit, 2)), 15 | sep = " ", collapse = " ") 16 | lower <- lower_limit 17 | upper <- upper_limit 18 | break 19 | } 20 | } else { 21 | if(rules_df[row_number, 2] == "<=") { 22 | upper_limit <- min(rules_df[row_number, 3], rules_df[row_number, 7]) 23 | if(true_value <= upper_limit) { 24 | label <- paste(c(rules_df[1, 1], "<=", round(upper_limit, 2)), 25 | sep = " ", collapse = " ") 26 | lower <- -Inf 27 | upper <- upper_limit 28 | break 29 | } 30 | } else { 31 | lower_limit <- max(rules_df[row_number, 3], rules_df[row_number, 7]) 32 | if(true_value > lower_limit) { 33 | label <- paste(c(rules_df[1, 1], ">", round(lower_limit, 2)), 34 | sep = " ", collapse = " ") 35 | lower <- lower_limit 36 | upper <- Inf 37 | } 38 | } 39 | } 40 | } 41 | } else { 42 | if(true_value <= as.numeric(rules_df[1, 3])) { 43 | label <- paste(rules_df[1, 1], "<=", round(as.numeric(rules_df[1, 3]), 2), 44 | sep = " ", collapse = " ") 45 | lower <- -Inf 46 | upper <- as.numeric(rules_df[1, 3]) 47 | } else { 48 | label <- paste(rules_df[1, 1], ">", round(as.numeric(rules_df[1, 3]), 2), 49 | sep = " ", collapse = " ") 50 | lower <- as.numeric(rules_df[1, 3]) 51 | upper <- Inf 52 | } 53 | } 54 | 55 | list( 56 | "label" = label, 57 | "interval" = as.numeric(c(lower, upper)) 58 | ) 59 | } 60 | 61 | 62 | extract_categorical_feature <- function(rules, true_value, unique_values, 63 | colname) { 64 | # To avoid cases such as matching both "female" and "male" when "male" is expected. 65 | true_value <- paste0("\"", true_value, "\"") 66 | 67 | values <- unique_values[sapply(unique_values, function(value) { 68 | grepl(paste0("\"", value, "\""), rules[which(grepl(true_value, rules))]) })] 69 | 70 | list( 71 | "label" = paste( 72 | colname, "=", 73 | paste(values, sep = ", ", collapse = ", ") 74 | ), 75 | "values" = as.character(values) 76 | ) 77 | } 78 | 79 | #' @author Krystian Igras 80 | 81 | `%:::%` <- function (pkg, name) { 82 | pkg <- as.character(substitute(pkg)) 83 | name <- as.character(substitute(name)) 84 | get(name, envir = asNamespace(pkg), inherits = FALSE) 85 | } 86 | 87 | marginal_relationships <- function(explainer, new_observation, column, predicted_names, is_numerical, ...) { 88 | if(is_numerical) { 89 | ceteris <- ingredients::ceteris_paribus( 90 | explainer, new_observation, ..., 91 | variables = column)[, c(column, "_yhat_", "_label_")] 92 | if(all(predicted_names == "yhat")) { 93 | ceteris_curves <- ceteris 94 | colnames(ceteris_curves)[2] <- "yhat" 95 | } else { 96 | ceteris_curves <- data.frame(lapply(predicted_names, function(name) { 97 | result <- ceteris[ceteris$`_label_` == paste(explainer$label, 98 | name, 99 | sep = "."), 2, 100 | drop = FALSE] 101 | colnames(result) <- name 102 | result 103 | })) 104 | } 105 | ceteris_curves[, column] <- unique(ceteris[, column]) 106 | } else { 107 | ceteris_curves <- as.data.frame( 108 | explainer$predict_function(explainer$model, 109 | explainer$data) 110 | ) 111 | if(ncol(ceteris_curves) == 1) { 112 | colnames(ceteris_curves) <- "yhat" 113 | } 114 | ceteris_curves[, column] <- explainer$data[, column] 115 | } 116 | ceteris_curves 117 | } 118 | 119 | fit_tree <- function(ceteris_curves, predicted_names, column, is_numerical) { 120 | tree_formula <- paste( 121 | paste(predicted_names, sep = " + ", collapse = " + "), 122 | column, 123 | sep = " ~ " 124 | ) 125 | 126 | if(is_numerical) { 127 | max_depth <- 2 128 | } else { 129 | max_depth <- 1 130 | } 131 | 132 | partykit::ctree(as.formula(tree_formula), 133 | data = ceteris_curves, 134 | maxdepth = max_depth) 135 | } 136 | 137 | prepare_rules <- function(fitted_tree, is_numerical) { 138 | extract_rules <- "partykit" %:::% ".list.rules.party" 139 | rules <- extract_rules(fitted_tree) 140 | if(is_numerical) { 141 | rules <- sapply(rules, function(rule) { 142 | if(!grepl("&", rule)) { 143 | paste(rule, rule, sep = " & ", collapse = " & ") 144 | } else { 145 | rule 146 | } 147 | }) 148 | } 149 | rules 150 | } 151 | 152 | #' @importFrom stats predict 153 | 154 | make_discretization_df <- function(ceteris_curves, predicted_names, 155 | fitted_tree, column) { 156 | ceteris_curves <- as.data.frame(ceteris_curves) 157 | if(any(colnames(ceteris_curves) == "_label_")) { 158 | ceteris_curves <- ceteris_curves[, -which(colnames(ceteris_curves) == "_label_")] 159 | } 160 | 161 | if(length(predicted_names) > 1) { 162 | predictions <- predict(fitted_tree, 163 | as.data.frame(list(ceteris_curves[, column]), 164 | col.names = column)) 165 | predictions_names <- paste(colnames(predictions), "discretization", 166 | sep = "_") 167 | discretization <- as.vector(as.matrix(predictions)) 168 | } else { 169 | discretization <- predict(fitted_tree, 170 | as.data.frame(list(ceteris_curves[, column]), 171 | col.names = column)) 172 | predictions_names <- "discretization" 173 | } 174 | 175 | which_column <- which(colnames(ceteris_curves) == column) 176 | output_names <- c(predicted_names, predictions_names) 177 | prepared_yhat <- as.vector(as.matrix(ceteris_curves[, -which_column])) 178 | data.frame( 179 | variable_name = column, 180 | variable = rep(ceteris_curves[, column], 181 | times = length(predicted_names) + length(predictions_names)), 182 | output = rep(output_names, each = nrow(ceteris_curves)), 183 | value = c(prepared_yhat, 184 | discretization) 185 | ) 186 | } 187 | 188 | 189 | 190 | feature_representation <- function(explainer, new_observation, column, 191 | predicted_names, ...) { 192 | is_numerical <- is.numeric(explainer$data[, column]) 193 | 194 | ceteris_curves <- marginal_relationships(explainer, new_observation, column, 195 | predicted_names, is_numerical, ...) 196 | 197 | fitted_tree <- fit_tree(ceteris_curves, predicted_names, column, is_numerical) 198 | rules <- prepare_rules(fitted_tree, is_numerical) 199 | 200 | if(all(rules == " & ") | all(rules == "")) { 201 | encoded_feature <- as.factor(rep("baseline", 202 | nrow(explainer$data))) 203 | } else { 204 | if(is_numerical) { 205 | interpretable_input <- extract_numerical_feature(rules, new_observation[, column]) 206 | encoded_feature <- ifelse(interpretable_input$interval[1] <= explainer$data[, column] 207 | & explainer$data[, column] < interpretable_input$interval[2], 208 | interpretable_input$label, 209 | "baseline") 210 | } else { 211 | interpretable_input <- extract_categorical_feature( 212 | rules, 213 | new_observation[, column], 214 | levels(explainer$data[, column]), 215 | column 216 | ) 217 | encoded_feature <- ifelse( 218 | explainer$data[, column] %in% interpretable_input$values, 219 | interpretable_input$label, 220 | "baseline" 221 | ) 222 | } 223 | } 224 | 225 | list( 226 | factor(encoded_feature, 227 | levels = c("baseline", setdiff(unique(encoded_feature), "baseline"))), 228 | make_discretization_df(ceteris_curves, predicted_names, 229 | fitted_tree, column) 230 | ) 231 | 232 | } 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # localModel: Local Explanations of Machine Learning Models for Tabular Data. 2 | 3 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/localModel)](https://cran.r-project.org/package=localModel) 4 | [![Travis-CI Build Status](https://travis-ci.org/ModelOriented/localModel.svg?branch=master)](https://travis-ci.org/ModelOriented/localModel) 5 | [![Coverage Status](https://img.shields.io/codecov/c/github/ModelOriented/localModel/master.svg)](https://codecov.io/github/ModelOriented/localModel?branch=master) 6 | 7 | 8 | `localModel` is a successor to the `live` package. It implements a variant of LIME method for explaining single predictions of black box machine learning models for tabular data. 9 | Interpretable features are created based on [Ceteris Paribus](https://github.com/ModelOriented/ceterisParibus2) plots. 10 | Details of the methodology are described in the vignette. 11 | `localModel` is currently undergoing rapid changes, including bug fixes, for a stable solution please see the `live` package. 12 | 13 | To get started, install the package from CRAN: 14 | 15 | ``` 16 | install.packages('localModel') 17 | ``` 18 | 19 | 20 | The development version can be installed from GitHub by using the code below. 21 | 22 | ``` 23 | devtools::install_github("r-lib/remotes") 24 | remotes::install_github("ModelOriented/localModel") 25 | ``` 26 | 27 | To get help, see examples and details of the methodology, please refer to package website and vignettes. 28 | 29 | ## Acknowledgments 30 | 31 | Work on this package is financially supported by the NCN Opus grant 2017/27/B/ST6/01307. 32 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | template: 2 | package: DrWhyTemplate 3 | default_assets: false 4 | params: 5 | ganalytics: UA-5650686-14 6 | noindex: true 7 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to survxai 2 | 3 | #### **Did you find a bug?** 4 | 5 | Please follow these rules when reporting bugs: 6 | 7 | * Install to the latest version of [localModel from GitHub](https://github.com/ModelOriented/localModel) and check whether the problem still occurs. 8 | 9 | * **Check, if the bug was not already reported** by searching [Issues](https://github.com/ModelOriented/localModel/issues). 10 | 11 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/ModelOriented/localModel/issues/new). Be sure to include a **title and clear description**, and a **sample code** demonstrating the problem. 12 | 13 | 14 | #### **Did you fix a bug?** 15 | 16 | * Make sure that changes meet the requirements of [The tidyverse style guide](http://style.tidyverse.org) and relevant tests are added. 17 | 18 | * Open a new GitHub pull request with the solution. 19 | 20 | * Ensure the PR clearly describes the problem and solution. Include the relevant issue number if applicable. 21 | 22 | 23 | #### **Do you intend to add a new feature or change an existing one?** 24 | 25 | * Suggest your change in the [Issues](https://github.com/ModelOriented/localModel/issues) and start writing code. 26 | 27 | 28 | Thanks! 29 | 30 | MI2 team 31 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Page not found (404) • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 |
76 |
77 | 139 | 140 | 141 | 142 |
143 | 144 |
145 |
146 | 149 | 150 | Content not found. Please use links in the navbar. 151 | 152 |
153 | 154 | 159 | 160 |
161 | 162 | 163 | 164 |
165 | 168 | 169 |
170 |

Site built with pkgdown.

171 |
172 | 173 |
174 |
175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/MI2logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/MI2logo.jpg -------------------------------------------------------------------------------- /docs/articles/classification_example_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/classification_example_files/figure-html/explanation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/articles/classification_example_files/figure-html/explanation-1.png -------------------------------------------------------------------------------- /docs/articles/classification_example_files/figure-html/explanation-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/articles/classification_example_files/figure-html/explanation-2.png -------------------------------------------------------------------------------- /docs/articles/classification_example_files/header-attrs-2.3/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/articles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Articles • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 |
76 |
77 | 139 | 140 | 141 | 142 |
143 | 144 |
145 |
146 | 149 | 150 |
151 |

All vignettes

152 |

153 | 154 |
155 |
Explaining classification models with localModel package
156 |
157 |
Methodology behind localModel package
158 |
159 |
Introduction to localModel package
160 |
161 |
162 |
163 |
164 |
165 | 166 | 167 |
168 | 171 | 172 |
173 |

Site built with pkgdown.

174 |
175 | 176 |
177 |
178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /docs/articles/localModel_methodology.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Methodology behind localModel package • localModel 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 31 |
32 |
95 | 96 | 97 | 98 | 99 |
100 |
101 | 110 | 111 | 112 | 113 |

localModel package is a variant of LIME. As in LIME, the analysis consists of

114 |
    115 |
  1. Creating a new dataset \(X'\) of \(m\) observations that are similarly in some sense to the observation, for which we explain the prediction. This dataset is usually built in terms of new, interpretable features rather than original features. Size \(m\) is a parameter to individual_surrogate_model function.

  2. 116 |
  3. Fitting black box model to the new dataset. This step requires a transformation back to the original input space and is non-trivial for numerical data.

  4. 117 |
  5. Fitting an explanation model (a glass box) to the outputs of black box model. Usually, LASSO regression is used to make sure that explanations are simple enough.

  6. 118 |
119 |

In the next few paragraph, we will shortly describe how each of the steps is performed in localModel. All of them are implemented by the individual_surrogate_model function.

120 |
121 |

122 | Sampling for local exploration

123 |

Interpretable features are created in a way that depends on the type of the predictor.

124 |
    125 |
  • For categorical predictors, original dataset is used to obtain black box predictions. Then, the marginal relationship between the feature and response is modeled via decision tree with a single split to dichotomize the feature.

  • 126 |
  • For numerical predictors, Ceteris Paribus profile is calculated and this marginal relationship is again dichotomized by a decision tree with maximum depth equal to 2.

  • 127 |
128 |

For both types of predictors, the intepretable input is an indicator variable equal to 1 for value of feature that falls into the group of levels or interval chosen by the decision tree. Other values of the predictor are treated as a single level, a baseline.

129 |

Sampling new observations is done by

130 |
    131 |
  1. Creating \(m\) copies of the explained observation.

  2. 132 |
  3. 133 |

    Iterating through these copies and in each step

    134 |
      135 |
    • Choosing random number \(k\) of feature to be switched to baseline value.
    • 136 |
    • Choosing, which \(k\) features will be changed.
    • 137 |
    • When the individual_surrogate_model function is called with argument sampling = "uniform", each of these values is changed to baseline, but when sampling = "non-uniform", it is changed with probability equal to the proportion of baseline values in the original dataset.
    • 138 |
    139 |
  4. 140 |
141 |
142 |
143 |

144 | Fitting black box model

145 |

Fitting the black box model to new observation requires transforming them into the original feature space. In localModel, this is done the following way. The original dataset is transformed into the interpretable feature space. Based on this transformation, we know which values of each feature are categorized as baseline and which as the explained value. Then, for each simulated observation, and for each feature, we pick a random value of this feature from the original dataset that corresponds either to the baseline group or the explained value. Black box model is fitted to these observations.

146 |
147 |
148 |

149 | Fitting the explanation model

150 |

LASSO model with penalty chosen via cross-validation is used in localModel package. Optionally, observation can be weighted according to their distance from the explained observation in the space of interpretable features. Weighting is controlled via kernel parameter to individual_surrogate_model.

151 |

The resulting model can be plotted using generic plot function. Models can be compared by passing several explainer object to plot.

152 |
153 |
154 | 155 | 160 | 161 |
162 | 163 | 164 | 165 |
168 | 169 |
170 |

Site built with pkgdown.

171 |
172 | 173 |
174 |
175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /docs/articles/localModel_methodology_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/localModel_methodology_files/header-attrs-2.3/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/articles/regression_example_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/regression_example_files/figure-html/plot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/articles/regression_example_files/figure-html/plot-1.png -------------------------------------------------------------------------------- /docs/articles/regression_example_files/header-attrs-2.3/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Authors • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 |
76 |
77 | 139 | 140 | 141 | 142 |
143 | 144 |
145 |
146 | 149 | 150 |
    151 |
  • 152 |

    Mateusz Staniak. Author, maintainer. 153 |

    154 |
  • 155 |
  • 156 |

    Przemyslaw Biecek. Author. 157 |

    158 |
  • 159 |
  • 160 |

    Krystian Igras. Contributor. 161 |

    162 |
  • 163 |
  • 164 |

    Alicja Gosiewska. Contributor. 165 |

    166 |
  • 167 |
  • 168 |

    Harel Lustiger. Contributor. 169 |

    170 |
  • 171 |
172 | 173 |
174 | 175 |
176 | 177 | 178 | 179 |
180 | 183 | 184 |
185 |

Site built with pkgdown.

186 |
187 | 188 |
189 |
190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/contributing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | NA • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 |
    76 |
    77 | 139 | 140 | 141 | 142 |
    143 | 144 |
    145 |
    146 | 149 | 150 |
    151 |

    152 | How to contribute to survxai

    153 |
    154 |

    155 | Did you find a bug? 156 |

    157 |

    Please follow these rules when reporting bugs:

    158 |
      159 |
    • Install to the latest version of localModel from GitHub and check whether the problem still occurs.

    • 160 |
    • Check, if the bug was not already reported by searching Issues.

    • 161 |
    • If you’re unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, and a sample code demonstrating the problem.

    • 162 |
    163 |
    164 |
    165 |

    166 | Did you fix a bug? 167 |

    168 |
      169 |
    • Make sure that changes meet the requirements of The tidyverse style guide and relevant tests are added.

    • 170 |
    • Open a new GitHub pull request with the solution.

    • 171 |
    • Ensure the PR clearly describes the problem and solution. Include the relevant issue number if applicable.

    • 172 |
    173 |
    174 |
    175 |

    176 | Do you intend to add a new feature or change an existing one? 177 |

    178 |
      179 |
    • Suggest your change in the Issues and start writing code.
    • 180 |
    181 |

    Thanks!

    182 |

    MI2 team

    183 |
    184 |
    185 | 186 |
    187 | 188 | 193 | 194 |
    195 | 196 | 197 | 198 |
    199 | 202 | 203 |
    204 |

    Site built with pkgdown.

    205 |
    206 | 207 |
    208 |
    209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /docs/dalexverse-2.css: -------------------------------------------------------------------------------- 1 | body {font-size: 16px; 2 | padding-top: 60px; 3 | } 4 | h1 {font-size: 40px; 5 | font-weight: 500; 6 | } 7 | h2 {font-size: 30px; 8 | font-weight: 500; 9 | } 10 | h3 {font-size: 23px; 11 | font-weight: 500; 12 | } 13 | 14 | 15 | .ref-arguments th {vertical-align: top;} 16 | 17 | /* navbar ----------------------------------------------- */ 18 | 19 | 20 | .navbar .info { 21 | color: #fff; 22 | float: left; 23 | height: 50px; 24 | width: 330px; 25 | font-size: 80%; 26 | position: relative; 27 | margin-left: 5px; 28 | } 29 | 30 | .navbar .info .partof .a { 31 | color: #7abaf5 !important; 32 | } 33 | 34 | .navbar .info .partof { 35 | position: absolute; 36 | top: 0; 37 | } 38 | 39 | .navbar .info .developedby { 40 | position: absolute; 41 | bottom: 0; 42 | right: 0; 43 | } 44 | 45 | .navbar .info .version { 46 | position: absolute; 47 | bottom: 0; 48 | } 49 | .navbar .info .version-danger { 50 | font-weight: bold; 51 | color: orange; 52 | } 53 | 54 | .navbar-form { 55 | margin-top: 3px; 56 | margin-bottom: 0; 57 | } 58 | 59 | .navbar-toggle { 60 | margin-top: 8px; 61 | margin-bottom: 5px; 62 | } 63 | 64 | /* new */ 65 | 66 | nav[data-toggle='toc'] .nav > li > a { 67 | background-color: #4a3c89; 68 | color: #fff; 69 | padding: 7px 12px; 70 | border-radius: 4px; 71 | border: 1px white solid; 72 | font-size: 16px; 73 | font-weight: 400; 74 | } 75 | 76 | nav[data-toggle='toc'] .nav > li > a:hover, 77 | nav[data-toggle='toc'] .nav > li > a:focus { 78 | background-color: #370f54; 79 | color: #fff; 80 | padding: 7px 12px; 81 | border-radius: 4px; 82 | border: 1px white solid; 83 | font-size: 16px; 84 | font-weight: 400; 85 | padding-left: 15px; 86 | border-left: 2px solid #fff; 87 | } 88 | 89 | nav[data-toggle='toc'] .nav > .active > a, 90 | nav[data-toggle='toc'] .nav > .active:hover > a, 91 | nav[data-toggle='toc'] .nav > .active:focus > a { 92 | background-color: #370f54; 93 | color: #fff; 94 | padding: 7px 12px; 95 | border-radius: 4px; 96 | border: 1px white solid; 97 | font-size: 16px; 98 | font-weight: 500; 99 | padding-left: 15px; 100 | border-left: 3px solid #fff; 101 | } 102 | 103 | 104 | nav[data-toggle='toc'] .nav .nav > li > a { 105 | background-color: #4a3c89; 106 | color: #fff; 107 | padding: 7px 12px; 108 | border-radius: 4px; 109 | border: 1px white solid; 110 | font-size: 14px; 111 | font-weight: 400; 112 | } 113 | 114 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 115 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 116 | background-color: #370f54; 117 | color: #fff; 118 | padding: 7px 12px; 119 | border-radius: 4px; 120 | border: 1px white solid; 121 | font-size: 14px; 122 | font-weight: 400; 123 | padding-left: 15px; 124 | border-left: 2px solid #fff; 125 | } 126 | 127 | nav[data-toggle='toc'] .nav .nav > .active > a, 128 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 129 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 130 | background-color: #370f54; 131 | color: #fff; 132 | padding: 7px 12px; 133 | border-radius: 4px; 134 | border: 1px white solid; 135 | font-size: 14px; 136 | font-weight: 500; 137 | padding-left: 15px; 138 | border-left: 3px solid #fff; 139 | } 140 | 141 | /* old */ 142 | .navbar-nav li a { 143 | padding-bottom: 10px; 144 | } 145 | .navbar-default .navbar-nav > .active > a, 146 | .navbar-default .navbar-nav > .active > a:hover, 147 | .navbar-default .navbar-nav > .active > a:focus { 148 | background-color: #370f54 !important; 149 | border-radius: 3px; 150 | } 151 | 152 | .nav-pils>li>a { 153 | position: relative; 154 | display: block; 155 | padding: 10px 15px; 156 | border-radius: 4px; 157 | } 158 | 159 | 160 | 161 | /* footer ------------------------------------------------ */ 162 | 163 | footer { 164 | margin-top: 45px; 165 | padding: 35px 0 36px; 166 | border-top: 1px solid #e5e5e5; 167 | 168 | display: flex; 169 | color: #666; 170 | } 171 | footer p { 172 | margin-bottom: 0; 173 | } 174 | footer .tidyverse { 175 | flex: 1; 176 | margin-right: 1em; 177 | } 178 | footer .author { 179 | flex: 1; 180 | text-align: left; 181 | } 182 | 183 | /* sidebar old ------------------------------------------------ */ 184 | 185 | #sidebar h2 { 186 | font-size: 1.6em; 187 | margin-top: 1em; 188 | margin-bottom: 0.25em; 189 | } 190 | 191 | #sidebar .list-unstyled li { 192 | margin-bottom: 0.5em; 193 | line-height: 1.4; 194 | } 195 | 196 | #sidebar small { 197 | color: #777; 198 | } 199 | 200 | #sidebar .nav { 201 | padding-left: 0px; 202 | list-style-type: none; 203 | color: #5a9ddb; 204 | } 205 | 206 | #sidebar .nav > li.active { 207 | background-position: left -240px; 208 | } 209 | 210 | /*#sidebar a { 211 | padding: 0px; 212 | color: #5a9ddb; 213 | background-color: transparent; 214 | } 215 | */ 216 | /*#sidebar a:hover { 217 | background-color: transparent; 218 | text-decoration: underline; 219 | }*/ 220 | 221 | /* sidebar new ------------------------------------------------ */ 222 | 223 | #pkgdown-sidebar h2 { 224 | font-size: 30px; 225 | margin-top: 1em; 226 | margin-bottom: 0.25em; 227 | } 228 | 229 | #pkgdown-sidebar .list-unstyled li { 230 | margin-bottom: 0.5em; 231 | line-height: 1.4; 232 | } 233 | 234 | #pkgdown-sidebar small { 235 | color: #777; 236 | } 237 | 238 | #pkgdown-sidebar .nav { 239 | padding-left: 0px; 240 | list-style-type: none; 241 | color: #5a9ddb; 242 | } 243 | 244 | #pkgdown-sidebar .nav > li.active { 245 | background-position: left -240px; 246 | } 247 | 248 | /* ---- *\/*----------------------------------*\ 249 | 250 | /* Sticker footer */ 251 | body > .container { 252 | display: flex; 253 | padding-top: 30px; 254 | min-height: calc(100vh); 255 | flex-direction: column; 256 | } 257 | 258 | /* Section anchors ---------------------------------*/ 259 | 260 | 261 | .hasAnchor:hover a.anchor { 262 | visibility: visible; 263 | } 264 | 265 | @media (max-width: 767px) { 266 | .hasAnchor:hover a.anchor { 267 | visibility: hidden; 268 | } 269 | } 270 | 271 | 272 | /* Static header placement on mobile devices */ 273 | @media (max-width: 767px) { 274 | .navbar-fixed-top { 275 | position: absolute; 276 | } 277 | .navbar { 278 | padding: 0; 279 | } 280 | } 281 | 282 | 283 | /* Syntax highlighting ---------------------------------------------------- */ 284 | 285 | 286 | table { 287 | display: block; 288 | overflow: auto; 289 | width: 100% !important; 290 | } 291 | 292 | .navbar-mi2logo { 293 | float: left; 294 | margin-right: 2px; 295 | margin-left: 2px; 296 | margin-bottom: 2px; 297 | margin-top: 2px; 298 | } 299 | 300 | .sidebar-logo { 301 | display:block; 302 | margin-left:auto; 303 | margin-right:auto; 304 | text-align: justify; 305 | } 306 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/drwhy_znak_violet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/drwhy_znak_violet.jpg -------------------------------------------------------------------------------- /docs/drwhy_znak_violet_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/drwhy_znak_violet_small.jpg -------------------------------------------------------------------------------- /docs/drwhylogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/drwhylogo.jpg -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | LIME-Based Explanations with Interpretable Inputs Based on Ceteris Paribus Profiles • localModel 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 33 | 34 | 35 |
    36 |
    99 | 100 | 101 | 102 | 103 |
    104 |
    105 |
    106 | 108 | 109 |

    localModel is a successor to the live package. It implements a variant of LIME method for explaining single predictions of black box machine learning models for tabular data. Interpretable features are created based on Ceteris Paribus plots. Details of the methodology are described in the vignette. localModel is currently undergoing rapid changes, including bug fixes, for a stable solution please see the live package.

    110 |

    To get started, install the package from CRAN:

    111 |
    install.packages('localModel')
    112 |

    The development version can be installe from GitHub by using the code below. Please do not use the devtools package, since it is affected a bug which makes localModel installation impossible. This issue was solved in the Github version of remotes.

    113 |
    devtools::install_github("r-lib/remotes")
    114 | remotes::install_github("ModelOriented/localModel")
    115 |

    To get help, see examples and details of the methodology, please refer to package website and vignettes.

    116 |
    117 |

    118 | Acknowledgments

    119 |

    Work on this package is financially supported by the NCN Opus grant 2017/27/B/ST6/01307.

    120 |
    121 |
    122 |
    123 | 124 | 166 |
    167 | 168 | 169 |
    172 | 173 |
    174 |

    Site built with pkgdown.

    175 |
    176 | 177 |
    178 |
    179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /docs/jquery.sticky-kit.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net 3 | */ 4 | (function(){var b,f;b=this.jQuery||window.jQuery;f=b(window);b.fn.stick_in_parent=function(d){var A,w,J,n,B,K,p,q,k,E,t;null==d&&(d={});t=d.sticky_class;B=d.inner_scrolling;E=d.recalc_every;k=d.parent;q=d.offset_top;p=d.spacer;w=d.bottoming;null==q&&(q=0);null==k&&(k=void 0);null==B&&(B=!0);null==t&&(t="is_stuck");A=b(document);null==w&&(w=!0);J=function(a,d,n,C,F,u,r,G){var v,H,m,D,I,c,g,x,y,z,h,l;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);I=A.height();g=a.parent();null!=k&&(g=g.closest(k)); 5 | if(!g.length)throw"failed to find stick parent";v=m=!1;(h=null!=p?p&&a.closest(p):b("
    "))&&h.css("position",a.css("position"));x=function(){var c,f,e;if(!G&&(I=A.height(),c=parseInt(g.css("border-top-width"),10),f=parseInt(g.css("padding-top"),10),d=parseInt(g.css("padding-bottom"),10),n=g.offset().top+c+f,C=g.height(),m&&(v=m=!1,null==p&&(a.insertAfter(h),h.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(t),e=!0),F=a.offset().top-(parseInt(a.css("margin-top"),10)||0)-q, 6 | u=a.outerHeight(!0),r=a.css("float"),h&&h.css({width:a.outerWidth(!0),height:u,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),e))return l()};x();if(u!==C)return D=void 0,c=q,z=E,l=function(){var b,l,e,k;if(!G&&(e=!1,null!=z&&(--z,0>=z&&(z=E,x(),e=!0)),e||A.height()===I||x(),e=f.scrollTop(),null!=D&&(l=e-D),D=e,m?(w&&(k=e+u+c>C+n,v&&!k&&(v=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),eb&&!v&&(c-=l,c=Math.max(b-u,c),c=Math.min(q,c),m&&a.css({top:c+"px"})))):e>F&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(t),null==p&&(a.after(h),"left"!==r&&"right"!==r||h.append(a)),a.trigger("sticky_kit:stick")),m&&w&&(null==k&&(k=e+u+c>C+n),!v&&k)))return v=!0,"static"===g.css("position")&&g.css({position:"relative"}), 8 | a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")},y=function(){x();return l()},H=function(){G=!0;f.off("touchmove",l);f.off("scroll",l);f.off("resize",y);b(document.body).off("sticky_kit:recalc",y);a.off("sticky_kit:detach",H);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});g.position("position","");if(m)return null==p&&("left"!==r&&"right"!==r||a.insertAfter(h),h.remove()),a.removeClass(t)},f.on("touchmove",l),f.on("scroll",l),f.on("resize", 9 | y),b(document.body).on("sticky_kit:recalc",y),a.on("sticky_kit:detach",H),setTimeout(l,0)}};n=0;for(K=this.length;n 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | position: relative; 22 | } 23 | 24 | body > .container { 25 | display: flex; 26 | height: 100%; 27 | flex-direction: column; 28 | } 29 | 30 | body > .container .row { 31 | flex: 1 0 auto; 32 | } 33 | 34 | footer { 35 | margin-top: 45px; 36 | padding: 35px 0 36px; 37 | border-top: 1px solid #e5e5e5; 38 | color: #666; 39 | display: flex; 40 | flex-shrink: 0; 41 | } 42 | footer p { 43 | margin-bottom: 0; 44 | } 45 | footer div { 46 | flex: 1; 47 | } 48 | footer .pkgdown { 49 | text-align: right; 50 | } 51 | footer p { 52 | margin-bottom: 0; 53 | } 54 | 55 | img.icon { 56 | float: right; 57 | } 58 | 59 | img { 60 | max-width: 100%; 61 | } 62 | 63 | /* Fix bug in bootstrap (only seen in firefox) */ 64 | summary { 65 | display: list-item; 66 | } 67 | 68 | /* Typographic tweaking ---------------------------------*/ 69 | 70 | .contents .page-header { 71 | margin-top: calc(-60px + 1em); 72 | } 73 | 74 | dd { 75 | margin-left: 3em; 76 | } 77 | 78 | /* Section anchors ---------------------------------*/ 79 | 80 | a.anchor { 81 | margin-left: -30px; 82 | display:inline-block; 83 | width: 30px; 84 | height: 30px; 85 | visibility: hidden; 86 | 87 | background-image: url(./link.svg); 88 | background-repeat: no-repeat; 89 | background-size: 20px 20px; 90 | background-position: center center; 91 | } 92 | 93 | .hasAnchor:hover a.anchor { 94 | visibility: visible; 95 | } 96 | 97 | @media (max-width: 767px) { 98 | .hasAnchor:hover a.anchor { 99 | visibility: hidden; 100 | } 101 | } 102 | 103 | 104 | /* Fixes for fixed navbar --------------------------*/ 105 | 106 | .contents h1, .contents h2, .contents h3, .contents h4 { 107 | padding-top: 60px; 108 | margin-top: -40px; 109 | } 110 | 111 | /* Navbar submenu --------------------------*/ 112 | 113 | .dropdown-submenu { 114 | position: relative; 115 | } 116 | 117 | .dropdown-submenu>.dropdown-menu { 118 | top: 0; 119 | left: 100%; 120 | margin-top: -6px; 121 | margin-left: -1px; 122 | border-radius: 0 6px 6px 6px; 123 | } 124 | 125 | .dropdown-submenu:hover>.dropdown-menu { 126 | display: block; 127 | } 128 | 129 | .dropdown-submenu>a:after { 130 | display: block; 131 | content: " "; 132 | float: right; 133 | width: 0; 134 | height: 0; 135 | border-color: transparent; 136 | border-style: solid; 137 | border-width: 5px 0 5px 5px; 138 | border-left-color: #cccccc; 139 | margin-top: 5px; 140 | margin-right: -10px; 141 | } 142 | 143 | .dropdown-submenu:hover>a:after { 144 | border-left-color: #ffffff; 145 | } 146 | 147 | .dropdown-submenu.pull-left { 148 | float: none; 149 | } 150 | 151 | .dropdown-submenu.pull-left>.dropdown-menu { 152 | left: -100%; 153 | margin-left: 10px; 154 | border-radius: 6px 0 6px 6px; 155 | } 156 | 157 | /* Sidebar --------------------------*/ 158 | 159 | #pkgdown-sidebar { 160 | margin-top: 30px; 161 | position: -webkit-sticky; 162 | position: sticky; 163 | top: 70px; 164 | } 165 | 166 | #pkgdown-sidebar h2 { 167 | font-size: 1.5em; 168 | margin-top: 1em; 169 | } 170 | 171 | #pkgdown-sidebar h2:first-child { 172 | margin-top: 0; 173 | } 174 | 175 | #pkgdown-sidebar .list-unstyled li { 176 | margin-bottom: 0.5em; 177 | } 178 | 179 | /* bootstrap-toc tweaks ------------------------------------------------------*/ 180 | 181 | /* All levels of nav */ 182 | 183 | /*nav[data-toggle='toc'] .nav > li > a { 184 | padding: 4px 20px 4px 6px; 185 | font-size: 1.5rem; 186 | font-weight: 400; 187 | color: inherit; 188 | } 189 | 190 | nav[data-toggle='toc'] .nav > li > a:hover, 191 | nav[data-toggle='toc'] .nav > li > a:focus { 192 | padding-left: 5px; 193 | color: inherit; 194 | border-left: 1px solid #878787; 195 | } 196 | 197 | nav[data-toggle='toc'] .nav > .active > a, 198 | nav[data-toggle='toc'] .nav > .active:hover > a, 199 | nav[data-toggle='toc'] .nav > .active:focus > a { 200 | padding-left: 5px; 201 | font-size: 1.5rem; 202 | font-weight: 400; 203 | color: inherit; 204 | border-left: 2px solid #878787; 205 | }*/ 206 | 207 | /* Nav: second level (shown on .active) */ 208 | 209 | nav[data-toggle='toc'] .nav .nav { 210 | display: none; /* Hide by default, but at >768px, show it */ 211 | padding-bottom: 10px; 212 | } 213 | /* 214 | nav[data-toggle='toc'] .nav .nav > li > a { 215 | padding-left: 16px; 216 | font-size: 1.35rem; 217 | } 218 | 219 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 220 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 221 | padding-left: 15px; 222 | } 223 | 224 | nav[data-toggle='toc'] .nav .nav > .active > a, 225 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 226 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 227 | padding-left: 15px; 228 | font-weight: 500; 229 | font-size: 1.35rem; 230 | }*/ 231 | 232 | /* orcid ------------------------------------------------------------------- */ 233 | 234 | .orcid { 235 | font-size: 16px; 236 | color: #A6CE39; 237 | /* margins are required by official ORCID trademark and display guidelines */ 238 | margin-left:4px; 239 | margin-right:4px; 240 | vertical-align: middle; 241 | } 242 | 243 | /* Reference index & topics ----------------------------------------------- */ 244 | 245 | .ref-index th {font-weight: normal;} 246 | 247 | .ref-index td {vertical-align: top;} 248 | .ref-index .icon {width: 40px;} 249 | .ref-index .alias {width: 40%;} 250 | .ref-index-icons .alias {width: calc(40% - 40px);} 251 | .ref-index .title {width: 60%;} 252 | 253 | .ref-arguments th {text-align: right; padding-right: 10px;} 254 | .ref-arguments th, .ref-arguments td {vertical-align: top;} 255 | .ref-arguments .name {width: 20%;} 256 | .ref-arguments .desc {width: 80%;} 257 | 258 | /* Nice scrolling for wide elements --------------------------------------- */ 259 | 260 | table { 261 | display: block; 262 | overflow: auto; 263 | } 264 | 265 | /* Syntax highlighting ---------------------------------------------------- */ 266 | 267 | pre { 268 | word-wrap: normal; 269 | word-break: normal; 270 | border: 1px solid #eee; 271 | } 272 | 273 | pre, code { 274 | background-color: #f8f8f8; 275 | color: #333; 276 | } 277 | 278 | pre code { 279 | overflow: auto; 280 | word-wrap: normal; 281 | white-space: pre; 282 | } 283 | 284 | pre .img { 285 | margin: 5px 0; 286 | } 287 | 288 | pre .img img { 289 | background-color: #fff; 290 | display: block; 291 | height: auto; 292 | } 293 | 294 | code a, pre a { 295 | color: #375f84; 296 | } 297 | 298 | a.sourceLine:hover { 299 | text-decoration: none; 300 | } 301 | 302 | .fl {color: #1514b5;} 303 | .fu {color: #000000;} /* function */ 304 | .ch,.st {color: #036a07;} /* string */ 305 | .kw {color: #264D66;} /* keyword */ 306 | .co {color: #888888;} /* comment */ 307 | 308 | .message { color: black; font-weight: bolder;} 309 | .error { color: orange; font-weight: bolder;} 310 | .warning { color: #6A0366; font-weight: bolder;} 311 | 312 | /* Clipboard --------------------------*/ 313 | 314 | .hasCopyButton { 315 | position: relative; 316 | } 317 | 318 | .btn-copy-ex { 319 | position: absolute; 320 | right: 0; 321 | top: 0; 322 | visibility: hidden; 323 | } 324 | 325 | .hasCopyButton:hover button.btn-copy-ex { 326 | visibility: visible; 327 | } 328 | 329 | /* headroom.js ------------------------ */ 330 | 331 | .headroom { 332 | will-change: transform; 333 | transition: transform 200ms linear; 334 | } 335 | .headroom--pinned { 336 | transform: translateY(0%); 337 | } 338 | .headroom--unpinned { 339 | transform: translateY(-100%); 340 | } 341 | 342 | /* mark.js ----------------------------*/ 343 | 344 | mark { 345 | background-color: rgba(255, 255, 51, 0.5); 346 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 347 | padding: 1px; 348 | } 349 | 350 | /* vertical spacing after htmlwidgets */ 351 | .html-widget { 352 | margin-bottom: 10px; 353 | } 354 | 355 | /* fontawesome ------------------------ */ 356 | 357 | .fab { 358 | font-family: "Font Awesome 5 Brands" !important; 359 | } 360 | 361 | /* don't display links in code chunks when printing */ 362 | /* source: https://stackoverflow.com/a/10781533 */ 363 | @media print { 364 | code a:link:after, code a:visited:after { 365 | content: ""; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $(".examples, div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent; 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.9.2 2 | pkgdown: 1.5.1 3 | pkgdown_sha: ~ 4 | articles: 5 | classification_example: classification_example.html 6 | localModel_methodology: localModel_methodology.html 7 | regression_example: regression_example.html 8 | last_built: 2020-08-29T18:18Z 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/gaussian_kernel-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/reference/gaussian_kernel-1.png -------------------------------------------------------------------------------- /docs/reference/grapes-colon-colon-colon-grapes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Import hidden object without CRAN notes. — %:::% • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 |
    39 |
    40 | 98 | 99 | 100 |
    101 | 102 |
    103 |
    104 | 107 | 108 | 109 |

    Import hidden object without CRAN notes.

    110 | 111 | 112 |
    pkg %:::% name
    113 | 114 |

    Arguments

    115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
    pkg

    Package name

    name

    Function name

    126 | 127 | 128 |
    129 | 140 |
    141 | 142 |
    143 | 146 | 147 |
    148 |

    Site built with pkgdown.

    149 |
    150 | 151 |
    152 |
    153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/reference/identity_kernel-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/reference/identity_kernel-1.png -------------------------------------------------------------------------------- /docs/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Function reference • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 |
    76 |
    77 | 139 | 140 | 141 | 142 |
    143 | 144 |
    145 |
    146 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 175 | 176 | 177 | 178 | 181 | 182 | 183 | 184 | 187 | 188 | 189 | 190 | 193 | 194 | 195 | 196 | 199 | 200 | 201 | 202 | 205 | 206 | 207 | 208 | 211 | 212 | 213 | 214 |
    161 |

    All functions

    162 |

    163 |
    173 |

    gaussian_kernel()

    174 |

    LIME kernel from the original article with sigma = 1.

    179 |

    identity_kernel()

    180 |

    LIME kernel that treats all observations as equally similar to the observation of interest.

    185 |

    individual_surrogate_model()

    186 |

    LIME-like explanations based on Ceteris Paribus curves

    191 |

    localModel

    192 |

    localModel: LIME-like explanations with interpretable features based on Ceteris Paribus profiles

    197 |

    plot(<local_surrogate_explainer>)

    198 |

    Generic plot function for local surrogate explainers

    203 |

    plot_interpretable_feature()

    204 |

    Plot Ceteris Paribus Profile and discretization

    209 |

    print(<local_surrogate_explainer>)

    210 |

    Generic print function for local surrogate explainers

    215 |
    216 | 217 | 222 |
    223 | 224 | 225 |
    226 | 229 | 230 |
    231 |

    Site built with pkgdown.

    232 |
    233 | 234 |
    235 |
    236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /docs/reference/individual_surrogate_model-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/reference/individual_surrogate_model-1.png -------------------------------------------------------------------------------- /docs/reference/localModel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | localModel: LIME-like explanations with interpretable features based on Ceteris Paribus profiles — localModel • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 74 | 75 | 76 | 77 | 78 |
    79 |
    80 | 142 | 143 | 144 | 145 |
    146 | 147 |
    148 |
    149 | 154 | 155 |
    156 |

    This package implements LIME-like explanation method 157 | (see Tulio Ribeiro, Singh, Guestrin (2016) <doi:10.1145/2939672.2939778>) in which interpretable 158 | inputs are created based on local rather than global behaviour of each original feature.#'

    159 |
    160 | 161 | 162 | 163 |

    Important functions

    164 | 165 | 166 | 167 |

    individual_surrogate_model generates an explanation for a single prediction with 168 | interpretable features based on Ceteris Paribus profiles. 169 | plot.local_surrogate_explainer plots the explanation.

    170 | 171 |
    172 | 177 |
    178 | 179 | 180 |
    181 | 184 | 185 |
    186 |

    Site built with pkgdown.

    187 |
    188 | 189 |
    190 |
    191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /docs/reference/model_type.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Model type for lime — model_type • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | 76 |
    77 |
    78 | 140 | 141 | 142 | 143 |
    144 | 145 |
    146 |
    147 | 152 | 153 |
    154 |

    Model type for lime

    155 |
    156 | 157 |
    # S3 method for dalex_explainer
    158 | model_type(x, ...)
    159 | 160 | 161 | 162 |
    163 | 168 |
    169 | 170 | 171 |
    172 | 175 | 176 |
    177 |

    Site built with pkgdown.

    178 |
    179 | 180 |
    181 |
    182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docs/reference/plot.local_surrogate_explainer-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/reference/plot.local_surrogate_explainer-1.png -------------------------------------------------------------------------------- /docs/reference/plot_interpretable_feature.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Plot Ceteris Paribus Profile and discretization — plot_interpretable_feature • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | 76 |
    77 |
    78 | 140 | 141 | 142 | 143 |
    144 | 145 |
    146 |
    147 | 152 | 153 |
    154 |

    Plot Ceteris Paribus Profile and discretization

    155 |
    156 | 157 |
    plot_interpretable_feature(x, variable)
    158 | 159 |

    Arguments

    160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 |
    x

    local_surrogate_explainer object

    variable

    chr, name of the variable to be plotted

    171 | 172 |

    Value

    173 | 174 |

    ggplot2 object

    175 | 176 |
    177 | 182 |
    183 | 184 | 185 |
    186 | 189 | 190 |
    191 |

    Site built with pkgdown.

    192 |
    193 | 194 |
    195 |
    196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /docs/reference/predict_model.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Predict model for lime — predict_model • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | 76 |
    77 |
    78 | 140 | 141 | 142 | 143 |
    144 | 145 |
    146 |
    147 | 152 | 153 |
    154 |

    Predict model for lime

    155 |
    156 | 157 |
    # S3 method for dalex_explainer
    158 | predict_model(x, newdata, ...)
    159 | 160 | 161 | 162 |
    163 | 168 |
    169 | 170 | 171 |
    172 | 175 | 176 |
    177 |

    Site built with pkgdown.

    178 |
    179 | 180 |
    181 |
    182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docs/reference/predict_surrogate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Instance Level Surrogate Models — predict_surrogate • localModel 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | 77 |
    78 |
    79 | 141 | 142 | 143 | 144 |
    145 | 146 |
    147 |
    148 | 153 | 154 |
    155 |

    Interface to different implementations of the LIME method. 156 | Find information how the LIME method works here: https://pbiecek.github.io/ema/LIME.html.

    157 |
    158 | 159 |
    predict_surrogate(explainer, new_observation, ..., type = "localModel")
    160 | 
    161 | predict_surrogate_local_model(
    162 |   explainer,
    163 |   new_observation,
    164 |   size = 1000,
    165 |   seed = 1313,
    166 |   ...
    167 | )
    168 | 
    169 | predict_surrogate_lime(
    170 |   explainer,
    171 |   new_observation,
    172 |   n_features = 4,
    173 |   n_permutations = 1000,
    174 |   labels = unique(explainer$y)[1],
    175 |   ...
    176 | )
    177 | 
    178 | # S3 method for predict_surrogate_lime
    179 | plot(x, ...)
    180 | 
    181 | predict_surrogate_iml(explainer, new_observation, k = 4, ...)
    182 | 183 |

    Arguments

    184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 |
    explainer

    a model to be explained, preprocessed by the 'explain' function

    new_observation

    a new observation for which predictions need to be explained

    ...

    other parameters that will be passed to

    type

    which implementation of thee LIME method should be used. Either localModel (default), lime or iml.

    size

    will be passed to the localModel implementation, by default 1000

    seed

    seed for random number generator, by default 1313

    n_features

    will be passed to the lime implementation, by default 4

    n_permutations

    will be passed to the lime implementation, by default 1000

    labels

    will be passed to the lime implementation, by default first value in the y vector

    k

    will be passed to the iml implementation, by default 4

    227 | 228 |

    Value

    229 | 230 |

    Depending on the type there are different classess of the resulting object.

    231 |

    References

    232 | 233 |

    Explanatory Model Analysis. Explore, Explain and Examine Predictive Models. https://pbiecek.github.io/ema/

    234 | 235 |
    236 | 241 |
    242 | 243 | 244 |
    245 | 248 | 249 |
    250 |

    Site built with pkgdown.

    251 |
    252 | 253 |
    254 |
    255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /docs/reference/print.local_surrogate_explainer-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelOriented/localModel/7cba8f3c33b50e300a883c44a92eee69ca947181/docs/reference/print.local_surrogate_explainer-1.png -------------------------------------------------------------------------------- /docs/tocBullet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /localModel.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /man/gaussian_kernel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/kernels.R 3 | \name{gaussian_kernel} 4 | \alias{gaussian_kernel} 5 | \title{LIME kernel from the original article with sigma = 1.} 6 | \usage{ 7 | gaussian_kernel(explained_instance, simulated_instance) 8 | } 9 | \arguments{ 10 | \item{explained_instance}{explained instance} 11 | 12 | \item{simulated_instance}{new observation} 13 | } 14 | \value{ 15 | numeric 16 | } 17 | \description{ 18 | Since only binary features are used, the weight associated with an observation 19 | is simply exp(-\{number of features that were changed compared to the original observation\}). 20 | Kernels are meant to be used as an argument to individual_surrogate_model function. 21 | Other custom functions can be used. Such functions take two vectors and 22 | return a single number. 23 | } 24 | \examples{ 25 | library(DALEX) 26 | library(randomForest) 27 | library(localModel) 28 | data('apartments') 29 | mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 30 | explainer <- explain(model = mrf, 31 | data = apartments[, -1]) 32 | model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 33 | size = 500, seed = 17, 34 | kernel = gaussian_kernel) 35 | # In this case each simulated observation has weight 36 | # that is small when the distance from original observation is large, 37 | # so closer observation have more weight. 38 | model_lok 39 | plot(model_lok) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /man/identity_kernel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/kernels.R 3 | \name{identity_kernel} 4 | \alias{identity_kernel} 5 | \title{LIME kernel that treats all observations as equally similar to the observation of interest.} 6 | \usage{ 7 | identity_kernel(explained_instance, simulated_instance) 8 | } 9 | \arguments{ 10 | \item{explained_instance}{explained instance} 11 | 12 | \item{simulated_instance}{new observation} 13 | } 14 | \value{ 15 | numeric 16 | } 17 | \description{ 18 | Kernels are meant to be used as an argument to individual_surrogate_model function. 19 | Other custom functions can be used. Such functions take two vectors and 20 | return a single number. 21 | } 22 | \examples{ 23 | library(DALEX) 24 | library(randomForest) 25 | library(localModel) 26 | data('apartments') 27 | mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 28 | explainer <- explain(model = mrf, 29 | data = apartments[, -1]) 30 | model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 31 | size = 500, seed = 17, 32 | kernel = identity_kernel) 33 | # In this case each simulated observation has equal weight 34 | # when explanation model (LASSO) is fitted. 35 | model_lok 36 | plot(model_lok) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /man/individual_surrogate_model.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/local_surrogate.R 3 | \name{individual_surrogate_model} 4 | \alias{individual_surrogate_model} 5 | \title{LIME-like explanations based on Ceteris Paribus curves} 6 | \usage{ 7 | individual_surrogate_model( 8 | x, 9 | new_observation, 10 | size, 11 | seed = NULL, 12 | kernel = identity_kernel, 13 | sampling = "uniform", 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{x}{an explainer created with the function DALEX::explain().} 19 | 20 | \item{new_observation}{an observation to be explained. Columns in should correspond to columns in the data argument to x.} 21 | 22 | \item{size}{number of similar observation to be sampled.} 23 | 24 | \item{seed}{If not NULL, seed will be set to this value for reproducibility.} 25 | 26 | \item{kernel}{Kernel function which will be used to weight simulated observations.} 27 | 28 | \item{sampling}{Parameter that controls sampling while creating new observations.} 29 | 30 | \item{...}{Additional arguments that will be passed to ingredients::ceteris_paribus.} 31 | } 32 | \value{ 33 | data.frame of class local_surrogate_explainer 34 | } 35 | \description{ 36 | This function fits a LIME-type explanation of a single prediction. 37 | Interpretable binary features that describe the local impact of features on 38 | the prediction are created based on Ceteris Paribus Profiles. 39 | Thend, a new dataset of similar observations is created and black box model 40 | predictions (scores in case of classification) are calculated for this dataset 41 | and LASSO regression model is fitted to them. 42 | This way, explanations are simplified and include only the most important features. 43 | More details about the methodology can be found in the vignettes. 44 | } 45 | \examples{ 46 | # Example based on apartments data from DALEX package. 47 | library(DALEX) 48 | library(randomForest) 49 | library(localModel) 50 | data('apartments') 51 | mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 52 | explainer <- explain(model = mrf, 53 | data = apartments[, -1]) 54 | model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 55 | size = 500, seed = 17) 56 | model_lok 57 | plot(model_lok) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /man/localModel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/localModel.R 3 | \docType{package} 4 | \name{localModel} 5 | \alias{localModel} 6 | \title{localModel: LIME-like explanations with interpretable features based on Ceteris Paribus profiles} 7 | \description{ 8 | This package implements LIME-like explanation method 9 | (see Tulio Ribeiro, Singh, Guestrin (2016) ) in which interpretable 10 | inputs are created based on local rather than global behaviour of each original feature.#' 11 | } 12 | \section{Important functions}{ 13 | 14 | \code{\link{individual_surrogate_model}} generates an explanation for a single prediction with 15 | interpretable features based on Ceteris Paribus profiles. 16 | \code{\link{plot.local_surrogate_explainer}} plots the explanation. 17 | } 18 | 19 | -------------------------------------------------------------------------------- /man/plot.local_surrogate_explainer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_print.R 3 | \name{plot.local_surrogate_explainer} 4 | \alias{plot.local_surrogate_explainer} 5 | \title{Generic plot function for local surrogate explainers} 6 | \usage{ 7 | \method{plot}{local_surrogate_explainer}(x, ..., geom = "bar") 8 | } 9 | \arguments{ 10 | \item{x}{object of class local_surrogate_explainer} 11 | 12 | \item{...}{other objects of class local_surrogate_explainer. 13 | If provided, models will be plotted in rows, response levels in columns.} 14 | 15 | \item{geom}{If "point", lines with points at the end will be plotted, 16 | if "bar", bars will be plotted and if "arrow", arrows.} 17 | } 18 | \description{ 19 | Generic plot function for local surrogate explainers 20 | } 21 | \examples{ 22 | # Example based on apartments data from DALEX package. 23 | library(DALEX) 24 | library(randomForest) 25 | library(localModel) 26 | data('apartments') 27 | mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 28 | explainer <- explain(model = mrf, 29 | data = apartments[, -1]) 30 | model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 31 | size = 500, seed = 17) 32 | model_lok 33 | plot(model_lok) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /man/plot_interpretable_feature.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_print.R 3 | \name{plot_interpretable_feature} 4 | \alias{plot_interpretable_feature} 5 | \title{Plot Ceteris Paribus Profile and discretization} 6 | \usage{ 7 | plot_interpretable_feature(x, variable) 8 | } 9 | \arguments{ 10 | \item{x}{local_surrogate_explainer object} 11 | 12 | \item{variable}{chr, name of the variable to be plotted} 13 | } 14 | \value{ 15 | ggplot2 object 16 | } 17 | \description{ 18 | Plot Ceteris Paribus Profile and discretization 19 | } 20 | -------------------------------------------------------------------------------- /man/print.local_surrogate_explainer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_print.R 3 | \name{print.local_surrogate_explainer} 4 | \alias{print.local_surrogate_explainer} 5 | \title{Generic print function for local surrogate explainers} 6 | \usage{ 7 | \method{print}{local_surrogate_explainer}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{object of class local_surrogate_explainer} 11 | 12 | \item{...}{currently ignored} 13 | } 14 | \description{ 15 | Generic print function for local surrogate explainers 16 | } 17 | \examples{ 18 | # Example based on apartments data from DALEX package. 19 | library(DALEX) 20 | library(randomForest) 21 | library(localModel) 22 | data('apartments') 23 | mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 24 | explainer <- explain(model = mrf, 25 | data = apartments[, -1]) 26 | model_lok <- individual_surrogate_model(explainer, apartments[5, -1], 27 | size = 500, seed = 17) 28 | plot(model_lok) 29 | model_lok 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(localModel) 3 | library(DALEX) 4 | library(randomForest) 5 | 6 | test_check("localModel") 7 | -------------------------------------------------------------------------------- /tests/testthat/setup-xyz.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ## Test Setup: The code in this file runs before tests are run ## 3 | ################################################################################ 4 | ## Clean up the envrionment 5 | suppressWarnings(rm(HR, apartments)) 6 | 7 | # Get the data 8 | data("HR", package = "DALEX") 9 | data("apartments", package = "DALEX") 10 | 11 | ## Fit models 12 | m_rf_hr <- randomForest::randomForest(status ~., data = HR[1:500, ], ntree = 10) 13 | m_rf_ap <- randomForest::randomForest(m2.price ~., data = apartments[1:500, ], ntree = 10) 14 | 15 | ## Create model explainers 16 | hr_explainer <- DALEX::explain(m_rf_hr, HR[1:500, ]) 17 | ap_explainer <- DALEX::explain(m_rf_ap, apartments[1:500, ]) 18 | 19 | ## Generate surrogate models 20 | local_model_explainer_hr <- 21 | localModel::individual_surrogate_model(hr_explainer, 22 | HR[5, -6], 23 | size = 50, 24 | seed = 17) 25 | local_model_explainer_ap <- 26 | localModel::individual_surrogate_model(ap_explainer, 27 | apartments[5, -1], 28 | size = 50, 29 | seed = 17) 30 | -------------------------------------------------------------------------------- /tests/testthat/teardown-xyz.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ## Test Teardown: The code in this file runs after all tests are completed ## 3 | ################################################################################ 4 | 5 | -------------------------------------------------------------------------------- /tests/testthat/test_generic.R: -------------------------------------------------------------------------------- 1 | context("Test generics") 2 | 3 | empty_explainer <- local_model_explainer_ap 4 | empty_explainer$estimated <- 0 5 | 6 | testthat::test_that("Plots work with different geoms", { 7 | testthat::expect_silent({ 8 | plot(local_model_explainer_ap) 9 | plot(local_model_explainer_ap, geom = "bar") 10 | plot(local_model_explainer_ap, geom = "arrow") 11 | }) 12 | testthat::expect_message({ 13 | plot(empty_explainer) 14 | }) 15 | }) 16 | 17 | testthat::test_that("Print is okay", { 18 | testthat::expect_output({ 19 | print(local_model_explainer_ap) 20 | }) 21 | }) 22 | 23 | testthat::test_that("Kernels work fine", { 24 | testthat::expect_true({ 25 | identity_kernel(HR[2, ], HR[1, ]) == 1 26 | }) 27 | testthat::expect_false({ 28 | gaussian_kernel(HR[2, 2:5], HR[1, 2:5]) == 1 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test_individual_surrogate_model.R: -------------------------------------------------------------------------------- 1 | context("Test individual_surrogate_model") 2 | 3 | 4 | # random forest and two classes ----------------------------------------------- 5 | 6 | test_that("individual_surrogate_model works with random forest and two classes", { 7 | ########### 8 | ## Setup ## 9 | ########### 10 | ## Get the data 11 | data("HR", package = "DALEX") 12 | ## Preprocess the data 13 | HR <- subset(HR, status %in% c("fired", "promoted")) 14 | ### Set the class of interest to "promoted" 15 | HR$status <- factor(HR$status, levels = c("fired", "promoted")) 16 | ## Fit a RF model 17 | mdl_obj <- randomForest::randomForest(status ~., data = HR[1:500, ], ntree = 10) 18 | ## Create model explainer 19 | mdl_exp <- DALEX::explain(model = mdl_obj, 20 | data = HR[1:500, ]) 21 | 22 | 23 | ########################### 24 | ## Valid Input Arguments ## 25 | ########################### 26 | ## Generate a surrogate model 27 | expect_silent( 28 | mdl_sur <- individual_surrogate_model(x = mdl_exp, 29 | new_observation = subset(HR[5,], select = -status), 30 | size = 50, 31 | seed = 17) 32 | ) 33 | ## Plot the surrogate model 34 | expect_true("ggplot" %in% class(plot(mdl_sur))) 35 | }) 36 | 37 | # glm and two classes ----------------------------------------------- 38 | 39 | test_that("individual_surrogate_model works with glm and two classes", { 40 | ########### 41 | ## Setup ## 42 | ########### 43 | ## Get the data 44 | data("HR", package = "DALEX") 45 | ## Preprocess the data 46 | HR <- subset(HR, status %in% c("fired", "promoted")) 47 | ### Set the class of interest to "promoted" 48 | HR$status <- factor(HR$status, levels = c("fired", "promoted")) 49 | ## Fit a RF model 50 | mdl_obj <- glm(status ~., family = binomial, data = HR[1:500, ]) 51 | ## Create model explainer 52 | mdl_exp <- DALEX::explain(model = mdl_obj, 53 | data = HR[1:500, ]) 54 | 55 | 56 | ########################### 57 | ## Valid Input Arguments ## 58 | ########################### 59 | ## Generate a surrogate model 60 | expect_silent( 61 | mdl_sur <- individual_surrogate_model(x = mdl_exp, 62 | new_observation = subset(HR[5,], select = -status), 63 | size = 50, 64 | seed = 17) 65 | ) 66 | ## Plot the surrogate model 67 | expect_true("ggplot" %in% class(plot(mdl_sur))) 68 | }) 69 | -------------------------------------------------------------------------------- /tests/testthat/test_interpretable_inputs.R: -------------------------------------------------------------------------------- 1 | context("Functions that create interpretable inputs") 2 | 3 | 4 | testthat::test_that("Extracting numerical features is okay", { 5 | testthat::expect_equal( 6 | extract_numerical_feature(c("x > 4", "x <= 4"), 5)$label, 7 | "x > 4" 8 | ) 9 | testthat::expect_equal( 10 | extract_numerical_feature(c("x > 4", "x <= 4"), 4)$label, 11 | "x <= 4" 12 | ) 13 | testthat::expect_equal( 14 | extract_numerical_feature(c("x <= 4 & x <= 5", 15 | "x > 5 & x <= 8"), 3)$label, 16 | "x <= 4" 17 | ) 18 | testthat::expect_equal( 19 | extract_numerical_feature(c("x <= 4 & x <= 5", 20 | "x > 5 & x <= 8"), 7)$label, 21 | "5 < x <= 8" 22 | ) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/testthat/test_main.R: -------------------------------------------------------------------------------- 1 | context("Test main functions") 2 | 3 | 4 | testthat::test_that( 5 | "Function works with single- and multi-column output", 6 | testthat::expect_output(individual_surrogate_model(hr_explainer, HR[5, -6], 50), 7 | regexp = NA) 8 | ) 9 | 10 | testthat::test_that( 11 | "Reproducibility", { 12 | testthat::expect_equal( 13 | individual_surrogate_model(hr_explainer, HR[5, -6], 50, seed = 17), 14 | individual_surrogate_model(hr_explainer, HR[5, -6], 50, seed = 17) 15 | ) 16 | testthat::expect_false( 17 | all( 18 | individual_surrogate_model(hr_explainer, HR[5, -6], 50) == 19 | individual_surrogate_model(hr_explainer, HR[5, -6], 50) 20 | ) 21 | ) 22 | }) 23 | 24 | testthat::test_that( 25 | "Non-uniform sampling is okay", { 26 | testthat::expect_output( 27 | individual_surrogate_model(hr_explainer, HR[5, -6], 50, 28 | sampling = "non-uniform"), 29 | regexp = NA 30 | ) 31 | } 32 | ) 33 | 34 | -------------------------------------------------------------------------------- /tests/testthat/test_transform_to_interpretable.R: -------------------------------------------------------------------------------- 1 | context("Test transform_to_interpretable") 2 | 3 | test_that("Column names correspond to row values", { 4 | # Test data 5 | exp <- list(data = DALEX::titanic_imputed[1:10, -8]) 6 | new <- DALEX::titanic_imputed[0, -8] 7 | new[1, ] <- list(gender = "male", age = 8, class = "1st", 8 | embarked = "Southampton", fare = 72, 9 | sibsp = 0, parch = 0) 10 | feature_rep <- list( 11 | list(factor(c(2, 1, 2), labels = c("baseline", "gender = male"))), 12 | list(factor(c(1, 2, 1), labels = c("baseline", "age <= 15.36"))), 13 | list(factor(c(1, 1, 2), 14 | labels = c("baseline", "class = 1st, 2nd, deck crew"))), 15 | list(factor(c(2, 1, 2), 16 | labels = c("baseline", "embarked = Belfast, Southampton"))), 17 | list(factor(c(1, 1, 1), labels = c("baseline"))), 18 | list(factor(c(1, 1, 1), labels = c("baseline"))), 19 | list(factor(c(1, 1, 1), labels = c("baseline"))) 20 | ) 21 | 22 | check_column_name <- function(df) { 23 | all(sapply(seq_along(df), function(x) { 24 | all(grepl(paste0(names(df)[x], "|baseline"), df[, x])) 25 | })) 26 | } 27 | 28 | # For assigning column names, those of the dataframe in the explainer 29 | # are compared to those of the dataframe with the new observation 30 | 31 | # Number and ordering of columns is the same 32 | df <- transform_to_interpretable(exp, new, feature_rep) 33 | testthat::expect_true(check_column_name(df)) 34 | 35 | # Number of columns is different 36 | new$extra <- "dummy data" 37 | df <- transform_to_interpretable(exp, new, feature_rep) 38 | testthat::expect_true(check_column_name(df)) 39 | new$extra <- NULL 40 | 41 | # Ordering of columns is different 42 | new <- new[, c("class", "gender", "age", "sibsp", 43 | "parch", "fare", "embarked")] 44 | df <- transform_to_interpretable(exp, new, feature_rep) 45 | testthat::expect_true(check_column_name(df)) 46 | }) 47 | -------------------------------------------------------------------------------- /vignettes/classification_example.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Explaining classification models with localModel package" 3 | author: "Mateusz Staniak" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Explaining classification models with localModel package} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r setup, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>", 15 | warning = FALSE, 16 | message = FALSE, 17 | error = FALSE 18 | ) 19 | ``` 20 | 21 | Explaining classification models with the [`localModel`](https://github.com/ModelOriented/localModel) package is just as simple as explaining regression. 22 | It is enough to work with predicted scores (class probabilities) rather than classes. 23 | In multiclass setting, a separate explanation is provided for each class probability. 24 | 25 | We will work with the `HR` dataset from [`DALEX2`](https://github.com/ModelOriented/DALEX2) package. 26 | As in the regression example from _Introduction to the localModel package_, we will first create a random forest model and a [`DALEX2`](https://github.com/ModelOriented/DALEX2) explainer. 27 | Details about the method can be found in the _Methodology behind localModel package_ vignette. 28 | 29 | ```{r model} 30 | library(DALEX) 31 | library(randomForest) 32 | library(localModel) 33 | 34 | data('HR') 35 | 36 | set.seed(17) 37 | mrf <- randomForest(status ~., data = HR, ntree = 100) 38 | explainer <- explain(mrf, 39 | HR[, -6], 40 | predict_function = function(x, y) predict(x, y, type = "prob")) 41 | new_observation <- HR[10, -6] 42 | new_observation 43 | ``` 44 | 45 | In [`DALEX2`](https://github.com/ModelOriented/DALEX2), we have built-in predict functions for some types of models. Random Forest is among these models. 46 | 47 | ```{r explanation} 48 | model_lok <- individual_surrogate_model(explainer, new_observation, 49 | size = 500, seed = 17) 50 | plot(model_lok) 51 | plot(model_lok, geom = "bar") 52 | ``` 53 | 54 | The plot shows how predictions for different classes are influenced by different features. 55 | For the actually predicted class, hours and evaluation have a strong positive effect. 56 | -------------------------------------------------------------------------------- /vignettes/localModel_methodology.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Methodology behind localModel package" 3 | author: "Mateusz Staniak" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Methodology behind localModel package} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r setup, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>" 15 | ) 16 | ``` 17 | 18 | `localModel` package is a variant of [LIME](https://arxiv.org/abs/1602.04938). 19 | As in LIME, the analysis consists of 20 | 21 | 1. Creating a new dataset $X'$ of $m$ observations that are similarly in some sense to the observation, for which we explain the prediction. This dataset is usually built in terms of new, interpretable features rather than original features. Size $m$ is a parameter to `individual_surrogate_model` function. 22 | 23 | 2. Fitting black box model to the new dataset. This step requires a transformation back to the original input space and is non-trivial for numerical data. 24 | 25 | 3. Fitting an explanation model (a glass box) to the outputs of black box model. 26 | Usually, LASSO regression is used to make sure that explanations are simple enough. 27 | 28 | In the next few paragraph, we will shortly describe how each of the steps is performed in `localModel`. 29 | All of them are implemented by the `individual_surrogate_model` function. 30 | 31 | ## Sampling for local exploration 32 | 33 | Interpretable features are created in a way that depends on the type of the predictor. 34 | 35 | - For categorical predictors, original dataset is used to obtain black box predictions. Then, the marginal relationship between the feature and response is modeled via decision tree with a single split to dichotomize the feature. 36 | 37 | - For numerical predictors, Ceteris Paribus profile is calculated and this marginal relationship is again dichotomized by a decision tree with maximum depth equal to 2. 38 | 39 | For both types of predictors, the intepretable input is an indicator variable equal to 1 for value of feature that falls into the group of levels or interval chosen by the decision tree. Other values of the predictor are treated as a single level, a baseline. 40 | 41 | Sampling new observations is done by 42 | 43 | 1. Creating $m$ copies of the explained observation. 44 | 45 | 2. Iterating through these copies and in each step 46 | 47 | - Choosing random number $k$ of feature to be switched to _baseline_ value. 48 | - Choosing, which $k$ features will be changed. 49 | - When the `individual_surrogate_model` function is called with argument `sampling = "uniform"`, each of these values is changed to _baseline_, but when `sampling = "non-uniform"`, it is changed with probability equal to the proportion of _baseline_ values in the original dataset. 50 | 51 | ## Fitting black box model 52 | 53 | Fitting the black box model to new observation requires transforming them into the original feature space. 54 | In `localModel`, this is done the following way. 55 | The original dataset is transformed into the interpretable feature space. 56 | Based on this transformation, we know which values of each feature are categorized as _baseline_ and which as the explained value. 57 | Then, for each simulated observation, and for each feature, we pick a random value of this feature from the original dataset that corresponds either to the _baseline_ group or the explained value. 58 | Black box model is fitted to these observations. 59 | 60 | 61 | ## Fitting the explanation model 62 | 63 | LASSO model with penalty chosen via cross-validation is used in `localModel` package. 64 | Optionally, observation can be weighted according to their distance from the explained observation in the space of interpretable features. 65 | Weighting is controlled via `kernel` parameter to `individual_surrogate_model`. 66 | 67 | The resulting model can be plotted using generic `plot` function. Models can be compared by passing several explainer object to `plot`. 68 | -------------------------------------------------------------------------------- /vignettes/regression_example.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to localModel package" 3 | author: "Mateusz Staniak" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Introduction to localModel package} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r setup, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>", 15 | message = FALSE, 16 | warning = FALSE, 17 | error = FALSE 18 | ) 19 | ``` 20 | 21 | This vignette shows how [`localModel`](https://github.com/ModelOriented/localModel) package can be used to explain regression models. 22 | We will use the `apartments` dataset from [`DALEX2`](https://github.com/ModelOriented/DALEX2) package. 23 | For more information about the dataset, please refer to the [Gentle introduction to DALEX](https://pbiecek.github.io/DALEX_docs/). 24 | 25 | We will need [`localModel`](https://github.com/ModelOriented/localModel) and [`DALEX2`](https://github.com/ModelOriented/DALEX2) packages. 26 | Random forest from `randomForest` package will serve as an example model, and linear regression as a simple model for comparison. 27 | 28 | ```{r pkgs} 29 | library(DALEX) 30 | library(localModel) 31 | library(randomForest) 32 | data('apartments') 33 | data('apartments_test') 34 | set.seed(69) 35 | mrf <- randomForest(m2.price ~., data = apartments, ntree = 50) 36 | mlm <- lm(m2.price ~., data = apartments) 37 | ``` 38 | 39 | 40 | First, we need to create an explainer object, using the `explain` function. 41 | We will explain the prediction for fifth observation in the test dataset. 42 | 43 | ```{r model} 44 | explainer <- DALEX::explain(model = mrf, 45 | data = apartments_test[, -1]) 46 | explainer2 <- DALEX::explain(model = mlm, 47 | data = apartments_test[, -1]) 48 | new_observation <- apartments_test[5, -1] 49 | new_observation 50 | ``` 51 | 52 | Local explanation is created via `individual_surrogate_model` function, which takes the explainer, observation of interest and number of new observations to simulate as argument. Optionally, we can set seed using the `seed` parameter for reproducibility. 53 | 54 | ```{r explanation} 55 | model_lok <- individual_surrogate_model(explainer, new_observation, 56 | size = 500, seed = 17) 57 | model_lok2 <- individual_surrogate_model(explainer2, new_observation, 58 | size = 500, seed = 17) 59 | ``` 60 | 61 | First, local interpretable features are created. Numerical features are discretized by using decision tree to model relationship between the feature and the corresponding Ceteris Paribus profile. 62 | Categorical features are also discretized by merging levels using the marginal relationship between the feature and the model response. 63 | Then, new dataset is simulated by switching a random number of interpretable inputs in the explained instance. 64 | This procedure mimics "graying out" areas (superpixels) in the original LIME method. 65 | LASSO regression model is fitted to the model response for these new observations, which makes the final explanations sparse and thus readable. 66 | More details can be found in the _Methodology behind localModel package_ vignette. 67 | 68 | The explanation can be plotted using generic `plot` function. 69 | The plot shows interpretable features and weights associated with them, starting at the model intercept. 70 | Negative weights are associated with features that decrease the apartment price, while positive weights increase it. 71 | We can plot explanation for two or more models together by passing several local explainer to `plot` function. 72 | 73 | ```{r plot} 74 | plot(model_lok, model_lok2) 75 | ``` 76 | 77 | We can see that for this observation, the price predicted by Random Forest is negatively influeced mostly by the district and construction year, while linear regression ignores the effect of construction year. 78 | --------------------------------------------------------------------------------