Skip to content

Fix groupby size dtype for masked series and apply group order for sort=False - #23260

Merged
rapids-bot[bot] merged 3 commits into
NVIDIA:mainfrom
galipremsagar:groupby-size-fixes
Jul 15, 2026
Merged

Fix groupby size dtype for masked series and apply group order for sort=False#23260
rapids-bot[bot] merged 3 commits into
NVIDIA:mainfrom
galipremsagar:groupby-size-fixes

Conversation

@galipremsagar

Copy link
Copy Markdown
Contributor

Description

Running pandas' own test suite under cudf.pandas, tests/groupby/methods/test_size.py had 6 failing tests. This PR fixes them (22/22 pass) and removes the corresponding xfail entries from the pandas-testing plugin. Two independent root causes:

SeriesGroupBy.size() dtype for masked series

pandas returns Int64 for size() on masked (Int*/UInt*/Float*/boolean) series (pandas GH#54132). Verified matrix on pandas 3.0.3:

.size() on result dtype
masked series (Int64, Float64, boolean, ...) Int64
string[pyarrow] series Int64
ArrowDtype series int64[pyarrow]
object / str / string[python] series int64
any DataFrameGroupBy int64

cuDF already special-cased the Arrow and string[pyarrow] rows; this adds the missing masked branch (Series-only, StringDtype excluded so string[python] stays int64).

GroupBy.apply group order with sort=False

pandas processes groups in order of first appearance when sort=False. libcudf returns groups sorted by key, and apply consumed that layout directly, so results came out key-sorted. (size() was already correct — the failing tests compare size() against apply(lambda a: a.shape[0]), and the apply side was the broken one.)

The grouped layout (group names, offsets, grouped keys and values) is now permuted into first-appearance order before either engine runs — the same device-side first-position/argsort technique GroupBy.__iter__ already uses — so every result shape (scalar-per-group, frame-per-group, transforms) sees pandas' iteration order.

Validation

  • pandas-tests tests/groupby/methods/test_size.py: 6 failed → 22/22 pass.
  • pandas-tests tests/groupby/test_apply.py + the full tests/groupby/methods/ directory: remaining failures are exactly the pre-existing known-failure entries (plus one pre-existing local-only test_nth failure that reproduces identically on main and is fixed by Rewrite GroupBy.nth as a pandas-compatible positional row filter #23257).
  • cuDF classic groupby suite passes; new classic regression tests pin both behaviors.

Checklist

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

…rt=False

Fixes the 6 failing tests in pandas' tests/groupby/methods/test_size.py
under cudf.pandas (22/22 pass) and removes their plugin entries.

* SeriesGroupBy.size on masked (Int*/UInt*/Float*/boolean) dtypes now
  returns Int64 like pandas (GH#54132), joining the existing
  string[pyarrow] -> Int64 and ArrowDtype -> int64[pyarrow] special
  cases. DataFrameGroupBy.size stays int64, also like pandas.

* GroupBy.apply with sort=False now processes groups in order of first
  appearance: libcudf returns groups sorted by key, so the grouped
  layout (group names, offsets, keys and values) is permuted to
  appearance order before either engine runs, mirroring pandas'
  iteration order for every result shape.
@galipremsagar
galipremsagar requested a review from a team as a code owner July 14, 2026 14:47
@copy-pr-bot

copy-pr-bot Bot commented Jul 14, 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 14, 2026
@galipremsagar galipremsagar added bug Something isn't working non-breaking Non-breaking change labels Jul 14, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved GroupBy.size() results for nullable Series dtypes to match pandas, including correct nullable integer output.
    • Corrected GroupBy.apply(sort=False) ordering to preserve groups in their order of first appearance.
  • Tests

    • Added coverage for nullable dtype sizing and groupby apply ordering.
    • Updated expected test outcomes for groupby ranking scenarios.

Walkthrough

Changes

GroupBy behavior alignment

Layer / File(s) Summary
Nullable size dtype handling
python/cudf/cudf/core/groupby/groupby.py, python/cudf/cudf/tests/groupby/test_size.py, python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
GroupBy.size() now handles additional nullable extension dtypes, with tests for Int64, Float64, and boolean Series. Expected-failure mappings are updated.
Unsorted apply ordering
python/cudf/cudf/core/groupby/groupby.py, python/cudf/cudf/tests/groupby/test_size.py
GroupBy.apply(sort=False) reorders groups by first appearance and is tested against pandas output.

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

Suggested reviewers: davidwendt

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the two main groupby fixes in the changeset.
Description check ✅ Passed The description is directly related to the changes and explains the same two fixes and validation.
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.

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

🧹 Nitpick comments (2)
python/cudf/cudf/core/groupby/groupby.py (1)

2494-2521: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate first-appearance-order computation; consider extracting a shared helper.

This block re-implements the exact same "gather earliest row position per group, then argsort" logic already present, unchanged, in __iter__ (lines 617-624). Extracting a small helper (e.g. _first_appearance_group_order()) would avoid two independent copies of this logic drifting apart over time.

Separately, row_order is built via a Python-level list comprehension over group_order plus np.concatenate, whereas the very similar permutation in _head_tail (lines 1635-1641) is fully vectorized with np.repeat/np.cumsum and avoids a per-group Python loop. Since this path is only exercised when sort=False and there is more than one group, and apply() already warns above _MAX_GROUPS_BEFORE_WARN groups for the iterative engine, the practical impact is limited, but the vectorized form would be more consistent with the rest of the file.

♻️ Sketch of vectorized row_order construction (mirrors `_head_tail`)
             first_pos = positions.take(as_column(pos_offsets[:-1]))
             group_order = first_pos.argsort().to_numpy()
             sizes = np.diff(np.asarray(offsets, dtype=SIZE_TYPE_DTYPE))
-            row_order = as_column(
-                np.concatenate(
-                    [
-                        np.arange(
-                            offsets[i], offsets[i + 1], dtype=SIZE_TYPE_DTYPE
-                        )
-                        for i in group_order
-                    ]
-                )
-            )
+            starts = np.asarray(offsets, dtype=SIZE_TYPE_DTYPE)[group_order]
+            ordered_sizes = sizes[group_order]
+            row_order_arr = np.arange(ordered_sizes.sum(), dtype=SIZE_TYPE_DTYPE)
+            fixup = np.empty_like(ordered_sizes)
+            fixup[0] = 0
+            np.cumsum(ordered_sizes[:-1], out=fixup[1:])
+            row_order_arr += np.repeat(starts - fixup, ordered_sizes)
+            row_order = as_column(row_order_arr)
🤖 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/groupby/groupby.py` around lines 2494 - 2521, Extract
the shared first-appearance ordering logic from __iter__ and this sort=False
branch into a helper such as _first_appearance_group_order(), then reuse it in
both call sites. In the same branch, replace the Python
list-comprehension/np.concatenate construction of row_order with the vectorized
np.repeat/np.cumsum approach used by _head_tail, preserving the existing group
and row permutation results.
python/cudf/cudf/tests/groupby/test_size.py (1)

24-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct, but consider adding a null-containing case.

The test only uses non-null values ([1, 1, 1]), so it doesn't exercise the masked dtype's actual nulls. Since the fix targets nullable/masked dtypes, a variant with pd.NA/None in the data (and possibly an empty or single-element group) would strengthen coverage of the edge cases this change is meant to support.

As per path instructions, python/**/test_*.py: "Ensure test files provide comprehensive edge case coverage (empty, all-null, single-element, mixed types) and do not depend on external datasets."

🤖 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_size.py` around lines 24 - 35, Add
null-containing coverage to test_size_series_masked_dtype using pd.NA or None in
the nullable input, while retaining validation of the Int64 result dtype and
groupby equality. Include relevant edge cases such as an all-null or
single-element group without relying on external data.

Source: Path instructions

🤖 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.

Nitpick comments:
In `@python/cudf/cudf/core/groupby/groupby.py`:
- Around line 2494-2521: Extract the shared first-appearance ordering logic from
__iter__ and this sort=False branch into a helper such as
_first_appearance_group_order(), then reuse it in both call sites. In the same
branch, replace the Python list-comprehension/np.concatenate construction of
row_order with the vectorized np.repeat/np.cumsum approach used by _head_tail,
preserving the existing group and row permutation results.

In `@python/cudf/cudf/tests/groupby/test_size.py`:
- Around line 24-35: Add null-containing coverage to
test_size_series_masked_dtype using pd.NA or None in the nullable input, while
retaining validation of the Int64 result dtype and groupby equality. Include
relevant edge cases such as an all-null or single-element group without relying
on external data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4f26d38f-8bac-4414-849c-1cb83d825e18

📥 Commits

Reviewing files that changed from the base of the PR and between 595810c and b1259c6.

📒 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_size.py
💤 Files with no reviewable changes (1)
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py

@galipremsagar galipremsagar added the 3 - Ready for Review Ready for review by team label Jul 14, 2026
@galipremsagar
galipremsagar requested a review from mroeschke July 14, 2026 18:46
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test defe55a

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 9eaba7c

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 783e214 into NVIDIA:main Jul 15, 2026
237 of 239 checks passed
@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 15, 2026
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 15, 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

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants