Last modified: May 10, 2026 By Alexander Williams
Polars Window Functions & Rolling Computations
Window functions and rolling computations are powerful tools in data analysis. They let you perform calculations across a set of rows related to the current row. In Polars, these operations are fast and expressive.
This guide will teach you how to use window functions and rolling computations in Polars. You will learn to calculate moving averages, cumulative sums, and rank data within groups. We will use clean examples with code and output.
What Are Window Functions?
A window function performs a calculation across a set of rows. This set is called a "window". Unlike regular aggregations, window functions do not collapse rows. Each row keeps its identity while gaining new information from its neighbors.
For example, you might want to see each sale and the average sale for that day. A window function can add this average as a new column without removing any rows.
Polars implements window functions using the over method. This method specifies how to partition the data. You can also use shift, diff, and cum_sum for rolling operations.
Setting Up Your Data
Let's start with a simple DataFrame. We will use sales data for a small store.
import polars as pl
# Create sample data
df = pl.DataFrame({
"date": ["2024-01-01", "2024-01-01", "2024-01-02", "2024-01-02", "2024-01-03"],
"product": ["A", "B", "A", "B", "A"],
"sales": [100, 150, 200, 120, 180]
})
print(df)
shape: (5, 3)
┌────────────┬─────────┬───────┐
│ date ┆ product ┆ sales │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 │
╞════════════╪═════════╪═══════╡
│ 2024-01-01 ┆ A ┆ 100 │
│ 2024-01-01 ┆ B ┆ 150 │
│ 2024-01-02 ┆ A ┆ 200 │
│ 2024-01-02 ┆ B ┆ 120 │
│ 2024-01-03 ┆ A ┆ 180 │
└────────────┴─────────┴───────┘
Basic Window Functions with over
The over method is the core of window functions in Polars. It partitions data by one or more columns. Then, it applies the expression to each partition.
For example, to get the total sales per date as a new column:
# Add total sales per date using a window function
df_with_total = df.with_columns(
pl.sum("sales").over("date").alias("total_sales_per_date")
)
print(df_with_total)
shape: (5, 4)
┌────────────┬─────────┬───────┬─────────────────────┐
│ date ┆ product ┆ sales ┆ total_sales_per_date │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ i64 │
╞════════════╪═════════╪═══════╪═════════════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ 250 │
│ 2024-01-01 ┆ B ┆ 150 ┆ 250 │
│ 2024-01-02 ┆ A ┆ 200 ┆ 320 │
│ 2024-01-02 ┆ B ┆ 120 ┆ 320 │
│ 2024-01-03 ┆ A ┆ 180 ┆ 180 │
└────────────┴─────────┴───────┴─────────────────────┘
Notice how each row keeps its original data. The new column shows the sum for that specific date. This is a classic window function pattern.
You can also partition by multiple columns. For example, to see sales rank within each product group:
# Rank sales within each product group
df_ranked = df.with_columns(
pl.col("sales").rank("dense").over("product").alias("sales_rank")
)
print(df_ranked)
shape: (5, 4)
┌────────────┬─────────┬───────┬────────────┐
│ date ┆ product ┆ sales ┆ sales_rank │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ u32 │
╞════════════╪═════════╪═══════╪════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ 1 │
│ 2024-01-01 ┆ B ┆ 150 ┆ 2 │
│ 2024-01-02 ┆ A ┆ 200 ┆ 3 │
│ 2024-01-02 ┆ B ┆ 120 ┆ 1 │
│ 2024-01-03 ┆ A ┆ 180 ┆ 2 │
└────────────┴─────────┴───────┴────────────┘
The rank method with "dense" assigns ranks without gaps. Product A has ranks 1, 2, 3 based on sales order. Product B has ranks 1, 2.
For more advanced chaining of expressions, check out our Polars Chaining Expressions Guide.
Rolling Computations
Rolling computations are a special type of window function. They operate over a fixed number of rows or a time window. Polars provides rolling methods for this purpose.
Common rolling computations include moving averages, rolling sums, and rolling minima. These are essential for time series analysis.
Rolling Mean
Let's compute a 2-day rolling average of sales. We need to sort the data by date first.
# Sort by date for rolling operations
df_sorted = df.sort("date")
# Compute 2-day rolling mean of sales
df_rolling = df_sorted.with_columns(
pl.col("sales").rolling_mean(window_size=2).alias("rolling_avg_2d")
)
print(df_rolling)
shape: (5, 4)
┌────────────┬─────────┬───────┬────────────────┐
│ date ┆ product ┆ sales ┆ rolling_avg_2d │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ f64 │
╞════════════╪═════════╪═══════╪════════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ null │
│ 2024-01-01 ┆ B ┆ 150 ┆ 125.0 │
│ 2024-01-02 ┆ A ┆ 200 ┆ 175.0 │
│ 2024-01-02 ┆ B ┆ 120 ┆ 160.0 │
│ 2024-01-03 ┆ A ┆ 180 ┆ 150.0 │
└────────────┴─────────┴───────┴────────────────┘
The first row is null because there is no previous row. The second row averages rows 1 and 2. This is a simple rolling mean over consecutive rows.
For time-based rolling, you can use the rolling method on a datetime column. This is useful for unevenly spaced time series.
Rolling Sum with Grouping
You can combine rolling computations with window partitions. For example, compute a rolling sum of sales per product.
# Rolling sum per product (sorted by date within each product)
df_rolling_group = df_sorted.with_columns(
pl.col("sales").rolling_sum(window_size=2).over("product").alias("rolling_sum_product")
)
print(df_rolling_group)
shape: (5, 4)
┌────────────┬─────────┬───────┬─────────────────────┐
│ date ┆ product ┆ sales ┆ rolling_sum_product │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ i64 │
╞════════════╪═════════╪═══════╪═════════════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ null │
│ 2024-01-01 ┆ B ┆ 150 ┆ null │
│ 2024-01-02 ┆ A ┆ 200 ┆ 300 │
│ 2024-01-02 ┆ B ┆ 120 ┆ 270 │
│ 2024-01-03 ┆ A ┆ 180 ┆ 380 │
└────────────┴─────────┴───────┴─────────────────────┘
Notice how product B's rolling sum starts at row 2. The over("product") ensures the window resets for each product.
Cumulative Operations
Cumulative operations are another type of window function. They accumulate values from the start of the window to the current row.
Use cum_sum, cum_min, cum_max, or cum_count for these tasks.
# Cumulative sum of sales per product
df_cum = df_sorted.with_columns(
pl.col("sales").cum_sum().over("product").alias("cumulative_sales")
)
print(df_cum)
shape: (5, 4)
┌────────────┬─────────┬───────┬──────────────────┐
│ date ┆ product ┆ sales ┆ cumulative_sales │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ i64 │
╞════════════╪═════════╪═══════╪══════════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ 100 │
│ 2024-01-01 ┆ B ┆ 150 ┆ 150 │
│ 2024-01-02 ┆ A ┆ 200 ┆ 300 │
│ 2024-01-02 ┆ B ┆ 120 ┆ 270 │
│ 2024-01-03 ┆ A ┆ 180 ┆ 480 │
└────────────┴─────────┴───────┴──────────────────┘
Each product's cumulative sum grows independently. This is useful for tracking running totals.
Using shift and diff
The shift method moves values up or down within a window. This is perfect for calculating changes from previous rows.
The diff method computes the difference between consecutive rows.
# Shift sales to get previous day's value per product
df_shift = df_sorted.with_columns([
pl.col("sales").shift(1).over("product").alias("prev_sales"),
pl.col("sales").diff(1).over("product").alias("sales_change")
])
print(df_shift)
shape: (5, 5)
┌────────────┬─────────┬───────┬────────────┬──────────────┐
│ date ┆ product ┆ sales ┆ prev_sales ┆ sales_change │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ i64 ┆ i64 │
╞════════════╪═════════╪═══════╪════════════╪══════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ null ┆ null │
│ 2024-01-01 ┆ B ┆ 150 ┆ null ┆ null │
│ 2024-01-02 ┆ A ┆ 200 ┆ 100 ┆ 100 │
│ 2024-01-02 ┆ B ┆ 120 ┆ 150 ┆ -30 │
│ 2024-01-03 ┆ A ┆ 180 ┆ 200 ┆ -20 │
└────────────┴─────────┴───────┴────────────┴──────────────┘
The shift(1) brings the previous row's value. The diff(1) calculates the change directly. Both respect the over("product") partition.
Performance Tips
Window functions in Polars are optimized for performance. However, you should follow some best practices.
Always sort your data before rolling computations. Polars does not assume row order unless you specify it. Use sort or sort_by to guarantee correct results.
Use over with caution on large datasets. Partitioning by many columns can create many small groups. This may reduce performance. Test with your data to find the best balance.
For complex transformations, combine window functions with other Polars features. For example, you can reshape data after computing window values. See our Reshape Data in Polars: Pivot, Melt & Transpose guide for more ideas.
If you need to join window results with other tables, check out the Polars DataFrame Joins & Merges Guide.
Real-World Example: Sales Analysis
Let's build a complete analysis. We want to find products with increasing sales trends.
# Create more realistic data
import datetime
df_sales = pl.DataFrame({
"date": pl.date_range(datetime.date(2024, 1, 1), datetime.date(2024, 1, 10), "1d").extend(3),
"product": ["A", "A", "A", "A", "A", "A", "A", "A", "A", "A",
"B", "B", "B"],
"sales": [100, 110, 105, 120, 130, 125, 140, 150, 145, 160,
200, 190, 210]
})
# Compute rolling 3-day average and trend
df_analysis = df_sales.with_columns([
pl.col("sales").rolling_mean(3).over("product").alias("rolling_avg_3d"),
pl.col("sales").diff(1).over("product").alias("daily_change")
]).sort(["product", "date"])
print(df_analysis)
shape: (13, 5)
┌────────────┬─────────┬───────┬────────────────┬──────────────┐
│ date ┆ product ┆ sales ┆ rolling_avg_3d ┆ daily_change │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ date ┆ str ┆ i64 ┆ f64 ┆ i64 │
╞════════════╪═════════╪═══════╪════════════════╪══════════════╡
│ 2024-01-01 ┆ A ┆ 100 ┆ null ┆ null │
│ 2024-01-02 ┆ A ┆ 110 ┆ null ┆ 10 │
│ 2024-01-03 ┆ A ┆ 105 ┆ 105.0 ┆ -5 │
│ ... ┆ ... ┆ ... ┆ ... ┆ ... │
│ 2024-01-10 ┆ A ┆ 160 ┆ 151.667 ┆ 15 │
│ 2024-01-01 ┆ B ┆ 200 ┆ null ┆ null │
│ 2024-01-02 ┆ B ┆ 190 ┆ null ┆ -10 │
│ 2024-01-03 ┆ B ┆ 210 ┆ 200.0 ┆ 20 │
└────────────┴─────────┴───────┴────────────────┴──────────────┘
You can now filter products where the rolling average is increasing. This shows the power of combining window functions with other operations.
Conclusion
Window functions and rolling computations are essential for modern data analysis. Polars makes them easy and fast. You learned to use over for partitioning, rolling_mean for moving averages, and cum_sum for running totals.
Start applying these techniques to your own data. They will help you uncover trends and patterns that simple aggregations miss. Remember to sort your data and test with small examples first.
For further learning, explore the Polars Expression System Core Concept to understand how these operations work under the hood.