When Your Feature Importance Is Lying to You
Here, I hid the ground truth in a dataset, then watched scikit-learn’s most popular importance metric rank a column of random ID numbers…
When Your Feature Importance Is Lying to You
Here, I hid the ground truth in a dataset, then watched scikit-learn’s most popular importance metric rank a column of random ID numbers above the real signal
For this example, I built a dataset with five features that actually drive the target, trained a random forest, printed feature_importances_, and sorted it. The third most important feature, according to scikit-learn, was a column of random ID numbers with no connection to the label. It beat three of the five real features and both copies of a genuine signal. Forty columns of pure noise sat just below, and the gap was small.
This is the metric almost everyone prints first, the default on every scikit-learn tree model, the one that ends up on the “top drivers” slide. On the kind of data you meet every day, ID columns and correlated columns, it is wrong in a way you cannot catch unless you already know the answer.
Below, I describe how I built a dataset where I do know the answer, ran seven selection methods against it, and scored each on whether it recovered the features I planted.
Let’s dive in!
A dataset where I know the right answer
On real data you can never grade feature importance; you do not know which columns matter. So I built the data myself: 5,000 rows and 48 columns with fixed roles.
- 5 informative binary features that generate the target
- 2 correlated copies of one informative feature (near duplicates of the same signal)
- 1 high-cardinality noise column: an integer ID with 3,161 unique values, wired to nothing
- 40 irrelevant binary columns
Cardinality just means how many distinct values a column holds. A yes/no flag has cardinality 2; the ID column has 3,161. Those seven are the answer key; everything else is a distractor.

Source: author
Only the noise ID is high-cardinality; the rest are binary, on purpose. If every column were continuous, nothing would stand out. Real tables look like this, and that contrast is where the bias lives. As a check, a model on the seven relevant features hit a cross-validated AUC of 0.94 (0.5 is random, 1.0 is perfect); the noise ID alone hit 0.49.
The default metric puts random IDs in the top three
Here is what a random forest hands you from feature_importances_: the mean decrease in impurity on the training data. Impurity is how mixed the labels are inside a tree node; a good split lowers it, and each feature earns credit for the drops it makes.

Source: author
Here, the red bar is a column of random IDs. It ranks third, above three of the five real features and both copies of the signal.
The noise ID lands at rank 3, with only two informative features above it and everything else below: three informative features, both copies, and all forty irrelevant features. A column that predicts nothing outranked most of the features that generate the label.
Now watch what a normal person does next. Keep the top five, drop the rest, and you have kept the noise ID while tossing a real feature plus both copies of a signal. That naive top five scores 0.87; the correct seven scores 0.94. Keeping all 48 columns still scores about 0.92, so the forest is fine. The damage is in what you drop, and in what you tell people matters.
Why a noise column climbs: more values, more places to split
This is not a bug: a feature earns credit every time a split on it lowers impurity, and a high-cardinality column offers far more places to split. The noise ID’s 3,161 values give the model thousands of thresholds, a few of which fit the labels by luck. A binary feature offers exactly one split.
Here, I regenerated the same dataset, turning up only the cardinality of the noise column.

Source: author
Same column, same lack of signal, but its importance climbs as you hand it more unique values.
With two unique values the noise column ranks 19th. At fifty it is 4th. At 5,000 it reaches rank 3. Nothing about its relationship to the target changed; only the number of split points did.
Two copies of one signal, and both look weak
The second trap has nothing to do with cardinality. It shows up whenever two columns carry the same information. The model treats them as interchangeable, so the credit for one strong signal splits between them and each looks about half as important as it should.

Source: author
In this examples, one signal split across three columns. Impurity, permutation importance, and SHAP each divide the credit, and the plain filter keeps all three high.
Here, I scored one informative feature and its two copies four ways. Impurity, permutation importance (shuffle a column and see how much accuracy drops), and SHAP (a method that shares each prediction’s credit across features) all divide the score three ways. Push the correlation between the copies toward 0.99 and the original halves while the copies rise to meet it; the group total stays flat. The signal did not weaken. It got shared out.
Permutation importance has the opposite failure: on held-out data it drives the copies toward zero, because either twin covers for the other. Only the univariate filter keeps every copy high, since it scores each column against the target on its own. Importance answers “how much does the model rely on this column, given the others,” not “how much signal is in this column.”
The same method tells two stories on train and test
Impurity is computed on the training data, so it rewards whatever the model used to fit, including what it used to overfit. Importance on data the model already saw is partly a measure of memorization. Permutation importance is the honest version, but only if you measure the accuracy drop on held-out data.

Source: author
On train, impurity ranks the noise ID 3rd. Permutation on the training set still ranks it 6th, because the model did fit those spurious splits. On the test set it drops to 17th, below all five informative features, because the splits it memorized do nothing on data it never saw. Same model, same method, opposite verdict, decided only by which data you measure on.
If a feature cannot beat shuffled noise, it is noise
Every method above hands you a number but never a cutoff. The honest question: is this feature more important than chance alone would make it look? Shuffle the target many times, refit, and record what each feature earns when there is nothing to learn. That gives a per-feature band of scores noise produces; anything above its band is real.

Source: author
Here, the black ticks mark the 95th percentile of importance under a shuffled target. Only seven features clear their null: the five informative features and both copies. The noise ID scores 0.060 against a band that reaches 0.100, so it fails, and all forty irrelevants fail too.
Boruta builds this into the model: it adds a shuffled shadow copy of every feature and keeps only the ones that beat the best shadow across many runs. A high-cardinality shadow is just as high-cardinality, so the noise ID must beat a scrambled version of itself, and cannot. My from-scratch Boruta accepted all these seven features and rejected all forty irrelevant features, giving perfect precision and recall.
Conclusion: the whole field, at a glance
In these examples, I ran everything head-to-head: seven methods, three datasets, scored on recovery at k and wall-clock time. Recovery at k is the share of truly relevant features in a method’s own top k, where k is the number actually relevant. Fix the count, and any method that wastes a slot on the noise ID drops a real one.

Source: author
Here, impurity recovers 0.86 in under half a second: the fastest, and one of the weakest, because that slot goes to the noise ID. Boruta and SHAP recover a perfect 1.0 at ten to thirty times the cost, and the filter also hits 1.0 for almost no compute. One caveat: on a standard benchmark with no identifier column, impurity recovers everything perfectly. It fails precisely when your data has the shape real data usually has, an ID column or correlated features, and it fails silently.
Stability is a trap of its own. Run a method on many resamples of the rows and see whether it keeps the same features.

Source: author
On the planted data, impurity is the most stable of all, picking the identical set every run, noise ID and all. Stability said the method was trustworthy while it quietly kept a random column. When columns outnumber rows, even that collapses. Stable is not the same as correct; you still need recovery and a null model to tell the difference.
Final words: what to actually do
Feature selection is not hopeless. Pick the method by what your data looks like, not by what prints first.
- For a fast, model-free screen, use a univariate filter. It is cheap and it keeps correlated groups together.
- For model-aware importance, use permutation importance on held-out data. Never read impurity on the training set as signal.
- To decide keep or drop, put a null under it: Boruta, or a target-shuffle threshold. If a feature cannot beat shuffled noise, it is shuffled noise.
- When features are correlated, cluster them first and keep the group together, or you will drop a real signal because it split its own credit.
- When you see a high-cardinality column, distrust the impurity ranking on sight and confirm with a held-out permutation test.
To summarize: the next time you print
feature_importances_and sort it, remember that the chart cannot tell you when it is wrong. It will rank a column of random IDs third and never flinch. Build a dataset where you know the answer, grade the method once, and trust it only on the kind of data where it passed.
Drop your questions in the comments below 😊
메타데이터
- post_id
- 98bf89bc4d06
- slug
- when-your-feature-importance-is-lying-to-you-98bf89bc4d06
- url
- https://medium.com/data-and-beyond/when-your-feature-importance-is-lying-to-you-98bf89bc4d06
- canonical_url
- https://medium.com/data-and-beyond/when-your-feature-importance-is-lying-to-you-98bf89bc4d06
- author_url
- https://medium.com/@dimaiakubovskyi
- status
- ok
- fetched_at
- 2026-07-11 14:08:24