Skip to content

Keep MultiIndex columns in GroupBy.agg for MultiIndex-column sources - #23366

Merged
rapids-bot[bot] merged 3 commits into
NVIDIA:release/26.08from
galipremsagar:groupby-agg-multiindex-columns
Jul 22, 2026
Merged

Keep MultiIndex columns in GroupBy.agg for MultiIndex-column sources#23366
rapids-bot[bot] merged 3 commits into
NVIDIA:release/26.08from
galipremsagar:groupby-agg-multiindex-columns

Conversation

@galipremsagar

Copy link
Copy Markdown
Contributor

Description

Split out of #23255 (5/6).

GroupBy.agg flattened a MultiIndex-column source's aggregation result to flat tuple labels instead of keeping hierarchical columns like pandas. Preserve the MultiIndex (and its per-level metadata) when the aggregation keeps the source's tuple labels; relabeling aggregations (agg(new=(col, func))) emit new flat labels, so the source's multi-level metadata is not attached to those.

Fixes 3 pandas-tests (test_groupby_with_hier_columns, test_wrap_aggregated_output_multindex, test_multiindex_custom_func[<lambda>0]); their xfail entries are removed. Attribution verified by running the node ids against an isolated build containing only this change (pass) and a clean build (fail).

Independent of the other #23255 split PRs; the unstack PR (4/6) depends on this one for two entangled tests.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

GroupBy.agg flattened a MultiIndex-column source's aggregation result to
flat tuple labels instead of keeping hierarchical columns like pandas.
Preserve the MultiIndex (and its per-level metadata) when the
aggregation keeps the source's tuple labels; relabeling aggregations
(agg(new=(col, func))) emit new flat labels, so the source's multi-level
metadata is not attached to those.

Fixes 3 pandas-tests (test_groupby_with_hier_columns,
test_wrap_aggregated_output_multindex, test_multiindex_custom_func);
their xfail entries are removed. Attribution verified against an
isolated build containing only this change.
@galipremsagar
galipremsagar requested a review from a team as a code owner July 21, 2026 14:03
@galipremsagar
galipremsagar requested review from bdice and mroeschke July 21, 2026 14:03
@copy-pr-bot

copy-pr-bot Bot commented Jul 21, 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 21, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 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: ae4e60b6-c0f4-4b75-8289-6edec5f4e71e

📥 Commits

Reviewing files that changed from the base of the PR and between 413f7b9 and dd39376.

📒 Files selected for processing (2)
  • python/cudf/cudf/core/groupby/groupby.py
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
💤 Files with no reviewable changes (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/core/groupby/groupby.py

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved GroupBy aggregation results for DataFrames with hierarchical (MultiIndex) columns.
    • Preserved hierarchical column labels and metadata when appropriate.
    • Ensured renamed or flattened aggregation outputs no longer retain incorrect MultiIndex metadata.
  • Tests

    • Added regression coverage for MultiIndex aggregation and named aggregations.

Walkthrough

GroupBy aggregation now conditionally preserves MultiIndex column metadata based on output labels, including empty results. Regression tests cover hierarchical and flat named-aggregation outputs, and related expected-failure entries are removed.

Changes

GroupBy MultiIndex aggregation

Layer / File(s) Summary
Preserve aggregation column metadata
python/cudf/cudf/core/groupby/groupby.py
GroupBy.agg preserves source MultiIndex metadata for matching tuple labels and uses flat metadata for relabeled outputs, including empty aggregation results.
Validate MultiIndex aggregation behavior
python/cudf/cudf/tests/groupby/test_agg.py, python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Regression tests cover hierarchical and flat named-aggregation outputs, while three related expected-failure entries are removed.

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

Possibly related PRs

  • rapidsai/cudf#23255: Updates overlapping GroupBy.agg MultiIndex and ColumnAccessor metadata reconstruction logic.

Suggested reviewers: bdice, mroeschke, vyasr

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change to GroupBy.agg MultiIndex column handling.
Description check ✅ Passed The description is directly related to the change and explains the behavior, tests, and scope clearly.
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.

@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 (2)
python/cudf/cudf/tests/groupby/test_agg.py (2)

824-835: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use named source levels in the flat-relabeling regression.

Because this source MultiIndex has no names, the test cannot detect level_names leaking into the flat result. Set names=["l0", "l1"] and assert that the relabeled output has a flat Index with name is None. Pandas uses the named-aggregation output keys as the result columns. (raw.githubusercontent.com)

As per coding guidelines, test changes should cover the metadata edge cases exercised by the implementation.

🤖 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/groupby/test_agg.py` around lines 824 - 835, Update
test_agg_relabel_flat_columns_from_multiindex to create the source MultiIndex
with names ["l0", "l1"], then explicitly assert that the named-aggregation
result columns are a flat Index whose name is None for both pandas and cuDF
outputs. Preserve the existing aggregation comparison while ensuring the
regression detects leaked source level names.

Source: Coding guidelines


809-821: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the empty MultiIndex result path.

GroupBy.agg now preserves level_dtypes when no value columns are aggregated, but this test only exercises a non-empty result. Add an empty-frame/no-value-columns case with named, non-object MultiIndex levels so the new metadata path is validated.

As per coding guidelines, Python tests must cover relevant empty and edge cases.

🤖 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/groupby/test_agg.py` around lines 809 - 821, Extend
test_agg_multiindex_columns_preserved with an empty-frame case containing named,
non-object MultiIndex levels and no value columns, then compare cudf and pandas
GroupBy.agg results to validate preservation of level_dtypes in the empty result
path.

Source: Coding guidelines

🤖 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/groupby/groupby.py`:
- Around line 1328-1351: The flat relabeled-aggregation branch in ColumnAccessor
construction must not inherit MultiIndex column metadata: in
python/cudf/cudf/core/groupby/groupby.py lines 1328-1351, remove the source
level_names from the multiindex=False branch while preserving flat-label
behavior. Add a named-MultiIndex regression test in
python/cudf/cudf/tests/groupby/test_agg.py lines 824-835 verifying the relabeled
result has no stale columns name.

---

Nitpick comments:
In `@python/cudf/cudf/tests/groupby/test_agg.py`:
- Around line 824-835: Update test_agg_relabel_flat_columns_from_multiindex to
create the source MultiIndex with names ["l0", "l1"], then explicitly assert
that the named-aggregation result columns are a flat Index whose name is None
for both pandas and cuDF outputs. Preserve the existing aggregation comparison
while ensuring the regression detects leaked source level names.
- Around line 809-821: Extend test_agg_multiindex_columns_preserved with an
empty-frame case containing named, non-object MultiIndex levels and no value
columns, then compare cudf and pandas GroupBy.agg results to validate
preservation of level_dtypes in the empty result path.
🪄 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: 4c603a1b-d90f-489e-8298-b28c6b4e7ff5

📥 Commits

Reviewing files that changed from the base of the PR and between 738fb74 and 413f7b9.

📒 Files selected for processing (3)
  • python/cudf/cudf/core/groupby/groupby.py
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
  • python/cudf/cudf/tests/groupby/test_agg.py
💤 Files with no reviewable changes (1)
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py

Comment thread python/cudf/cudf/core/groupby/groupby.py Outdated
@galipremsagar galipremsagar added bug Something isn't working non-breaking Non-breaking change 3 - Ready for Review Ready for review by team labels Jul 21, 2026
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 413f7b9

Comment on lines +1337 to +1338
multiindex=True,
level_names=self.obj._data.level_names,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor nit: You could instead assign these arguments to a kwargs dict that defaults to empty so that you only need to write the ColumnAccessor call once along with the other arguments.

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.

Done in c37af8d — the two branches differed only in multiindex/level_dtypes, so those now live in a dict that defaults to empty and the ColumnAccessor construction is written once.

@galipremsagar galipremsagar added 5 - Ready to Merge Testing and reviews complete, ready to merge and removed 3 - Ready for Review Ready for review by team labels Jul 21, 2026
The MultiIndex-preserving and flat-relabel branches differed only in
multiindex/level_dtypes; collect those in a dict that defaults to empty
so the ColumnAccessor construction is written once.
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test dd39376

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 09b08da into NVIDIA:release/26.08 Jul 22, 2026
232 of 239 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 22, 2026
rapids-bot Bot pushed a commit that referenced this pull request Jul 23, 2026
Split out of #23255 (4/6). **Depends on #23365 (fidelity), the stack PR (#23370), and #23366 (groupby agg)** — 8 of the 29 un-xfailed pandas-tests need those fixes too, so this PR's pandas-tests job goes green once they merge.

- Order result rows/columns by the removed level's codes (level order preserved, missing keys first) instead of sorted values with nulls last, by encoding the integer code columns instead of the level values.
- Propagate the source frame's column-axis level names into the result instead of hardcoding `None`; also fixes the `ValueError: Length of names must match number of levels` crash when unstacking MultiIndex-column frames.
- Promote integer source columns to float64 when the reshape introduces missing cells (pandas' block semantics), gated on `mode.pandas_compatible`; `pivot_table`/`crosstab` opt out via a module-private `_unstack` parameter when `fill_value` fills the cells afterwards.
- Preserve unused categories of the removed level in the result's column levels (pandas GH 17845); also fixes a libcudf `Column sizes don't match` crash for indexes with unused categorical categories.
- Validate the level on flat-index frames (`KeyError`) and duplicated index names (`ValueError`) like pandas; `pivot` with `values=` drops the original columns-axis names.

Fixes 29 pandas-tests; their xfail entries are removed, three remaining `test_stack_unstack.py` entries get real failure reasons, and two classic categorical unstack params are un-xfailed. Attribution verified per node id against isolated builds: 21 pass with only this change, 4 need the stack PR, 2 need stack+fidelity, 2 need the groupby-agg PR.

Authors:
  - GALI PREM SAGAR (https://github.com/galipremsagar)

Approvers:
  - Matthew Roeschke (https://github.com/mroeschke)

URL: #23368
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

None yet

Development

Successfully merging this pull request may close these issues.

4 participants