Model-Based Feature Selection
Model-based feature selection uses a selector that is first fitted with fit(X_train, y_train) and then applied with transform(X_train) to produce a reduced feature matrix.
Starting With Too Many Columns
Many real datasets arrive with far more input columns than a model can comfortably use. Some of those columns carry useful information about what you want to predict. Others are noise: values that vary randomly and have no real connection to the target. Feature selection is the family of techniques for deciding which columns to keep before a model is trained on them.
What do you think happens?
If you were told to keep only half of a set of columns, what would you use to decide which half is worth keeping?
Reveal answer
Score each column against the target and keep the strongest half
The approach this article covers judges each column by its own relationship with the target. Columns that show a weak relationship become candidates for removal. Picking by position or by raw magnitude ignores whether the column is related to what you want to predict.
Scoring Each Feature on Its Own
Univariate feature selection scores each feature separately against the target using a statistical test, then discards features whose p-values are too high. The word univariate means one variable at a time: every column is tested against the target separately, without asking how it interacts with the other columns. A high p-value means the feature is unlikely to be related to the target, so it becomes a candidate for removal.
Two decisions are needed. First, you choose a statistical test. The source names f_classif as the default for classification and f_regression for regression. Second, you choose a rule for discarding features based on the p-values that test produces. Every discarding method uses a threshold, and the methods differ in how they compute that threshold.
Because each test is independent, two features can end up with very different results. One column may show a clear connection to the target while another, placed right beside it, shows none. The selector only sees each result separately and then applies the threshold to each one.
Keeping a Share of the Columns
The source names two of the simplest discarding methods. SelectKBest selects a fixed number k of features. SelectPercentile selects a fixed percentage of features. The difference lies only in how the size of the kept set is specified. With SelectKBest you say how many columns survive, regardless of how many columns the dataset has. With SelectPercentile you say what share of the columns survives, so the count adjusts automatically as the dataset grows or shrinks.
The percentile argument is a number from 0 to 100 that sets the share of features to keep. The source uses 50, meaning half of the features. Choosing between a fixed count and a fixed percentage is a judgment call. A percentage adapts to the dataset's width, which helps when the same pipeline must handle inputs of different sizes. A fixed count gives direct control over the model's input size, which helps when a downstream constraint sets a maximum number of columns.
Tracing the Fit and Transform Steps
The usage pattern has three steps. You create the selector with a percentile, fit it so that it can score the features against the training targets, and then transform the training data to keep only the selected columns. The code below follows that pattern. The source snippet does not show how the selector was created, so the setup line is assumed here as an illustration.
SelectPercentile(percentile=50)keeps half of the input features- fit receives both the training inputs and the training targets
- transform returns a new array and leaves X_train unchanged
- The two print calls report the shape of each array, rows first then columns
The method select.fit(X_train, y_train) is the step where the selector examines the training data. It receives the feature matrix X_train and the target values y_train. The fit call changes the internal state of the select object, but it does not return a new dataset. The method select.transform(X_train) then applies the decision. It returns a new array containing only the kept features, which the source stores in a new variable called X_train_l1.
The order matters. The selector must be fitted before transform is called, because transform relies on the decision made during fit. Calling transform on an unfitted selector has no decision to apply, so the fit step always comes first.
Reading the Shape Before and After
Shape is the most useful first check in this exercise because it reports two facts at once: how many samples remain and how many features remain. A shape is written as a pair of numbers, rows first and then columns. Comparing the shapes printed before and after transform tells you what the transform step did.
In the source exercise, the feature count drops from 80 to 40 while the 284 samples remain unchanged. The shape printout is the main evidence that the selection worked. If the row count changed, something other than feature selection has gone wrong, because this step is meant to remove columns only.
Reading the Mask Back to Column Positions
Transforming the data tells you how many features survived, but not which ones. The get_support method answers the second question. It returns a Boolean mask, an array with one True or False value per original feature. True marks a feature that was kept and False marks a feature that was removed. The position of each entry in the mask matches the position of that feature in the original input, so the mask is a direct map from original column positions to keep-or-drop decisions.
Decoding a six-feature mask
Suppose a selector was applied to six features and get_support returns the mask True, False, True, True, False, False. Which features were kept and which were dropped?
- 1Pair each entry with its position
Position 0 is True, position 1 is False, position 2 is True, position 3 is True, position 4 is False, and position 5 is False.
- 2Collect the True positions
True entries sit at positions 0, 2 and 3, so those original features were kept.
- 3Collect the False positions
False entries sit at positions 1, 4 and 5, so those original features were dropped.
- 4Check the count
Three of six features survived, which matches the share a percentile of 50 would keep.
Features at positions 0, 2 and 3 were kept. Features at positions 1, 4 and 5 were dropped.
Reading the mask this way lets you check the selector's decisions against the original column order. If you know that a column at position 2 is important, you can look up its entry directly instead of guessing from the transformed array, which has lost the original positions.
Displaying the Mask With matshow
After the reduced array has been checked, the source looks at which features were selected. The mask is reshaped with mask.reshape(1, -1) before it is displayed. This turns the one-dimensional mask into a single row with one column per original feature. The matshow function then draws that row as a strip of cells. The cmap='gray_r' argument chooses a reversed grayscale palette in which True cells are black and False cells are white.
get_supportreturns one Boolean per original featurereshape(1, -1)makes the mask a single row of cells- cmap='gray_r' draws True as black and False as white
Because the mask is drawn as one row, the horizontal positions in the plot correspond to original feature positions. The source's tick marks run from 0 to 70 along the horizontal axis, which covers the range of feature positions across the strip. Read the strip from left to right: each black cell is a kept feature and each white cell is a removed feature.
Does Removing Features Help a Model
Removing features only matters if the model does as well or better afterward. The source checks this by training logistic regression twice on the same split: once on all features and once on only the selected features, then scoring both on the test set. In the source's synthetic example, the reduced feature set scored slightly higher. Removing the noise columns improved performance, even though some genuine features were lost along the way.
The source's dataset was a breast cancer dataset with 30 real features, to which 50 columns of random noise were appended, giving 80 columns. The selection was imperfect: some real features were dropped and some noise columns were kept. The recovery was not perfect, yet the reduced set still scored higher.
Univariate selection is still useful in two situations the source names: when there are so many features that building a model on all of them is infeasible, and when you suspect that many features are completely uninformative.
Mistakes That Hide the Real Result
Common mistake Fitting the selector on the test data
Why it failsThe selector should learn which features to keep from training data only. Fitting it on test data lets the test set influence the selection.
Do this insteadCall fit with X_train and y_train, then call transform on both the training and test data.
Common mistake Trusting the transformed array to show which features survived
Why it failsThe transformed array shows how many columns remain but not which original positions they came from.
Do this insteadUse get_support to read the Boolean mask and map each True or False entry back to its feature position.
Common mistake Calling transform before fit
Why it failsTransform applies the decision made during fit, so without a fitted selector there is no decision to apply.
Do this insteadAlways call fit before transform.
Common mistake Assuming a better score proves selection helps
Why it failsThe source's example was simple and synthetic, and outcomes on real data are usually mixed.
Do this insteadCompare the model on all features against the model on selected features, and treat the result as evidence for one dataset only.
Locating a Failure in a Selection Script
When a selection script misbehaves, locate the failure by comparing each step's output with its expected output, starting from the fit call and moving forward. The shape printouts act as checkpoints. If X_train_l1 has the wrong number of columns, the problem lies in the fit step, the transform step, or the data passed to them. Work through the chain in order rather than guessing.
A selector with percentile 50 is fitted on a training set with 284 samples and 80 features. After transform, the printed shape of X_train_l1 is (284, 80). Which step should you inspect first, and why?
Hints
- Compare the expected feature count after a 50 percent selection with the printed count
- The number of samples should be unchanged, so focus on the columns
Key Takeaways
- Univariate feature selection judges each feature by its own relationship with the target, using a statistical test whose p-value decides whether the feature is a candidate for removal.
- SelectPercentile keeps a fixed share of the features, so the kept count scales with the dataset, while SelectKBest keeps a fixed count.
- A selector is fitted on training data with
fit(X_train, y_train)and then applied with transform, and the Boolean mask fromget_supportmaps every kept or dropped decision back to an original feature position. - Shape checks show what changed and what stayed the same: in the source exercise, features fell from 80 to 40 while 284 samples remained.
- Whether selection helps must be checked by comparing model performance on all features against the selected features, because results on real data are usually mixed.