How do I calculate a cumulative average in pandas?
Short answer
Use expanding, which grows the window from the start rather than keeping a fixed size, then apply mean or another aggregation. It answers the question of the average so far, at each point.
The short version
Use .expanding() followed by an aggregation. Unlike rolling, which uses a fixed-size window, expanding grows the window from the beginning, so each row's result is computed over everything up to and including that row. This is how you get an "average so far" or a running standard deviation.
The calculation
import pandas as pd
s = pd.Series([1, 2, 3])
s.expanding().mean().tolist() # [1.0, 1.5, 2.0]
Row 0 averages just itself. Row 1 averages 1 and 2. Row 2 averages all three. Each result uses everything seen so far.
Expanding versus rolling
rolling(3).mean() averages the last three rows and forgets anything older, which is what you want to smooth a series and track recent behaviour.
expanding().mean() never forgets, so early values keep influencing the result forever, and the line becomes progressively less sensitive to recent changes.
Choose by the question. "What has the average been so far?" is expanding. "What has it been recently?" is rolling.
Why cumsum is not the same
cumsum() gives a running total, which is expanding with sum. But there is no cummean, which is why expanding().mean() exists. You could compute it as cumsum() / (arange + 1), but expanding is clearer and handles missing values properly.
The other aggregations
Everything you can do with rolling works with expanding.
s.expanding().sum() # same as cumsum
s.expanding().max() # same as cummax
s.expanding().std() # running standard deviation
s.expanding().count() # how many observations so far
The running standard deviation is genuinely useful and has no cum equivalent, so expanding is the only way to get it.
min_periods
By default expanding produces a value from the very first row, even though a standard deviation of one observation is undefined and gives NaN. Set min_periods to require a minimum before producing anything.
s.expanding(min_periods=3).mean() # NaN until three observations exist
This matters for statistics that need several points to be meaningful. An "average so far" based on one observation is technically correct and practically useless.
Within groups
As always, group so the expansion never spans two groups.
df = df.sort_values(["city", "date"])
df["avg_so_far"] = df.groupby("city")["amount"].expanding().mean().reset_index(level=0, drop=True)
The reset_index realigns the grouped result back to the original rows. Without the grouping, one city's history would feed into another's running average.
The look-ahead warning
Expanding statistics use only past and present rows, never future ones, which makes them safe to use as features in a time-series model. That is a genuine virtue: computing a feature from the whole column (a global mean, say) leaks future information into past rows, which inflates model performance in testing and collapses in production. Expanding windows avoid that by construction.
Enjoying these?
Go from piecing together answers to real fluency.
Our courses take you from looking things up to doing the work with confidence, step by step on real data.
Browse the courses