openquant.feature_importance
Pure Python. Signatures are as written in the source; descriptions are the docstrings. All Python modules.
MDI, MDA and SFI feature importance (AFML chapter 8) for any scikit-learn-style model.
MDA and SFI are cross-validated, and always on purged k-fold splits built from the label spans t0/t1, which are required (AFML §8.3, and issue #27). There are two ways in:
- :func:
mean_decrease_accuracyand :func:single_feature_importancetake an estimator withfit(X, y, sample_weight=...)andpredict_proba(X), run the fit/predict loop here in Python, and hand the out-of-sample probabilities to Rust for scoring. - :func:
mda_from_probabilitiesand :func:sfi_from_probabilitiestake those probabilities directly, for a model trained elsewhere on the folds of :func:openquant.cross_validation.purged_kfold_splitswith the same arguments.
Either way the scoring, the MDA normalisation and the aggregation are the Rust openquant::feature_importance code. Results map each feature name to {"mean": ..., "std": ...}, where std is the standard error across folds (MDA, SFI) or trees (MDI), in the order of feature_names.
Functions
mda_from_probabilities
mda_from_probabilities(
y: Any,
t0: Any,
t1: Any,
base_proba: Any,
permuted_proba: Any,
*,
n_splits: int,
pct_embargo: float = 0.0,
scoring: str = 'neg_log_loss',
sample_weight: Any = None,
feature_names: Sequence[str] | None = None,
seed: int | None = None,
) -> dict[str, dict[str, float]]MDA (AFML Snippet 8.3) scored from out-of-sample probabilities of y == 1.
base_proba[i] is the probability for sample i from the model trained on the fold that tests i; permuted_proba[i, j] is the same model's probability after feature j was shuffled within that test fold. The folds are purged_kfold_splits(t0, t1, n_splits, pct_embargo); every sample is tested once. Per fold, a feature's importance is (base - permuted) / (1 - permuted) for accuracy and F1, and (base - permuted) / -permuted for negative log loss. sample_weight weights the scores.
seed is deprecated and passing it emits a DeprecationWarning: the shuffling happened when permuted_proba was computed, so no seed here can change the result.
mean_decrease_accuracy
mean_decrease_accuracy(
estimator: Any,
X: Any,
y: Any,
t0: Any,
t1: Any,
*,
n_splits: int = 5,
pct_embargo: float = 0.01,
scoring: str = 'neg_log_loss',
sample_weight: Any = None,
feature_names: Sequence[str] | None = None,
seed: int = 42,
) -> dict[str, dict[str, float]]MDA (AFML Snippet 8.3) for a binary classifier with fit and predict_proba.
For each purged fold a copy of estimator (sklearn.base.clone when scikit-learn is installed) is fitted on the training samples with their sample_weight, then each feature column of the test fold is shuffled in turn with numpy.random.default_rng(seed). seed defaults to 42, as in :func:openquant.feature_diagnostics.mda_importance. Labels must be 0/1. See :func:mda_from_probabilities for the scoring.
mean_decrease_impurity
mean_decrease_impurity(
per_tree_importances: Any,
feature_names: Sequence[str] | None = None,
) -> dict[str, dict[str, float]]MDI (AFML Snippet 8.2) from one row of impurity importances per tree.
With scikit-learn: [t.feature_importances_ for t in forest.estimators_]. A zero means the tree never split on the feature and is left out of that feature's mean. Means are normalised to sum to 1. AFML fits the forest with max_features=1 so that every feature gets a chance to be split on.
sfi_from_probabilities
sfi_from_probabilities(
y: Any,
t0: Any,
t1: Any,
proba: Any,
*,
n_splits: int,
pct_embargo: float = 0.0,
scoring: str = 'neg_log_loss',
sample_weight: Any = None,
feature_names: Sequence[str] | None = None,
) -> dict[str, dict[str, float]]SFI (AFML Snippet 8.4) scored from out-of-sample probabilities of y == 1.
proba[i, j] is the probability for sample i from a model trained on feature j alone, on the fold of purged_kfold_splits(t0, t1, n_splits, pct_embargo) that tests i. sample_weight weights each fold's score, as in :func:mda_from_probabilities and AFML's cvScore; weight the fit on your side as well.
single_feature_importance
single_feature_importance(
estimator: Any,
X: Any,
y: Any,
t0: Any,
t1: Any,
*,
n_splits: int = 5,
pct_embargo: float = 0.01,
scoring: str = 'neg_log_loss',
sample_weight: Any = None,
feature_names: Sequence[str] | None = None,
) -> dict[str, dict[str, float]]SFI (AFML Snippet 8.4): each feature's out-of-sample score when the model sees only it.
A copy of estimator is fitted per feature and purged fold on that one column, with the training sample_weight. Labels must be 0/1. See :func:sfi_from_probabilities.