Skip to content

Support aggregate rolling expressions inside grouped over - #23627

Open
rjzamora wants to merge 8 commits into
NVIDIA:mainfrom
rjzamora:agg-rolling-over
Open

Support aggregate rolling expressions inside grouped over#23627
rjzamora wants to merge 8 commits into
NVIDIA:mainfrom
rjzamora:agg-rolling-over

Conversation

@rjzamora

@rjzamora rjzamora commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Adds cudf-polars support for aggregate range-rolling expressions inside grouped over(...), including common forms like sum, min, max, mean, count, and len.

Closes #23623.
Partially addresses #23606.

@rjzamora
rjzamora requested a review from a team as a code owner August 12, 2026 16:25
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels Aug 12, 2026
@rjzamora rjzamora added feature request New feature or request 2 - In Progress Currently a work in progress non-breaking Non-breaking change labels Aug 12, 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.

🧹 Nitpick comments (2)
python/cudf_polars/tests/expressions/test_rolling.py (1)

208-208: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test restoration to the original input-row order.

These fixtures sort rows by group and rolling index. This does not exercise the required backward remap after grouped rolling evaluation. Use interleaved groups while keeping each group's index ordered, such as A0, B0, A1, B2, A3, B3. Keep check_row_order=True for both execution paths.

  • python/cudf_polars/tests/expressions/test_rolling.py#L208-L208: use interleaved ric rows and enable check_row_order=True.
  • python/cudf_polars/tests/expressions/test_rolling.py#L241-L241: use interleaved g rows and enable check_row_order=True.
  • python/cudf_polars/tests/streaming/test_rolling.py#L127-L127: use interleaved ric rows instead of globally sorting by ric.
  • python/cudf_polars/tests/streaming/test_rolling.py#L163-L163: use interleaved g rows instead of globally sorting by g.
🤖 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/tests/expressions/test_rolling.py` at line 208, Restore
interleaved input-row ordering to exercise backward remapping after grouped
rolling evaluation: in python/cudf_polars/tests/expressions/test_rolling.py
lines 208-208 and 241-241, use interleaved ric/g rows with each group’s index
ordered and enable check_row_order=True for both execution paths; in
python/cudf_polars/tests/streaming/test_rolling.py lines 127-127 and 163-163,
use interleaved ric/g rows instead of globally sorting by the group column.
python/cudf_polars/cudf_polars/dsl/expressions/rolling.py (1)

775-874: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider caching the resolved rolling index name.

_rolling_orderby_name runs at Line 785 and again at Line 1488 for the same named_exprs. You can store the name on RollingWindowOp at dispatch time and read it here. This removes the duplicate validation pass and keeps one source of truth for the index column.

🤖 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/expressions/rolling.py` around lines 775 -
874, Cache the resolved rolling index name on RollingWindowOp during dispatch,
where _rolling_orderby_name is already evaluated for the same named_exprs.
Update the _apply_unary_op handler for RollingWindowOp to read that cached name
instead of calling _rolling_orderby_name again, keeping validation and
index-column selection centralized.
🤖 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_polars/cudf_polars/dsl/expressions/rolling.py`:
- Around line 775-874: Cache the resolved rolling index name on RollingWindowOp
during dispatch, where _rolling_orderby_name is already evaluated for the same
named_exprs. Update the _apply_unary_op handler for RollingWindowOp to read that
cached name instead of calling _rolling_orderby_name again, keeping validation
and index-column selection centralized.

In `@python/cudf_polars/tests/expressions/test_rolling.py`:
- Line 208: Restore interleaved input-row ordering to exercise backward
remapping after grouped rolling evaluation: in
python/cudf_polars/tests/expressions/test_rolling.py lines 208-208 and 241-241,
use interleaved ric/g rows with each group’s index ordered and enable
check_row_order=True for both execution paths; in
python/cudf_polars/tests/streaming/test_rolling.py lines 127-127 and 163-163,
use interleaved ric/g rows instead of globally sorting by the group column.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 73728fb8-69f4-482b-a0e8-722b8031ffc0

📥 Commits

Reviewing files that changed from the base of the PR and between 481e42a and 55180fe.

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

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for grouped, time-based rolling-window calculations.
    • Added rolling aggregations for sum, minimum, maximum, mean, count, and length.
    • Preserved row order and data types in grouped rolling results.
    • Added support for rolling windows within grouped expressions.
  • Bug Fixes

    • Improved compatibility between rolling windows and cumulative, shift, and fill operations.
  • Tests

    • Added coverage for grouped and streaming rolling calculations using time-based and integer windows.

Walkthrough

The change adds grouped range-based rolling aggregates in cuDF-Polars. It introduces ordered input gathering, shared unary-operation execution, dependency handling, aggregation decomposition, and expression and streaming test coverage.

Changes

Grouped rolling support

Layer / File(s) Summary
Ordered rolling execution
python/cudf_polars/cudf_polars/dsl/expressions/rolling.py
Rolling requests resolve source columns and support optional row-order gathering. Cumulative sum, shift, fixed-size rolling, and related unary operations use shared ordering logic.
Grouped range rolling
python/cudf_polars/cudf_polars/dsl/expressions/rolling.py
Grouped RollingWindow expressions validate a single index column, evaluate compatible range windows, preserve result types, and restore output order.
Dependency and aggregation integration
python/cudf_polars/cudf_polars/dsl/translate.py, python/cudf_polars/cudf_polars/dsl/utils/aggregations.py
Grouped-window dependency collection and aggregation decomposition now include range-rolling inputs, order-by columns, and related window expressions.
Grouped rolling validation
python/cudf_polars/tests/expressions/test_rolling.py, python/cudf_polars/tests/streaming/test_rolling.py
Tests cover grouped time-based rolling sums and grouped sum, min, max, mean, count, and len expressions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: matt711, wence-, mroeschke

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% 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 support for aggregate rolling expressions inside grouped over operations.
Description check ✅ Passed The description directly describes grouped range-rolling support and identifies the supported aggregate functions.
Linked Issues check ✅ Passed The implementation supports composable grouped range-rolling aggregates, including sum, min, max, mean, count, and len, as required by issue #23623.
Out of Scope Changes check ✅ Passed The changes are limited to grouped rolling implementation, dependency handling, aggregation decomposition, and related tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 3

🧹 Nitpick comments (2)
python/cudf_polars/tests/expressions/test_rolling.py (1)

233-250: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the new branches.

These two tests cover the happy path only. The new grouped range-rolling code adds branches that no test exercises:

  • The null-index RuntimeError in the RollingWindowOp handler.
  • The NotImplementedError from _rolling_orderby_name when two rolling expressions use different index columns.
  • The INT64 cast for a non-Int64 integral index. The ts column here is Int64, so the cast is skipped.

Add cases with a nullable index column, with two different index columns in one select, and with an Int32/UInt32 index.

🤖 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/tests/expressions/test_rolling.py` around lines 233 - 250,
Extend test_rolling_common_aggs_over coverage to exercise the new
RollingWindowOp error branches and index casting: add a nullable index case that
expects the null-index RuntimeError, a select containing rolling expressions
ordered by different index columns that expects _rolling_orderby_name’s
NotImplementedError, and an equivalent case using Int32 or UInt32 index data to
verify the INT64 cast path.
python/cudf_polars/tests/streaming/test_rolling.py (1)

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

Extract the repeated skip condition.

The same skipif condition and reason appear twice. python/cudf_polars/tests/expressions/test_rolling.py already uses a named marker constant for the identical condition. Define one constant in this module and reuse it.

♻️ Proposed refactor
+skip_rolling_expr_136_to_138 = pytest.mark.skipif(
+    not POLARS_VERSION_LT_136 and POLARS_VERSION_LT_139,
+    reason="Rolling window expressions are not accessible in polars 1.36-1.38",
+)
+
+
-@pytest.mark.skipif(
-    not POLARS_VERSION_LT_136 and POLARS_VERSION_LT_139,
-    reason="Rolling window expressions are not accessible in polars 1.36-1.38",
-)
+@skip_rolling_expr_136_to_138
 def test_rolling_sum_over(engine):

Apply the same replacement above test_rolling_common_aggs_over.

Also applies to: 152-155

🤖 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/tests/streaming/test_rolling.py` around lines 105 - 108,
Define a named skip marker constant in the rolling test module for the existing
POLARS_VERSION condition and reason, matching the established constant used by
the expressions rolling tests. Replace both repeated skipif decorators,
including the one above test_rolling_common_aggs_over, with that shared
constant.
🤖 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/expressions/rolling.py`:
- Around line 1487-1500: Update the range-rolling branch in the surrounding
window-expression implementation to detect when a range-rolling expression is
combined with an over-clause order_by, using the existing order-direction and
null-ordering fields (over_ob_desc and over_ob_nulls_last). Raise
NotImplementedError for that combination unless _apply_ordered_unary_op is
updated to honor those ordering semantics; preserve the current rolling-index
ordering for cases without over(order_by=...).

In `@python/cudf_polars/cudf_polars/dsl/translate.py`:
- Around line 1219-1244: Update the dependency collection loop for named
aggregations to handle FixedSizeRollingWindow values explicitly. Add
v.children[0] to child_deps so FixedSizeRollingOp receives its input column
after projection, while preserving the existing RollingWindow and aggregation
handling.

In `@python/cudf_polars/cudf_polars/dsl/utils/aggregations.py`:
- Around line 166-177: Extend the nested-window guard in the RollingWindow
translation branch to also detect an expr.RollingWindow within agg.children[0],
alongside _contains_window_only_unary and _contains_fixed_size_rolling_window.
Raise NotImplementedError during translation so nested range rolling follows the
existing unsupported-operation fallback instead of reaching
RollingWindow.do_evaluate.

---

Nitpick comments:
In `@python/cudf_polars/tests/expressions/test_rolling.py`:
- Around line 233-250: Extend test_rolling_common_aggs_over coverage to exercise
the new RollingWindowOp error branches and index casting: add a nullable index
case that expects the null-index RuntimeError, a select containing rolling
expressions ordered by different index columns that expects
_rolling_orderby_name’s NotImplementedError, and an equivalent case using Int32
or UInt32 index data to verify the INT64 cast path.

In `@python/cudf_polars/tests/streaming/test_rolling.py`:
- Around line 105-108: Define a named skip marker constant in the rolling test
module for the existing POLARS_VERSION condition and reason, matching the
established constant used by the expressions rolling tests. Replace both
repeated skipif decorators, including the one above
test_rolling_common_aggs_over, with that shared constant.
🪄 Autofix

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: Pro Plus

Run ID: 7a52a88e-8ead-4d01-8f1e-7d994923a59a

📥 Commits

Reviewing files that changed from the base of the PR and between 40ba83d and bedda39.

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

Comment on lines +1487 to 1500
if rolling_named := unary_window_ops["range_rolling"]:
orderby_name = self._rolling_orderby_name(rolling_named)
rolling_order_by_col = df.column_map[orderby_name]
broadcasted_cols.extend(
self._reorder_to_input(
row_id,
self._apply_ordered_unary_op(
RollingWindowOp(named_exprs=rolling_named),
df,
grouper,
by_cols,
df.num_rows,
tables,
names,
dtypes,
order_index=order_index,
stream=df.stream,
row_id,
order_by_col=rolling_order_by_col,
require_sorted_groups=True,
)
)

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

🧩 Analysis chain

🌐 Web query:

polars over order_by combined with rolling expression semantics

💡 Result:

In Polars, over and rolling represent two distinct paradigms for windowing operations, and they interact with order_by differently [1][2][3]. The over expression is used for window functions that operate over defined groups, where the order_by argument allows you to impose a specific sequence within those groups before the calculation is performed [1]. This is essential for operations that are sensitive to row order, such as cum_sum, cum_min, cum_max, or shift [1][4]. When using over(..., order_by=...), Polars conceptually sorts the data within each partition according to the specified columns, applies the expression, and maps the result back to the original row positions [1]. In contrast, the rolling expression is specialized for time-series or index-based interval calculations [3]. It is explicitly designed for window operations where the window size is defined by a period (e.g., "3d", "5h") or an integer range based on an index_column [3]. Key distinctions and interaction points include: 1. Order Sensitivity: over relies on order_by to handle sequence-dependent operations [1]. rolling requires the provided index_column to be pre-sorted in ascending order [3]. 2. Semantics: over is typically used for cumulative, ranking, or shift operations across fixed groups [1][5]. rolling is used to aggregate over a moving temporal or integer-based window [3]. 3. Combining Logic: Because rolling and over serve different structural purposes, they are not typically nested as "a rolling expression inside an over". If you need to perform a rolling aggregation within groups, you would generally use group_by in combination with rolling or standard windowing logic, or rely on over for functions that support cumulative definitions [1][3]. 4. SQL Compatibility: Recent updates have improved Polars' over clause to better align with SQL-style windowing semantics (e.g., handling of NULLs in cumulative operations) when order_by is specified, making over behave more like standard SQL OVER (ORDER BY ...) clauses [6][4]. In summary, use over with order_by when you need to define an explicit row sequence for group-local operations like cumulative sums [1], and use rolling when you need to compute aggregations based on temporal or continuous index intervals [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

file="python/cudf_polars/cudf_polars/dsl/expressions/rolling.py"

printf '%s\n' '--- target implementation ---'
sed -n '1420,1525p' "$file"

printf '%s\n' '--- order_by and range_rolling references ---'
rg -n -C 5 '_order_by_expr|over_ob_desc|over_ob_nulls_last|range_rolling|_rolling_orderby_name|require_sorted_groups' \
  python/cudf_polars/cudf_polars python/cudf_polars/tests tests 2>/dev/null || true

printf '%s\n' '--- rolling-related tests and fixtures ---'
git ls-files | rg '(^|/)(test|tests|python/cudf_polars).*rolling|rolling.*test|window' | head -200

Repository: NVIDIA/cudf

Length of output: 31207


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- Python rolling tests involving over/order_by ---'
rg -n -C 8 'rolling|over\(' python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/tests/test_rolling.py \
  python/cudf_polars/tests/test_window_functions.py \
  python/cudf_polars/tests/streaming/test_rolling.py

printf '%s\n' '--- RollingWindow and decomposition definitions ---'
sed -n '180,255p' python/cudf_polars/cudf_polars/dsl/expressions/rolling.py
sed -n '330,430p' python/cudf_polars/cudf_polars/dsl/expressions/rolling.py
sed -n '920,980p' python/cudf_polars/cudf_polars/dsl/expressions/rolling.py
sed -n '1210,1430p' python/cudf_polars/cudf_polars/dsl/expressions/rolling.py

printf '%s\n' '--- Polars version and local compatibility references ---'
rg -n 'polars|required.*polars|POLARS' pyproject.toml python/cudf_polars -g '*.toml' -g '*.py' | head -200

Repository: NVIDIA/cudf

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -eu

file="python/cudf_polars/cudf_polars/dsl/expressions/rolling.py"

printf '%s\n' '--- RollingWindow implementation ---'
sed -n '180,255p' "$file"

printf '%s\n' '--- Window expression constructor ---'
sed -n '330,430p' "$file"

printf '%s\n' '--- Rolling decomposition and order_by handling ---'
sed -n '920,980p' "$file"
sed -n '1210,1345p' "$file"
sed -n '1385,1420p' "$file"

printf '%s\n' '--- Exact rolling-over tests after the first test block ---'
sed -n '185,255p' python/cudf_polars/tests/expressions/test_rolling.py
rg -n -C 4 'order_by|order_by=' python/cudf_polars/tests/expressions/test_rolling.py \
  python/cudf_polars/tests/test_window_functions.py

Repository: NVIDIA/cudf

Length of output: 36867


🌐 Web query:

Polars Expr rolling over order_by "order_by" "rolling" over window expression

💡 Result:

In Polars, expressions for rolling windows and window operations like over() handle ordering and partitioning differently. As of August 2026, there is no direct way to pass an order_by argument to Expr.rolling() because rolling() is intended for specific index-based or temporal windowing rather than general window aggregation [1][2]. 1. Understanding Expr.rolling() The Expr.rolling() method is used for temporal or index-based rolling windows (e.g., sum, mean) [1]. It requires an index_column that must be pre-sorted [1][3]. You cannot provide an order_by argument to Expr.rolling() to change the internal sort order; the data must be sorted correctly before or during the rolling operation [1][3]. 2. Understanding Expr.over() The Expr.over() method is a true window function used for partitioning and ordering [4]. It fully supports partition_by and order_by arguments, which are used to control the sorting of rows within each partition before the expression is evaluated [4]. This is the correct tool for operations that require sorting, such as cum_sum, diff, or shift [4][5]. 3. Combining Operations If you need to perform rolling-style aggregations on grouped, ordered data, you have two primary approaches: - Pre-sort the DataFrame: Sort your DataFrame by the desired ordering columns using df.sort() before applying rolling() [1][3]. If you have groups, you can use group_by_dynamic() or DataFrame.rolling() (with the group_by parameter) to handle partitions [3]. - Use rolling_*_by expressions: Polars provides specific expressions like rolling_mean_by, rolling_sum_by, etc., which accept an by argument (a column to associate with values for the window) [2][6]. These can be combined with over() for grouped operations [2]. In summary, use Expr.over(partition_by=..., order_by=...) for general ordered window operations [4], and use DataFrame.rolling() or Expr.rolling_*_by expressions for time/index-based rolling aggregations where sorting is managed via the index or the by parameter [2][3][6].

Citations:


🌐 Web query:

site:github.com/pola-rs/polars rolling over order_by RollingGroupOptions order_by

💡 Result:

In the Polars library, RollingGroupOptions is a configuration struct used to define the behavior of rolling window operations, typically within the context of a rolling group-by (e.g., groupby_rolling) [1][2][3]. It does not have an order_by field or property [2][3]. Rolling window operations in Polars are generally expected to be performed on data that is already sorted by the time index [4][5]. The structure typically includes the following configuration options [2][3]: - index_column: The name of the column representing the time or index [1][2]. - period: The duration or length of the rolling window [1][2][3]. - offset: The offset used to shift the window relative to the current index [1][5][2][3]. - closed_window: Determines whether the window is closed on the left, right, both, or neither [4][5][2][3]. - check_sorted: A boolean flag to verify if the index column is sorted [5][3]. If you need to order your data before performing a rolling operation, you should apply a sorting operation (such as sort) to your DataFrame or LazyFrame before initiating the rolling group-by [5]. The rolling operation itself relies on this pre-sorted order to function correctly [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- Current rolling-over tests and nearby unsupported cases ---'
sed -n '185,255p' python/cudf_polars/tests/expressions/test_rolling.py
sed -n '540,640p' python/cudf_polars/tests/expressions/test_rolling.py

printf '%s\n' '--- All test uses combining rolling and over(order_by) ---'
rg -n -U -P '(?s)rolling\\([^\\n]*\\).*?over\\([^\\n]*order_by|over\\([^\\n]*order_by[^\\n]*\\).*?rolling\\(' \
  python/cudf_polars/tests python/cudf_polars/cudf_polars || true

Repository: NVIDIA/cudf

Length of output: 5071


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import importlib.util

spec = importlib.util.find_spec("polars")
print("polars_available:", spec is not None)
if spec is None:
    raise SystemExit(0)

import polars as pl

print("polars_version:", pl.__version__)

df = pl.DataFrame(
    {
        "g": ["a", "a", "a", "a"],
        "idx": [1, 2, 3, 4],
        "ob": [40, 10, 30, 20],
        "x": [1, 2, 4, 8],
    }
).lazy()

expr = pl.col("x").sum().rolling("idx", period="2i").over(
    "g", order_by="ob"
)

try:
    result = df.select(expr.alias("out")).collect()
    print("result:")
    print(result)
except Exception as exc:
    print("exception_type:", type(exc).__name__)
    print("exception:", exc)
PY

Repository: NVIDIA/cudf

Length of output: 173


🌐 Web query:

Polars source GroupedWindow rolling expression over order_by Rolling

💡 Result:

In Polars, the concepts of "GroupedWindow" (often referred to as window functions via the.over expression) and "Rolling" operations are distinct but complementary mechanisms [1][2][3]. 1. Rolling Operations and Order By Rolling operations in Polars are generally index-based or time-based rather than explicitly ordered by a generic column within an.over context [4][5]. - DataFrame.rolling: This method creates rolling groups based on a temporal or integer index column [4]. When using rolling windows, the index column must be sorted in ascending order (or sorted within each group if a 'group_by' is provided) [4][6][5]. - Rolling Expression Methods: Methods like.rolling_sum_by or.rolling_mean_by allow you to compute rolling statistics over a window defined by an index column [3][7]. These are designed to handle non-uniform intervals or specific index-based logic [4]. 2. Window Functions (over) and Order By If you are looking to perform order-sensitive operations (like cumulative sums or window-based offsets) within groups, you use the.over expression, which explicitly supports the 'order_by' parameter [8][9]. - The.over method allows you to partition data (partition_by) and order rows within those partitions (order_by) before applying an expression [8]. - This is the standard way to handle order-sensitive window functions in Polars [8]. Key Distinction - If your goal is a rolling window (a sliding window over time or index), use.rolling or the.rolling__by expression family [4][3]. Ensure your index column is sorted [4][5]. - If your goal is an order-dependent calculation within a group (e.g., cumulative sum, shifted values relative to a sorted column), use.over(partition_by=..., order_by=...) [8][9]. You cannot combine an explicit "rolling" window definition inside an.over clause in the same way you define an SQL window frame; instead, choose the mechanism that matches your requirement: either a time/index-based sliding window (Rolling) or an order-sensitive partition-based calculation (Over) [8][4].

Citations:


🌐 Web query:

site:github.com/pola-rs/polars "rolling" "order_by" "over" "InvalidOperationError"

💡 Result:

The InvalidOperationError encountered when using rolling operations (e.g., rolling_mean, rolling_sum, or the.rolling method) in Polars typically occurs because the library requires the index column (or the 'by' column) to be explicitly sorted in ascending order [1][2][3]. When Polars performs a rolling calculation, it relies on the data being sorted to efficiently compute the windowed results [3]. If the index column is not marked as sorted, Polars raises an InvalidOperationError [2][3]. To resolve this issue, you have a few options: 1. Sort the data: Ensure your DataFrame is sorted by the relevant index or 'by' column before performing the rolling operation [1][2]. 2. Set the sorted flag: If your data is already sorted but Polars has lost the sortedness metadata (which can sometimes happen during intermediate operations), you can explicitly inform Polars by using the.set_sorted method on the relevant column [2]. 3. Use group_by_rolling: If you are performing rolling operations within groups, ensure you are using group_by_rolling or the appropriate.rolling method on the DataFrame, which is designed to handle these operations correctly when configured with the correct index and grouping columns [2][3]. Note that in some cases, particularly in older versions or specific contexts like streaming, this check might behave inconsistently (sometimes failing to raise an error even when data is unsorted, or conversely, raising an error even when data is sorted due to metadata tracking issues) [1][4]. Always ensure you are using the latest version of Polars, as many of these issues are actively tracked and fixed by the maintainers [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

path = Path("python/cudf_polars/tests/expressions/test_rolling.py")
text = path.read_text()

rolling_over = []
for match in re.finditer(r"rolling\(", text):
    start = max(0, match.start() - 180)
    end = min(len(text), match.end() + 300)
    snippet = text[start:end]
    if ".over(" in snippet:
        rolling_over.append(" ".join(snippet.split()))

print("rolling_over_occurrences:", len(rolling_over))
for snippet in rolling_over:
    print(snippet)
PY

Repository: NVIDIA/cudf

Length of output: 3822


🌐 Web query:

"rolling(\"ts\"" ".over(\"g\", order_by" Polars

💡 Result:

In Polars, there is no direct expression syntax exactly like rolling("ts").over("g", order_by=...); rather, rolling window operations and grouping operations are handled through distinct API patterns [1]. Depending on your goal, you should choose one of the following approaches: 1. Rolling Aggregations over Groups (DataFrame level) If you need to perform multiple aggregations over a rolling window within groups, use the DataFrame.rolling method with the group_by parameter [2][3]. The index_column must be sorted within each group [2][3]. df.rolling(index_column="ts", period="1d", group_by="g").agg( pl.col("value").sum.alias("sum_val"), pl.col("value").mean.alias("mean_val")) 2. Rolling Expression (Expression level) If you are performing a single rolling aggregation (like rolling_mean, rolling_sum, etc.) on an expression, you can use the built-in rolling expressions directly [4][5][6]. To apply these within groups, chain them with.over [7]. Note that rolling_sum and rolling_mean do not take an explicit order_by argument because they operate on the existing order of the expression [4][6]. df.with_columns( pl.col("value").rolling_sum(window_size=5).over("g")) 3. Rolling by Another Column If your rolling operation depends on a time column (and not just row counts), use the specific rolling_..._by methods (e.g., rolling_sum_by) [5][6]. df.with_columns( pl.col("value").rolling_sum_by(by="ts", window_size="1d").over("g")) Key Implementation Details: - Sorting: Methods like DataFrame.rolling require the index_column to be sorted [2][3]. - Expression over: The.over method supports an order_by parameter to control row order within partitions for operations like cum_sum or diff, but it does not define the window size for rolling operations [8][9]. - Performance: DataFrame.rolling is generally more performant than chaining multiple rolling expressions with.over because it can cache the window calculations [4][5][6].

Citations:


🌐 Web query:

"Expr.rolling" "over" "order_by" Polars issue

💡 Result:

In Polars, the primary requirement when using rolling operations (both Expr.rolling and DataFrame.rolling) is that the index_column must be sorted in ascending order [1][2][3][4]. If the data is not sorted, Polars will typically raise an InvalidOperationError because the rolling window logic relies on the assumption of sorted temporal or integer index values [5][3]. Key points regarding common "order_by" and rolling issues include: 1. Requirement for Sorted Data: The index_column used in rolling must be sorted [1][3]. You should ensure your data is sorted before performing the operation, for example by using .set_sorted() if you know the data is already sorted, or by performing a full .sort() if necessary [1][2]. 2. Difference Between Expression and DataFrame Rolling: - Expr.rolling is used for rolling aggregations based on a time or integer window. It does not accept a by argument [6][7]. - DataFrame.rolling is often more flexible and supports a group_by parameter, which allows you to perform rolling operations independently within each group (where each group must be sorted by the index column) [3][4]. 3. Common Misconceptions: - Users sometimes attempt to pass order_by or by arguments into Expr.rolling based on similarities with other Polars functions (like over), which causes a TypeError or InvalidOperationError because these arguments are not implemented for Expr.rolling [6][7]. - If you need to perform rolling operations within groups (partitioned data), use DataFrame.rolling(..., group_by="your_column") rather than trying to force it into a single expression [3][4]. 4. Streaming Engine Behavior: Note that there have been reports where the streaming engine might not raise an error for unsorted index columns in rolling operations, even when the in-memory engine does [5]. It is best practice to always ensure your data is explicitly sorted regardless of the engine being used [1][3]. If you need to perform operations that involve specific row ordering within windows that are not time-based, consider using Expr.over with order_by, which is designed for order-sensitive operations such as cum_sum or diff, but note that over is generally for window functions, not the dynamic window-based rolling aggregations provided by rolling [8][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

# Query the public Polars repository without cloning it.
for url in \
  "https://api.github.com/search/code?q=rolling+over+order_by+repo%3Apola-rs%2Fpolars" \
  "https://api.github.com/repos/pola-rs/polars/contents/crates/polars-plan/src/dsl" \
  "https://api.github.com/repos/pola-rs/polars/contents/crates/polars-plan/src/logical_plan"
do
  printf '\n--- %s ---\n' "$url"
  curl -L --fail --silent --show-error "$url" |
    python3 -c 'import json,sys; x=json.load(sys.stdin); print(json.dumps(x, indent=2)[:30000])' ||
    true
done

Repository: NVIDIA/cudf

Length of output: 2393


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

urls=(
  "https://raw.githubusercontent.com/pola-rs/polars/main/crates/polars-plan/src/dsl/expr.rs"
  "https://raw.githubusercontent.com/pola-rs/polars/main/crates/polars-plan/src/dsl/window.rs"
  "https://raw.githubusercontent.com/pola-rs/polars/main/crates/polars-plan/src/logical_plan/expr.rs"
  "https://raw.githubusercontent.com/pola-rs/polars/main/crates/polars-lazy/src/dsl/mod.rs"
)

for i in "${!urls[@]}"; do
  file="$tmp/$i"
  if curl -L --fail --silent --show-error "${urls[$i]}" -o "$file"; then
    printf '\n--- %s ---\n' "${urls[$i]}"
    rg -n -C 4 'Rolling|rolling|Window|order_by' "$file" | head -250 || true
  fi
done

Repository: NVIDIA/cudf

Length of output: 393


🌐 Web query:

github polars "RollingGroupOptions" "order_by" "Window"

💡 Result:

In the Polars library, RollingGroupOptions is a configuration structure used within the rolling window operations, primarily in the Rust API [1]. There is no direct "order_by" parameter within the RollingGroupOptions struct itself [1]; instead, Polars handles ordering requirements for rolling operations primarily through the mandatory sorting of the index column [2][3][4]. Key details regarding ordering and rolling windows in Polars include: 1. Rolling Operations and Index Columns: When performing a rolling operation (e.g., using.rolling in Python or.rolling on an Expr in Rust), you must specify an index_column [2][3][5]. This column must be sorted in ascending order [3][4]. If a "group_by" (or "by") column is specified, the index_column must be sorted within each group [2][3][4]. 2. RollingGroupOptions Struct: This structure is used in the Rust API to define the rolling configuration (index_column, period, offset, and closed_window) [1]. It does not contain an "order_by" field [1]. 3. Window Functions and Ordering: While rolling operations rely on a pre-sorted index column, there have been long-standing feature requests to support explicit "order_by" clauses within window functions (e.g., inside.over) to avoid the need for pre-sorting the entire DataFrame [6]. Currently, to achieve specific ordering within an.over window, users generally must ensure the DataFrame is sorted by the desired keys before applying the window function [6]. 4. Distinction from Window: The Window object in Polars (seen in the Rust/internal representation) is distinct from rolling options [7]. It manages partitioning (partition_by) and contains fields related to ordering (order_by, order_by_descending, etc.) that are used for window functions like shift, rank, or cumulative aggregations, rather than rolling temporal/index windows [7]. In summary, there is no RollingGroupOptions.order_by field because rolling windows in Polars are fundamentally defined by the sorted state of the index column provided at execution time [2][3][4]. If you require custom ordering for non-rolling window operations, you should use the.over method with a pre-sorted DataFrame [6].

Citations:


🌐 Web query:

github polars "rolling_expr" "Window" "order_by"

💡 Result:

In Polars, the functionality for ordering rows within windows is handled primarily through the order_by parameter in the Expr.over method, rather than within Expr.rolling [1][2]. Expr.over and order_by The Expr.over method is used to compute expressions over partitions (groups) of data [1][3]. The order_by parameter allows you to specify a column to sort rows within each partition before the expression is evaluated [1][4]. This is essential for operations that are sensitive to row order, such as cum_sum, diff, or shift [1][5]. Example: df.with_columns( cumulative_sales=pl.col("sales").cum_sum.over("store_id", order_by="date")) Rolling Operations Expr.rolling and Expr.rolling_mean_by are designed for time-series or index-based window calculations [6][7]. Unlike over, which sorts within groups, rolling methods generally require the input index column to be pre-sorted [6][8]. - Expr.rolling(index_column=...): Requires the index_column to be sorted in ascending order [6]. - DataFrame.rolling(index_column=..., group_by=...): If a group_by is specified, the index_column must be sorted within each group [8]. In summary, if you need to control the order of evaluation within a window, use over(..., order_by=...) [1]. If you are performing a rolling window calculation, ensure your data is already sorted by the index column before applying the rolling operation [6][8].

Citations:


🌐 Web query:

Polars rolling expression over partition order_by semantics source code

💡 Result:

In Polars, the concepts of partitioning and ordering are handled differently depending on whether you are using window functions via.over or rolling aggregation methods..over Semantics The.over expression is designed for window functions (like cum_sum, rank, or aggregates) and explicitly supports both partition_by and order_by [1][2]. - partition_by: Defines the groups over which the expression is calculated [1]. - order_by: Defines the sorting order within each partition before the expression is evaluated [1]. This is required for operations that are sensitive to row order, such as cumulative sums [1][2]. - Source Behavior: Internally, Polars uses these parameters to perform efficient window computations. Recent updates have focused on ensuring that these operations align with standard SQL window function semantics, particularly regarding how null values are handled in cumulative operations [3]..rolling Semantics Rolling operations in Polars are handled either via DataFrame.rolling (for time-based or index-based windows) or via expression-level methods like Expr.rolling_sum [4][5][6]. - Indexing: Unlike.over, rolling operations require a specific index_column (e.g., date, datetime, or integer) that must be sorted [4][6]. - Grouping: If grouping is required, the rolling method accepts a group_by parameter [6]. The requirement is that the data must be sorted by the index_column within each of those groups [4][6]. - Source Logic: The DataFrame.rolling implementation creates a RollingGroupBy object [6]. Its internal logic (found in polars/dataframe/group_by.py) performs a lazy operation using the specified index_column and group_by columns, ensuring data is grouped and aggregated correctly while respecting the temporal or positional order [7][8]. Key Distinction While.over allows you to specify arbitrary expressions for ordering within partitions [1],.rolling is strictly bound to a temporal or index column that defines the window's boundaries [4][6]. If you need to perform rolling operations over partitioned groups, you must use the.rolling method on the DataFrame (or a LazyFrame) and specify the group_by parameter, ensuring the data is pre-sorted by the index column within those groups [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

base="https://raw.githubusercontent.com/pola-rs/polars/py-1.43.0"
paths=(
  "crates/polars-plan/src/dsl/mod.rs"
  "crates/polars-plan/src/dsl/window.rs"
  "crates/polars-plan/src/logical_plan/expr.rs"
  "crates/polars-lazy/src/dsl/mod.rs"
  "crates/polars-lazy/src/frame/mod.rs"
  "crates/polars-python/src/lazyframe/visitor/expr_nodes.rs"
)

for path in "${paths[@]}"; do
  out="$tmp/$(basename "$path")"
  if curl -L --fail --silent --show-error "$base/$path" -o "$out"; then
    printf '\n--- %s ---\n' "$path"
    rg -n -C 5 'Rolling|rolling|order_by|Window' "$out" | head -300 || true
  else
    printf 'unavailable: %s\n' "$path"
  fi
done

Repository: NVIDIA/cudf

Length of output: 27099


🌐 Web query:

site:github.com/pola-rs/polars "AExpr::Over" "AExpr::Rolling"

💡 Result:

AExpr::Over and AExpr::Rolling are internal variants of the AExpr (Arena Expression) enum within the Polars library, specifically found in the polars-plan crate [1]. These variants represent analytical expressions used in the library's internal expression planning and logical query optimization pipeline [1][2]. AExpr::Over This variant represents window function expressions that operate over a set of rows [1]. It encompasses the logic for windowing operations that include partition-by keys, optional ordering, and window mapping strategies [1][3]. It is used to transform standard aggregate or window functions into a form that the execution engine can process as a window operation [2]. AExpr::Rolling This variant represents rolling window operations, which are typically time-series or index-based window calculations [1]. It is used when a calculation needs to be performed over a dynamic window defined by a period and offset relative to an index column [1][2]. This variant is conditional on the dynamic_group_by feature flag in Polars [1][4]. In the context of the Polars execution planner, both variants are considered non-scalar expressions because they implicitly zip with the context (i.e., they are not simple element-wise operations) [1]. During the planning phase, these AExpr nodes are converted into physical expressions that the execution engine uses to perform the actual data calculations [2].

Citations:


🌐 Web query:

site:github.com/pola-rs/polars "IRNode::Window" "Rolling"

💡 Result:

In the Polars library, IRNode::Window (and related rolling window structures) refers to the internal representation (IR) nodes used to handle rolling window operations within the lazy execution engine [1][2]. Key aspects of these nodes include: Internal Representation (IR) Variants: Polars uses specific IR variants to distinguish different types of rolling operations [1][2]. For example: - RollingExpr: Handles fixed-size window operations where the window size is defined by a number of slots [1][2]. - RollingExprBy: Handles operations that rely on a dynamic window or specific index-based logic [1][2]. Technical Implementation: - These nodes are defined within the Polars Rust codebase (primarily in the polars-plan crate) [1]. - They encapsulate the rolling function (e.g., Min, Max, Mean, Sum, Quantile, Var, Std, Rank, Skew, Kurtosis) along with specific options such as window size, minimum periods, and window weights [1][3][2]. - Recent developments have focused on exposing these internal expressions to the Python API, enabling external engines (such as cudf-polars) to intercept and optimize rolling operations, for example by offloading them to GPU execution [4][3]. Context: The IRNode structures allow the Polars query planner to perform optimizations—such as fusion and predicate pushdown—before the plan is executed [2][5][6]. The system separates the definition of the rolling operation from the execution strategy, which is critical for supporting both standard CPU-based rolling windows and specialized implementations for dynamic or time-based windows [7][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

base="https://raw.githubusercontent.com/pola-rs/polars/py-1.43.0"
paths=(
  "crates/polars-lazy/src/physical_plan/executors/window.rs"
  "crates/polars-lazy/src/physical_plan/executors/mod.rs"
  "crates/polars-stream/src/nodes/compute/window.rs"
  "crates/polars-stream/src/ir/mod.rs"
  "crates/polars-plan/src/plans/aexpr/mod.rs"
  "crates/polars-plan/src/plans/ir/mod.rs"
)

for path in "${paths[@]}"; do
  out="$tmp/$(echo "$path" | tr / _)"
  if curl -L --fail --silent --show-error "$base/$path" -o "$out"; then
    printf '\n--- %s ---\n' "$path"
    rg -n -C 5 'Rolling|rolling|Window|order_by|AExpr::Over' "$out" | head -400 || true
  else
    printf 'unavailable: %s\n' "$path"
  fi
done

Repository: NVIDIA/cudf

Length of output: 1246


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

base="https://raw.githubusercontent.com/pola-rs/polars/b6ae1153"
paths=(
  "crates/polars-expr/src/planner.rs"
  "crates/polars-plan/src/plans/conversion/ir_to_dsl.rs"
  "crates/polars-plan/src/plans/conversion/dsl_to_ir/mod.rs"
  "crates/polars-plan/src/plans/conversion/dsl_to_ir/functions.rs"
  "crates/polars-plan/src/plans/aexpr/function_expr/mod.rs"
)

for path in "${paths[@]}"; do
  out="$tmp/$(echo "$path" | tr / _)"
  if curl -L --fail --silent --show-error "$base/$path" -o "$out"; then
    printf '\n--- %s ---\n' "$path"
    rg -n -C 8 'AExpr::Over|AExpr::Rolling|Expr::Over|Expr::Rolling|order_by|rolling' "$out" | head -500 || true
  else
    printf 'unavailable: %s\n' "$path"
  fi
done

Repository: NVIDIA/cudf

Length of output: 13631


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

base="https://raw.githubusercontent.com/pola-rs/polars/b6ae1153"
paths=(
  "crates/polars-expr/src/expressions/window.rs"
  "crates/polars-expr/src/expressions/window/mod.rs"
  "crates/polars-expr/src/window.rs"
  "crates/polars-expr/src/window/mod.rs"
  "crates/polars-expr/src/expressions/mod.rs"
)

for path in "${paths[@]}"; do
  out="$tmp/$(echo "$path" | tr / _)"
  if curl -L --fail --silent --show-error "$base/$path" -o "$out"; then
    printf '\n--- %s ---\n' "$path"
    rg -n -C 8 'order_by|phys_function|evaluate|Rolling|rolling|sort' "$out" | head -600 || true
  else
    printf 'unavailable: %s\n' "$path"
  fi
done

Repository: NVIDIA/cudf

Length of output: 35372


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

base="https://raw.githubusercontent.com/pola-rs/polars/b6ae1153"
path="crates/polars-expr/src/expressions/rolling.rs"
out="$tmp/rolling.rs"

curl -L --fail --silent --show-error "$base/$path" -o "$out"

printf '%s\n' '--- RollingExpr implementation ---'
rg -n -C 12 'struct RollingExpr|impl PhysicalExpr for RollingExpr|evaluate_on_groups|evaluate_impl|order|sorted|Rolling' "$out" | head -800

Repository: NVIDIA/cudf

Length of output: 6839


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

url="https://raw.githubusercontent.com/pola-rs/polars/b6ae1153/crates/polars-expr/src/expressions/rolling.rs"
out="$tmp/rolling.rs"
curl -L --fail --silent --show-error "$url" -o "$out"

printf '%s\n' '--- RollingExpr group ordering and window construction ---'
sed -n '77,245p' "$out"

printf '%s\n' '--- RollingWindower sortedness checks in Polars time crate ---'
for path in \
  "crates/polars-time/src/group_by/rolling.rs" \
  "crates/polars-time/src/chunkedarray/rolling_window.rs" \
  "crates/polars-time/src/group_by/mod.rs"
do
  candidate="$tmp/$(echo "$path" | tr / _)"
  if curl -L --fail --silent --show-error \
      "https://raw.githubusercontent.com/pola-rs/polars/b6ae1153/$path" \
      -o "$candidate"; then
    printf '\n--- %s ---\n' "$path"
    rg -n -C 8 'RollingWindower|sorted|monotonic|window|out of order' "$candidate" | head -500 || true
  fi
done

Repository: NVIDIA/cudf

Length of output: 7434


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import ast
from pathlib import Path

path = Path("python/cudf_polars/cudf_polars/dsl/expressions/rolling.py")
tree = ast.parse(path.read_text())

matches = []
for node in ast.walk(tree):
    if not isinstance(node, ast.If):
        continue
    test = ast.unparse(node.test)
    if 'unary_window_ops["range_rolling"]' not in test:
        continue

    calls = [
        n for n in ast.walk(node)
        if isinstance(n, ast.Call)
        and isinstance(n.func, ast.Attribute)
        and n.func.attr == "_apply_ordered_unary_op"
    ]
    for call in calls:
        keywords = {kw.arg: ast.unparse(kw.value) for kw in call.keywords}
        matches.append(
            {
                "guard": test,
                "order_by_col": keywords.get("order_by_col"),
                "ob_desc": keywords.get("ob_desc"),
                "ob_nulls_last": keywords.get("ob_nulls_last"),
                "require_sorted_groups": keywords.get("require_sorted_groups"),
            }
        )

print(matches)
print("range_rolling_guard_checks_self_order_by:",
      any(
          "self._order_by_expr" in ast.unparse(n.test)
          for n in ast.walk(tree)
          if isinstance(n, ast.If)
          and 'unary_window_ops["range_rolling"]' in ast.unparse(n.test)
      ))
PY

Repository: NVIDIA/cudf

Length of output: 200


Reject over(order_by=...) with range-rolling expressions. The CPU window implementation sorts each group by order_by before evaluating the rolling expression. This branch always orders by the rolling index and omits over_ob_desc and over_ob_nulls_last, which can produce different results. Raise NotImplementedError when both conditions apply, or implement the same ordering semantics.

🤖 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/expressions/rolling.py` around lines 1487
- 1500, Update the range-rolling branch in the surrounding window-expression
implementation to detect when a range-rolling expression is combined with an
over-clause order_by, using the existing order-direction and null-ordering
fields (over_ob_desc and over_ob_nulls_last). Raise NotImplementedError for that
combination unless _apply_ordered_unary_op is updated to honor those ordering
semantics; preserve the current rolling-index ordering for cases without
over(order_by=...).

Comment on lines +1219 to +1244
child_deps: list[expr.Expr] = []
for ne in named_aggs:
v = ne.value
if (
isinstance(v, expr.UnaryFunction)
and v.name == "fill_null_with_strategy"
and isinstance(v.children[0], expr.UnaryFunction)
and v.children[0].name == "cum_sum"
)
else v.children[0]
for ne in named_aggs
for v in (ne.value,)
if isinstance(v, expr.Agg)
or (
):
child_deps.append(v.children[0].children[0])
elif isinstance(v, expr.RollingWindow):
child_deps.append(v.children[0])
child_deps.append(expr.Col(schema[v.orderby], v.orderby))
elif isinstance(v, expr.Agg) or (
isinstance(v, expr.UnaryFunction)
and v.name
in {
"rank",
"fill_null_with_strategy",
"cum_sum",
"diff",
"shift",
"shift_and_fill",
}
)
]
):
child_deps.append(v.children[0])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the previous child-dependency construction for GroupedWindow and locate FixedSizeRollingWindow handling.
set -euo pipefail

# Show the current window translation block.
rg -n -C 6 'child_deps' python/cudf_polars/cudf_polars/dsl/translate.py

# Show the previous revision of the same region.
git log -1 --format=%H -- python/cudf_polars/cudf_polars/dsl/translate.py
git show HEAD~1:python/cudf_polars/cudf_polars/dsl/translate.py 2>/dev/null | rg -n -C 8 'by_exprs|FixedSizeRollingWindow|children = \(' | head -80

# Find where FixedSizeRollingWindow children are evaluated at runtime.
rg -n -C 4 'FixedSizeRollingWindow' python/cudf_polars/cudf_polars/dsl/

Repository: NVIDIA/cudf

Length of output: 14000


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- parent implementation ---'
git show HEAD~1:python/cudf_polars/cudf_polars/dsl/translate.py | sed -n '1180,1260p'

echo '--- current implementation ---'
sed -n '1180,1260p' python/cudf_polars/cudf_polars/dsl/translate.py

echo '--- aggregation decomposition ---'
sed -n '110,180p' python/cudf_polars/cudf_polars/dsl/utils/aggregations.py

echo '--- GroupedWindow dependency consumers ---'
rg -n -C 8 'class GroupedWindow|by_cols_for_scan|op\.children|children' \
  python/cudf_polars/cudf_polars/dsl/expressions/rolling.py

Repository: NVIDIA/cudf

Length of output: 34386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- expression dependency traversal ---'
rg -n -C 10 'def .*gather.*column|_gather_columns|\.children' \
  python/cudf_polars/cudf_polars/dsl python/cudf_polars

echo '--- projection dependency handling ---'
rg -n -C 10 'GroupedWindow|named_aggs|child_deps|children.*evaluate|evaluate.*children' \
  python/cudf_polars/cudf_polars

echo '--- parent/current diff for this file ---'
git diff --no-ext-diff --unified=20 HEAD~1 HEAD -- \
  python/cudf_polars/cudf_polars/dsl/translate.py | sed -n '1,220p'

Repository: NVIDIA/cudf

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- dependency helper locations ---'
rg -n --glob '*.py' 'def _gather_columns|def gather_columns|_gather_columns\(' \
  python/cudf_polars/cudf_polars/dsl python/cudf_polars/cudf_polars

echo '--- base expression implementation ---'
rg -l --glob '*.py' 'class Expr' python/cudf_polars/cudf_polars/dsl \
  | head -20

echo '--- current commit diff only ---'
git diff --no-ext-diff --unified=8 HEAD~1 HEAD -- \
  python/cudf_polars/cudf_polars/dsl/translate.py \
  | rg -n -C 12 'child_deps|FixedSizeRollingWindow|^[-+]' \
  | head -240

Repository: NVIDIA/cudf

Length of output: 1087


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- expression base class ---'
sed -n '1,240p' python/cudf_polars/cudf_polars/dsl/expressions/base.py

echo '--- GroupedWindow dependency setup ---'
sed -n '1000,1080p' python/cudf_polars/cudf_polars/dsl/expressions/rolling.py

echo '--- projection and expression-column dependency code ---'
rg -n --glob '*.py' -C 5 \
  'projection|column_names|columns_needed|required_columns|collect.*column|traversal\(' \
  python/cudf_polars/cudf_polars | head -300

Repository: NVIDIA/cudf

Length of output: 39428


Register fixed-size rolling dependencies.

FixedSizeRollingWindow is accepted by GroupedWindow, but its data child is not added to child_deps. Add a branch that appends v.children[0] so FixedSizeRollingOp can evaluate the required input column after projection.

🤖 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/translate.py` around lines 1219 - 1244,
Update the dependency collection loop for named aggregations to handle
FixedSizeRollingWindow values explicitly. Add v.children[0] to child_deps so
FixedSizeRollingOp receives its input column after projection, while preserving
the existing RollingWindow and aggregation handling.

Comment on lines +166 to +177
if isinstance(agg, expr.RollingWindow):
if context != ExecutionContext.WINDOW:
raise NotImplementedError(
"Range rolling is not supported in groupby or rolling context"
)
if _contains_window_only_unary(agg.children[0]) or (
_contains_fixed_size_rolling_window(agg.children[0])
):
raise NotImplementedError(
"Range rolling over a window does not support nested window expressions"
)
return [(named_expr, True)], named_expr.reconstruct(expr.Col(agg.dtype, name))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Also reject a nested RollingWindow.

The guard checks _contains_window_only_unary and _contains_fixed_size_rolling_window on agg.children[0]. It does not check for a nested expr.RollingWindow. A nested range rolling therefore passes translation and reaches RollingWindow.do_evaluate, which raises RuntimeError for a non-FRAME context. A RuntimeError does not trigger the CPU fallback, so the user gets a hard failure instead of an unsupported-operation fallback.

Add a nested range-rolling check so translation raises NotImplementedError first.

🛡️ Proposed guard
+def _contains_range_rolling_window(value: expr.Expr) -> bool:
+    return any(isinstance(node, expr.RollingWindow) for node in traversal([value]))
+
+
 def decompose_single_agg(
     if isinstance(agg, expr.RollingWindow):
         if context != ExecutionContext.WINDOW:
             raise NotImplementedError(
                 "Range rolling is not supported in groupby or rolling context"
             )
         if _contains_window_only_unary(agg.children[0]) or (
             _contains_fixed_size_rolling_window(agg.children[0])
+        ) or _contains_range_rolling_window(agg.children[0]):
-        ):
             raise NotImplementedError(
                 "Range rolling over a window does not support nested window expressions"
             )
📝 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 isinstance(agg, expr.RollingWindow):
if context != ExecutionContext.WINDOW:
raise NotImplementedError(
"Range rolling is not supported in groupby or rolling context"
)
if _contains_window_only_unary(agg.children[0]) or (
_contains_fixed_size_rolling_window(agg.children[0])
):
raise NotImplementedError(
"Range rolling over a window does not support nested window expressions"
)
return [(named_expr, True)], named_expr.reconstruct(expr.Col(agg.dtype, name))
def _contains_range_rolling_window(value: expr.Expr) -> bool:
return any(isinstance(node, expr.RollingWindow) for node in traversal([value]))
if isinstance(agg, expr.RollingWindow):
if context != ExecutionContext.WINDOW:
raise NotImplementedError(
"Range rolling is not supported in groupby or rolling context"
)
if _contains_window_only_unary(agg.children[0]) or (
_contains_fixed_size_rolling_window(agg.children[0])
) or _contains_range_rolling_window(agg.children[0]):
raise NotImplementedError(
"Range rolling over a window does not support nested window expressions"
)
return [(named_expr, True)], named_expr.reconstruct(expr.Col(agg.dtype, name))
🤖 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` around lines 166 -
177, Extend the nested-window guard in the RollingWindow translation branch to
also detect an expr.RollingWindow within agg.children[0], alongside
_contains_window_only_unary and _contains_fixed_size_rolling_window. Raise
NotImplementedError during translation so nested range rolling follows the
existing unsupported-operation fallback instead of reaching
RollingWindow.do_evaluate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 - In Progress Currently a work in progress 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: Todo

Development

Successfully merging this pull request may close these issues.

[FEA] Support aggregate rolling expressions inside over() in cuDF-Polars

1 participant