Skip to content

gcm Shapley least-squares estimation broken on scikit-learn 1.9 (extreme sample weights) #1698

Description

@immu4989

Description

The EXACT_FAST and SUBSET_SAMPLING Shapley approximation methods in dowhy.gcm.shapley return incorrect values on scikit-learn 1.9.0. Three tests fail:

  • test_given_many_features_when_estimate_shapley_values_exact_fast_then_returns_correct_linear_shapley_values
  • test_given_many_features_when_estimate_shapley_values_with_subset_sampling_then_returns_correct_linear_shapley_values
  • test_given_specific_random_seed_when_estimate_shapley_values_with_subset_sampling_then_returns_deterministic_result

Caught by the latest-deps CI work in #1665 / #1672 (fails there on every run with scikit-learn 1.9.0; passes on 1.8.0).

Root cause

_approximate_shapley_values_via_least_squares_regression solves a weighted least squares problem via LinearRegression().fit(..., sample_weight=weights) where the full/empty subsets get weight 10**20 (to approximate equality constraints) and all other subsets weight ~1. scikit-learn 1.9 changed LinearRegression's solve path for sample weights, and with weights spanning 20 orders of magnitude the solution now loses all precision.

Minimal repro independent of DoWhy:

import numpy as np
from sklearn.linear_model import LinearRegression
rng = np.random.RandomState(0)
X = (rng.uniform(size=(500, 12)) < 0.5).astype(float)
X[0] = 0.0; X[1] = 1.0
beta = rng.normal(size=12)
y = X @ beta + 3.0
w = np.ones(500); w[0] = w[1] = 1e20
coef = LinearRegression().fit(X, y, sample_weight=w).coef_
print(np.max(np.abs(coef - beta)))
# scikit-learn 1.8.0: ~1e-14
# scikit-learn 1.9.0: ~2.08

Fix

Solve the weighted least squares explicitly (weighted centering + np.linalg.lstsq on the square-root-weighted system), which reproduces the numerically stable behavior and makes the result independent of solver changes in third-party libraries. PR: #1699.

Note

test_anomaly_attribution.py::...feature_relevance... also fails in the latest-deps CI runs, but that turns out to be a pre-existing order/RNG-sensitive test (it fails on main with scikit-learn 1.8 too depending on the random state; the @flaky retries can't save it in CI because the suite ordering makes the failure deterministic). That's a separate test-robustness issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions