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

125 lines
3.7 KiB
Python

import numpy as np
import pandas as pd
import pytest
import scipy.sparse as ssp
import shap
@pytest.mark.parametrize(
"arr",
[
np.arange(100),
["zz"] * 100,
pd.Series(range(100), name="test"),
pd.DataFrame(np.random.RandomState(0).randn(100, 2), columns=["a", "b"]),
],
)
def test_sample_basic(arr):
"""Tests the basic functionality of `sample()` on a variety of array-like objects."""
new_arr = shap.utils.sample(arr, 30, random_state=42)
assert len(new_arr) == 30
def test_sample_basic_sparse():
"""Tests the basic functionality of `sample()` on sparse objects."""
arr = ssp.csr_matrix((100, 3), dtype=np.int8)
new_arr = shap.utils.sample(arr, 30, random_state=42)
assert new_arr.shape[0] == 30
def test_sample_no_op():
"""Ensures that `sample()` is a no-op when numsamples is larger
than the size of X.
"""
arr = np.arange(50)
new_arr = shap.utils.sample(arr, 100, random_state=42)
assert len(arr) == len(new_arr)
def test_sample_sampling_without_replacement():
"""Ensures that `sample()` is performing sampling without replacement.
See GH dsgibbons#36.
"""
arr = np.arange(100)
new_arr = shap.utils.sample(arr, 99, random_state=0)
assert len(new_arr) == 99
assert len(np.unique(new_arr)) == 99
def test_sample_can_be_zipped():
"""Ensures that the sampling is done via indexing.
That is, sampling X and y separately would give the same result as sampling
concat(X, y), up to a random state. Our `datasets` module relies on
this behaviour.
"""
arr1 = pd.Series(np.arange(100))
arr2 = pd.Series(np.repeat(np.arange(25), 4))
combined = pd.DataFrame(
{
"arr1": arr1,
"arr2": arr2,
}
)
new_arr1 = shap.utils.sample(arr1, 75, random_state=42)
new_arr2 = shap.utils.sample(arr2, 75, random_state=42)
new_combined = shap.utils.sample(combined, 75, random_state=42)
assert (new_arr1 == new_combined["arr1"]).all()
assert (new_arr2 == new_combined["arr2"]).all()
def test_opchain_repr():
"""Ensures OpChain repr is working properly"""
opchain = (
shap.utils.OpChain("shap.DummyExplanation")
.foo.foo(0, "big_blue_bear")
.foo(0, v1=10)
.foo(k1="alpha", k2="beta")
.baz
)
expected_repr = "shap.DummyExplanation.foo.foo(0, 'big_blue_bear').foo(0, v1=10).foo(k1='alpha', k2='beta').baz"
assert repr(opchain) == expected_repr
def test_format_value_empty_string():
"""Tests that format_value() handles empty strings without raising IndexError."""
# Test with empty string
result = shap.utils._general.format_value("", "%0.03f")
assert result == ""
def test_format_value_negative_number():
"""Tests that format_value() correctly formats negative numbers with unicode minus sign."""
result = shap.utils._general.format_value(-1.5, "%0.03f")
assert result == "\u2212" + "1.5"
def test_format_value_positive_number():
"""Tests that format_value() correctly formats positive numbers."""
result = shap.utils._general.format_value(1.5, "%0.03f")
assert result == "1.5"
def test_format_value_trailing_zeros():
"""Tests that format_value() removes trailing zeros."""
result = shap.utils._general.format_value(1.5000, "%0.03f")
assert result == "1.5"
def test_format_value_string_input():
"""Tests that format_value() handles string inputs correctly."""
# Test with non-empty string
result = shap.utils._general.format_value("test_string", "%0.03f")
assert result == "test_string"
# Test with string that starts with minus
result = shap.utils._general.format_value("-123", "%0.03f")
assert result == "\u2212" + "123"