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

714 lines
23 KiB
Python

from __future__ import annotations
import os
from typing import TYPE_CHECKING, Final, Literal, overload
from urllib.request import urlretrieve
import numpy as np
import pandas as pd
import sklearn.datasets
import shap
if TYPE_CHECKING: # pragma: no cover
import scipy.sparse as ssp
github_data_url: Final[str] = "https://github.com/shap/shap/raw/master/data/"
def imagenet50(resolution: int = 224, n_points: int | None = None) -> tuple[np.ndarray, np.ndarray]:
"""Return a set of 50 images representative of ImageNet images.
Parameters
----------
resolution : int
The resolution of the images. At present, the only supported value is 224.
n_points : int, optional
Number of data points to sample. If None, the entire dataset is used.
Returns
-------
X : np.ndarray
Represents images from ImageNet of a certain resolution.
y : np.ndarray
The target variables, that is, the ImageNet classes.
Notes
-----
This dataset was collected by randomly finding a working ImageNet link and then pasting the
original ImageNet image into Google image search restricted to images licensed for reuse. A
similar image (now with rights to reuse) was downloaded as a rough replacement for the original
ImageNet image. The point is to have a random sample of ImageNet for use as a background
distribution for explaining models trained on ImageNet data.
Note that because the images are only rough replacements, the labels might no longer be correct.
Examples
--------
To get the processed images and labels::
images, labels = shap.datasets.imagenet50()
"""
prefix = github_data_url + "imagenet50_"
X: np.ndarray = np.load(cache(f"{prefix}{resolution}x{resolution}.npy")).astype(np.float32)
y: np.ndarray = np.loadtxt(cache(f"{prefix}labels.csv"))
if n_points is not None:
X = shap.utils.sample(X, n_points, random_state=0)
y = shap.utils.sample(y, n_points, random_state=0)
return X, y
def california(n_points: int | None = None) -> tuple[pd.DataFrame, np.ndarray]:
"""Return the California housing data in a tabular format.
Used in predictive regression tasks.
Parameters
----------
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number of points.
Returns
-------
X : pd.DataFrame
The feature data.
y : np.ndarray
The target variable.
Notes
-----
The returned feature matrix ``X`` includes the following features:
- ``MedInc`` (float): Median income in block
- ``HouseAge`` (float): Median house age in block
- ``AveRooms`` (float): Average rooms in dwelling
- ``AveBedrms`` (float): Average bedrooms in dwelling
- ``Population`` (float): Block population
- ``AveOccup`` (float): Average house occupancy
- ``Latitude`` (float): House block latitude
- ``Longitude`` (float): House block longitude
The target column represents the median house value for California districts.
References
----------
California housing dataset: :external+scikit-learn:func:`sklearn.datasets.fetch_california_housing`
Examples
--------
To get the processed data and target labels::
data, target = shap.datasets.california()
"""
d = sklearn.datasets.fetch_california_housing()
df = pd.DataFrame(data=d.data, columns=d.feature_names)
target: np.ndarray = d.target
if n_points is not None:
df = shap.utils.sample(df, n_points, random_state=0)
target = shap.utils.sample(target, n_points, random_state=0)
return df, target
def linnerud(n_points: int | None = None) -> tuple[pd.DataFrame, pd.DataFrame]:
"""Return the Linnerud dataset in a convenient package for multi-target regression.
Parameters
----------
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number
of points.
Returns
-------
X : pd.DataFrame
The feature data.
y : pd.DataFrame
The multiclass target variables.
Notes
-----
- The Linnerud dataset contains physiological and exercise data for 20 individuals.
- The feature matrix ``X`` includes three exercise variables: ``Chins``, ``Situps``, ``Jumps``.
- The target variables ``y`` include three physiological measurements: ``Weight``, ``Waist``, ``Pulse``.
More details: :external+scikit-learn:func:`sklearn.datasets.load_linnerud`
Examples
--------
To get the feature matrix and target variables::
features, targets = shap.datasets.linnerud()
To get a subset of the data::
subset_features, subset_targets = shap.datasets.linnerud(n_points=100)
"""
d = sklearn.datasets.load_linnerud()
X = pd.DataFrame(d.data, columns=d.feature_names)
y = pd.DataFrame(d.target, columns=d.target_names)
if n_points is not None:
X = shap.utils.sample(X, n_points, random_state=0)
y = shap.utils.sample(y, n_points, random_state=0)
return X, y
def imdb(n_points: int | None = None) -> tuple[list[str], np.ndarray]:
"""Return the classic IMDB sentiment analysis training data in a nice package.
Used in binary text classification tasks.
Parameters
----------
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number of points.
Returns
-------
X : list of strings
Text data, where each string is a movie review.
y : np.ndarray
The target variable. Contains booleans, where True indicates a positive sentiment and False
indicates a negative sentiment.
Notes
-----
Full data is at: http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
Paper to cite when using the data is: http://www.aclweb.org/anthology/P11-1015
Examples
--------
To get the processed text data and labels::
text_data, labels = shap.datasets.imdb()
"""
with open(cache(github_data_url + "imdb_train.txt"), encoding="utf-8") as f:
data = f.readlines()
y = np.ones(25000, dtype=bool)
y[:12500] = 0
if n_points is not None:
data = shap.utils.sample(data, n_points, random_state=0)
y = shap.utils.sample(y, n_points, random_state=0)
return data, y
def communitiesandcrime(n_points: int | None = None) -> tuple[pd.DataFrame, np.ndarray]:
"""Predict the total number of violent crimes per 100K population.
This dataset is from the classic UCI Machine Learning repository:
https://archive.ics.uci.edu/ml/datasets/Communities+and+Crime+Unnormalized
Used in predictive regression tasks.
Parameters
----------
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number of points.
Returns
-------
X : pd.DataFrame
The feature data.
y : np.ndarray
The target variable.
Examples
--------
To get the processed data and target labels::
data, target = shap.datasets.communitiesandcrime()
"""
raw_data = pd.read_csv(cache(github_data_url + "CommViolPredUnnormalizedData.txt"), na_values="?")
# find the indices where the total violent crimes are known
valid_inds = np.where(np.invert(np.isnan(raw_data.iloc[:, -2])))[0]
if n_points is not None:
valid_inds = shap.utils.sample(valid_inds, n_points, random_state=0)
y = np.array(raw_data.iloc[valid_inds, -2], dtype=float)
# extract the predictive features and remove columns with missing values
X = raw_data.iloc[valid_inds, 5:-18]
valid_cols = np.where(np.isnan(X.values).sum(0) == 0)[0]
X = X.iloc[:, valid_cols]
return X, y
def diabetes(n_points: int | None = None) -> tuple[pd.DataFrame, np.ndarray]:
"""Return the diabetes data in a nice package.
Used in predictive regression tasks.
Parameters
----------
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number of points.
Returns
-------
X : pd.DataFrame
The feature data.
y : np.ndarray
The target variable.
Notes
-----
Feature Columns in ``X``:
- ``age`` (float): Age in years
- ``sex`` (float): Sex
- ``bmi`` (float): Body mass index
- ``bp`` (float): Average blood pressure
- ``s1`` (float): Total serum cholesterol
- ``s2`` (float): Low-density lipoproteins (LDL cholesterol)
- ``s3`` (float): High-density lipoproteins (HDL cholesterol)
- ``s4`` (float): Total cholesterol / HDL cholesterol ratio
- ``s5`` (float): Log of serum triglycerides level
- ``s6`` (float): Blood sugar level
Target ``y``:
- Progression of diabetes one year after baseline (float)
The diabetes dataset is a subset of the larger diabetes dataset from scikit-learn.
More details: :external+scikit-learn:func:`sklearn.datasets.load_diabetes`
Examples
--------
To get the processed data and target labels::
data, target = shap.datasets.diabetes()
"""
d = sklearn.datasets.load_diabetes()
df = pd.DataFrame(data=d.data, columns=d.feature_names)
target = d.target
if n_points is not None:
df = shap.utils.sample(df, n_points, random_state=0)
target = shap.utils.sample(target, n_points, random_state=0)
return df, target
@overload
def iris(display: Literal[False] = ..., n_points: int | None = ...) -> tuple[pd.DataFrame, np.ndarray]: ...
@overload
def iris(display: Literal[True] = ..., n_points: int | None = ...) -> tuple[pd.DataFrame, list[str]]: ...
def iris(display: bool = False, n_points: int | None = None) -> tuple[pd.DataFrame, np.ndarray | list[str]]:
"""Return the classic Iris dataset in a convenient package.
Parameters
----------
display : bool
If True, return the original feature matrix along with class labels (as strings). Default is False.
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number of points.
Returns
-------
X : pd.DataFrame
The feature matrix.
y : np.ndarray or a list of strings
If ``display`` is False, a numpy array representing the class labels encoded as integers is returned.
If ``display`` is True, then a list of class labels is returned.
Notes
-----
- The dataset includes measurements of sepal length, sepal width, petal length, and petal width for three
species of iris flowers.
- Class labels are encoded as integers (0, 1, 2) representing the species (setosa, versicolor, virginica).
- If ``display`` is True, class labels are returned as strings.
Examples
--------
To get the feature matrix and class labels::
features, labels = shap.datasets.iris()
To get the feature matrix and class labels as strings::
features, class_labels = shap.datasets.iris(display=True)
"""
d = sklearn.datasets.load_iris()
df = pd.DataFrame(data=d.data, columns=d.feature_names)
target: np.ndarray = d.target
if n_points is not None:
df = shap.utils.sample(df, n_points, random_state=0)
target = shap.utils.sample(target, n_points, random_state=0)
if display:
return df, [str(d.target_names[v]) for v in target]
return df, target
def adult(display: bool = False, n_points: int | None = None) -> tuple[pd.DataFrame, np.ndarray]:
"""Return the Adult census data in a structured format.
Used in binary classification tasks.
Parameters
----------
display : bool, optional
If True, return the raw data without target and redundant columns.
n_points : int, optional
Number of data points to sample. If provided, randomly samples the specified number of points.
Returns
-------
X : pd.DataFrame
If ``display`` is True, ``X`` contains the raw data without the 'Education', 'Target', and 'fnlwgt' columns.
Otherwise, ``X`` contains the processed data without the 'Target' and 'fnlwgt' columns.
y : np.ndarray
The 'Target' column returned as an array.
Notes
-----
- The original data includes the following columns:
- ``Age`` (float) : Age in years.
- ``Workclass`` (category) : Type of employment.
- ``fnlwgt`` (float) : Final weight; the number of units in the target population that the record represents.
- ``Education`` (category) : Highest level of education achieved.
- ``Education-Num`` (float) : Numeric representation of education level.
- ``Marital Status`` (category) : Marital status of the individual.
- ``Occupation`` (category) : Type of occupation.
- ``Relationship`` (category) : Relationship status.
- ``Race`` (category) : Ethnicity of the individual.
- ``Sex`` (category) : Gender of the individual.
- ``Capital Gain`` (float) : Capital gains recorded.
- ``Capital Loss`` (float) : Capital losses recorded.
- ``Hours per week`` (float) : Number of hours worked per week.
- ``Country`` (category) : Country of origin.
- ``Target`` (category) : Binary target variable indicating whether the individual earns more than 50K.
- The Education' column is redundant with 'Education-Num' and is dropped for simplicity.
- The 'Target' column is converted to binary (True/False) where '>50K' is True and '<=50K' is False.
- Certain categorical columns are encoded for numerical representation.
Examples
--------
To get the processed data and target labels::
data, target = shap.datasets.adult()
To get the raw data for display::
raw_data, target = shap.datasets.adult(display=True)
"""
dtypes = [
("Age", "float32"),
("Workclass", "category"),
("fnlwgt", "float32"),
("Education", "category"),
("Education-Num", "float32"),
("Marital Status", "category"),
("Occupation", "category"),
("Relationship", "category"),
("Race", "category"),
("Sex", "category"),
("Capital Gain", "float32"),
("Capital Loss", "float32"),
("Hours per week", "float32"),
("Country", "category"),
("Target", "category"),
]
raw_data = pd.read_csv(
cache(github_data_url + "adult.data"), names=[d[0] for d in dtypes], na_values="?", dtype=dict(dtypes)
)
if n_points is not None:
raw_data = shap.utils.sample(raw_data, n_points, random_state=0)
data = raw_data.drop(["Education"], axis=1) # redundant with Education-Num
filt_dtypes = list(filter(lambda x: x[0] not in ["Target", "Education"], dtypes))
data["Target"] = data["Target"] == " >50K"
rcode = {"Not-in-family": 0, "Unmarried": 1, "Other-relative": 2, "Own-child": 3, "Husband": 4, "Wife": 5}
for k, dtype in filt_dtypes:
if dtype == "category":
if k == "Relationship":
data[k] = np.array([rcode[v.strip()] for v in data[k]])
else:
data[k] = data[k].cat.codes
if display:
return raw_data.drop(["Education", "Target", "fnlwgt"], axis=1), data["Target"].values
return data.drop(["Target", "fnlwgt"], axis=1), data["Target"].values
def nhanesi(display: bool = False, n_points: int | None = None) -> tuple[pd.DataFrame, np.ndarray]:
"""Return a nicely packaged version of NHANES I data with survival times as labels.
Used in survival analysis tasks.
Parameters
----------
display : bool, optional
If True, returns the features with a modified display. Default is False.
n_points : int, optional
Number of data points to sample. Default is None (returns the entire dataset).
Returns
-------
X : pd.DataFrame
The feature data matrix. If ``display`` is True, a modified version of the features for display
is returned as ``X`` instead.
y : np.ndarray
The target variables representing survival times.
Examples
--------
Usage example::
features, survival_times = shap.datasets.nhanesi(display=True, n_points=100)
"""
X = pd.read_csv(cache(github_data_url + "NHANESI_X.csv"), index_col=0)
y = pd.read_csv(cache(github_data_url + "NHANESI_y.csv"), index_col=0)["y"]
if n_points is not None:
X = shap.utils.sample(X, n_points, random_state=0)
y = shap.utils.sample(y, n_points, random_state=0)
if display:
X_display = X.copy()
# X_display["sex_isFemale"] = ["Female" if v else "Male" for v in X["sex_isFemale"]]
return X_display, np.array(y)
return X, np.array(y)
def corrgroups60(n_points: int = 1_000) -> tuple[pd.DataFrame, np.ndarray]:
"""Correlated Groups (60 features)
A synthetic dataset consisting of 60 features with tight correlations among distinct groups of features.
Parameters
----------
n_points : int, optional
Number of data points to generate. Default is 1,000.
Returns
-------
X : pd.DataFrame
The feature data matrix
y : np.ndarray
The target variables
Notes
-----
- The dataset is generated with known correlations among distinct groups of features.
- Each feature is a unit variance Gaussian random variable centred around 0.
- The labels are generated based on a linear function of the features with added random noise.
Examples
--------
.. code-block:: python
data, target = shap.datasets.corrgroups60()
"""
# set a constant seed
old_seed = np.random.seed()
np.random.seed(0)
# generate dataset with known correlation
N, M = n_points, 60
# set one coefficient from each group of 3 to 1
beta = np.zeros(M)
beta[0:30:3] = 1
# build a correlation matrix with groups of 3 tightly correlated features
C = np.eye(M)
for i in range(0, 30, 3):
C[i, i + 1] = C[i + 1, i] = 0.99
C[i, i + 2] = C[i + 2, i] = 0.99
C[i + 1, i + 2] = C[i + 2, i + 1] = 0.99
def f(X):
return np.matmul(X, beta)
# Make sure the sample correlation is a perfect match
X_start = np.random.randn(N, M)
X_centered = X_start - X_start.mean(0)
Sigma = np.matmul(X_centered.T, X_centered) / X_centered.shape[0]
W = np.linalg.cholesky(np.linalg.inv(Sigma)).T
X_white = np.matmul(X_centered, W.T)
assert (
np.linalg.norm(np.corrcoef(np.matmul(X_centered, W.T).T) - np.eye(M)) < 1e-6
) # ensure this decorrelates the data
# create the final data
X_final = np.matmul(X_white, np.linalg.cholesky(C).T)
X = X_final
y = f(X) + np.random.randn(N) * 1e-2
# restore the previous numpy random seed
np.random.seed(old_seed)
return pd.DataFrame(X), y
def independentlinear60(n_points: int = 1_000) -> tuple[pd.DataFrame, np.ndarray]:
"""Independent Linear (60 features)
A synthetic dataset consisting of 60 features.
Parameters
----------
n_points : int, optional
Number of data points to generate. Default is 1,000.
Returns
-------
X : pd.DataFrame
The feature data matrix
y : np.ndarray
The target variables
Notes
-----
- Each feature is a unit variance Gaussian random variable centred around 0.
- The labels are generated based on a linear function of the features with added random noise.
Examples
--------
.. code-block:: python
features, labels = shap.datasets.independentlinear60()
"""
# set a constant seed
old_seed = np.random.seed()
np.random.seed(0)
# generate dataset with known correlation
N, M = n_points, 60
# set one coefficient from each group of 3 to 1
beta = np.zeros(M)
beta[0:30:3] = 1
def f(X):
return np.matmul(X, beta)
# Make sure the sample correlation is a perfect match
X_start = np.random.randn(N, M)
X = X_start - X_start.mean(0)
y = f(X) + np.random.randn(N) * 1e-2
# restore the previous numpy random seed
np.random.seed(old_seed)
return pd.DataFrame(X), y
def a1a(n_points: int | None = None) -> tuple[ssp.csr_matrix, np.ndarray]:
"""
Return a sparse dataset in scipy csr matrix format.
Data Source: :external+scikit-learn:func:`sklearn.datasets.load_svmlight_file`
Parameters
----------
n_points : int, optional
Number of data points to sample. If None, returns the entire dataset. Default is None.
Returns
-------
X : scipy.sparse.csr_matrix
Sparse feature matrix.
y : np.ndarray
Target labels.
Examples
--------
.. code-block:: python
data, target = shap.datasets.a1a()
"""
data: ssp.csr_matrix
target: np.ndarray
data, target = sklearn.datasets.load_svmlight_file(cache(github_data_url + "a1a.svmlight"))
if n_points is not None:
data = shap.utils.sample(data, n_points, random_state=0)
target = shap.utils.sample(target, n_points, random_state=0)
return data, target
def rank() -> tuple[ssp.csr_matrix, np.ndarray, ssp.csr_matrix, np.ndarray, np.ndarray, np.ndarray]:
"""Return ranking datasets from the LightGBM repository.
Used in ranking tasks.
Returns
-------
x_train : scipy.sparse.csr_matrix
Training feature matrix.
y_train : numpy.ndarray
Training labels.
x_test : scipy.sparse.csr_matrix
Testing feature matrix.
y_test : numpy.ndarray
Testing labels.
q_train : numpy.ndarray
Training query information.
q_test : numpy.ndarray
Testing query information.
Notes
-----
Data Source: LightGBM repository https://github.com/lightgbm-org/LightGBM/tree/master/examples/lambdarank
Examples
--------
.. code-block:: python
x_train, y_train, x_test, y_test, q_train, q_test = shap.datasets.rank()
"""
rank_data_url = "https://raw.githubusercontent.com/lightgbm-org/LightGBM/master/examples/lambdarank/"
x_train, y_train = sklearn.datasets.load_svmlight_file(cache(rank_data_url + "rank.train"))
x_test, y_test = sklearn.datasets.load_svmlight_file(cache(rank_data_url + "rank.test"))
q_train = np.loadtxt(cache(rank_data_url + "rank.train.query"))
q_test = np.loadtxt(cache(rank_data_url + "rank.test.query"))
return x_train, y_train, x_test, y_test, q_train, q_test
def cache(url: str, file_name: str | None = None) -> str:
"""Loads a file from the URL and caches it locally."""
if file_name is None:
file_name = os.path.basename(url)
data_dir = os.path.join(os.path.dirname(__file__), "cached_data")
os.makedirs(data_dir, exist_ok=True)
file_path: str = os.path.join(data_dir, file_name)
if not os.path.isfile(file_path):
urlretrieve(url, file_path)
return file_path