Skip to content

Preserve masked integer dtypes in GroupBy cumsum/cumprod - #23299

Merged
rapids-bot[bot] merged 11 commits into
NVIDIA:release/26.08from
galipremsagar:groupby-cumscan-masked-dtype
Jul 22, 2026
Merged

Preserve masked integer dtypes in GroupBy cumsum/cumprod#23299
rapids-bot[bot] merged 11 commits into
NVIDIA:release/26.08from
galipremsagar:groupby-cumscan-masked-dtype

Conversation

@galipremsagar

Copy link
Copy Markdown
Contributor

Description

libcudf's SUM/PRODUCT scans promote narrow integers to 64-bit. pandas does the same for numpy dtypes (int8 -> int64, pandas GH#37493) but preserves masked extension dtypes (Int16 stays Int16, pandas GH#58811), wrapping on overflow. Cast the scan result back to the original dtype for masked integer inputs only.

Fixes 8 pandas-tests (test_groupby_cumsum_mask[Int*/UInt*-True-3], test_nan_in_cumsum_group_label); their xfail entries are removed.

Checklist

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

@galipremsagar
galipremsagar requested a review from a team as a code owner July 16, 2026 18:47
@galipremsagar galipremsagar added bug Something isn't working Python Affects Python cuDF API. non-breaking Non-breaking change labels Jul 16, 2026
@galipremsagar
galipremsagar requested a review from vyasr July 16, 2026 18:47
@galipremsagar galipremsagar added the cudf.pandas Issues specific to cudf.pandas label Jul 16, 2026
@galipremsagar galipremsagar added bug Something isn't working Python Affects Python cuDF API. non-breaking Non-breaking change cudf.pandas Issues specific to cudf.pandas labels Jul 16, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jul 16, 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.

@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 16, 2026

@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_cummulative.py (2)

123-131: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover NumPy cumprod promotion too.

This regression test only exercises cumsum; parameterize it over both cumsum and cumprod so narrow NumPy inputs retain their expected 64-bit promotion for both scan operations.

Based on the PR objective, the cumulative dtype contract applies to both cumsum and cumprod.

Suggested parameterization
-def test_groupby_cumsum_numpy_dtype_promotes():
+@pytest.mark.parametrize("op", ["cumsum", "cumprod"])
+def test_groupby_cumscan_numpy_dtype_promotes(op):
...
-    expected = pdf.groupby("a").cumsum()
-    result = gdf.groupby("a").cumsum()
+    expected = getattr(pdf.groupby("a"), op)()
+    result = getattr(gdf.groupby("a"), op)()
🤖 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_cummulative.py` around lines 123 - 131,
Parameterize test_groupby_cumsum_numpy_dtype_promotes over both the cumsum and
cumprod groupby operations, invoking the selected operation on pandas and cuDF
inputs. Preserve the narrow int8 fixture and assert each result against pandas
so both scans verify expected 64-bit dtype promotion.

110-122: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Exercise masked-integer overflow wrapping.

The current values never overflow Int16, so this test would still pass if the final cast failed to wrap overflowing cumulative results. Add a case such as two 20_000 values and assert against pandas for both operators.

Based on the PR objective, overflow wrapping is part of the behavior this change is intended to guarantee.

Suggested test adjustment
-    pdf = pd.DataFrame({"a": [1, 1, 2], "b": [1, pd.NA, 2]}, dtype="Int16")
+    pdf = pd.DataFrame(
+        {"a": [1, 1, 2], "b": [20_000, 20_000, pd.NA]},
+        dtype="Int16",
+    )
🤖 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_cummulative.py` around lines 110 - 122,
Extend test_groupby_cumscan_masked_dtype_preserved with masked Int16 input
containing values such as two 20,000 entries so cumulative cumsum and cumprod
results exceed Int16 range. Keep parametrization for both operators and continue
comparing the cudf result with pandas via assert_eq, thereby validating wrapped
overflow behavior.
🤖 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/tests/groupby/test_cummulative.py`:
- Around line 110-131: Add a unit benchmark alongside
test_groupby_cumscan_masked_dtype_preserved covering grouped nullable-integer
cumsum and cumprod, with representative group sizes and overflow-inducing
values, so the post-scan casting path is measured for both operations.

---

Nitpick comments:
In `@python/cudf/cudf/tests/groupby/test_cummulative.py`:
- Around line 123-131: Parameterize test_groupby_cumsum_numpy_dtype_promotes
over both the cumsum and cumprod groupby operations, invoking the selected
operation on pandas and cuDF inputs. Preserve the narrow int8 fixture and assert
each result against pandas so both scans verify expected 64-bit dtype promotion.
- Around line 110-122: Extend test_groupby_cumscan_masked_dtype_preserved with
masked Int16 input containing values such as two 20,000 entries so cumulative
cumsum and cumprod results exceed Int16 range. Keep parametrization for both
operators and continue comparing the cudf result with pandas via assert_eq,
thereby validating wrapped overflow behavior.
🪄 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: da0fd433-9586-4fcc-b6eb-915aaf6bd00b

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9fec8 and 64ce353.

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

Comment on lines +110 to +131
@pytest.mark.parametrize("op", ["cumsum", "cumprod"])
def test_groupby_cumscan_masked_dtype_preserved(op):
# pandas preserves masked extension dtypes for groupby cum-scans
# (Int16 stays Int16, GH#58811) while numpy ints promote to 64-bit
pdf = pd.DataFrame({"a": [1, 1, 2], "b": [1, pd.NA, 2]}, dtype="Int16")
gdf = cudf.DataFrame(pdf)

expected = getattr(pdf.groupby("a")["b"], op)()
result = getattr(gdf.groupby("a")["b"], op)()

assert_eq(expected, result)


def test_groupby_cumsum_numpy_dtype_promotes():
# numpy int8 promotes to int64 (pandas GH#37493)
pdf = pd.DataFrame({"a": [1, 1], "b": [111, 111]}, dtype="int8")
gdf = cudf.DataFrame(pdf)

expected = pdf.groupby("a").cumsum()
result = gdf.groupby("a").cumsum()

assert_eq(expected, result)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Add a unit benchmark for the new casting path.

The changed files add correctness tests but no benchmark for this feature/bug fix. Add a benchmark covering nullable-integer cumsum/cumprod, ideally including representative group sizes and overflow cases, to track the cost of the post-scan cast.

As per coding guidelines, “Add unit tests and unit benchmarks for feature and bug-fix contributions.”

🤖 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_cummulative.py` around lines 110 - 131,
Add a unit benchmark alongside test_groupby_cumscan_masked_dtype_preserved
covering grouped nullable-integer cumsum and cumprod, with representative group
sizes and overflow-inducing values, so the post-scan casting path is measured
for both operations.

Source: Coding guidelines

libcudf's SUM/PRODUCT scans promote narrow integers to 64-bit. pandas
does the same for numpy dtypes (int8 -> int64, GH#37493) but preserves
masked extension dtypes (Int16 stays Int16, GH#58811), wrapping on
overflow. Cast the scan result back to the original dtype for masked
integer inputs only.
@galipremsagar
galipremsagar force-pushed the groupby-cumscan-masked-dtype branch from 64ce353 to 455b357 Compare July 17, 2026 01:40
@galipremsagar
galipremsagar changed the base branch from main to release/26.08 July 17, 2026 01:40
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Groupby cumulative scans now preserve pandas nullable integer dtypes, with tests covering masked dtypes and NumPy integer promotion. Related pandas testing known-failure mappings are updated.

Changes

Groupby cumulative dtype handling

Layer / File(s) Summary
Cumulative dtype preservation and validation
python/cudf/cudf/core/groupby/groupby.py, python/cudf/cudf/tests/groupby/test_cummulative.py
Nullable integer extension dtypes are preserved for groupby cumsum and cumprod; tests cover masked dtypes and NumPy integer promotion.
Pandas testing metadata updates
python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Known-failure entries are updated for groupby cumulative and null-group reducer cases.

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

Possibly related PRs

Suggested reviewers: brandon-b-miller, vyasr, mroeschke

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: preserving masked integer dtypes for GroupBy cumsum/cumprod.
Description check ✅ Passed The description matches the implemented dtype-casting behavior and the test updates described in the changeset.
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 (1)
python/cudf/cudf/tests/groupby/test_cummulative.py (1)

110-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add edge case coverage for overflow wrapping and unsigned masked dtypes.

The masked dtype test uses values [1, pd.NA, 2] which don't overflow Int16, so the overflow-wrapping behavior described in the groupby.py comment (line 1242) is not exercised. Additionally, the code checks for kind "u" (unsigned) but no test covers UInt8/UInt16 etc. Consider adding:

  • A test with values that overflow the masked dtype (e.g., Int8 with values near 127) to verify wrapping matches pandas.
  • A UInt dtype case to cover the "u" branch.

As per coding guidelines, "Ensure test files provide comprehensive edge case coverage (empty, all-null, single-element, mixed types)."

🧪 Suggested additional test cases
 def test_groupby_cumscan_masked_dtype_preserved(op):
     # pandas preserves masked extension dtypes for groupby cum-scans
     # (Int16 stays Int16, GH#58811) while numpy ints promote to 64-bit
     pdf = pd.DataFrame({"a": [1, 1, 2], "b": [1, pd.NA, 2]}, dtype="Int16")
     gdf = cudf.DataFrame(pdf)

     expected = getattr(pdf.groupby("a")["b"], op)()
     result = getattr(gdf.groupby("a")["b"], op)()

     assert_eq(expected, result)


+@pytest.mark.parametrize("op", ["cumsum", "cumprod"])
+@pytest.mark.parametrize("dtype", ["Int8", "UInt8"])
+def test_groupby_cumscan_masked_dtype_overflow_wrapping(op, dtype):
+    # Verify overflow wrapping matches pandas for masked integer dtypes
+    info = np.iinfo(dtype.lower())
+    vals = [info.max, info.max, 1]
+    pdf = pd.DataFrame({"a": [1, 1, 2], "b": vals}, dtype=dtype)
+    gdf = cudf.DataFrame(pdf)
+    expected = getattr(pdf.groupby("a")["b"], op)()
+    result = getattr(gdf.groupby("a")["b"], op)()
+    assert_eq(expected, result)
🤖 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_cummulative.py` around lines 110 - 120,
Add edge-case coverage to test_groupby_cumscan_masked_dtype_preserved: include
masked signed-integer inputs near the dtype limit that overflow during cumsum or
cumprod, and verify pandas and cuDF preserve matching wrapping behavior. Add a
parameterized unsigned masked dtype case such as UInt8 or UInt16 to exercise the
groupby.py unsigned-kind branch, while retaining the existing Int16 coverage.

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.

Nitpick comments:
In `@python/cudf/cudf/tests/groupby/test_cummulative.py`:
- Around line 110-120: Add edge-case coverage to
test_groupby_cumscan_masked_dtype_preserved: include masked signed-integer
inputs near the dtype limit that overflow during cumsum or cumprod, and verify
pandas and cuDF preserve matching wrapping behavior. Add a parameterized
unsigned masked dtype case such as UInt8 or UInt16 to exercise the groupby.py
unsigned-kind branch, while retaining the existing Int16 coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6f06be42-47f0-4996-9d50-23481ed2efd4

📥 Commits

Reviewing files that changed from the base of the PR and between 3c56d8d and 455b357.

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

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 5e61811

@galipremsagar
galipremsagar requested a review from mroeschke July 17, 2026 13:51
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test e188265

Comment thread python/cudf/cudf/core/groupby/groupby.py Outdated
galipremsagar and others added 2 commits July 20, 2026 18:32
Co-authored-by: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com>
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 7a2c200

@galipremsagar galipremsagar added the 5 - Ready to Merge Testing and reviews complete, ready to merge label Jul 20, 2026
…mscan-masked-dtype

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

Copy link
Copy Markdown
Contributor Author

/ok to test 001b0ca

…mscan-masked-dtype

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

Copy link
Copy Markdown
Contributor Author

/okay to test 4f426ad

…mscan-masked-dtype

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

Copy link
Copy Markdown
Contributor Author

/okay to test f49e2e6

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test b45e108

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 8fc070f

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 908b36d into NVIDIA:release/26.08 Jul 22, 2026
124 of 126 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 22, 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