Q&A library Python

How do I apply a custom function over a rolling window in pandas?

Python Advanced applyrollingwindow

Short answer

Use rolling(n).apply(func) with a function that reduces each window to a single number. It is slow because it runs Python per window, so prefer the built-in rolling methods where one exists.

The short version

Use .rolling(n).apply(func), where func takes each window as a Series (or array) and returns one number. It works for any custom calculation, but it runs a Python call per window, so it is slow. Use the built-in rolling methods (mean, sum, std, max) whenever one covers your need, because those run in compiled code.

A custom window calculation

import pandas as pd

s = pd.Series([1, 2, 3, 4])

spread = s.rolling(2).apply(lambda w: w.max() - w.min())
print(spread.tolist())   # [nan, 1.0, 1.0, 1.0]

Each window of two values is reduced to its range. The first entry is NaN because a full window of two is not yet available.

The function must return a single number

rolling.apply expects a scalar back from each window. Returning a Series or an array raises. If you need several outputs per window, run several rolling calls, one per output, rather than trying to return them together.

Why it is slow

Every window is passed into your Python function individually, so a frame with a million rows means a million Python calls. The built-in methods instead compute the whole rolling statistic in one compiled pass, which is orders of magnitude faster. The difference is easy to feel on real data.

s.rolling(2).mean()      # fast, compiled
s.rolling(2).apply(lambda w: w.mean())   # same answer, far slower

If a built-in exists, always use it.

Speeding up a genuine custom function

If your calculation really has no built-in equivalent, raw=True passes each window as a NumPy array rather than a Series, which is noticeably faster because it skips building a Series each time.

s.rolling(2).apply(lambda w: w.max() - w.min(), raw=True)

Your function must then work on arrays rather than Series, which is usually a small change and worth it.

Composing built-ins instead

Many apparently custom calculations can be expressed with built-ins combined.

spread = s.rolling(2).max() - s.rolling(2).min()   # same result, much faster

This is the same range calculation, done as two fast rolling operations and a subtraction. Before writing an apply, ask whether you can build the result from the built-in statistics, because you usually can.

The usual reminders

The data must be sorted, or the windows combine rows that are not really adjacent. Use min_periods to control whether partial windows produce a value. And combine with groupby when windows must not span across groups. These apply to rolling.apply exactly as they do to the built-in rolling methods.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project