Statistical Inference with FDFI
FDFI provides CLT-based confidence intervals and hypothesis tests for feature importances. This page explains how inference works, the options available, and how to use them correctly.
How FDFI Inference Works
The unit-effect independent features (UEIFs) are defined per test sample.
Under regularity conditions, their sample mean phi is asymptotically
normal:
where n is the number of test samples and hat{sigma}_j is the
estimated standard deviation of the per-sample UEIFs for feature j. This
gives the standard error se_j = hat{sigma}_j / sqrt(n) used in confidence
intervals.
Note
The central-limit-theorem approximation works best when n >= 30. With
very small test sets the coverage of the reported CIs will be approximate.
Variance Floor
When many features have near-zero importance, their raw SEs can be
spuriously small, causing inflated z-scores. FDFI addresses this by fitting
a TwoComponentMixture to the distribution of raw standard
errors and using the upper quantile of the smaller (noise) component as a
variance floor. All SEs below the floor are clamped to the floor before
computing z-statistics.
This prevents noise-level importances from appearing statistically significant and is especially useful when the number of features is large.
Controlling the floor:
ci = explainer.conf_int(
alpha=0.05,
variance_floor_q=0.90, # quantile of the noise component (default 0.90)
variance_floor_method="mixture",
)
The Margin Parameter
By default, conf_int() tests the null hypothesis phi_j = 0. A
positive margin shifts the null to phi_j = margin, testing whether
feature importance is practically significant (not merely nonzero):
ci = explainer.conf_int(alpha=0.05, margin=0.05)
Three margin strategies are available:
|
Behaviour |
|---|---|
|
Use the provided |
|
Set the margin to the midpoint of the largest gap in the sorted point estimates. Useful for separating important from unimportant features automatically. |
|
Fit a |
One-sided vs. Two-sided Tests
The alternative parameter controls the direction of the test:
Value |
Hypothesis |
When to use |
|---|---|---|
|
|
Default. Detect both positive and negative importance, or features that may suppress the target. |
|
|
You are only interested in positive contributions. The CI has an open upper bound (+∞); the plot renders it with a ► caret. |
|
|
You are only interested in negative contributions. The CI has an open lower bound (−∞); the plot renders it with a ◄ caret. |
# One-sided test (greater)
ci = explainer.conf_int(alpha=0.05, alternative="greater")
from fdfi.plots import confidence_interval_plot
confidence_interval_plot(ci, feature_names=feature_names)
For a side-by-side comparison see the Confidence Intervals and Statistical Inference tutorial.
Multiple-Testing Correction
When testing many features simultaneously, the family-wise type-I error rate
is inflated. Pass multitest_method to apply one of the
statsmodels.stats.multitest.multipletests() methods:
ci = explainer.conf_int(
alpha=0.05,
alternative="greater",
multitest_method="fdr_bh", # Benjamini-Hochberg FDR control
)
# Inspect adjusted p-values and rejection flags
import pandas as pd
pd.DataFrame({
"feature": feature_names,
"score": ci["score"].round(4),
"pvalue_adj": ci["pvalue_adj"].round(4),
"reject": ci["reject_null"],
})
Commonly used methods:
'fdr_bh'— Benjamini-Hochberg (FDR control; recommended for feature selection).'bonferroni'— Bonferroni correction (family-wise error rate; conservative).'holm'— Holm-Bonferroni (family-wise, less conservative).
Cross-fitting
When the test set is small or the model is highly non-linear, the empirical cross-covariance used for the Gaussian OT map can over-fit to the background sample, inducing finite-sample bias in the attributions. Cross-fitting addresses this by alternating which half of the data is used to fit the OT map vs. evaluate the UEIFs:
from fdfi.explainers import OTExplainer
explainer = OTExplainer(
model.predict,
data=X_background,
nsamples=100,
crossfit=True, # enable cross-fitting
n_folds=5, # number of cross-fitting folds (default 5)
)
Cross-fitting is especially helpful when len(X_background) is small
(< 200) and the number of features is large.
Complete Example
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from fdfi.explainers import OTExplainer
from fdfi.plots import summary_bar, confidence_interval_plot
rng = np.random.default_rng(42)
n, d = 300, 8
X = rng.standard_normal((n, d))
y = X[:, 0] * 2 + X[:, 1] - X[:, 3] * 0.5 + rng.standard_normal(n) * 0.5
model = GradientBoostingRegressor(n_estimators=100, random_state=42)
model.fit(X[:200], y[:200])
feature_names = [f"F{i}" for i in range(d)]
explainer = OTExplainer(model.predict, data=X[:200], nsamples=80)
results = explainer(X[200:])
# Bar chart with error bars
summary_bar(results["phi_X"], results["se_X"], feature_names=feature_names)
# One-sided confidence intervals (greater) with FDR correction
ci = explainer.conf_int(
alpha=0.05,
alternative="greater",
multitest_method="fdr_bh",
)
confidence_interval_plot(ci, feature_names=feature_names)
See also
Interpreting Results — reference table for all output dictionary keys.
Confidence Intervals and Statistical Inference — worked tutorial with visual comparisons of one-sided and two-sided CIs.