Skip to content

Support diff() inside over() in cudf-polars - #23497

Merged
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
rjzamora:diff-over-support
Aug 3, 2026
Merged

Support diff() inside over() in cudf-polars#23497
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
rjzamora:diff-over-support

Conversation

@rjzamora

Copy link
Copy Markdown
Contributor

Description

Adds cudf-polars support for diff(...).over(...) in grouped window expressions.

This reuses the existing grouped shift path to respect group boundaries, then applies the subtraction needed for diff. Supports literal n with null_behavior="ignore" for both in-memory and streaming execution.

Partially addresses #19934

Checklist

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

@rjzamora rjzamora self-assigned this Jul 31, 2026
@rjzamora
rjzamora requested a review from a team as a code owner July 31, 2026 15:49
@rjzamora
rjzamora requested a review from Matt711 July 31, 2026 15:49
@rjzamora rjzamora added feature request New feature or request 2 - In Progress Currently a work in progress non-breaking Non-breaking change labels Jul 31, 2026
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels Jul 31, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 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: 7b2154ac-d643-44a8-990f-d52c4205619c

📥 Commits

Reviewing files that changed from the base of the PR and between e7ec0ff and 33b0774.

📒 Files selected for processing (2)
  • python/cudf_polars/tests/expressions/test_rolling.py
  • python/cudf_polars/tests/streaming/test_spmd.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/cudf_polars/tests/streaming/test_spmd.py
  • python/cudf_polars/tests/expressions/test_rolling.py

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for diff() in grouped and window expressions.
    • diff() now supports positive, negative, and larger offsets, including ordered streaming and distributed execution.
    • Results preserve null boundaries and correctly calculate differences from shifted values.
  • Bug Fixes

    • Improved compatibility between diff(), shift(), and shift_and_fill().
    • Added validation for unsupported nonliteral offsets and null-handling settings.

Walkthrough

Grouped window expressions now support diff with literal offsets and null_behavior="ignore". Execution computes differences from shifted values. Streaming detection and GPU, rolling, and SPMD tests cover ordered and unordered grouped windows.

Changes

Grouped window diff

Layer / File(s) Summary
Diff execution and validation
python/cudf_polars/cudf_polars/dsl/expressions/rolling.py, python/cudf_polars/cudf_polars/dsl/utils/aggregations.py, python/cudf_polars/tests/expressions/test_rolling.py
Grouped windows route diff through shift handling, validate literal offsets and null_behavior="ignore", and compute differences from shifted values. Tests cover offsets, ordering, unsupported arguments, and updated ordering annotations.
Streaming order detection
python/cudf_polars/cudf_polars/streaming/utils.py, python/cudf_polars/tests/streaming/test_rolling.py
Streaming classification treats diff as input-order-sensitive. Ordered grouped-window coverage includes diff.
SPMD diff coverage
python/cudf_polars/tests/streaming/test_spmd.py
SPMD tests cover ordered, cross-rank, and shared-ordering diff expressions with null boundary results and multiple offsets.

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

Possibly related PRs

Suggested reviewers: matt711, mroeschke

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 summarizes the main change: adding grouped window support for diff() in cudf-polars.
Description check ✅ Passed The description directly explains the diff(...).over(...) implementation, supported behavior, execution modes, and related issue.
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: 2

🤖 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_polars/cudf_polars/dsl/utils/aggregations.py`:
- Line 31: Update the affected union type annotations so None appears at the
end, using the existing symbols and preserving their current types:
python/cudf_polars/tests/expressions/test_rolling.py lines 306, 325, 346, 367,
398, and 438 require direct changes. The listed sites in
python/cudf_polars/cudf_polars/dsl/utils/aggregations.py lines 31-31,
python/cudf_polars/cudf_polars/dsl/expressions/rolling.py lines 377-377,
python/cudf_polars/tests/expressions/test_rolling.py lines 477-489,
python/cudf_polars/cudf_polars/streaming/utils.py lines 133-133,
python/cudf_polars/tests/streaming/test_rolling.py lines 66-84, and
python/cudf_polars/tests/streaming/test_spmd.py lines 503-503 require no direct
change unless their annotations contain the same RUF036 pattern; ensure Ruff
formatting passes afterward.

In `@python/cudf_polars/tests/streaming/test_spmd.py`:
- Line 597: Extend the shared-group SPMD test cases around the existing diff
expression to include literal offsets n=2 and n=-1 alongside the default diff()
case. Keep the expected-result labels aligned with each added expression so the
test validates state transfer across partition boundaries for both supported
non-default offsets.
🪄 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: 6aea5f7d-8f82-4cca-b5ed-4de588f1a546

📥 Commits

Reviewing files that changed from the base of the PR and between 9f78795 and e7ec0ff.

📒 Files selected for processing (6)
  • python/cudf_polars/cudf_polars/dsl/expressions/rolling.py
  • python/cudf_polars/cudf_polars/dsl/utils/aggregations.py
  • python/cudf_polars/cudf_polars/streaming/utils.py
  • python/cudf_polars/tests/expressions/test_rolling.py
  • python/cudf_polars/tests/streaming/test_rolling.py
  • python/cudf_polars/tests/streaming/test_spmd.py


_WINDOW_ONLY_UNARY_FUNCTIONS = frozenset(
{"rank", "fill_null_with_strategy", "cum_sum", "shift", "shift_and_fill"}
{"rank", "fill_null_with_strategy", "cum_sum", "diff", "shift", "shift_and_fill"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

ruff check \
  python/cudf_polars/cudf_polars/dsl/utils/aggregations.py \
  python/cudf_polars/cudf_polars/dsl/expressions/rolling.py \
  python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/cudf_polars/streaming/utils.py \
  python/cudf_polars/tests/streaming/test_rolling.py \
  python/cudf_polars/tests/streaming/test_spmd.py

ruff format --check \
  python/cudf_polars/cudf_polars/dsl/utils/aggregations.py \
  python/cudf_polars/cudf_polars/dsl/expressions/rolling.py \
  python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/cudf_polars/streaming/utils.py \
  python/cudf_polars/tests/streaming/test_rolling.py \
  python/cudf_polars/tests/streaming/test_spmd.py

Repository: rapidsai/cudf

Length of output: 6062


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- changed-file summary ---'
git diff --stat

printf '%s\n' '--- Ruff configuration ---'
rg -n --hidden --glob 'pyproject.toml' --glob 'ruff.toml' --glob '.ruff.toml' \
  '^\[tool\.ruff|select|extend-select|ignore|line-length|target-version' .

printf '%s\n' '--- cited locations ---'
for f in \
  python/cudf_polars/cudf_polars/dsl/utils/aggregations.py \
  python/cudf_polars/cudf_polars/dsl/expressions/rolling.py \
  python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/cudf_polars/streaming/utils.py \
  python/cudf_polars/tests/streaming/test_rolling.py \
  python/cudf_polars/tests/streaming/test_spmd.py
do
  echo "### $f"
  case "$f" in
    */aggregations.py) sed -n '24,36p' "$f" ;;
    */expressions/rolling.py) sed -n '370,382p' "$f" ;;
    */tests/expressions/test_rolling.py) sed -n '470,495p' "$f" ;;
    */streaming/utils.py) sed -n '126,138p' "$f" ;;
    */tests/streaming/test_rolling.py) sed -n '60,88p' "$f" ;;
    */tests/streaming/test_spmd.py) sed -n '498,508p' "$f" ;;
  esac
done

printf '%s\n' '--- Ruff check with independent status ---'
ruff check \
  python/cudf_polars/cudf_polars/dsl/utils/aggregations.py \
  python/cudf_polars/cudf_polars/dsl/expressions/rolling.py \
  python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/cudf_polars/streaming/utils.py \
  python/cudf_polars/tests/streaming/test_rolling.py \
  python/cudf_polars/tests/streaming/test_spmd.py
check_status=$?
echo "ruff check exit status: $check_status"

printf '%s\n' '--- Ruff format check with independent status ---'
ruff format --check \
  python/cudf_polars/cudf_polars/dsl/utils/aggregations.py \
  python/cudf_polars/cudf_polars/dsl/expressions/rolling.py \
  python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/cudf_polars/streaming/utils.py \
  python/cudf_polars/tests/streaming/test_rolling.py \
  python/cudf_polars/tests/streaming/test_spmd.py
format_status=$?
echo "ruff format --check exit status: $format_status"

exit 0

Repository: rapidsai/cudf

Length of output: 16136


Fix the six RUF036 errors in python/cudf_polars/tests/expressions/test_rolling.py at lines 306, 325, 346, 367, 398, and 438. Move None to the end of each union, such as list[str | pl.Expr] | None. Ruff formatting passes for all six files.

📍 Affects 6 files
  • python/cudf_polars/cudf_polars/dsl/utils/aggregations.py#L31-L31 (this comment)
  • python/cudf_polars/cudf_polars/dsl/expressions/rolling.py#L377-L377
  • python/cudf_polars/tests/expressions/test_rolling.py#L477-L489
  • python/cudf_polars/cudf_polars/streaming/utils.py#L133-L133
  • python/cudf_polars/tests/streaming/test_rolling.py#L66-L84
  • python/cudf_polars/tests/streaming/test_spmd.py#L503-L503
🤖 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_polars/cudf_polars/dsl/utils/aggregations.py` at line 31, Update
the affected union type annotations so None appears at the end, using the
existing symbols and preserving their current types:
python/cudf_polars/tests/expressions/test_rolling.py lines 306, 325, 346, 367,
398, and 438 require direct changes. The listed sites in
python/cudf_polars/cudf_polars/dsl/utils/aggregations.py lines 31-31,
python/cudf_polars/cudf_polars/dsl/expressions/rolling.py lines 377-377,
python/cudf_polars/tests/expressions/test_rolling.py lines 477-489,
python/cudf_polars/cudf_polars/streaming/utils.py lines 133-133,
python/cudf_polars/tests/streaming/test_rolling.py lines 66-84, and
python/cudf_polars/tests/streaming/test_spmd.py lines 503-503 require no direct
change unless their annotations contain the same RUF036 pattern; ensure Ruff
formatting passes afterward.

Source: Coding guidelines

Comment thread python/cudf_polars/tests/streaming/test_spmd.py
):
shift_expr = ne.value
assert isinstance(shift_expr, expr.UnaryFunction)
if shift_expr.name == "diff":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

comment: If we wanted to support polars.Expr.pct_change with over, would that implementation go here?

xref https://github.com/rapidsai/cudf/pull/23225/changes for the pylibcudf APIs used to implement pct_change

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.

Yes, exactly. I'd expect pct_change().over(...) to fit here, I think. Instead of value - shifted, we would just want (value / shifted) - 1 for "pct_change".

@rjzamora rjzamora added 5 - Ready to Merge Testing and reviews complete, ready to merge and removed 2 - In Progress Currently a work in progress labels Aug 3, 2026
@rjzamora

rjzamora commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 85746e8 into NVIDIA:main Aug 3, 2026
110 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Aug 3, 2026
@rjzamora
rjzamora deleted the diff-over-support branch August 3, 2026 19:43
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 cudf-polars Issues specific to cudf-polars feature request New feature or request 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