Skip to content
C Codeloom
Pandas

Pandas Window Functions Tutorial

Use Pandas rolling, expanding, and ewm window functions to compute moving averages, running totals, and time-aware aggregations with clear examples.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What rolling, expanding, and ewm windows compute
  • How window size, min_periods, and center change results
  • Time based vs row based windows
  • How to combine windows with groupby
  • Performance and edge case tips

Prerequisites

  • Basic Pandas DataFrame and Series usage

What and Why

Aggregations like sum and mean collapse a column into one number. Real analytical work usually wants something in between: a value for each row that depends on its neighbours. The seven day average of sales, the running total of orders, the volatility over the last thirty trades.

Pandas window functions compute these in one line. They walk a window across the data and aggregate inside the window at every step, returning a Series the same length as the input.

Mental Model

A window is a moving subset of rows. You choose how it moves and how big it is, then you apply an aggregation inside. There are three families.

Rolling windows have a fixed size and slide forward. Expanding windows grow from the start of the data. Exponentially weighted windows give more weight to recent rows and fade older ones. Each family shares the same .agg, .mean, .sum, .std interface, so once you know one you know all three.

Hands-on Example

Set up a small time series.

import pandas as pd
import numpy as np

dates = pd.date_range("2026-01-01", periods=10, freq="D")
df = pd.DataFrame({"date": dates,
                   "sales": [10, 12, 9, 15, 14, 18, 20, 19, 22, 25]})
df = df.set_index("date")

df["roll3"] = df["sales"].rolling(window=3).mean()
df["expand"] = df["sales"].expanding().sum()
df["ewm"] = df["sales"].ewm(alpha=0.5).mean()

rolling(3).mean() averages the current row and the two before it. The first two rows are NaN because the window is not yet full. Pass min_periods=1 to start earlier with partial windows.

sales:     10   12   9    15   14   18   20   19   22   25
                                                        
rolling(3) NaN  NaN  10.3 12.0 12.6 15.6 17.3 19.0 20.3 22.0
           |--win--|
                |--win--|
                     |--win--|   (slides forward)

expanding  10   22   31   46   60   78   98   117  139  164
         |---grows from start each step----------------|

ewm a=0.5  10   11.3 10.1 13.0 13.6 16.0 18.1 18.5 20.4 23.0
         recent rows weighted more, old rows decay
Three window families over the same series

For time aware windows, pass a duration string instead of an integer: df["sales"].rolling("7D").mean() averages the last seven days regardless of how many rows fall inside. This handles missing days and irregular timestamps without resampling.

Combine with groupby to compute per-group windows. df.groupby("product")["sales"].rolling(7).mean() gives a seven row moving average within each product, which is the right answer for almost every business question that starts “per product”.

Trade-offs

Row based windows are fast and simple, but they assume your data is regularly spaced. One missing day silently shifts the window and the answer is wrong.

Time based windows are slightly slower but robust to gaps. They require a sorted DatetimeIndex; forget the sort and Pandas raises.

Expanding windows are great for cumulative metrics but they grow without bound, so summary statistics drift as more history accumulates. Use them for totals, not for things like “recent volatility”.

Exponentially weighted windows smooth without a hard cutoff, which is friendlier to streaming data than rolling. The cost is a less intuitive parameter; alpha and halflife take some experimenting to feel right.

Practical Tips

Always sort the index before windowing. Window results on an unsorted index are silently wrong, not erroring.

Use min_periods deliberately. The default forces a full window, which gives clean math but loses the first few rows. Set it to one when you want partial windows from the start of the series.

Set center=True to align the window around the row instead of trailing behind it. This is right for smoothing visualisations, wrong for anything causal like forecasting.

Avoid .apply with a Python function inside rolling unless you must. Built ins like mean, sum, std, and quantile use vectorised C code and are tens of times faster.

For groupby plus rolling, use groupby(...).rolling(...).agg(...) and then .droplevel(0) to flatten the multi-index back if you want a clean DataFrame join.

Watch memory with expanding plus many columns. Each call builds an intermediate structure, so prefer cumsum, cummax, and cummin for simple cumulative needs.

Wrap-up

Rolling, expanding, and ewm windows cover most of the per-row-with-context calculations in analytics work. Pick the family by what kind of memory you want: fixed, full, or fading. Mind the index sort, the min_periods flag, and the difference between row and time windows, and most of your time series chores collapse into a single line of code.