refactor: use sklearn OrdinalEncoder as source of truth for DataTransformer categorical encoding (#1564)#1569
Open
immu4989 wants to merge 1 commit into
Conversation
…former categorical encoding (microsoft#1564)
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.
Why are these changes needed?
Implements the refactor agreed on #1564 (
-1sentinel greenlit by @thinkall). Replaces the ad-hoc_cat_categoriesdict introduced in #1561 with a fittedsklearn.preprocessing.OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1, encoded_missing_value=-1)as the source of truth for the per-column category list.The observable behavior is preserved (see the existing #1561 tests): known categories still get stable integer codes across fit/predict, unseen values still emit a
UserWarningand get remapped to the"__NAN__"sentinel category, and pandas categorical dtype is still returned so that LGBM'scategorical_featuredetection, CatBoost'scat_featuresinference, and the sklearn/KNeighbors estimator wrappers all continue to work unchanged.Scope note
My original RFC on #1564 speculated that the refactor could switch to a numeric integer output (with
-1visible in the returned DataFrame). While implementing, I audited the downstream consumers inflaml/automl/model.py:LGBMEstimatorandKNeighborsEstimator_preprocesspaths (select_dtypes(include=["category"])+.cat.codes)CatBoostEstimatorcat_features = X.select_dtypes(include="category").columnsSwitching to a numeric output would silently break CatBoost's categorical-column detection and would need a coordinated per-estimator update. That's a much bigger surgery than the RFC scoped, and the observable value — better sklearn idiom internally — is the same either way. So this PR keeps the category-dtype output and uses OrdinalEncoder as the internal source of truth. If we want the numeric-output form later, I'll open a follow-up RFC.
What the PR does
flaml/automl/data.py—DataTransformer.fit_transform:X[cat_columns].astype("category")call, fits a per-columnOrdinalEncoderonX[cat_columns].astype(object)and stores it asself._ordinal_encoder.self._cat_categories(the_ordinal_encoder'scategories_array is the equivalent, sklearn-standard representation).flaml/automl/data.py—DataTransformer.transform:Three-tier fallback for cross-version pickle compatibility:
_ordinal_encoderpresent (post-[Proposal]: Replace home-rolled categorical encoding with sklearn OrdinalEncoder in DataTransformer (follow-up to #1101 / #1561) #1564) — use it as the source-of-truth category list, pinpd.Categorical(..., categories=known_cats + ["__NAN__"])._cat_categoriespresent (post-fix: DataTransformer pins categorical codes at fit time + warns on unseen values (#1101) #1561 but pre-[Proposal]: Replace home-rolled categorical encoding with sklearn OrdinalEncoder in DataTransformer (follow-up to #1101 / #1561) #1564) — fall back to the existing dict-based path.astype("category")as before.Unseen-value handling (
UserWarning+ remap to"__NAN__") preserved in both_ordinal_encoderand_cat_categoriesbranches.test/automl/test_preprocess_api.py— newTestOrdinalEncoderBackedTransformclass:test_fit_transform_installs_ordinal_encoder— asserts_ordinal_encoderis a fitted sklearnOrdinalEncoderafterfit_transform.test_ordinal_encoder_path_matches_1561_semantics— behavioral equivalence with the fix: DataTransformer pins categorical codes at fit time + warns on unseen values (#1101) #1561 defensive patch: stable known-cat codes,UserWarningon unseen values, unseen rows remapped to the sentinel code.test_transform_falls_back_to_cat_categories_when_encoder_missing— simulates a fix: DataTransformer pins categorical codes at fit time + warns on unseen values (#1101) #1561-era pickle (removes_ordinal_encoder, installs_cat_categories) and assertstransform()still produces stable codes.test_transform_legacy_pickle_without_either_attribute— simulates a pre-fix: DataTransformer pins categorical codes at fit time + warns on unseen values (#1101) #1561 pickle (neither attribute present) and assertstransform()doesn't raise; the legacyastype("category")path is exercised.Verified locally
pytest test/automl/test_preprocess_api.py::TestOrdinalEncoderBackedTransform— 4/4 pass.TestCategoricalEncodingStability) — 2/2 pass unchanged.test_preprocess_api.pyfile — 14/14 pass.test_split.py+test_multioutput+test_ensemble_component_predict_via_public_preprocess— 10/10 pass.pre-commit run --files flaml/automl/data.py test/automl/test_preprocess_api.py— all hooks pass.Related issue / PRs
automl.preprocess(X)see identical output.Checks