"""Tests for maskers through public explainer APIs. These tests exercise masker functionality by using public explainers like shap.Explainer, shap.KernelExplainer, etc., which internally use maskers. """ import numpy as np import shap def test_masker_with_kernel_explainer(): """Test masker functionality through KernelExplainer (public API).""" # Create simple model def model(x): return np.sum(x, axis=1) # Create background data for masker background = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) # KernelExplainer uses Independent masker internally explainer = shap.KernelExplainer(model, background) # Test data test_data = np.array([[1, 2, 3]]) # This exercises masker's __call__ with various masks shap_values = explainer.shap_values(test_data, nsamples=10) assert shap_values is not None assert shap_values.shape == (1, 3) def test_independent_masker_with_kernel_explainer(): """Test Independent masker through KernelExplainer.""" def model(x): return np.sum(x, axis=1) background = np.array([[0, 0, 0], [1, 1, 1]]) # KernelExplainer creates Independent masker internally from data explainer = shap.KernelExplainer(model, background) test_data = np.array([[1, 2, 3]]) shap_values = explainer.shap_values(test_data, nsamples=10) assert shap_values is not None assert shap_values.shape == (1, 3) def test_independent_masker_basic(): """Test Independent masker basic functionality.""" background = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) masker = shap.maskers.Independent(background) # Test shape assert masker.shape == (3, 3) # Test with True mask (should select all features) result = masker(True, background[0]) assert isinstance(result, tuple) def test_independent_masker_with_false_mask(): """Test Independent masker with False mask.""" background = np.array([[0, 0], [1, 1], [2, 2]]) masker = shap.maskers.Independent(background) # False mask should select no features result = masker(False, background[0]) assert isinstance(result, tuple) def test_independent_masker_with_partial_mask(): """Test Independent masker with partial mask array.""" background = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) masker = shap.maskers.Independent(background) # Partial mask - select first two features mask = np.array([True, True, False]) result = masker(mask, np.array([5, 6, 7])) assert isinstance(result, tuple) assert len(result) == 1 # Should return background samples with first two features from input assert result[0].shape[0] == 3 # Same as background rows def test_partition_masker_basic(): """Test Partition masker basic functionality.""" background = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) masker = shap.maskers.Partition(background) # Test with True mask result = masker(True, background[0]) assert isinstance(result, tuple) def test_partition_masker_with_false_mask(): """Test Partition masker with False mask.""" background = np.array([[0, 0], [1, 1]]) masker = shap.maskers.Partition(background) result = masker(False, background[0]) assert isinstance(result, tuple) def test_masker_with_explainer_auto_detection(): """Test that Explainer auto-detects and uses appropriate masker.""" def model(x): return np.sum(x, axis=1) background = np.array([[0, 0, 0], [1, 1, 1]]) # Explainer should auto-select masker based on model and data explainer = shap.Explainer(model, background) test_data = np.array([[1, 2, 3]]) # This internally uses maskers shap_values = explainer(test_data) assert shap_values is not None assert isinstance(shap_values, shap.Explanation) assert shap_values.values.shape == (1, 3) def test_independent_masker_clustering(): """Test that Independent masker has clustering attribute.""" background = np.array([[0, 0, 0], [1, 1, 1]]) masker = shap.maskers.Independent(background) # Should have clustering attribute assert hasattr(masker, "clustering") def test_partition_masker_clustering(): """Test that Partition masker has clustering attribute.""" background = np.array([[0, 0, 0], [1, 1, 1]]) masker = shap.maskers.Partition(background) # Should have clustering attribute assert hasattr(masker, "clustering") def test_independent_masker_shape_property(): """Test Independent masker shape property with different data sizes.""" # 5 samples, 4 features background = np.random.rand(5, 4) masker = shap.maskers.Independent(background) assert masker.shape == (5, 4) def test_partition_masker_with_clustering(): """Test Partition masker with explicit clustering.""" background = np.array([[0, 0, 0, 0], [1, 1, 1, 1]]) # Partition masker can take clustering parameter masker = shap.maskers.Partition(background, clustering="correlation") assert hasattr(masker, "clustering") def test_composite_masker_with_independent_maskers(): """Test Composite masker combining Independent maskers.""" background1 = np.array([[0, 0], [1, 1]]) background2 = np.array([[2, 2, 2], [3, 3, 3]]) masker1 = shap.maskers.Independent(background1) masker2 = shap.maskers.Independent(background2) composite = shap.maskers.Composite(masker1, masker2) # Shape should be sum of both shape = composite.shape(background1[0], background2[0]) assert shape == (2, 5) # 2 rows (min), 2+3 = 5 cols def test_independent_masker_with_varying_masks(): """Test Independent masker with different mask patterns.""" background = np.array([[0, 0, 0, 0], [1, 1, 1, 1], [2, 2, 2, 2]]) masker = shap.maskers.Independent(background) # All True result = masker(np.ones(4, dtype=bool), np.array([5, 6, 7, 8])) assert result[0].shape == (3, 4) # All False result = masker(np.zeros(4, dtype=bool), np.array([5, 6, 7, 8])) assert result[0].shape == (3, 4) # Alternating result = masker(np.array([True, False, True, False]), np.array([5, 6, 7, 8])) assert result[0].shape == (3, 4)