A good approach is:1) compute baseline score once,2) for each feature and repeat, permute that column (using a seeded RNG), predict on the dataset with that single-column permutation, compute score drop,3) parallelize across (feature, repeat) jobs using joblib, and4) average drops per feature to return mean importances.Implementation:python
import numpy as np
from joblib import Parallel, delayed
from sklearn.utils import check_random_state
import copy
def permutation_importance(estimator, X, y, metric, n_repeats=5, random_state=None, n_jobs=1):
"""
Returns mean importance per feature = mean(score_baseline - score_permuted)
estimator: fitted scikit-learn estimator with predict (or accept metric on predictions)
X: 2D array-like (n_samples, n_features)
y: true labels/targets
metric: function(y_true, y_pred) -> scalar (higher is better)
n_repeats: repeats per feature
random_state: int or RandomState
n_jobs: parallel jobs for joblib
"""
X = np.asarray(X)
rng = check_random_state(random_state)
# baseline predictions and score (predict once)
y_pred_baseline = estimator.predict(X)
baseline_score = metric(y, y_pred_baseline)
n_features = X.shape[1]
# worker that permutes one feature copy and returns score drop
def _permute_and_score(feature_idx, seed):
rs = np.random.RandomState(seed)
X_permuted = X.copy()
X_permuted[:, feature_idx] = X_permuted[rs.permutation(X_permuted.shape[0]), feature_idx]
y_pred = estimator.predict(X_permuted)
score = metric(y, y_pred)
return baseline_score - score # importance = drop
# prepare seeds for reproducibility
seeds = rng.randint(0, 2**31 - 1, size=(n_features, n_repeats))
# parallel over features and repeats
results = Parallel(n_jobs=n_jobs)(
delayed(_permute_and_score)(fi, int(seeds[fi, r]))
for fi in range(n_features) for r in range(n_repeats)
)
# reshape and average
res = np.array(results).reshape(n_features, n_repeats)
mean_importances = res.mean(axis=1)
return mean_importances
Key points:- Baseline predictions done once to avoid repetition.- Permutations only change one column; we copy X once per job.- Parallelized across (feature, repeat) with joblib.- metric must accept (y_true, y_pred). If using predict_proba, adapt accordingly.Complexity: O(n_features * n_repeats * predict_cost). Edge cases: ensure estimator is fitted; for stochastic estimators, set estimator randomness or use more repeats; large X may increase memory due to copies — consider permuting a view of a column or streaming by indices to reduce memory.