Skip to content

Implement factorize for MultiIndex - #23139

Merged
rapids-bot[bot] merged 5 commits into
rapidsai:mainfrom
galipremsagar:mi_factorize
Jul 8, 2026
Merged

Implement factorize for MultiIndex#23139
rapids-bot[bot] merged 5 commits into
rapidsai:mainfrom
galipremsagar:mi_factorize

Conversation

@galipremsagar

Copy link
Copy Markdown
Contributor
  • Implements MultiIndex._factorize so factorize on a MultiIndex (and tuple data) returns codes and uniques across all levels, with first-appearance or lexicographic ordering per sort.
  • Removes 5 now-passing entries from the pandas-testing plugin and documents the reason for one remaining inherent failure.
  • Split out from Fix factorize, value_counts, and to_datetime pandas compatibility issues #23138.

@galipremsagar
galipremsagar requested a review from a team as a code owner July 7, 2026 07:51
@copy-pr-bot

copy-pr-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added Python Affects Python cuDF API. cudf.pandas Issues specific to cudf.pandas labels Jul 7, 2026
@galipremsagar
galipremsagar requested a review from mroeschke July 7, 2026 07:51
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 7, 2026
@galipremsagar galipremsagar added bug Something isn't working non-breaking Non-breaking change labels Jul 7, 2026
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test ca37851

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a250a631-134c-4c19-b83d-d926e26cfe53

📥 Commits

Reviewing files that changed from the base of the PR and between 8b8dd84 and 4d6588e.

📒 Files selected for processing (1)
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added pandas.MultiIndex factorization support, returning integer codes and uniques.
    • Empty MultiIndex inputs now produce zero-length codes and uniques.
  • Bug Fixes
    • Factorization ordering now matches pandas behavior for both sort=True and sort=False.
    • Updated test expectations for NaN handling and resulting uniques dtype.
  • Tests
    • Added unit tests validating MultiIndex factorization for sorted/unsorted cases and empty inputs.

Walkthrough

This PR adds MultiIndex._factorize with groupby/merge-based code generation, and updates factorize tests plus pandas-testing-plugin expected-failure annotations.

Changes

MultiIndex Factorize Support

Layer / File(s) Summary
Core _factorize implementation
python/cudf/cudf/core/multiindex.py
Adds MultiIndex._factorize(sort, use_na_sentinel), rejecting null-containing inputs, handling empty indexes, and computing codes and uniques from grouped level combinations.
Tests and expected-failure updates
python/cudf/cudf/tests/general_functions/test_factorize.py, python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Adds MultiIndex factorize tests for populated and empty inputs, updates the test module import/header, and revises the expected-failure note for test_int_factorize_use_na_sentinel_false.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: improvement

Suggested reviewers: TomAugspurger, brandon-b-miller, Matt711

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding factorize support for MultiIndex.
Description check ✅ Passed The description is directly related to the changeset and accurately summarizes the implementation and test updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

Comment thread python/cudf/cudf/core/multiindex.py Outdated
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test bf33cd4

@galipremsagar galipremsagar added the 5 - Ready to Merge Testing and reviews complete, ready to merge label Jul 7, 2026
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
python/cudf/cudf/tests/general_functions/test_factorize.py (1)

108-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a named empty-MultiIndex case.

The current empty test uses an unnamed MultiIndex, so it won't catch the names-handling gap between the empty and non-empty branches of _factorize (see multiindex.py comment). Adding a variant with names=[...] would close this coverage gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudf/cudf/tests/general_functions/test_factorize.py` around lines 108
- 116, The empty MultiIndex factorize test only covers an unnamed case, so it
misses the names-handling path difference in `_factorize`. Update
`test_factorize_multiindex_empty` in `test_factorize.py` to add a named empty
`MultiIndex` variant using `pd.MultiIndex.from_arrays(..., names=[...])` and
assert `factorize()` preserves the expected empty result and names via
`gmi[:0]`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cudf/cudf/core/multiindex.py`:
- Around line 1804-1805: The empty MultiIndex path in `MultiIndex.factorize`
returns `self.copy()`, which preserves level names and makes the `uniques`
result inconsistent with the non-empty branch. Update the `len(self) == 0`
branch so the returned empty `MultiIndex` mirrors the non-empty behavior by
clearing names to `[None] * self.nlevels`, using the existing
`MultiIndex.factorize`/`self.copy()` flow as the place to apply the fix.

---

Nitpick comments:
In `@python/cudf/cudf/tests/general_functions/test_factorize.py`:
- Around line 108-116: The empty MultiIndex factorize test only covers an
unnamed case, so it misses the names-handling path difference in `_factorize`.
Update `test_factorize_multiindex_empty` in `test_factorize.py` to add a named
empty `MultiIndex` variant using `pd.MultiIndex.from_arrays(..., names=[...])`
and assert `factorize()` preserves the expected empty result and names via
`gmi[:0]`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e50634f1-ff7f-4bd9-b906-358df5ce2285

📥 Commits

Reviewing files that changed from the base of the PR and between bd5e3cc and bf33cd4.

📒 Files selected for processing (3)
  • python/cudf/cudf/core/multiindex.py
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
  • python/cudf/cudf/tests/general_functions/test_factorize.py

Comment on lines +1804 to +1805
if len(self) == 0:
return cp.empty(0, dtype=np.intp), self.copy()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty MultiIndex factorize doesn't strip level names, unlike the non-empty path.

For the non-empty path, uniques_mi.names is explicitly reset to [None] * self.nlevels to match pandas (per the comment at line 1838). But the empty branch returns self.copy(), which preserves the original names. For a named empty MultiIndex, this makes uniques inconsistent with the non-empty behavior (and, per the code's own rationale, with pandas). The current empty test doesn't catch this because it constructs an unnamed MultiIndex.

🐛 Proposed fix
         if len(self) == 0:
-            return cp.empty(0, dtype=np.intp), self.copy()
+            empty = self.copy()
+            empty.names = [None] * self.nlevels
+            return cp.empty(0, dtype=np.intp), empty
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(self) == 0:
return cp.empty(0, dtype=np.intp), self.copy()
if len(self) == 0:
empty = self.copy()
empty.names = [None] * self.nlevels
return cp.empty(0, dtype=np.intp), empty
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudf/cudf/core/multiindex.py` around lines 1804 - 1805, The empty
MultiIndex path in `MultiIndex.factorize` returns `self.copy()`, which preserves
level names and makes the `uniques` result inconsistent with the non-empty
branch. Update the `len(self) == 0` branch so the returned empty `MultiIndex`
mirrors the non-empty behavior by clearing names to `[None] * self.nlevels`,
using the existing `MultiIndex.factorize`/`self.copy()` flow as the place to
apply the fix.

# Conflicts:
#	python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 8b8dd84

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 4d6588e

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 32b4d27 into rapidsai:main Jul 8, 2026
126 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge bug Something isn't working cudf.pandas Issues specific to cudf.pandas non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants