DSM
80 XP
30 minsIntermediate80 XP

Window Functions (rolling, expanding)

Daily sales jump around so much the chart looks like a seismograph — but the 7-day average reveals a clean upward trend. 'Up 12% versus last week' compares each row to a row seven positions earlier. Neither is a groupby: no rows collapse, no groups form. Each row gets an answer computed from a WINDOW of its neighbours — a third kind of computation, and the language of every trend metric you've ever read.

What you'll learn

  • Smooth noisy series with rolling(n).mean() and explain the NaN warm-up rows
  • Compute running totals and records-to-date with expanding and cumsum/cummax
  • Compare rows to prior rows with shift, diff, and pct_change
  • Choose window size as a bias-variance trade-off
  • Apply windows per group with groupby + the within-group ordering discipline

What

Window operations compute a statistic per row over a sliding or growing slice of ordered data: rolling(n) for fixed-width windows (moving averages), expanding() for everything-so-far (running totals, records-to-date), and the offset family — shift, diff, pct_change — for comparing rows to earlier rows.

Why

Time-ordered questions dominate analytics: trends, momentum, growth versus yesterday, records to date, smoothing noise. Aggregation answers 'how much overall'; windows answer 'how is it MOVING' — while keeping one row per observation so results plot and merge naturally.

Where it's used

Stock charts (50-day moving average), dashboard smoothing, week-over-week growth, cumulative revenue against targets, sensor noise filtering, and feature engineering for any model that predicts the future from the recent past.

Where this runs in production

Goldman SachsMoving averages as trading signals

The 50-day vs 200-day moving-average crossover is a classic momentum signal — rolling means over price series, computed exactly as this lesson does, at market scale.

Johns HopkinsThe 7-day case average

The pandemic dashboards the world watched plotted rolling 7-day averages of new cases, because raw daily counts swing with weekend reporting artifacts — smoothing made the trend readable.

DuolingoStreaks and engagement windows

Streak counts are expanding-window logic over daily activity, and churn models use rolling 7- and 30-day activity features per user — window functions grouped by user.

Theory

The core ideas, in plain language.

rolling(window=n) hands each row a slice of the last n rows (itself plus n−1 predecessors); the chained aggregation computes over that slice. The output has the SAME length as the input — nothing collapses — but the first n−1 rows are NaN because their window isn't full yet.
s = pd.Series([10, 12, 9, 14, 13, 16])
s.rolling(3).mean()
# [NaN, NaN, 10.33, 11.67, 12.0, 14.33]

s.rolling(3, min_periods=1).mean()   # partial windows allowed: no NaN warm-up

Row 2 averages rows 0–2; row 3 averages rows 1–3 — the window slides. min_periods=1 lets incomplete windows compute (row 0 averages just itself). Deliberate choice: NaN warm-up is honest about insufficient data; min_periods gives you numbers everywhere.

Key Concept
Window size is a smoothing dial

Small windows follow the data closely but keep the noise; large windows smooth hard but lag behind turns and blur real changes. A 7-day window kills day-of-week seasonality (each window holds one of each weekday); a 30-day window shows the quarter's shape. There is no correct size — there's a size matched to the question, and serious dashboards often show two.

s.expanding().sum()    # running total: everything from start to here
s.expanding().max()    # record-to-date
s.cumsum()             # same running total, faster shortcut
s.cummax()             # same record-to-date

expanding() is a window anchored at the start that grows one row at a time — 'all history so far'. The cum* shortcuts (cumsum, cumprod, cummax, cummin) cover the common cases directly.

s.shift(1)        # each row sees the PREVIOUS value
s.diff()          # s - s.shift(1): change vs previous
s.pct_change()    # fractional change vs previous: 0.25 = +25%
s.shift(7)        # same weekday last week

shift moves values down k positions (NaN fills the top). diff and pct_change are the pre-packaged comparisons built on it. shift(7) on daily data aligns each day with the same weekday a week earlier — the honest week-over-week comparison.

Watch out
Windows assume the rows are in order

rolling, expanding, shift, diff — all operate on POSITION. If the rows aren't sorted by time, 'previous row' is meaningless and every result is quietly wrong. Sort by the time column first, every time. And for grouped data (many stores, users, sensors in one table), compute windows per group — groupby('store')['sales'].rolling(7)... or transform-style shifts — or the window will leak across the boundary from one store's last day into the next store's first.

Analogy: Three ways to watch a road trip

The odometer is an expanding window — total distance since the start, only ever growing. The 'average speed, last 10 minutes' readout is a rolling window — a fixed slice that slides along with you, smoothing out each traffic light. And 'we did 80 km this hour versus 60 the hour before' is diff — this window against the previous one. Same journey, three lenses: total-so-far, recent-typical, and change.

Visual Learning

See the concept, then explore it.

Three window shapes over one series

Click each — what slice of the ordered data does row 5 see?

Select a type to see its full definition, operations, and data science usage.

Worked Examples

Watch it built up, one line at a time.

Very EasyA 3-day moving average

Smooth a noisy daily series and understand the NaN warm-up.

Step 1 of 3

Five days of jumpy sales.

Code
01import pandas as pd
02sales = pd.Series([10, 20, 15, 25, 30])
Practice Coding

Your turn — write the code.

Your task

A week of website visits: smooth it with a 3-day rolling mean, track the cumulative total, and find the biggest single-day jump.

Expected output
[None, None, 120.0, 140.0, 160.0, 183.3, 203.3]
[120, 270, 360, 540, 750, 910, 1150]
90.0

Write your solution in the editor on the right, then hit Run.

Exercises

Prove it. Reach 80% to complete the lesson.

Mastery Gate0% / 80% required
Easy0/2 solved

Why are the first n−1 values of s.rolling(n).mean() NaN?

Which pair computes the same thing?

Medium0/2 solved

For daily data with strong weekday/weekend patterns, why compare with shift(7) rather than shift(1)?

ScenarioA table holds daily sales for 50 stores stacked vertically (store, date, sales). An analyst sorts by date only and computes df['sales'].rolling(7).mean() for a per-store trend column.

What's wrong?

Hard0/2 solved

Compute a leak-free feature: for each day, the mean of the PREVIOUS 3 days' sales (excluding the current day). Print the result as a list (NaN as None is fine via the given print). s = pd.Series([10, 20, 30, 40, 50])

Shift first:shift(1) moves the series back one day so the window never includes the current row
Window:Day 3 averages days 0–2 (10,20,30)=20.0; day 4 averages days 1–3 (20,30,40)=30.0; three warm-up NaNs

A churn model uses rolling(7).mean() of daily activity (including the current day) as a feature, and backtests brilliantly but fails in production. The likely cause?

Complete 80% more exercises to unlock.
Interview Prep

How this shows up in real interviews.

Contrast groupby aggregation with window functions — when is each the right tool?

Show model answer

Both compute statistics over row subsets; they differ in output shape and the subset's definition. groupby partitions by key VALUE and collapses each group to one summary row — right when the question is 'how much per category': revenue by region, users by plan. Window functions define each row's subset by POSITION in an ordering — the last 7 rows, everything so far, the row 7 back — and return one value PER ROW, preserving the table's shape. That's right when the question is about movement: trend, growth, cumulative progress, records. The shapes hint at usage too: grouped summaries feed report tables; window columns sit alongside the raw data for plotting and modelling. And they compose — per-store rolling averages are groupby and rolling together: partition by entity, window within each partition.

How do you choose a rolling window size?

Show model answer

It's a smoothing dial with a trade-off on each end: small windows track the signal closely but keep the noise; large windows suppress noise but lag turning points and can smooth real events out of existence. Three anchors guide me. First, known seasonality: a 7-day window on daily data holds exactly one of each weekday, cancelling the weekly cycle — that's why 7 is the default for daily business metrics, not aesthetics. Second, the decision horizon: an ops team reacting daily needs a window short enough to show this week's problem; a strategy review can afford 28 or 90 days. Third, event visibility: if I must detect a 3-day incident, a 30-day mean will bury it. In practice I plot two or three candidate windows over the raw series and pick per audience — and often ship a short and a long window together, because their crossover itself carries signal, which is exactly how traders read the 50-versus-200-day pair.

What is temporal leakage in feature engineering, and how do window functions cause or prevent it?

Show model answer

Leakage is training a model on information that won't exist at prediction time — and time-window features are the most common source. rolling(7).mean() includes the current row, so as a feature for predicting the current day it embeds part of the answer; the backtest looks great and production disappoints. center=True is worse: the window spans future rows outright. The discipline is to make every feature answer 'what did I know at the moment of prediction?' — mechanically, shift before rolling: shift(1).rolling(7) is yesterday-backwards-seven-days, purely historical. The same reasoning extends beyond windows: fill statistics, encodings, and scalers must all be fitted on training data only. My habit for any time-indexed feature is to write down the feature's timestamp coverage explicitly — 'uses days t−7 through t−1' — because the act of writing it exposes a t hiding in the window. And I validate with a strictly walk-forward backtest, which is the harness that catches leaks the code review missed.

Common Mistakes to Avoid

1) Running windows on unsorted data — position-based operations demand time order first. 2) Letting windows cross entity boundaries in stacked tables — groupby before rolling/shift. 3) Treating the NaN warm-up as an error and dropping those rows without thought — or hiding it with min_periods=1 without noting early values average fewer points. 4) Comparing shift(1) across strong weekly seasonality — shift(7) compares like with like. 5) Including the current row in predictive features — shift(1).rolling(n), always. 6) Using center=True for anything that feeds a decision made in real time. 7) pct_change on a series containing zeros — division blows up to inf; check the denominator.

Ask the AI Tutor

Try these prompts in the AI Tutor panel: • 'ELI5: rolling vs expanding vs shift with the road-trip analogy.' • 'Show me a 7-day rolling average taming weekend dips, step by step.' • 'Quiz me: which window tool answers each business question you invent?' • 'Demonstrate leakage: same feature with and without shift(1), and why backtests lie.' • 'Interview mode: make me justify a window size for an ops dashboard.'

Glossary

Window function — computes a per-row statistic over an ordered slice of neighbouring rows. rolling(n) — fixed-width sliding window ending at each row. min_periods — how many values a window needs before producing a number. expanding() — window from the start up to each row. cumsum/cummax/cummin/cumprod — shortcuts for common expanding stats. shift(k) — displace values k positions (NaN fills the gap). diff() — change vs the previous row. pct_change() — relative change vs the previous row. center=True — window centred on the row (uses future data). Temporal leakage — features containing information unavailable at prediction time.

Recommended Resources

• Docs: pandas user guide 'Windowing operations' — rolling, expanding, ewm, and their parameters. • Read: any explainer of moving-average crossover signals to see window sizes carrying meaning in the wild. • Practice: download a stock's daily closes, plot the raw series with 7- and 30-day rolling means, and mark where they cross; then build the leak-free shift(1).rolling(7) version and compare. • Next in DSM: rolling and shift cover most needs — Apply & Transform handles the rest: custom functions over groups, and the vectorize-first judgement of when NOT to.

Recap

✓ Window functions keep one row per observation and compute over ordered neighbours — the third shape, next to element-wise ops and groupby collapses. ✓ rolling(n) slides a fixed window (NaN warm-up; min_periods to allow partial); window size is a smoothing dial matched to seasonality and decision horizon. ✓ expanding()/cumsum/cummax answer everything-so-far questions: running totals, records to date. ✓ shift(k) enables row-vs-earlier-row comparisons; diff and pct_change package the common ones; shift(7) respects weekly seasonality. ✓ Sort by time first, window within groups via groupby, and never let predictive features see the current row — shift(1).rolling(n). Next up: Apply & Transform. Built-in aggregations and windows cover 95% of needs — the last lesson of this module covers the escape hatches for the other 5%, and the vectorize-first mindset that keeps them rare.

scratchpad — preview this lesson's challenge anytime
windows_practice.pyPython
Ready
Output

Run your code to see the output here.