Skip to content
datarekha

Feature selection

Choose a smaller, more useful set of input columns without leaking labels, missing interactions, or mistaking explanation for selection.

Written and reviewed by Last reviewed

12 min read Intermediate Machine Learning Lesson 24 of 39

What you'll learn

  • How filter, wrapper, and embedded selection methods make different bets
  • Why irrelevant columns increase variance, computation, and explanation cost
  • How permutation importance works, and why correlated features fool it
  • How to select features safely inside cross-validation and recognise failure modes

Before you start

A support team wants to predict whether a new ticket will be escalated. The table has 100,000 rows and 80 plausible columns: customer plan, message length, previous escalations, product area, time of day, and so on.

It also has ticket_id, an internal routing code, 12 nearly duplicated customer-history columns, and resolution_code—a field filled in after the ticket is closed.

Throw every column into a model and the validation score rises from 0.81 to 0.91 ROC AUC. The model looks brilliant. Six weeks after launch, it quietly falls back to roughly 0.80 because resolution_code was not available at prediction time and the routing code captured an old team assignment.

This is the problem feature selection exists to solve: not “how do I make the table skinny?” but “which information is available, useful, and worth making the model depend on?”

Feature selection means keeping a subset of the original input columns and discarding the rest. It is different from feature extraction, which combines columns into new representations, and from explanation, which describes how an already-trained model uses them.

The three families

There are three broad strategies. They differ in what they optimise, so there is no universal winner.

Filterscore alonemisses pairsfast / model-freeWrappertest subsetsmany fitsaccurate / costlyEmbeddedselect in fitL1 / treesefficient / built-in
Filters score columns alone; wrappers search subsets; embedded methods select while fitting the model.

Filter methods

A filter method scores each feature without fitting the final predictive model, then keeps the best-scoring columns. Pearson correlation measures linear association with a numeric target; chi-squared tests work for suitable categorical or count data; mutual information measures more general dependence.

Filters are fast because columns can be scored before an expensive model runs. The catch is that they score columns independently. message_length and contains_angry_words might be weak alone but decisive together, so a filter can discard both. Correlation also misses curved relationships; mutual information detects more shapes but can be noisy with small samples or high-cardinality columns.

Wrapper methods

A wrapper method uses the model as its measuring instrument: try a subset, fit the estimator, measure validation performance, and compare it with another subset. It can detect interactions because the model evaluates features in context, but repeated fitting is expensive.

Recursive Feature Elimination (RFE) repeatedly fits an estimator, removes the weakest features according to coef_ or feature_importances_, and stops at the requested number. RFECV adds cross-validated scoring to choose how many to keep. A wrapper is tied to its estimator and metric: a subset that suits a random forest may not suit logistic regression.

Embedded methods

An embedded method selects features during model training. With L1 regularization, the objective is:

loss + λ × (|w1| + |w2| + ... + |wp|)

A large enough λ drives some coefficients exactly to zero, selecting those columns while the model is fitted. Regularization explains the broader bias–variance trade-off.

Tree models offer another embedded signal through impurity-based feature importance. It is useful for a first look, but high-cardinality continuous columns can look important by chance, and correlated columns can share or steal importance. Treat it as a clue, not a verdict.

Why fewer columns can generalize better

Training performance rewards memorisation. Generalization means performing well on new rows from the same process. Selection helps when it removes opportunities to learn accidents while retaining recurring signal.

Suppose 10,000 tickets contain five predictive fields and 995 random ones. A flexible estimator can test many relationships among those columns. Some random relationships will look impressive in the training sample simply because there are so many chances to get lucky. They do not repeat next month, so the model has spent capacity fitting quirks instead of escalation behaviour.

This is one face of the curse of dimensionality: data becomes sparse relative to the space of possible inputs. Neighbourhood models have fewer genuinely similar neighbours, trees have more candidate splits, and linear models have more coefficients to estimate. The common problem is limited data spread across too many degrees of freedom.

Fewer columns also reduce collection, validation, documentation, serving memory, and explanation costs. But fewer is not automatically better. Discarded columns may contain real signal, and a strongly regularized model or a large dataset may tolerate irrelevant inputs cheaply. Selection can add instability without improving accuracy.

The useful question is: “Does removing these columns improve the metric, cost, reliability, or maintainability that matters here?”

A worked example: what does “important” mean?

Imagine the ticket model has a held-out set of 20,000 tickets. Its baseline ROC AUC is 0.86: the probability that it ranks a randomly chosen escalated ticket above a randomly chosen non-escalated ticket.

Now permute one feature by randomly reordering it among held-out rows. Its distribution remains, but its connection to the correct ticket is broken:

FeatureAUC after shufflingDrop from 0.86First interpretation
prior_escalations_90d0.740.12The model relies heavily on it
message_length0.840.02Useful, but modestly
ticket_id0.8590.001Little unique reliance
agent_id0.850.01Some predictive information or proxy

This is permutation importance. For prior_escalations_90d, 0.86 - 0.74 = 0.12; a larger drop means the fitted model depended more on that feature for this evaluation set. The method is model-agnostic because it needs only predictions and a scoring rule.

It measures model reliance, not truth, causation, or business value. agent_id may be predictive because some agents receive harder tickets. Removing it could lower AUC while making the system fairer or more portable. A high score is a reason to investigate, not permission to deploy the column.

Correlated features create another problem. If prior_escalations_30d and prior_escalations_90d carry nearly the same history, shuffling either one alone may produce a drop of 0.04: the model can use its surviving twin. Shuffle both together and the drop may be 0.11. Assess correlated groups together, or choose the cleaner representation. Repeat permutations and examine the average drop and spread, not one ranking.

import numpy as np

from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import RFE, SelectKBest, mutual_info_classif
from sklearn.inspection import permutation_importance
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.pipeline import make_pipeline

# Twenty columns: five informative, the rest noise.
X, y = make_classification(
    n_samples=1500,
    n_features=20,
    n_informative=5,
    n_redundant=0,
    n_repeated=0,
    random_state=0,
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=0, stratify=y
)

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

permutation = permutation_importance(
    model, X_test, y_test, scoring="roc_auc", n_repeats=10, random_state=0
)
top_six = np.argsort(permutation.importances_mean)[::-1][:6]
print("top six:", top_six.tolist())

rfe = RFE(model, n_features_to_select=5)
rfe.fit(X_train, y_train)
print("RFE selection:", np.flatnonzero(rfe.support_).tolist())

# The filter is part of the pipeline, so each CV training fold
# learns its own scores and chooses its own 10 columns.
pipeline = make_pipeline(
    SelectKBest(mutual_info_classif, k=10),
    LogisticRegression(max_iter=2000),
)
scores = cross_val_score(pipeline, X, y, cv=5, scoring="roc_auc")
print("CV AUC mean:", scores.mean())

The forest receives test rows only for held-out permutation importance. RFE uses training rows. The final pipeline learns SelectKBest separately inside each cross-validation training fold, then applies that choice to that fold’s validation rows.

An independent test set has a second job. Use cross-validation to choose k, selectors, and other settings, then evaluate once on a test set that stayed untouched. If you repeatedly choose the test winner, it has become training data. Nested cross-validation separates tuning from evaluation when the comparison is high-stakes; see model selection.

A production pattern that survives contact with data

Start with an all-feature baseline using only columns genuinely available at prediction time. Record its metric, latency, memory, and operational cost. Then remove obvious non-features:

  • A unique row identifier usually identifies a row, not its outcome.
  • A post-outcome field is not valid for an earlier prediction.
  • Timestamps must be represented using information available at decision time.
  • Duplicate fields may be consolidated for reliability.

Investigate suspicious IDs rather than deleting them automatically: an ID can encode geography, product version, or a real batch effect. The question is whether that relationship will exist when the model runs.

Choose the method according to the constraint:

SituationSensible first choiceWhy
Tens of thousands of columnsFilter, then modelCheap screening reduces the search space
Small table and fixed estimatorEmbedded selection or RFEModel-aware selection is affordable
Linear model with sparse inputsL1 or Elastic NetSelection is learned with the coefficients
Tree model with a few dozen columnsOften no hard pruningSelection may be less stable than the model
Strongly correlated measurementsGroup or domain-based selectionIndividual rankings mislead

If serving cost motivates selection, measure serving cost. A feature that takes 200 milliseconds to fetch may deserve removal despite a score drop of only 0.002; deleting a cheap feature for an insignificant accuracy change may be pointless.

Finally, check stability across folds or training seeds. Track selection frequency and groups of equivalent columns. A feature chosen in five of five folds is more reassuring than one chosen in one of five, though neither proves future usefulness. If the all-feature model has AUC 0.86, the selected model 0.861, and 40 percent of selected columns change across folds, that is not a meaningful victory.

Failure modes you can see first

Production performance collapses. Inspect selected columns for leakage and train–serve skew; a pipeline prevents cross-validation leakage but cannot make a post-resolution field available at prediction time.

A filter removes a feature and accuracy falls. Look for interactions. Use a model-aware selector, retain a domain-mandated interaction, or engineer it explicitly.

Correlated features all have small permutation scores. Permute the group together and choose the cheaper, earlier, more stable, or less sensitive representation.

Selection changes wildly between folds. Weak signal, small samples, and correlated predictors cause instability. Check whether performance differences are real; then consider stronger regularization, grouped selection, more data, or no hard selection.

A model becomes easier to explain but less fair. Predictive features can encode unequal treatment or access, and proxies may preserve the same information. Evaluate subgroup performance and policy consequences alongside the main metric. Feature selection is not a substitute for fairness.

Quick check

Quick check

0/3
Q1A filter method gives both message_length and contains_angry_words low individual scores, but a model using both performs much better. Why can this happen?
Q2A model scores ROC AUC 0.86 normally and 0.74 after shuffling prior_escalations_90d. What does the drop of 0.12 tell you?
Q3You select the 50 best columns using all 100,000 labelled rows, then run five-fold cross-validation on those 50 columns. Your score is unusually high. What should you change, and why?

Next

If you want to combine columns rather than discard them, see PCA. For explanations of individual predictions, see SHAP and model interpretability. For a deeper treatment of leakage, see data leakage.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What's the difference between feature selection and dimensionality reduction like PCA?

Feature selection keeps a subset of the original features, while dimensionality reduction such as PCA creates new features by combining the originals. Use selection when named, explainable inputs matter, and PCA when compactness and handling correlated numeric data matter more than direct interpretability.

Compare filter, wrapper, and embedded feature-selection methods. When would you use each?

Filter methods rank individual features without fitting the predictive model, so they are fast and model-agnostic but can miss interactions. Wrapper methods search subsets by repeatedly training and validating a model, while embedded methods select during model fitting; wrappers suit small feature sets, and embedded methods are usually the practical default.

Walk me through how you'd select between competing models without fooling yourself with data leakage.

Freeze a representative test set before experimentation, then use only development data for preprocessing, feature selection, tuning, and model comparison. Fit every learned transformation inside each cross-validation fold, choose the model on development data, and evaluate the frozen choice on the test set once, using group or forward-chaining splits when rows are related or time-ordered.

What is feature engineering, and can you walk through how you'd engineer features to improve a model?

Feature engineering is creating, transforming, or selecting input variables so a model can capture patterns more easily. Common techniques include scaling, encoding categoricals, binning, interaction and ratio features, date/time decomposition, and domain-derived aggregates. It often matters more than the choice of algorithm because models can only learn from the signal present in their inputs.

Related lessons

Explore further