Files
2026-07-13 13:22:52 +08:00

1706 lines
74 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"# Python Version of Tree SHAP\n",
"\n",
"This is a sample implementation of Tree SHAP written in Python for easy reading."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:45.176673980Z",
"start_time": "2023-10-14T15:13:42.734552921Z"
},
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\programming\\shap\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n"
]
}
],
"source": [
"import time\n",
"\n",
"import numba\n",
"import numpy as np\n",
"import pandas as pd\n",
"import sklearn.ensemble\n",
"import xgboost\n",
"\n",
"import shap"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### Load California dataset"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:45.191494227Z",
"start_time": "2023-10-14T15:13:45.179770187Z"
},
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"(1000, 8)"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"X, y = shap.datasets.california(n_points=1000)\n",
"X.shape"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### Train sklearn random forest"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:46.873411331Z",
"start_time": "2023-10-14T15:13:45.219992128Z"
},
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<style>#sk-container-id-1 {\n",
" /* Definition of color scheme common for light and dark mode */\n",
" --sklearn-color-text: #000;\n",
" --sklearn-color-text-muted: #666;\n",
" --sklearn-color-line: gray;\n",
" /* Definition of color scheme for unfitted estimators */\n",
" --sklearn-color-unfitted-level-0: #fff5e6;\n",
" --sklearn-color-unfitted-level-1: #f6e4d2;\n",
" --sklearn-color-unfitted-level-2: #ffe0b3;\n",
" --sklearn-color-unfitted-level-3: chocolate;\n",
" /* Definition of color scheme for fitted estimators */\n",
" --sklearn-color-fitted-level-0: #f0f8ff;\n",
" --sklearn-color-fitted-level-1: #d4ebff;\n",
" --sklearn-color-fitted-level-2: #b3dbfd;\n",
" --sklearn-color-fitted-level-3: cornflowerblue;\n",
"}\n",
"\n",
"#sk-container-id-1.light {\n",
" /* Specific color for light theme */\n",
" --sklearn-color-text-on-default-background: black;\n",
" --sklearn-color-background: white;\n",
" --sklearn-color-border-box: black;\n",
" --sklearn-color-icon: #696969;\n",
"}\n",
"\n",
"#sk-container-id-1.dark {\n",
" --sklearn-color-text-on-default-background: white;\n",
" --sklearn-color-background: #111;\n",
" --sklearn-color-border-box: white;\n",
" --sklearn-color-icon: #878787;\n",
"}\n",
"\n",
"#sk-container-id-1 {\n",
" color: var(--sklearn-color-text);\n",
"}\n",
"\n",
"#sk-container-id-1 pre {\n",
" padding: 0;\n",
"}\n",
"\n",
"#sk-container-id-1 input.sk-hidden--visually {\n",
" border: 0;\n",
" clip: rect(1px 1px 1px 1px);\n",
" clip: rect(1px, 1px, 1px, 1px);\n",
" height: 1px;\n",
" margin: -1px;\n",
" overflow: hidden;\n",
" padding: 0;\n",
" position: absolute;\n",
" width: 1px;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-dashed-wrapped {\n",
" border: 1px dashed var(--sklearn-color-line);\n",
" margin: 0 0.4em 0.5em 0.4em;\n",
" box-sizing: border-box;\n",
" padding-bottom: 0.4em;\n",
" background-color: var(--sklearn-color-background);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-container {\n",
" /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
" but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
" so we also need the `!important` here to be able to override the\n",
" default hidden behavior on the sphinx rendered scikit-learn.org.\n",
" See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
" display: inline-block !important;\n",
" position: relative;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-text-repr-fallback {\n",
" display: none;\n",
"}\n",
"\n",
"div.sk-parallel-item,\n",
"div.sk-serial,\n",
"div.sk-item {\n",
" /* draw centered vertical line to link estimators */\n",
" background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
" background-size: 2px 100%;\n",
" background-repeat: no-repeat;\n",
" background-position: center center;\n",
"}\n",
"\n",
"/* Parallel-specific style estimator block */\n",
"\n",
"#sk-container-id-1 div.sk-parallel-item::after {\n",
" content: \"\";\n",
" width: 100%;\n",
" border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
" flex-grow: 1;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-parallel {\n",
" display: flex;\n",
" align-items: stretch;\n",
" justify-content: center;\n",
" background-color: var(--sklearn-color-background);\n",
" position: relative;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-parallel-item {\n",
" display: flex;\n",
" flex-direction: column;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
" align-self: flex-end;\n",
" width: 50%;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
" align-self: flex-start;\n",
" width: 50%;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
" width: 0;\n",
"}\n",
"\n",
"/* Serial-specific style estimator block */\n",
"\n",
"#sk-container-id-1 div.sk-serial {\n",
" display: flex;\n",
" flex-direction: column;\n",
" align-items: center;\n",
" background-color: var(--sklearn-color-background);\n",
" padding-right: 1em;\n",
" padding-left: 1em;\n",
"}\n",
"\n",
"\n",
"/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
"clickable and can be expanded/collapsed.\n",
"- Pipeline and ColumnTransformer use this feature and define the default style\n",
"- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
"*/\n",
"\n",
"/* Pipeline and ColumnTransformer style (default) */\n",
"\n",
"#sk-container-id-1 div.sk-toggleable {\n",
" /* Default theme specific background. It is overwritten whether we have a\n",
" specific estimator or a Pipeline/ColumnTransformer */\n",
" background-color: var(--sklearn-color-background);\n",
"}\n",
"\n",
"/* Toggleable label */\n",
"#sk-container-id-1 label.sk-toggleable__label {\n",
" cursor: pointer;\n",
" display: flex;\n",
" width: 100%;\n",
" margin-bottom: 0;\n",
" padding: 0.5em;\n",
" box-sizing: border-box;\n",
" text-align: center;\n",
" align-items: center;\n",
" justify-content: center;\n",
" gap: 0.5em;\n",
"}\n",
"\n",
"#sk-container-id-1 label.sk-toggleable__label .caption {\n",
" font-size: 0.6rem;\n",
" font-weight: lighter;\n",
" color: var(--sklearn-color-text-muted);\n",
"}\n",
"\n",
"#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
" /* Arrow on the left of the label */\n",
" content: \"▸\";\n",
" float: left;\n",
" margin-right: 0.25em;\n",
" color: var(--sklearn-color-icon);\n",
"}\n",
"\n",
"#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
" color: var(--sklearn-color-text);\n",
"}\n",
"\n",
"/* Toggleable content - dropdown */\n",
"\n",
"#sk-container-id-1 div.sk-toggleable__content {\n",
" display: none;\n",
" text-align: left;\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-0);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-0);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-toggleable__content pre {\n",
" margin: 0.2em;\n",
" border-radius: 0.25em;\n",
" color: var(--sklearn-color-text);\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-0);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-fitted-level-0);\n",
"}\n",
"\n",
"#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
" /* Expand drop-down */\n",
" display: block;\n",
" width: 100%;\n",
" overflow: visible;\n",
"}\n",
"\n",
"#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
" content: \"▾\";\n",
"}\n",
"\n",
"/* Pipeline/ColumnTransformer-specific style */\n",
"\n",
"#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
" color: var(--sklearn-color-text);\n",
" background-color: var(--sklearn-color-unfitted-level-2);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
" background-color: var(--sklearn-color-fitted-level-2);\n",
"}\n",
"\n",
"/* Estimator-specific style */\n",
"\n",
"/* Colorize estimator box */\n",
"#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-2);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-2);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
"#sk-container-id-1 div.sk-label label {\n",
" /* The background is the default theme color */\n",
" color: var(--sklearn-color-text-on-default-background);\n",
"}\n",
"\n",
"/* On hover, darken the color of the background */\n",
"#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
" color: var(--sklearn-color-text);\n",
" background-color: var(--sklearn-color-unfitted-level-2);\n",
"}\n",
"\n",
"/* Label box, darken color on hover, fitted */\n",
"#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
" color: var(--sklearn-color-text);\n",
" background-color: var(--sklearn-color-fitted-level-2);\n",
"}\n",
"\n",
"/* Estimator label */\n",
"\n",
"#sk-container-id-1 div.sk-label label {\n",
" font-family: monospace;\n",
" font-weight: bold;\n",
" line-height: 1.2em;\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-label-container {\n",
" text-align: center;\n",
"}\n",
"\n",
"/* Estimator-specific */\n",
"#sk-container-id-1 div.sk-estimator {\n",
" font-family: monospace;\n",
" border: 1px dotted var(--sklearn-color-border-box);\n",
" border-radius: 0.25em;\n",
" box-sizing: border-box;\n",
" margin-bottom: 0.5em;\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-0);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-estimator.fitted {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-0);\n",
"}\n",
"\n",
"/* on hover */\n",
"#sk-container-id-1 div.sk-estimator:hover {\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-2);\n",
"}\n",
"\n",
"#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-2);\n",
"}\n",
"\n",
"/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
"\n",
"/* Common style for \"i\" and \"?\" */\n",
"\n",
".sk-estimator-doc-link,\n",
"a:link.sk-estimator-doc-link,\n",
"a:visited.sk-estimator-doc-link {\n",
" float: right;\n",
" font-size: smaller;\n",
" line-height: 1em;\n",
" font-family: monospace;\n",
" background-color: var(--sklearn-color-unfitted-level-0);\n",
" border-radius: 1em;\n",
" height: 1em;\n",
" width: 1em;\n",
" text-decoration: none !important;\n",
" margin-left: 0.5em;\n",
" text-align: center;\n",
" /* unfitted */\n",
" border: var(--sklearn-color-unfitted-level-3) 1pt solid;\n",
" color: var(--sklearn-color-unfitted-level-3);\n",
"}\n",
"\n",
".sk-estimator-doc-link.fitted,\n",
"a:link.sk-estimator-doc-link.fitted,\n",
"a:visited.sk-estimator-doc-link.fitted {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-0);\n",
" border: var(--sklearn-color-fitted-level-3) 1pt solid;\n",
" color: var(--sklearn-color-fitted-level-3);\n",
"}\n",
"\n",
"/* On hover */\n",
"div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
".sk-estimator-doc-link:hover,\n",
"div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
".sk-estimator-doc-link:hover {\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-3);\n",
" border: var(--sklearn-color-fitted-level-0) 1pt solid;\n",
" color: var(--sklearn-color-unfitted-level-0);\n",
" text-decoration: none;\n",
"}\n",
"\n",
"div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
".sk-estimator-doc-link.fitted:hover,\n",
"div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
".sk-estimator-doc-link.fitted:hover {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-3);\n",
" border: var(--sklearn-color-fitted-level-0) 1pt solid;\n",
" color: var(--sklearn-color-fitted-level-0);\n",
" text-decoration: none;\n",
"}\n",
"\n",
"/* Span, style for the box shown on hovering the info icon */\n",
".sk-estimator-doc-link span {\n",
" display: none;\n",
" z-index: 9999;\n",
" position: relative;\n",
" font-weight: normal;\n",
" right: .2ex;\n",
" padding: .5ex;\n",
" margin: .5ex;\n",
" width: min-content;\n",
" min-width: 20ex;\n",
" max-width: 50ex;\n",
" color: var(--sklearn-color-text);\n",
" box-shadow: 2pt 2pt 4pt #999;\n",
" /* unfitted */\n",
" background: var(--sklearn-color-unfitted-level-0);\n",
" border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
"}\n",
"\n",
".sk-estimator-doc-link.fitted span {\n",
" /* fitted */\n",
" background: var(--sklearn-color-fitted-level-0);\n",
" border: var(--sklearn-color-fitted-level-3);\n",
"}\n",
"\n",
".sk-estimator-doc-link:hover span {\n",
" display: block;\n",
"}\n",
"\n",
"/* \"?\"-specific style due to the `<a>` HTML tag */\n",
"\n",
"#sk-container-id-1 a.estimator_doc_link {\n",
" float: right;\n",
" font-size: 1rem;\n",
" line-height: 1em;\n",
" font-family: monospace;\n",
" background-color: var(--sklearn-color-unfitted-level-0);\n",
" border-radius: 1rem;\n",
" height: 1rem;\n",
" width: 1rem;\n",
" text-decoration: none;\n",
" /* unfitted */\n",
" color: var(--sklearn-color-unfitted-level-1);\n",
" border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
"}\n",
"\n",
"#sk-container-id-1 a.estimator_doc_link.fitted {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-0);\n",
" border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
" color: var(--sklearn-color-fitted-level-1);\n",
"}\n",
"\n",
"/* On hover */\n",
"#sk-container-id-1 a.estimator_doc_link:hover {\n",
" /* unfitted */\n",
" background-color: var(--sklearn-color-unfitted-level-3);\n",
" color: var(--sklearn-color-background);\n",
" text-decoration: none;\n",
"}\n",
"\n",
"#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
" /* fitted */\n",
" background-color: var(--sklearn-color-fitted-level-3);\n",
"}\n",
"\n",
".estimator-table {\n",
" font-family: monospace;\n",
"}\n",
"\n",
".estimator-table summary {\n",
" padding: .5rem;\n",
" cursor: pointer;\n",
"}\n",
"\n",
".estimator-table summary::marker {\n",
" font-size: 0.7rem;\n",
"}\n",
"\n",
".estimator-table details[open] {\n",
" padding-left: 0.1rem;\n",
" padding-right: 0.1rem;\n",
" padding-bottom: 0.3rem;\n",
"}\n",
"\n",
".estimator-table .parameters-table {\n",
" margin-left: auto !important;\n",
" margin-right: auto !important;\n",
" margin-top: 0;\n",
"}\n",
"\n",
".estimator-table .parameters-table tr:nth-child(odd) {\n",
" background-color: #fff;\n",
"}\n",
"\n",
".estimator-table .parameters-table tr:nth-child(even) {\n",
" background-color: #f6f6f6;\n",
"}\n",
"\n",
".estimator-table .parameters-table tr:hover {\n",
" background-color: #e0e0e0;\n",
"}\n",
"\n",
".estimator-table table td {\n",
" border: 1px solid rgba(106, 105, 104, 0.232);\n",
"}\n",
"\n",
"/*\n",
" `table td`is set in notebook with right text-align.\n",
" We need to overwrite it.\n",
"*/\n",
".estimator-table table td.param {\n",
" text-align: left;\n",
" position: relative;\n",
" padding: 0;\n",
"}\n",
"\n",
".user-set td {\n",
" color:rgb(255, 94, 0);\n",
" text-align: left !important;\n",
"}\n",
"\n",
".user-set td.value {\n",
" color:rgb(255, 94, 0);\n",
" background-color: transparent;\n",
"}\n",
"\n",
".default td {\n",
" color: black;\n",
" text-align: left !important;\n",
"}\n",
"\n",
".user-set td i,\n",
".default td i {\n",
" color: black;\n",
"}\n",
"\n",
"/*\n",
" Styles for parameter documentation links\n",
" We need styling for visited so jupyter doesn't overwrite it\n",
"*/\n",
"a.param-doc-link,\n",
"a.param-doc-link:link,\n",
"a.param-doc-link:visited {\n",
" text-decoration: underline dashed;\n",
" text-underline-offset: .3em;\n",
" color: inherit;\n",
" display: block;\n",
" padding: .5em;\n",
"}\n",
"\n",
"/* \"hack\" to make the entire area of the cell containing the link clickable */\n",
"a.param-doc-link::before {\n",
" position: absolute;\n",
" content: \"\";\n",
" inset: 0;\n",
"}\n",
"\n",
".param-doc-description {\n",
" display: none;\n",
" position: absolute;\n",
" z-index: 9999;\n",
" left: 0;\n",
" padding: .5ex;\n",
" margin-left: 1.5em;\n",
" color: var(--sklearn-color-text);\n",
" box-shadow: .3em .3em .4em #999;\n",
" width: max-content;\n",
" text-align: left;\n",
" max-height: 10em;\n",
" overflow-y: auto;\n",
"\n",
" /* unfitted */\n",
" background: var(--sklearn-color-unfitted-level-0);\n",
" border: thin solid var(--sklearn-color-unfitted-level-3);\n",
"}\n",
"\n",
"/* Fitted state for parameter tooltips */\n",
".fitted .param-doc-description {\n",
" /* fitted */\n",
" background: var(--sklearn-color-fitted-level-0);\n",
" border: thin solid var(--sklearn-color-fitted-level-3);\n",
"}\n",
"\n",
".param-doc-link:hover .param-doc-description {\n",
" display: block;\n",
"}\n",
"\n",
".copy-paste-icon {\n",
" background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTIwOCAwTDMzMi4xIDBjMTIuNyAwIDI0LjkgNS4xIDMzLjkgMTQuMWw2Ny45IDY3LjljOSA5IDE0LjEgMjEuMiAxNC4xIDMzLjlMNDQ4IDMzNmMwIDI2LjUtMjEuNSA0OC00OCA0OGwtMTkyIDBjLTI2LjUgMC00OC0yMS41LTQ4LTQ4bDAtMjg4YzAtMjYuNSAyMS41LTQ4IDQ4LTQ4ek00OCAxMjhsODAgMCAwIDY0LTY0IDAgMCAyNTYgMTkyIDAgMC0zMiA2NCAwIDAgNDhjMCAyNi41LTIxLjUgNDgtNDggNDhMNDggNTEyYy0yNi41IDAtNDgtMjEuNS00OC00OEwwIDE3NmMwLTI2LjUgMjEuNS00OCA0OC00OHoiLz48L3N2Zz4=);\n",
" background-repeat: no-repeat;\n",
" background-size: 14px 14px;\n",
" background-position: 0;\n",
" display: inline-block;\n",
" width: 14px;\n",
" height: 14px;\n",
" cursor: pointer;\n",
"}\n",
"</style><body><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>RandomForestRegressor(max_depth=4, n_estimators=1000)</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>RandomForestRegressor</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html\">?<span>Documentation for RandomForestRegressor</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\" data-param-prefix=\"\">\n",
" <div class=\"estimator-table\">\n",
" <details>\n",
" <summary>Parameters</summary>\n",
" <table class=\"parameters-table\">\n",
" <tbody>\n",
" \n",
" <tr class=\"user-set\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('n_estimators',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=n_estimators,-int%2C%20default%3D100\">\n",
" n_estimators\n",
" <span class=\"param-doc-description\">n_estimators: int, default=100<br><br>The number of trees in the forest.<br><br>.. versionchanged:: 0.22<br> The default value of ``n_estimators`` changed from 10 to 100<br> in 0.22.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">1000</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('criterion',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=criterion,-%7B%22squared_error%22%2C%20%22absolute_error%22%2C%20%22friedman_mse%22%2C%20%22poisson%22%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20default%3D%22squared_error%22\">\n",
" criterion\n",
" <span class=\"param-doc-description\">criterion: {\"squared_error\", \"absolute_error\", \"friedman_mse\", \"poisson\"}, default=\"squared_error\"<br><br>The function to measure the quality of a split. Supported criteria<br>are \"squared_error\" for the mean squared error, which is equal to<br>variance reduction as feature selection criterion and minimizes the L2<br>loss using the mean of each terminal node, \"friedman_mse\", which uses<br>mean squared error with Friedman's improvement score for potential<br>splits, \"absolute_error\" for the mean absolute error, which minimizes<br>the L1 loss using the median of each terminal node, and \"poisson\" which<br>uses reduction in Poisson deviance to find splits.<br>Training using \"absolute_error\" is significantly slower<br>than when using \"squared_error\".<br><br>.. versionadded:: 0.18<br> Mean Absolute Error (MAE) criterion.<br><br>.. versionadded:: 1.0<br> Poisson criterion.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">&#x27;squared_error&#x27;</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"user-set\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('max_depth',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=max_depth,-int%2C%20default%3DNone\">\n",
" max_depth\n",
" <span class=\"param-doc-description\">max_depth: int, default=None<br><br>The maximum depth of the tree. If None, then nodes are expanded until<br>all leaves are pure or until all leaves contain less than<br>min_samples_split samples.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">4</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('min_samples_split',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=min_samples_split,-int%20or%20float%2C%20default%3D2\">\n",
" min_samples_split\n",
" <span class=\"param-doc-description\">min_samples_split: int or float, default=2<br><br>The minimum number of samples required to split an internal node:<br><br>- If int, then consider `min_samples_split` as the minimum number.<br>- If float, then `min_samples_split` is a fraction and<br> `ceil(min_samples_split * n_samples)` are the minimum<br> number of samples for each split.<br><br>.. versionchanged:: 0.18<br> Added float values for fractions.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">2</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('min_samples_leaf',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=min_samples_leaf,-int%20or%20float%2C%20default%3D1\">\n",
" min_samples_leaf\n",
" <span class=\"param-doc-description\">min_samples_leaf: int or float, default=1<br><br>The minimum number of samples required to be at a leaf node.<br>A split point at any depth will only be considered if it leaves at<br>least ``min_samples_leaf`` training samples in each of the left and<br>right branches. This may have the effect of smoothing the model,<br>especially in regression.<br><br>- If int, then consider `min_samples_leaf` as the minimum number.<br>- If float, then `min_samples_leaf` is a fraction and<br> `ceil(min_samples_leaf * n_samples)` are the minimum<br> number of samples for each node.<br><br>.. versionchanged:: 0.18<br> Added float values for fractions.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">1</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('min_weight_fraction_leaf',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=min_weight_fraction_leaf,-float%2C%20default%3D0.0\">\n",
" min_weight_fraction_leaf\n",
" <span class=\"param-doc-description\">min_weight_fraction_leaf: float, default=0.0<br><br>The minimum weighted fraction of the sum total of weights (of all<br>the input samples) required to be at a leaf node. Samples have<br>equal weight when sample_weight is not provided.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">0.0</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('max_features',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=max_features,-%7B%22sqrt%22%2C%20%22log2%22%2C%20None%7D%2C%20int%20or%20float%2C%20default%3D1.0\">\n",
" max_features\n",
" <span class=\"param-doc-description\">max_features: {\"sqrt\", \"log2\", None}, int or float, default=1.0<br><br>The number of features to consider when looking for the best split:<br><br>- If int, then consider `max_features` features at each split.<br>- If float, then `max_features` is a fraction and<br> `max(1, int(max_features * n_features_in_))` features are considered at each<br> split.<br>- If \"sqrt\", then `max_features=sqrt(n_features)`.<br>- If \"log2\", then `max_features=log2(n_features)`.<br>- If None or 1.0, then `max_features=n_features`.<br><br>.. note::<br> The default of 1.0 is equivalent to bagged trees and more<br> randomness can be achieved by setting smaller values, e.g. 0.3.<br><br>.. versionchanged:: 1.1<br> The default of `max_features` changed from `\"auto\"` to 1.0.<br><br>Note: the search for a split does not stop until at least one<br>valid partition of the node samples is found, even if it requires to<br>effectively inspect more than ``max_features`` features.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">1.0</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('max_leaf_nodes',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=max_leaf_nodes,-int%2C%20default%3DNone\">\n",
" max_leaf_nodes\n",
" <span class=\"param-doc-description\">max_leaf_nodes: int, default=None<br><br>Grow trees with ``max_leaf_nodes`` in best-first fashion.<br>Best nodes are defined as relative reduction in impurity.<br>If None then unlimited number of leaf nodes.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">None</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('min_impurity_decrease',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=min_impurity_decrease,-float%2C%20default%3D0.0\">\n",
" min_impurity_decrease\n",
" <span class=\"param-doc-description\">min_impurity_decrease: float, default=0.0<br><br>A node will be split if this split induces a decrease of the impurity<br>greater than or equal to this value.<br><br>The weighted impurity decrease equation is the following::<br><br> N_t / N * (impurity - N_t_R / N_t * right_impurity<br> - N_t_L / N_t * left_impurity)<br><br>where ``N`` is the total number of samples, ``N_t`` is the number of<br>samples at the current node, ``N_t_L`` is the number of samples in the<br>left child, and ``N_t_R`` is the number of samples in the right child.<br><br>``N``, ``N_t``, ``N_t_R`` and ``N_t_L`` all refer to the weighted sum,<br>if ``sample_weight`` is passed.<br><br>.. versionadded:: 0.19</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">0.0</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('bootstrap',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=bootstrap,-bool%2C%20default%3DTrue\">\n",
" bootstrap\n",
" <span class=\"param-doc-description\">bootstrap: bool, default=True<br><br>Whether bootstrap samples are used when building trees. If False, the<br>whole dataset is used to build each tree.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">True</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('oob_score',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=oob_score,-bool%20or%20callable%2C%20default%3DFalse\">\n",
" oob_score\n",
" <span class=\"param-doc-description\">oob_score: bool or callable, default=False<br><br>Whether to use out-of-bag samples to estimate the generalization score.<br>By default, :func:`~sklearn.metrics.r2_score` is used.<br>Provide a callable with signature `metric(y_true, y_pred)` to use a<br>custom metric. Only available if `bootstrap=True`.<br><br>For an illustration of out-of-bag (OOB) error estimation, see the example<br>:ref:`sphx_glr_auto_examples_ensemble_plot_ensemble_oob.py`.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">False</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('n_jobs',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=n_jobs,-int%2C%20default%3DNone\">\n",
" n_jobs\n",
" <span class=\"param-doc-description\">n_jobs: int, default=None<br><br>The number of jobs to run in parallel. :meth:`fit`, :meth:`predict`,<br>:meth:`decision_path` and :meth:`apply` are all parallelized over the<br>trees. ``None`` means 1 unless in a :obj:`joblib.parallel_backend`<br>context. ``-1`` means using all processors. See :term:`Glossary<br><n_jobs>` for more details.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">None</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('random_state',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=random_state,-int%2C%20RandomState%20instance%20or%20None%2C%20default%3DNone\">\n",
" random_state\n",
" <span class=\"param-doc-description\">random_state: int, RandomState instance or None, default=None<br><br>Controls both the randomness of the bootstrapping of the samples used<br>when building trees (if ``bootstrap=True``) and the sampling of the<br>features to consider when looking for the best split at each node<br>(if ``max_features < n_features``).<br>See :term:`Glossary <random_state>` for details.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">None</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('verbose',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=verbose,-int%2C%20default%3D0\">\n",
" verbose\n",
" <span class=\"param-doc-description\">verbose: int, default=0<br><br>Controls the verbosity when fitting and predicting.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">0</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('warm_start',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=warm_start,-bool%2C%20default%3DFalse\">\n",
" warm_start\n",
" <span class=\"param-doc-description\">warm_start: bool, default=False<br><br>When set to ``True``, reuse the solution of the previous call to fit<br>and add more estimators to the ensemble, otherwise, just fit a whole<br>new forest. See :term:`Glossary <warm_start>` and<br>:ref:`tree_ensemble_warm_start` for details.</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">False</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('ccp_alpha',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=ccp_alpha,-non-negative%20float%2C%20default%3D0.0\">\n",
" ccp_alpha\n",
" <span class=\"param-doc-description\">ccp_alpha: non-negative float, default=0.0<br><br>Complexity parameter used for Minimal Cost-Complexity Pruning. The<br>subtree with the largest cost complexity that is smaller than<br>``ccp_alpha`` will be chosen. By default, no pruning is performed. See<br>:ref:`minimal_cost_complexity_pruning` for details. See<br>:ref:`sphx_glr_auto_examples_tree_plot_cost_complexity_pruning.py`<br>for an example of such pruning.<br><br>.. versionadded:: 0.22</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">0.0</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('max_samples',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=max_samples,-int%20or%20float%2C%20default%3DNone\">\n",
" max_samples\n",
" <span class=\"param-doc-description\">max_samples: int or float, default=None<br><br>If bootstrap is True, the number of samples to draw from X<br>to train each base estimator.<br><br>- If None (default), then draw `X.shape[0]` samples.<br>- If int, then draw `max_samples` samples.<br>- If float, then draw `max(round(n_samples * max_samples), 1)` samples. Thus,<br> `max_samples` should be in the interval `(0.0, 1.0]`.<br><br>.. versionadded:: 0.22</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">None</td>\n",
" </tr>\n",
" \n",
"\n",
" <tr class=\"default\">\n",
" <td><i class=\"copy-paste-icon\"\n",
" onclick=\"copyToClipboard('monotonic_cst',\n",
" this.parentElement.nextElementSibling)\"\n",
" ></i></td>\n",
" <td class=\"param\">\n",
" <a class=\"param-doc-link\"\n",
" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.8/modules/generated/sklearn.ensemble.RandomForestRegressor.html#:~:text=monotonic_cst,-array-like%20of%20int%20of%20shape%20%28n_features%29%2C%20default%3DNone\">\n",
" monotonic_cst\n",
" <span class=\"param-doc-description\">monotonic_cst: array-like of int of shape (n_features), default=None<br><br>Indicates the monotonicity constraint to enforce on each feature.<br> - 1: monotonically increasing<br> - 0: no constraint<br> - -1: monotonically decreasing<br><br>If monotonic_cst is None, no constraints are applied.<br><br>Monotonicity constraints are not supported for:<br> - multioutput regressions (i.e. when `n_outputs_ > 1`),<br> - regressions trained on data with missing values.<br><br>Read more in the :ref:`User Guide <monotonic_cst_gbdt>`.<br><br>.. versionadded:: 1.4</span>\n",
" </a>\n",
" </td>\n",
" <td class=\"value\">None</td>\n",
" </tr>\n",
" \n",
" </tbody>\n",
" </table>\n",
" </details>\n",
" </div>\n",
" </div></div></div></div></div><script>function copyToClipboard(text, element) {\n",
" // Get the parameter prefix from the closest toggleable content\n",
" const toggleableContent = element.closest('.sk-toggleable__content');\n",
" const paramPrefix = toggleableContent ? toggleableContent.dataset.paramPrefix : '';\n",
" const fullParamName = paramPrefix ? `${paramPrefix}${text}` : text;\n",
"\n",
" const originalStyle = element.style;\n",
" const computedStyle = window.getComputedStyle(element);\n",
" const originalWidth = computedStyle.width;\n",
" const originalHTML = element.innerHTML.replace('Copied!', '');\n",
"\n",
" navigator.clipboard.writeText(fullParamName)\n",
" .then(() => {\n",
" element.style.width = originalWidth;\n",
" element.style.color = 'green';\n",
" element.innerHTML = \"Copied!\";\n",
"\n",
" setTimeout(() => {\n",
" element.innerHTML = originalHTML;\n",
" element.style = originalStyle;\n",
" }, 2000);\n",
" })\n",
" .catch(err => {\n",
" console.error('Failed to copy:', err);\n",
" element.style.color = 'red';\n",
" element.innerHTML = \"Failed!\";\n",
" setTimeout(() => {\n",
" element.innerHTML = originalHTML;\n",
" element.style = originalStyle;\n",
" }, 2000);\n",
" });\n",
" return false;\n",
"}\n",
"\n",
"document.querySelectorAll('.copy-paste-icon').forEach(function(element) {\n",
" const toggleableContent = element.closest('.sk-toggleable__content');\n",
" const paramPrefix = toggleableContent ? toggleableContent.dataset.paramPrefix : '';\n",
" const paramName = element.parentElement.nextElementSibling\n",
" .textContent.trim().split(' ')[0];\n",
" const fullParamName = paramPrefix ? `${paramPrefix}${paramName}` : paramName;\n",
"\n",
" element.setAttribute('title', fullParamName);\n",
"});\n",
"\n",
"\n",
"/**\n",
" * Adapted from Skrub\n",
" * https://github.com/skrub-data/skrub/blob/403466d1d5d4dc76a7ef569b3f8228db59a31dc3/skrub/_reporting/_data/templates/report.js#L789\n",
" * @returns \"light\" or \"dark\"\n",
" */\n",
"function detectTheme(element) {\n",
" const body = document.querySelector('body');\n",
"\n",
" // Check VSCode theme\n",
" const themeKindAttr = body.getAttribute('data-vscode-theme-kind');\n",
" const themeNameAttr = body.getAttribute('data-vscode-theme-name');\n",
"\n",
" if (themeKindAttr && themeNameAttr) {\n",
" const themeKind = themeKindAttr.toLowerCase();\n",
" const themeName = themeNameAttr.toLowerCase();\n",
"\n",
" if (themeKind.includes(\"dark\") || themeName.includes(\"dark\")) {\n",
" return \"dark\";\n",
" }\n",
" if (themeKind.includes(\"light\") || themeName.includes(\"light\")) {\n",
" return \"light\";\n",
" }\n",
" }\n",
"\n",
" // Check Jupyter theme\n",
" if (body.getAttribute('data-jp-theme-light') === 'false') {\n",
" return 'dark';\n",
" } else if (body.getAttribute('data-jp-theme-light') === 'true') {\n",
" return 'light';\n",
" }\n",
"\n",
" // Guess based on a parent element's color\n",
" const color = window.getComputedStyle(element.parentNode, null).getPropertyValue('color');\n",
" const match = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)\\s*$/i);\n",
" if (match) {\n",
" const [r, g, b] = [\n",
" parseFloat(match[1]),\n",
" parseFloat(match[2]),\n",
" parseFloat(match[3])\n",
" ];\n",
"\n",
" // https://en.wikipedia.org/wiki/HSL_and_HSV#Lightness\n",
" const luma = 0.299 * r + 0.587 * g + 0.114 * b;\n",
"\n",
" if (luma > 180) {\n",
" // If the text is very bright we have a dark theme\n",
" return 'dark';\n",
" }\n",
" if (luma < 75) {\n",
" // If the text is very dark we have a light theme\n",
" return 'light';\n",
" }\n",
" // Otherwise fall back to the next heuristic.\n",
" }\n",
"\n",
" // Fallback to system preference\n",
" return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n",
"}\n",
"\n",
"\n",
"function forceTheme(elementId) {\n",
" const estimatorElement = document.querySelector(`#${elementId}`);\n",
" if (estimatorElement === null) {\n",
" console.error(`Element with id ${elementId} not found.`);\n",
" } else {\n",
" const theme = detectTheme(estimatorElement);\n",
" estimatorElement.classList.add(theme);\n",
" }\n",
"}\n",
"\n",
"forceTheme('sk-container-id-1');</script></body>"
],
"text/plain": [
"RandomForestRegressor(max_depth=4, n_estimators=1000)"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = sklearn.ensemble.RandomForestRegressor(n_estimators=1000, max_depth=4)\n",
"model.fit(X, y)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### Train XGBoost model"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:47.891842198Z",
"start_time": "2023-10-14T15:13:46.874216599Z"
},
"collapsed": false
},
"outputs": [],
"source": [
"bst = xgboost.train({\"learning_rate\": 0.01, \"max_depth\": 4}, xgboost.DMatrix(X, label=y), 1000)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### Python TreeExplainer\n",
"\n",
"This uses numba to speed things up."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:47.899642452Z",
"start_time": "2023-10-14T15:13:47.898748678Z"
},
"collapsed": false
},
"outputs": [],
"source": [
"class TreeExplainer:\n",
" def __init__(self, model, **kwargs):\n",
" if str(type(model)).endswith(\"sklearn.ensemble._forest.RandomForestRegressor'>\"):\n",
" # self.trees = [Tree(e.tree_) for e in model.estimators_]\n",
" self.trees = [\n",
" Tree(\n",
" children_left=e.tree_.children_left,\n",
" children_right=e.tree_.children_right,\n",
" children_default=e.tree_.children_right,\n",
" feature=e.tree_.feature,\n",
" threshold=e.tree_.threshold,\n",
" value=e.tree_.value[:, 0, 0],\n",
" node_sample_weight=e.tree_.weighted_n_node_samples,\n",
" )\n",
" for e in model.estimators_\n",
" ]\n",
"\n",
" # Preallocate space for the unique path data\n",
" maxd = np.max([t.max_depth for t in self.trees]) + 2\n",
" s = (maxd * (maxd + 1)) // 2\n",
" self.feature_indexes = np.zeros(s, dtype=np.int32)\n",
" self.zero_fractions = np.zeros(s, dtype=np.float64)\n",
" self.one_fractions = np.zeros(s, dtype=np.float64)\n",
" self.pweights = np.zeros(s, dtype=np.float64)\n",
"\n",
" def shap_values(self, X, **kwargs):\n",
" # convert dataframes\n",
" if isinstance(X, pd.Series):\n",
" X = X.values\n",
" elif isinstance(X, pd.DataFrame):\n",
" X = X.values\n",
"\n",
" X = np.array(X, dtype=np.float64, copy=True, order=\"C\")\n",
"\n",
" assert isinstance(X, np.ndarray), \"Unknown instance type: \" + str(type(X))\n",
" assert len(X.shape) == 1 or len(X.shape) == 2, \"Instance must have 1 or 2 dimensions!\"\n",
"\n",
" # single instance\n",
" if len(X.shape) == 1:\n",
" phi = np.zeros(X.shape[0] + 1)\n",
" x_missing = np.zeros(X.shape[0], dtype=bool)\n",
" for t in self.trees:\n",
" self.tree_shap(t, X, x_missing, phi)\n",
" phi /= len(self.trees)\n",
" elif len(X.shape) == 2:\n",
" phi = np.zeros((X.shape[0], X.shape[1] + 1))\n",
" x_missing = np.zeros(X.shape[1], dtype=bool)\n",
" for i in range(X.shape[0]):\n",
" for t in self.trees:\n",
" self.tree_shap(t, X[i, :], x_missing, phi[i, :])\n",
" phi /= len(self.trees)\n",
" return phi\n",
"\n",
" def tree_shap(self, tree, x, x_missing, phi, condition=0, condition_feature=0):\n",
" # update the bias term, which is the last index in phi\n",
" # (note the paper has this as phi_0 instead of phi_M)\n",
" if condition == 0:\n",
" phi[-1] += tree.values[0]\n",
"\n",
" # start the recursive algorithm\n",
" tree_shap_recursive(\n",
" tree.children_left,\n",
" tree.children_right,\n",
" tree.children_default,\n",
" tree.features,\n",
" tree.thresholds,\n",
" tree.values,\n",
" tree.node_sample_weight,\n",
" x,\n",
" x_missing,\n",
" phi,\n",
" 0,\n",
" 0,\n",
" self.feature_indexes,\n",
" self.zero_fractions,\n",
" self.one_fractions,\n",
" self.pweights,\n",
" 1.0,\n",
" 1.0,\n",
" -1,\n",
" condition,\n",
" condition_feature,\n",
" 1.0,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:51.276386697Z",
"start_time": "2023-10-14T15:13:47.916741034Z"
},
"collapsed": false
},
"outputs": [],
"source": [
"# extend our decision path with a fraction of one and zero extensions\n",
"@numba.jit(\n",
" numba.types.void(\n",
" numba.types.int32[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.int32,\n",
" numba.types.float64,\n",
" numba.types.float64,\n",
" numba.types.int32,\n",
" ),\n",
" nopython=True,\n",
" nogil=True,\n",
")\n",
"def extend_path(\n",
" feature_indexes,\n",
" zero_fractions,\n",
" one_fractions,\n",
" pweights,\n",
" unique_depth,\n",
" zero_fraction,\n",
" one_fraction,\n",
" feature_index,\n",
"):\n",
" feature_indexes[unique_depth] = feature_index\n",
" zero_fractions[unique_depth] = zero_fraction\n",
" one_fractions[unique_depth] = one_fraction\n",
" if unique_depth == 0:\n",
" pweights[unique_depth] = 1\n",
" else:\n",
" pweights[unique_depth] = 0\n",
"\n",
" for i in range(unique_depth - 1, -1, -1):\n",
" pweights[i + 1] += one_fraction * pweights[i] * (i + 1) / (unique_depth + 1)\n",
" pweights[i] = zero_fraction * pweights[i] * (unique_depth - i) / (unique_depth + 1)\n",
"\n",
"\n",
"# undo a previous extension of the decision path\n",
"@numba.jit(\n",
" numba.types.void(\n",
" numba.types.int32[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.int32,\n",
" numba.types.int32,\n",
" ),\n",
" nopython=True,\n",
" nogil=True,\n",
")\n",
"def unwind_path(feature_indexes, zero_fractions, one_fractions, pweights, unique_depth, path_index):\n",
" one_fraction = one_fractions[path_index]\n",
" zero_fraction = zero_fractions[path_index]\n",
" next_one_portion = pweights[unique_depth]\n",
"\n",
" for i in range(unique_depth - 1, -1, -1):\n",
" if one_fraction != 0:\n",
" tmp = pweights[i]\n",
" pweights[i] = next_one_portion * (unique_depth + 1) / ((i + 1) * one_fraction)\n",
" next_one_portion = tmp - pweights[i] * zero_fraction * (unique_depth - i) / (unique_depth + 1)\n",
" else:\n",
" pweights[i] = (pweights[i] * (unique_depth + 1)) / (zero_fraction * (unique_depth - i))\n",
"\n",
" for i in range(path_index, unique_depth):\n",
" feature_indexes[i] = feature_indexes[i + 1]\n",
" zero_fractions[i] = zero_fractions[i + 1]\n",
" one_fractions[i] = one_fractions[i + 1]\n",
"\n",
"\n",
"# determine what the total permuation weight would be if\n",
"# we unwound a previous extension in the decision path\n",
"@numba.jit(\n",
" numba.types.float64(\n",
" numba.types.int32[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.int32,\n",
" numba.types.int32,\n",
" ),\n",
" nopython=True,\n",
" nogil=True,\n",
")\n",
"def unwound_path_sum(feature_indexes, zero_fractions, one_fractions, pweights, unique_depth, path_index):\n",
" one_fraction = one_fractions[path_index]\n",
" zero_fraction = zero_fractions[path_index]\n",
" next_one_portion = pweights[unique_depth]\n",
" total = 0\n",
"\n",
" for i in range(unique_depth - 1, -1, -1):\n",
" if one_fraction != 0:\n",
" tmp = next_one_portion * (unique_depth + 1) / ((i + 1) * one_fraction)\n",
" total += tmp\n",
" next_one_portion = pweights[i] - tmp * zero_fraction * ((unique_depth - i) / (unique_depth + 1))\n",
" else:\n",
" total += (pweights[i] / zero_fraction) / ((unique_depth - i) / (unique_depth + 1))\n",
"\n",
" return total\n",
"\n",
"\n",
"class Tree:\n",
" def __init__(\n",
" self,\n",
" children_left,\n",
" children_right,\n",
" children_default,\n",
" feature,\n",
" threshold,\n",
" value,\n",
" node_sample_weight,\n",
" ):\n",
" self.children_left = children_left.astype(np.int32)\n",
" self.children_right = children_right.astype(np.int32)\n",
" self.children_default = children_default.astype(np.int32)\n",
" self.features = feature.astype(np.int32)\n",
" self.thresholds = threshold\n",
" self.values = value\n",
" self.node_sample_weight = node_sample_weight\n",
"\n",
" self.max_depth = compute_expectations(\n",
" self.children_left,\n",
" self.children_right,\n",
" self.node_sample_weight,\n",
" self.values,\n",
" 0,\n",
" )\n",
"\n",
"\n",
"@numba.jit(nopython=True)\n",
"def compute_expectations(children_left, children_right, node_sample_weight, values, i, depth=0):\n",
" if children_right[i] == -1:\n",
" values[i] = values[i]\n",
" return 0\n",
" else:\n",
" li = children_left[i]\n",
" ri = children_right[i]\n",
" depth_left = compute_expectations(children_left, children_right, node_sample_weight, values, li, depth + 1)\n",
" depth_right = compute_expectations(children_left, children_right, node_sample_weight, values, ri, depth + 1)\n",
" left_weight = node_sample_weight[li]\n",
" right_weight = node_sample_weight[ri]\n",
" v = (left_weight * values[li] + right_weight * values[ri]) / (left_weight + right_weight)\n",
" values[i] = v\n",
" return max(depth_left, depth_right) + 1\n",
"\n",
"\n",
"# recursive computation of SHAP values for a decision tree\n",
"@numba.jit(\n",
" numba.types.void(\n",
" numba.types.int32[:],\n",
" numba.types.int32[:],\n",
" numba.types.int32[:],\n",
" numba.types.int32[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.boolean[:],\n",
" numba.types.float64[:],\n",
" numba.types.int64,\n",
" numba.types.int64,\n",
" numba.types.int32[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64[:],\n",
" numba.types.float64,\n",
" numba.types.float64,\n",
" numba.types.int64,\n",
" numba.types.int64,\n",
" numba.types.int64,\n",
" numba.types.float64,\n",
" ),\n",
" nopython=True,\n",
" nogil=True,\n",
")\n",
"def tree_shap_recursive(\n",
" children_left,\n",
" children_right,\n",
" children_default,\n",
" features,\n",
" thresholds,\n",
" values,\n",
" node_sample_weight,\n",
" x,\n",
" x_missing,\n",
" phi,\n",
" node_index,\n",
" unique_depth,\n",
" parent_feature_indexes,\n",
" parent_zero_fractions,\n",
" parent_one_fractions,\n",
" parent_pweights,\n",
" parent_zero_fraction,\n",
" parent_one_fraction,\n",
" parent_feature_index,\n",
" condition,\n",
" condition_feature,\n",
" condition_fraction,\n",
"):\n",
" # stop if we have no weight coming down to us\n",
" if condition_fraction == 0:\n",
" return\n",
"\n",
" # extend the unique path\n",
" feature_indexes = parent_feature_indexes[unique_depth + 1 :]\n",
" feature_indexes[: unique_depth + 1] = parent_feature_indexes[: unique_depth + 1]\n",
" zero_fractions = parent_zero_fractions[unique_depth + 1 :]\n",
" zero_fractions[: unique_depth + 1] = parent_zero_fractions[: unique_depth + 1]\n",
" one_fractions = parent_one_fractions[unique_depth + 1 :]\n",
" one_fractions[: unique_depth + 1] = parent_one_fractions[: unique_depth + 1]\n",
" pweights = parent_pweights[unique_depth + 1 :]\n",
" pweights[: unique_depth + 1] = parent_pweights[: unique_depth + 1]\n",
"\n",
" if condition == 0 or condition_feature != parent_feature_index:\n",
" extend_path(\n",
" feature_indexes,\n",
" zero_fractions,\n",
" one_fractions,\n",
" pweights,\n",
" unique_depth,\n",
" parent_zero_fraction,\n",
" parent_one_fraction,\n",
" parent_feature_index,\n",
" )\n",
"\n",
" split_index = features[node_index]\n",
"\n",
" # leaf node\n",
" if children_right[node_index] == -1:\n",
" for i in range(1, unique_depth + 1):\n",
" w = unwound_path_sum(\n",
" feature_indexes,\n",
" zero_fractions,\n",
" one_fractions,\n",
" pweights,\n",
" unique_depth,\n",
" i,\n",
" )\n",
" phi[feature_indexes[i]] += (\n",
" w * (one_fractions[i] - zero_fractions[i]) * values[node_index] * condition_fraction\n",
" )\n",
"\n",
" # internal node\n",
" else:\n",
" # find which branch is \"hot\" (meaning x would follow it)\n",
" hot_index = 0\n",
" cleft = children_left[node_index]\n",
" cright = children_right[node_index]\n",
" if x_missing[split_index] == 1:\n",
" hot_index = children_default[node_index]\n",
" elif x[split_index] < thresholds[node_index]:\n",
" hot_index = cleft\n",
" else:\n",
" hot_index = cright\n",
" cold_index = cright if hot_index == cleft else cleft\n",
" w = node_sample_weight[node_index]\n",
" hot_zero_fraction = node_sample_weight[hot_index] / w\n",
" cold_zero_fraction = node_sample_weight[cold_index] / w\n",
" incoming_zero_fraction = 1\n",
" incoming_one_fraction = 1\n",
"\n",
" # see if we have already split on this feature,\n",
" # if so we undo that split so we can redo it for this node\n",
" path_index = 0\n",
" while path_index <= unique_depth:\n",
" if feature_indexes[path_index] == split_index:\n",
" break\n",
" path_index += 1\n",
"\n",
" if path_index != unique_depth + 1:\n",
" incoming_zero_fraction = zero_fractions[path_index]\n",
" incoming_one_fraction = one_fractions[path_index]\n",
" unwind_path(\n",
" feature_indexes,\n",
" zero_fractions,\n",
" one_fractions,\n",
" pweights,\n",
" unique_depth,\n",
" path_index,\n",
" )\n",
" unique_depth -= 1\n",
"\n",
" # divide up the condition_fraction among the recursive calls\n",
" hot_condition_fraction = condition_fraction\n",
" cold_condition_fraction = condition_fraction\n",
" if condition > 0 and split_index == condition_feature:\n",
" cold_condition_fraction = 0\n",
" unique_depth -= 1\n",
" elif condition < 0 and split_index == condition_feature:\n",
" hot_condition_fraction *= hot_zero_fraction\n",
" cold_condition_fraction *= cold_zero_fraction\n",
" unique_depth -= 1\n",
"\n",
" tree_shap_recursive(\n",
" children_left,\n",
" children_right,\n",
" children_default,\n",
" features,\n",
" thresholds,\n",
" values,\n",
" node_sample_weight,\n",
" x,\n",
" x_missing,\n",
" phi,\n",
" hot_index,\n",
" unique_depth + 1,\n",
" feature_indexes,\n",
" zero_fractions,\n",
" one_fractions,\n",
" pweights,\n",
" hot_zero_fraction * incoming_zero_fraction,\n",
" incoming_one_fraction,\n",
" split_index,\n",
" condition,\n",
" condition_feature,\n",
" hot_condition_fraction,\n",
" )\n",
"\n",
" tree_shap_recursive(\n",
" children_left,\n",
" children_right,\n",
" children_default,\n",
" features,\n",
" thresholds,\n",
" values,\n",
" node_sample_weight,\n",
" x,\n",
" x_missing,\n",
" phi,\n",
" cold_index,\n",
" unique_depth + 1,\n",
" feature_indexes,\n",
" zero_fractions,\n",
" one_fractions,\n",
" pweights,\n",
" cold_zero_fraction * incoming_zero_fraction,\n",
" 0,\n",
" split_index,\n",
" condition,\n",
" condition_feature,\n",
" cold_condition_fraction,\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### Compare runtime of XGBoost Tree SHAP..."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:51.429322880Z",
"start_time": "2023-10-14T15:13:51.285956536Z"
},
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.4652073383331299\n"
]
}
],
"source": [
"start = time.time()\n",
"shap_values = bst.predict(xgboost.DMatrix(X), pred_contribs=True)\n",
"print(time.time() - start)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### Versus the Python (numba) Tree SHAP..."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:13:51.678083888Z",
"start_time": "2023-10-14T15:13:51.429216249Z"
},
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([-0.52625503, 0.07485998, 0.10129917, -0.00780694, 0.18581504,\n",
" 0.35883265, 0.14973339, -0.03848465, 2.04406351])"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x = np.ones(X.shape[1])\n",
"TreeExplainer(model).shap_values(x)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2023-10-14T15:14:01.899083922Z",
"start_time": "2023-10-14T15:13:51.678228170Z"
},
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.013829231262207031\n",
"18.91119885444641\n"
]
}
],
"source": [
"start = time.time()\n",
"ex = TreeExplainer(model)\n",
"print(time.time() - start)\n",
"start = time.time()\n",
"ex.shap_values(X.iloc[:, :])\n",
"print(time.time() - start)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### ...about a hundred times slower in python, even with numba"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "shap",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 1
}