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

115 lines
3.6 KiB
Python

import matplotlib.pyplot as plt
import numpy as np
import pytest
import shap
@pytest.mark.mpl_image_compare
def test_scatter_single(explainer):
explanation = explainer(explainer.data)
shap.plots.scatter(explanation[:, "Age"], show=False)
plt.tight_layout()
return plt.gcf()
@pytest.mark.mpl_image_compare
def test_scatter_interaction(explainer):
explanation = explainer(explainer.data)
shap.plots.scatter(explanation[:, "Age"], color=explanation[:, "Workclass"], show=False)
plt.tight_layout()
return plt.gcf()
@pytest.mark.mpl_image_compare
def test_scatter_dotchain(explainer):
explanation = explainer(explainer.data)
shap.plots.scatter(explanation[:, explanation.abs.mean(0).argsort[-2]], show=False)
plt.tight_layout()
return plt.gcf()
@pytest.mark.mpl_image_compare
def test_scatter_multiple_cols_overlay(explainer):
explanation = explainer(explainer.data)
shap_values = explanation[:, ["Age", "Workclass"]]
overlay = {
"foo": [
([20, 40, 70], [0, 1, 2]),
([1, 4, 6], [2, 1, 0]),
],
}
shap.plots.scatter(shap_values, overlay=overlay, show=False)
plt.tight_layout()
return plt.gcf()
@pytest.mark.mpl_image_compare
def test_scatter_custom(explainer):
# Test with custom x/y limits, alpha and colormap
explanation = explainer(explainer.data)
age = explanation[:, "Age"]
shap.plots.scatter(
age,
color=explanation[:, "Workclass"],
xmin=age.percentile(20),
xmax=age.percentile(80),
ymin=age.percentile(10),
ymax=age.percentile(90),
alpha=0.5,
cmap=plt.get_cmap("cool"),
show=False,
)
plt.tight_layout()
return plt.gcf()
@pytest.fixture()
def categorical_explanation():
"""Adopted from explainer in conftest.py but using a categorical input."""
xgboost = pytest.importorskip("xgboost")
# get a dataset on income prediction
X, y = shap.datasets.diabetes()
# Swap the input data from a "float-category" to categorical
# Note: XGBoost with enable_categorical=True requires integer categories
# when using pandas 3.0+, so we use integer categories to test categorical handling
X.loc[X["sex"] < 0, "sex"] = 0
X.loc[X["sex"] > 0, "sex"] = 1
X["sex"] = X["sex"].astype(int).astype("category")
# train an XGBoost model (but any other model type would also work)
model = xgboost.XGBRegressor(random_state=0, enable_categorical=True, max_cat_to_onehot=1, base_score=0.5)
model.fit(X, y)
# build an Exact explainer and explain the model predictions on the given dataset
# We aren't providing masker directly because there appears
# to be an error with categorical features when using masker like this
# TODO: Investigate the error when this line is `return shap.Explainer(model, X)``
explainer = shap.TreeExplainer(model)
shap_values = explainer(X)
return shap_values
@pytest.mark.mpl_image_compare(tolerance=3)
def test_scatter_categorical(categorical_explanation):
"""Test the scatter plot with categorical data. See GH #3135"""
fig, ax = plt.subplots()
shap.plots.scatter(categorical_explanation[:, "sex"], ax=ax, show=False)
plt.tight_layout()
return fig
@pytest.mark.mpl_image_compare
@pytest.mark.parametrize("input", [np.array([[1], [1]]), np.array([[1e-10], [1e-9]]), np.array([[1]])])
def test_scatter_plot_value_input(input):
"""Test scatter plot with different input values. See GH #4037"""
explanations = shap.Explanation(
input,
data=input,
feature_names=["feature1"],
)
shap.plots.scatter(explanations, show=False)
plt.tight_layout()
return plt.gcf()