Skip to content

Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline - #2225

Open
ljdyer wants to merge 12 commits into
skrub-data:mainfrom
ljdyer:fix-issue-2114
Open

Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline#2225
ljdyer wants to merge 12 commits into
skrub-data:mainfrom
ljdyer:fix-issue-2114

Conversation

@ljdyer

@ljdyer ljdyer commented Jul 19, 2026

Copy link
Copy Markdown

Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline

Description

All suggestions in the issue comments made sense, so I implemented them as specified. There were three main changes:

  1. Loosened the requirement for estimator to inherit from sklearn.BaseEstimator
  • Per the suggestion in the issue comments, the new implementation is to require a 'scikit-learn compatible' estimator, which is defined as an object that has get_params and set_params attributes
  • Check made explicit with is_scikit_learn_compatible variable
  • isinstance(estimator, type) check carried out before is_scikit_learn_compatible check since the validity check no longer makes sense for types. Class name no longer included in error message, since it may be displayed even for invalid types. Still serves its purpose of helping the user to spot the mistake.
  • Tested (in test_sklearn_incompatible_learner_fails and test_sklearn_incompatible_learner_succeeds) with dummy classes that define get_params/set_params.
  1. Replaced HGBT and tree ensemble estimator checks with class name-based checks
  • Per suggestion in issue comments, replaced specific isinstance checks with broader substring match checks to determine need to HGBT/tree ensemble treatment (HistGradientBoosting, RandomForest)
  • This ensures compatibility with libraries that use same class names as scikit-learn (e.g. cuml)
  • XGB and LGBM included in substrings for tree ensemble check, for compatibility with xgboost and lightgbm libraries
  • Checks are made explicit (_is_hgbt_estimator, _is_tree_ensemble_estimator)
  • Existing tests pass with no modifications.
  • Added tests for tree ensemble treatment for dummy classes with RandomForest/XGB in their names
  1. Added parameters to allow the caller to request tree ensemble/HBGT treatment, overriding default heuristics
  • Added is_hgbt_estimator/is_tree_ensemble_estimator parameters, per the suggestion in the issue comments
  • Explained in docstrings that default heuristics still apply and will work in most cases, but that these parameters can be used for overrides
  • Added test to verify that is_tree_ensemble_estimator results in tree ensemble treatment in a case where this would not be triggered by the default heuristic

Addresses #2114

Checklist

  • [ X ] I have read the contributing guidelines
  • [ X ] I have added tests that verify the bug fix
  • [ X ] I have added an entry to CHANGES.rst describing the fix
  • [ X ] My code follows the code style of this project
  • [ X ] I have checked my code and corrected any misspellings

How Has This Been Tested?

All existing and new tests in test_tabular_pipeline.py
(+ entire test suite)

AI Disclosure

  • [ X ] This PR contains AI-generated code
    • [ X ] I have tested the code generated in my PR
    • [ X ] I have read and understood every line that has been generated by the AI agent
    • [ X ] I can explain what the AI-generated code does

@ljdyer ljdyer changed the title Fix issue 2114 Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline Jul 19, 2026

@jeromedockes jeromedockes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you very much @ljdyer !! this looks great.

  • I think we can remove the parameters that override the kind of estimator for now, because we would need to discuss more what are the classes of estimator and the parameter names
  • the tests can be simplified slightly

looks great otherwise, thanks :)

Comment thread skrub/_tabular_pipeline.py Outdated
means 1 unless in a joblib ``parallel_backend`` context. ``-1`` means using all
processors.

is_tree_ensemble_estimator : bool, default=None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry that wasn't clear from the issue discussion, but let's keep things simple for now and not add those extra parameters

Comment thread skrub/_tabular_pipeline.py Outdated
" or 'classifier' as its first argument."
"tabular_pipeline expects a scikit-learn compatible estimator as its first"
" argument. The estimator object must have 'get_params' and 'set_params'"
" attributes."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add 'fit' and 'predict' to the list? thanks!

@ljdyer ljdyer Jul 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, will do.
Just want to point out that this makes the requirements a bit more stringent that previously with BaseEstimator inheritance. For example AgglomerativeClustering inherits from BaseEstimator and has get_params/set_params/fit but does not have have predict. But perhaps we want to exclude it or are not concerned because it is neither a regressor or a classifier?
Another example that has the first three but does not have predict is PCA, though this does not inherit from BaseEstimator.

Comment thread skrub/_tabular_pipeline.py Outdated
@@ -285,7 +311,7 @@ def tabular_pipeline(estimator, *, n_jobs=None):
if not is_estimator_from_tabicl:
if not get_tags(estimator).input_tags.allow_nan:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we could be a bit more lenient on input estimators that don't implement the full scikit-learn api with something like

try:
    allow_nan = get_tags(estimator).input_tags.allow_nan
except AttributeError:
    allow_nan = False # assume we need imputation
if not allow_nan:
    steps.append(SimpleImputer(add_indicator=True))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - implementing more or less as you wrote

Comment thread skrub/tests/test_tabular_pipeline.py Outdated
)


def fake_get_tags(_):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the catching tag attribute error suggested below we can remove this

Comment thread skrub/tests/test_tabular_pipeline.py Outdated
tabular_pipeline(sklearn_incompatible_learner)


def test_sklearn_compatible_learner_succeeds(monkeypatch):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we create a real estimator instead,

class Regressor:
    def fit(self, X, y=None): return self
    def predict(self, X): return np.zeros(X.shape[0])
    def get_params(self): return {}
    def set_params(self, **params): return self

then make a pipeline and check we can call fit and predict on toy data and get the expected prediction? thanks

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to add the fit test with a pandas DataFrame, but with predict we run into issues as check_is_fitted gets called. Could monkeypatch, but in the interest of test simplicity could we skip the test with toy data?

Comment thread skrub/tests/test_tabular_pipeline.py Outdated
def test_tree_ensemble_treatment_for_any_random_forest(monkeypatch):
"""Test that special treatment for tree ensemble models is applied when
substring 'RandomForest' appears in estimator class name"""
IAmARandomForestEstimator = type(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in those instead of calling type we can subclass the dummy regressor defined earlier eg

class UserRandomForest(Regressor): pass

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great advice - makes the tests much simpler

Comment thread skrub/_tabular_pipeline.py Outdated
if not isinstance(estimator, BaseEstimator):

is_scikit_learn_compatible = hasattr(estimator, "get_params") and hasattr(
estimator, "set_params"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This accepts non-callable attributes, so an object with get_params = 1 and set_params = 1 passes the guard but the returned pipeline raises TypeError as soon as get_params() or clone() touches it. Should these checks use callable(getattr(..., None)) instead?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Sanjay, I agree that it's fine to check for callable( as well. Extending this to fit and predict too. Only caveat is what I wrote in my response to Jerome's comment above about estimators that don't implement predict.

@ljdyer

ljdyer commented Jul 27, 2026

Copy link
Copy Markdown
Author

Thanks @jeromedockes for the feedback! I'll be working on this again in the next few days. Cheers

@ljdyer

ljdyer commented Jul 29, 2026

Copy link
Copy Markdown
Author

Thank you @jeromedockes for the review. I have resubmitted so please review again when convenient.

@jeromedockes

Copy link
Copy Markdown
Member

thanks a lot for making the changes @ljdyer ! I will look at it again in the next few days :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants