shap-rs 0.1.0

Native Rust implementations of model-agnostic, linear, and TreeSHAP explainers
Documentation
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
pub mod analysis;
pub mod background;
#[cfg(feature = "burn-adapter")]
pub mod burn_adapter;
pub mod coalition;
pub mod error;
pub mod evaluation;
pub mod explainer;
pub mod explainers;
pub mod explanation;
pub mod interactions;
pub mod link;
pub mod masker;
pub mod metadata;
pub mod metrics;
pub mod model;
#[cfg(feature = "parallel")]
pub mod parallel;
pub mod plot;
pub mod sparse;
pub mod tree;
pub use background::Background;
pub use error::{Result, ShapError};
pub use evaluation::EvaluationConfig;
pub use explainer::{Explainer, ExplainerExt, MetadataExplainer};
pub use explanation::{AttributionSemantics, Explanation, UncertainExplanation};
pub use link::Link;
pub use masker::{
    ConditionalTabularMasker, FixedMasker, FnMasker, FnStreamingMasker, GroupedMasker,
    ImageBaseline, ImageMasker, IndependentMasker, InpaintingImageMasker, Masker,
    SegmentedImageMasker, SpecialTokenPolicy, TextMasker, TextToken, TokenizedTextMasker,
};
pub use metadata::{FeatureKind, FeatureMetadata, OutputKind, OutputMetadata};
pub use model::{
    AcceleratedPredict, CachedModel, DeepAttribution, DeviceModel, DifferentiablePredict,
    ExecutionDevice, FnAcceleratedModel, FnModel, FnOwnedAcceleratedModel, Predict,
};
#[cfg(feature = "parallel")]
pub use parallel::ParallelExplainerExt;
pub use sparse::{
    FnSparseModel, SparseIndependentMasker, SparseMatrix, SparsePermutationExplainer,
    SparsePredict, SparseRowView,
};
pub use tree::{
    MissingBranch, MissingValuePolicy, Node, SplitComparison, Tree, TreeArrays, TreeEnsemble,
};

/// Compatibility helper for scalar-output Kernel SHAP.
pub fn explain_sample<F>(
    predict: F,
    sample: &[f64],
    background: &[Vec<f64>],
    nsamples: usize,
) -> Result<Vec<f64>>
where
    F: Fn(&[Vec<f64>]) -> Vec<f64>,
{
    use ndarray::{Array2, ArrayView2};
    let m = sample.len();
    if background.is_empty() {
        return Err(ShapError::EmptyBackground);
    }
    if background.iter().any(|r| r.len() != m) {
        return Err(ShapError::DimensionMismatch {
            expected: format!("{m} features"),
            found: "ragged background".into(),
        });
    }
    let bg = Background::new(
        Array2::from_shape_vec(
            (background.len(), m),
            background.iter().flatten().copied().collect(),
        )
        .map_err(|e| ShapError::Other(e.to_string()))?,
    )?;
    let model = FnModel::new(move |x: ArrayView2<'_, f64>| {
        let rows = x.rows().into_iter().map(|r| r.to_vec()).collect::<Vec<_>>();
        let y = predict(&rows);
        if y.len() != x.nrows() {
            return Err(ShapError::OutputDimensionMismatch {
                expected: x.nrows(),
                found: y.len(),
            });
        }
        Array2::from_shape_vec((y.len(), 1), y).map_err(|e| ShapError::ModelError(e.to_string()))
    });
    let e = explainers::KernelExplainer::new(model, bg)
        .with_nsamples(nsamples)
        .explain(
            Array2::from_shape_vec((1, m), sample.to_vec())
                .unwrap()
                .view(),
        )?;
    Ok((0..m).map(|j| e.values()[[0, j, 0]]).collect())
}