Conversation
…s support BaseSelector.transform() returns the retained features in the train set order, in the same library as the input (pandas X[features], narwhals select otherwise). BaseRecursiveSelector.fit() trains the estimators on native frames and returns (nw_X, y). The helpers in base_selection_functions no longer import pandas: correlations are computed with numpy (np.corrcoef, or matrix products for pairwise complete observations when there are missing values), and feature importances are pandas Series for pandas input and dicts otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fit() no longer imports pandas. The statistics used to order the features (missing values, standard deviation, cardinality) use pandas methods for pandas input and narwhals expressions otherwise. The correlation with the target is computed with numpy/scipy per feature (pandas and other backends) or with polars expressions for polars input. Features are sorted with a stable numpy argsort, which reproduces pandas' ordering. X and y are checked with check_X_y when the selection method needs the target, so mismatched pandas indexes now raise an error instead of silently aligning. In model_performance, each correlated group is evaluated in alphabetical order so that ties are resolved deterministically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1070 (selection base classes): its commit shows in the diff until #1070 is merged.
Summary
Migrates
SmartCorrelatedSelectionto narwhals: it now accepts pandas and polars dataframes (and other narwhals-supported libraries) andtransform()(inherited fromBaseSelector) returns the same library as the input.fit()no longer uses pandas.nw_X = check_X(X), ornw_X, y = check_X_y(X, y)whenselection_methodis"model_performance"or"corr_with_target".isna().sum(),std(),nunique()), narwhals expressions otherwise (null_count(),std(),drop_nulls().n_unique()).corrwith()uses (np.corrcoef,spearmanr,kendalltau, on the rows where the feature is not NaN). For polars with pearson/spearman, polars expressions vianw.get_native_namespace(X)(corr()on the non-NaN rows).np.argsort(_sort_features), which gives the same order as pandas'sort_values(kind="mergesort"), with ties kept in the input order and NaN last. I checked this on 2,000 random vectors with ties and NaN.model_performancepicks the best feature with_sort_featuresinstead ofpd.Series(...).sort_values().__init__:missing_valuesandselection_methodare checked withisinstance(..., str)before the membership test. The messages don't change.x1, x3, notx2, x3), fixed. Removed references topandas.corr()and added a polars example.pandas.corr(). Added a "With polars" section, with its output pasted from a real run. I re-ran all existing examples. The values match, except that sets and thestd()output are printed in set order, which varies from run to run.Benchmarks
The machine was shared with other jobs (load average 15-45), so single numbers are noisy. Medians of 5-7 repeats, with 5% missing values in every column.
Statistic by statistic, same data, candidates timed alternately (ms):
pandas
For pandas, missing, std and nunique are close between candidates and swap places from size to size. I kept pandas' own methods: they are as fast as the others at 500k rows and give exactly pandas' results. For the correlation with the target, the numpy/scipy loop won at every size (it is what
corrwith()runs inside, without the per-column overhead). A vectorised numpy version was not faster. Narwhals on pandas was 8-10x slower.polars
For the polars correlation with the target, native polars
corr()was 3-5x faster than the same expression written in narwhals, and 1.1-2x faster over the wholefit(). Timings for the wholefit(), pearson: 500k x 100 went from 767 to 527 ms, and 1M x 100 from 2848 to 1706 ms. That's why this is the one place that uses polars throughnw.get_native_namespace.Whole
fit(), pandas, main (pandas-only code) vs this PR (ms, pearson, best of 2 runs):Most of this gain comes from the faster correlation matrix in #1070. With 10 columns, the new code is as fast or faster at every size.
Whole
fit(), polars, this PR (ms): missing_values 912 / 2252, variance 553 / 587, cardinality 398 / 893, corr_with_target 390 / 712, at 500k / 1M rows x 100 columns.model_performanceis dominated by cross-validation, and the selector's own work there is unchanged.Behaviour
I recorded the outputs of 127 cases on
main(pandas) and compared them with this branch. The cases cover every selection method × pearson/spearman/kendall, NaN, inf, constant columns, thresholds, int and nullableInt64columns, integer column names, non-default index, list/array targets, cv generators, groups,variables/confirm_variables, reordered columns in transform, callables and all error paths. I comparedfeatures_to_drop_,correlated_feature_sets_,correlated_feature_dict_(including key order),variables_,feature_names_in_,get_support()and the transformed data.methodstring, see "Needs decision".missing_valuescounts nulls, andcardinalitydoes not count null as a value, likenunique()in pandas. A float NaN in a polars column is a value for those two methods, like in the imputers. The correlations skip it, like the base correlation matrix does. The user guide says this in one sentence.corr_with_targetwith an invalidmethodstring: pandas raised pandas' "method must be either ..." error. It now raisesTypeError: 'str' object is not callable, on both backends. The other selection methods still raise pandas' error on pandas input, because that error comes from the base helper.yis required and missing, the "y is needed" error is now raised before the variable checks. It used to come after them. The message is unchanged.Bugs fixed
corr_with_target, pandas'corrwith()alignedytoXby index. When the indexes differed, it silently correlated the wrong rows and returned a different selection (maindropsvar_0instead ofvar_8in the user-guide data). Withmodel_performance,ywas not checked at all. Both now go throughcheck_X_y, which raises "The indexes of X and y do not match.", as in the other migrated transformers. Test:test_error_if_index_of_X_and_y_differ.model_performancewere not deterministic. Each correlated group (aset) was passed tosingle_feature_performancein set iteration order, which changes withPYTHONHASHSEED. When two features trained equally good models (e.g. duplicated columns), the retained feature changed between runs. Onmain, 4 of 6 seeds keptvar_0_duplicatedand 2 keptvar_0. The group is now evaluated in alphabetical order, so the tie goes to the alphabetically first feature. That's the same rule the existing "duplicated features" test describes forcorr_with_target. Test:test_model_performance_ties_keep_first_feature_alphabetically, which fails without the fix (checked with 4 hash seeds). This changes results only for exact ties.Tests
tests/test_selection/test_smart_correlation_selection.pyis rewritten to the conventions. It has 93 tests:test_init_param_assignmentcovering every init parameter.variables/confirm_variables, and that the input is not modified. All of these run on both backends viamake_df, with data as dicts.Expected values are explicit. New ones were computed on
main.tests/test_selection, base (origin/narwhals-selection-base) vs this branch:There are no new failures. The 12 tests that now pass are the 8 old SmartCorrelatedSelection tests and 4
test_check_estimator_selectors.pychecks for this class.tests/parametrize_with_checks_selection_v16.pyfails on the same tests before and after (289 failures).flake8 feature_engine testsis clean, andmypy feature_enginestill shows the 2 errors that are already on the base.Needs decision
methodin__init__. Neither this selector norDropCorrelatedFeaturesvalidatesmethod. The error comes from pandas atfit()for pandas input, and is aTypeErrorfor polars orcorr_with_target. I suggest validating it in__init__('pearson','spearman','kendall'or a callable, ending "Got {method} instead."), for both selectors, or in the base. I left it as is because it changes when the error is raised, and the error for most paths comes from the base helper.corr()throughnw.get_native_namespace(X)(see benchmarks). If you prefer narwhals-only code, the narwhals expression version is about 3-5x slower for that step. It is still 3x faster than numpy.Pre-existing issues, not fixed
test_error_if_method_not_permittedstays pandas-only, because the message comes from pandas (see "Needs decision").