Skip to content

[python-package] Improve pandas categorical test coverage - #7376

Merged
jameslamb merged 11 commits into
lightgbm-org:mainfrom
maxzw:tests/pandas-categorical-null-and-registred-but-unobserved
Jul 27, 2026
Merged

[python-package] Improve pandas categorical test coverage#7376
jameslamb merged 11 commits into
lightgbm-org:mainfrom
maxzw:tests/pandas-categorical-null-and-registred-but-unobserved

Conversation

@maxzw

@maxzw maxzw commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

This PR introduces two new tests related to how pandas handles categoricals, which are necessary to detect regression when implementing backend-agnostic categorical handling (#7343):

test_pandas_categorical_with_missing_values

This test checks that pandas null|NaN values in categorical columns are not interpreted as distinct categories but are handled like missing values.

test_pandas_categorical_encoding_registered_but_unobserved

This test verifies the encoding behavior when categorical values are defined in the dtype but never appear in training data. For unordered categoricals, such values map to NaN (the C++ bin mapper has no bin for them). For ordered categoricals (treated as numeric), such values interpolate or clip to the nearest observed bin boundary. This matches standard continuous feature behavior for out-of-range values.

@maxzw maxzw changed the title [python-package] Increase pandas categorical test coverage [python-package] Improve pandas categorical test coverage Jul 22, 2026

@jameslamb jameslamb 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.

Thanks for splitting out these tests into a separate PR. Very supportive of doing things this way and I'll try to get you quick reviews so you can keep making progress on the larger project(s) you're working on.

Left a few suggestions.

ref_valid_ds = lgb.Dataset(ref_valid_df, label=[0, 1, 0], reference=train_ds, params=dummy_dataset_params())
ref_valid_ds.construct()

assert_datasets_equal(tmp_path, valid_ds, ref_valid_ds)

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.

This tests that these Datasets are equal but they could also be equal in the case of a bug where these inputs were all handled incorrectly. For example, if they were all converted to floats and treated as continuous variables.

I think this test should be strengthened with some assertions that the variables were handled in the expected way.

Like:

  • both columns were detected as categorical
  • the encodings in Dataset are as expected (I think you could see this in a model file if you added an lgb.train() to this or output of Dataset._dump_text(), don't recall exactly and can't spend the time to look right now)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've rewritten the test a little bit to improve the strictness.

  • Aligned the (relative) values and categories between ordered / unordered to emphasize the differences in how they are handled.
  • Check more metadata (e.g. pandas_categorical and params["categorical_column"])
  • Inspect the resulting encodings from _data_from_pandas directly (so python-side, but I've also kept the C++ side verification with a comment explaining why the output is expected)

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.

Yep that's good for this PR, much stricter, thank you.

Comment thread tests/python_package_test/test_pandas.py Outdated
@maxzw
maxzw requested a review from jameslamb July 23, 2026 06:42

@jameslamb jameslamb 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.

Just one more small recommendation, then I think this is ready to merge.

pandas_categorical=train_ds.pandas_categorical,
)[0]
assert valid_df_encoded[:, 0].tolist() == [0.0, 1.0, 3.0] # a -> 0, b -> 1, d -> 3
assert valid_df_encoded[:, 1].tolist() == [0.0, 1.0, 3.0] # e -> 0, f -> 1, h -> 3

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.

Could you please update this to some example that doesn't happen to encode to identical values?

That'd help us catch issues like the categorical code leakage you mentioned over in #7343 (comment)

@maxzw
maxzw requested a review from jameslamb July 25, 2026 09:08

@jameslamb jameslamb 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.

Looks good to me, we can merge this once you pull in latest main and fix merge conflicts.

@maxzw

maxzw commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Looks good to me, we can merge this once you pull in latest main and fix merge conflicts.

Thanks! Done 👍🏻

@jameslamb jameslamb 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.

Great, thanks!

I will try to make some time for #7343 this week, now that we have some of these testing changes on main. Very grateful that you broke it up like this, I think that'll help a lot with reviewing.

@jameslamb
jameslamb merged commit f9bf8d1 into lightgbm-org:main Jul 27, 2026
62 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants