Fixing Feature Importance Scores That Mislead You With Correlated Inputs

May 18, 2026 5 min read 4 views
A minimalist bar chart illustration showing correlated feature importance scores with subtle connecting arcs on a soft blue-gray background

You trained a model, checked the feature importance chart, and the top features look plausible β€” until you notice that two nearly identical columns are both ranked near the bottom, each carrying half the weight they should. Correlation is splitting the credit between them, and your importance scores are lying to you.

This is one of the most common and quietly damaging problems in applied machine learning. The model may still predict well, but the explanation is wrong, and wrong explanations lead to wrong decisions about which features to collect, which to drop, and what the model is actually doing.

What you'll learn

  • Why correlated features distort standard feature importance scores
  • How to detect problematic correlations before they mislead you
  • The difference between impurity-based, permutation, and SHAP-based importance
  • Practical techniques to correct or account for correlation
  • When to drop features versus when to keep them and adjust your analysis

Why Correlation Breaks Feature Importance

Most tree-based models compute feature importance by measuring how much each feature reduces impurity across all splits β€” this is called mean decrease in impurity (MDI), or sometimes Gini importance. The fundamental problem: when two features carry the same information, the model can use either one at each split. It spreads its usage across both, so each feature looks less important than it actually is.

Imagine you have income_annual and income_monthly in your dataset, correlated at 0.99. The tree might split on income_annual at the top of some trees and on income_monthly at others. The actual predictive information is fully captured by either one, but the importance chart shows both at half-strength. If you use that chart to decide which features to invest in collecting, you undervalue income entirely.

The same effect happens with less obvious correlations β€” age and years of work experience, page views and session duration, temperature and humidity in sensor data. Any pair with a Pearson correlation above roughly 0.7 is a candidate for this problem.

The Three Types of Feature Importance (and Their Blind Spots)

Impurity-based (MDI)

This is the default feature_importances_ attribute on scikit-learn's RandomForestClassifier and GradientBoostingClassifier. It is fast and intuitive, but it has two known problems: it favors high-cardinality features, and it distributes importance unevenly across correlated pairs. Do not use it as your only signal.

Permutation importance

Permutation importance shuffles one feature at a time and measures the drop in model performance. This is more reliable than MDI for correlated features β€” but only partially. If two features are highly correlated, shuffling one of them doesn't fully break the signal, because the model can lean on the other. You'll still underestimate the combined importance of a correlated group.

SHAP values

SHAP (SHapley Additive exPlanations) assigns each feature a contribution to each individual prediction. It is theoretically grounded and handles nonlinear models well. With correlated features, SHAP distributes credit between them in a mathematically consistent way, but the distributed credit can still mask how important the underlying concept is. You need to read correlated SHAP values as a group, not individually.

Step 1 β€” Detect Correlation in Your Feature Set

Before you trust any importance chart, check the correlation structure. For continuous features, a heatmap of Pearson correlations is the fastest starting point.

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# Assume X is your feature DataFrame
corr_matrix = X.corr()

plt.figure(figsize=(12, 10))
sns.heatmap(
    corr_matrix,
    annot=False,
    cmap="coolwarm",
    center=0,
    vmin=-1,
    vmax=1
)
plt.title("Feature Correlation Matrix")
plt.tight_layout()
plt.show()

To get a quick list of pairs above a threshold rather than reading a heatmap:

threshold = 0.75

# Get the upper triangle to avoid duplicate pairs
upper = corr_matrix.where(
    pd.np.triu(pd.np.ones(corr_matrix.shape), k=1).astype(bool)
)

high_corr_pairs = [
    (col, row, upper.loc[row, col])
    for col in upper.columns
    for row in upper.index
    if abs(upper.loc[row, col]) > threshold
]

for a, b, r in sorted(high_corr_pairs, key=lambda x: -abs(x[2])):
    print(f"{a} <-> {b}: {r:.3f}")

For categorical or mixed data, consider mutual information instead of Pearson correlation. Scikit-learn's mutual_info_classif and mutual_info_regression work well here.

Step 2 β€” Compute Permutation Importance Correctly

Once you've trained your model, compute permutation importance on a held-out validation set, not the training set. Using training data inflates the scores for features the model memorized.

from sklearn.inspection import permutation_importance
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

model = RandomForestClassifier(n_estimators=200, random_state=42)
model.fit(X_train, y_train)

result = permutation_importance(
    model, X_val, y_val,
    n_repeats=30,       # repeat shuffling to get stable estimates
    random_state=42,
    scoring="roc_auc"
)

importances = pd.Series(
    result.importances_mean,
    index=X.columns
).sort_values(ascending=False)

print(importances.head(15))

The n_repeats=30 setting matters. With fewer repeats, permutation importance has high variance and correlated features can produce inconsistent rankings across runs.

Step 3 β€” Use SHAP for Individual-Level Insight

SHAP values show you what each feature contributes to each prediction. This makes it easier to spot when two correlated features are both contributing in the same direction β€” a sign they're measuring the same thing.

import shap

explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_val)

# For binary classification, shap_values is a list [class_0, class_1]
# Use index 1 for the positive class
shap.summary_plot(shap_values[1], X_val, plot_type="bar")

Look at the summary plot and identify features with similar SHAP distributions. If income_annual and income_monthly have nearly identical bar heights and the same directional pattern in the beeswarm plot, they're sharing credit for the same signal.

Step 4 β€” Group Correlated Features and Measure Group Importance

The cleanest fix for correlated-feature credit-splitting is to measure the importance of the entire group at once. You shuffle all correlated features together and observe the performance drop. This tells you how much the group matters, regardless of how the model divided attention between its members.

import numpy as np
from sklearn.metrics import roc_auc_score

def group_permutation_importance(model, X, y, groups, n_repeats=20, scoring=roc_auc_score):
    """
    groups: dict of {group_name: [list of column names]}
    Returns a dict of {group_name: mean importance score}
    """
    baseline = scoring(y, model.predict_proba(X)[:, 1])
    results = {}

    for group_name, cols in groups.items():
        drops = []
        for _ in range(n_repeats):
            X_permuted = X.copy()
            # Shuffle all columns in the group together (same row permutation)
            perm_idx = np.random.permutation(len(X_permuted))
            X_permuted[cols] = X_permuted[cols].values[perm_idx]
            score = scoring(y, model.predict_proba(X_permuted)[:, 1])
            drops.append(baseline - score)
        results[group_name] = np.mean(drops)

    return results

# Example usage
feature_groups = {
    "income": ["income_annual", "income_monthly"],
    "engagement": ["page_views", "session_duration", "click_rate"],
    "age_experience": ["age", "years_experience"]
}

group_scores = group_permutation_importance(model, X_val, y_val, feature_groups)
for name, score in sorted(group_scores.items(), key=lambda x: -x[1]):
    print(f"{name}: {score:.4f}")

Shuffling all group members with the same row permutation is the key detail. If you shuffle them independently, the correlation between them breaks in the permuted data but not in real data, which produces a different kind of distortion.

Step 5 β€” Decide Whether to Drop, Combine, or Keep

Once you know a correlated group carries meaningful importance, you have three options:

  • Drop redundant features: If two features are correlated above 0.95 and they represent the same concept (like annual vs monthly income), keep the one that is easier to collect or explain. The model won't lose predictive power and your importance scores will be cleaner.
  • Combine them: Create a single feature via PCA, averaging, or domain knowledge. For example, a composite

πŸ“€ Share this article

Sign in to save

Comments (0)

No comments yet. Be the first!

Leave a Comment

Sign in to comment with your profile.

πŸ“¬ Weekly Newsletter

Stay ahead of the curve

Get the best programming tutorials, data analytics tips, and tool reviews delivered to your inbox every week.

No spam. Unsubscribe anytime.