Expanding Windows
Calculate cumulative statistics that include all rows from the start up to each point using expanding().sum().
Expanding Windows is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is an Expanding Window?
An expanding window includes all rows from the very beginning of the Series up to the current row — the window grows with each new row rather than staying fixed. At row 0 it contains only one value; at row 5 it contains six values; at row 100 it contains 101 values. Expanding windows are used to compute cumulative statistics that represent the running total, running average, or running maximum from the start of the dataset to the current point in time.
import pandas as pd
import numpy as np
sales = pd.Series(
[100, 150, 120, 200, 180, 160, 220, 190],
index=pd.date_range('2024-01-01', periods=8)
)
# Expanding mean — grows with each row
df = pd.DataFrame({'sales': sales})
df['cumulative_mean'] = df['sales'].expanding().mean()
print(df)The expanding() Method
Series.expanding(min_periods=1) returns an Expanding object. The min_periods parameter (default 1) sets the minimum number of observations required for the first non-NaN result. Unlike rolling(n), the window in expanding() is not fixed — it always starts at the beginning. Chain any aggregation: .sum(), .mean(), .std(), .min(), .max().
import pandas as pd
import numpy as np
np.random.seed(42)
monthly_revenue = pd.Series(
np.random.randint(500, 1500, 12),
index=pd.date_range('2024-01', periods=12, freq='ME'),
name='revenue'
)
df = pd.DataFrame({'revenue': monthly_revenue})
df['cumsum'] = df['revenue'].expanding().sum()
df['cummean'] = df['revenue'].expanding().mean()
df['cummax'] = df['revenue'].expanding().max()
df['cummin'] = df['revenue'].expanding().min()
print(df)Pandas cumsum, cumprod, cummax, cummin
For the most common cumulative statistics, Pandas provides direct methods on Series and DataFrame without needing expanding(): cumsum(), cumprod(), cummax(), and cummin(). These are optimised implementations that are slightly faster than their expanding() equivalents. Use these shortcuts for typical business metrics like year-to-date revenue (cumsum) or all-time maximum price (cummax).
import pandas as pd
import numpy as np
prices = pd.Series(
[100, 95, 110, 105, 120, 115, 130, 125],
index=pd.date_range('2024-01-01', periods=8)
)
df = pd.DataFrame({'price': prices})
df['cummax'] = df['price'].cummax() # all-time high
df['cummin'] = df['price'].cummin() # all-time low
df['cumsum'] = df['price'].cumsum() # cumulative total
df['cumprod'] = (1 + df['price'].pct_change()).cumprod() # growth index
print(df.round(3))Year-to-Date Calculation with Expanding
A classic use of expanding windows is year-to-date (YTD) revenue. For each day, you want the sum of all revenue from January 1st of the current year up to that day. This is just cumsum() applied within each year group. Implement it with groupby(year).cumsum() — the cumsum resets at the start of each calendar year while accumulating through the year.
import pandas as pd
import numpy as np
np.random.seed(0)
dates = pd.date_range('2023-01-01', '2024-06-30', freq='ME')
daily = pd.DataFrame({
'date': dates,
'revenue': np.random.randint(100, 500, len(dates))
})
# Year-to-date cumulative revenue
daily['year'] = daily['date'].dt.year
daily['ytd_revenue'] = daily.groupby('year')['revenue'].cumsum()
daily = daily.drop(columns='year')
print(daily.tail(10).to_string(index=False))Expanding Window Standard Deviation
expanding().std() computes the running standard deviation using all data from the start to the current row. This is useful for monitoring whether the variability of a metric is increasing or decreasing over time — for example, tracking whether a product's daily sales are becoming more or less predictable as the business matures. Unlike rolling std (which only reflects recent variability), expanding std captures the full historical dispersion.
import pandas as pd
import numpy as np
np.random.seed(42)
sales = pd.Series(
np.concatenate([
np.random.normal(100, 5, 30), # stable period
np.random.normal(100, 25, 30) # volatile period
]),
index=pd.date_range('2024-01-01', periods=60)
)
df = pd.DataFrame({'sales': sales})
df['expanding_std'] = df['sales'].expanding().std()
df['rolling_30d_std'] = df['sales'].rolling(30).std()
print(df.tail(10).round(2))Comparing Rolling vs Expanding
The key distinction: rolling windows reflect recent performance (last n days) and are insensitive to long-ago data, while expanding windows incorporate all history and thus converge slowly toward stable values. Use rolling when you care about recent trends (a 7-day moving average is more responsive to recent changes than a 90-day one). Use expanding when you want all-time statistics or YTD metrics that must never forget earlier data.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(10)
data = pd.Series(
np.random.randn(60).cumsum() + 50,
index=pd.date_range('2024-01-01', periods=60)
)
df = pd.DataFrame({'value': data})
df['rolling_10'] = data.rolling(10).mean()
df['expanding'] = data.expanding().mean()
df.plot(figsize=(12, 5), title='Rolling 10-day Mean vs. Expanding Mean')
plt.ylabel('Value')
plt.show()Expanding Apply for Custom Cumulative Stats
expanding().apply(func) applies a custom function to all data from the start to the current row, just like rolling().apply(). This enables cumulative statistics not available as built-ins — for example, the cumulative coefficient of variation (std/mean), the running median, or a cumulative Sharpe ratio. The function receives a NumPy array of all values seen so far and must return a scalar.
import pandas as pd
import numpy as np
np.random.seed(0)
returns = pd.Series(
np.random.normal(0.001, 0.02, 60),
index=pd.date_range('2024-01-01', periods=60)
)
# Cumulative Sharpe ratio (mean/std of returns)
def sharpe(arr):
if len(arr) < 2:
return float('nan')
return arr.mean() / arr.std() * (252 ** 0.5) # annualised
df = pd.DataFrame({'returns': returns})
df['cum_sharpe'] = df['returns'].expanding().apply(sharpe, raw=True)
print(df.tail(8).round(4))Expanding Windows with NaN Handling
Expanding windows skip NaN values by default — a NaN in the middle of the Series is ignored when computing the cumulative sum or mean. You can check this with skipna=True (the default in most Pandas aggregations). When you have time series with genuine missing values (e.g. no trading on weekends), expanding statistics will be computed only from the non-NaN values seen so far, which is usually the desired behaviour.
import pandas as pd
import numpy as np
data = pd.Series([10, np.nan, 20, 30, np.nan, 40, 50])
# cumsum skips NaN by default
df = pd.DataFrame({'value': data})
df['cumsum'] = df['value'].cumsum() # NaN propagates in cumsum!
df['expanding_sum'] = df['value'].expanding().sum() # NaN skipped
print(df)
print('\nNote: cumsum propagates NaN; expanding().sum() skips it.')Expanding Windows for Running Benchmarks
Expanding windows are ideal for computing running benchmarks: the all-time maximum, the best-quarter-ever, or the lowest error rate since launch. These KPIs require remembering all historical values — a rolling window would forget old records. Using cummax() or cummin() returns the running best (or worst) at each point, making it easy to track when new records were set.
import pandas as pd
import numpy as np
np.random.seed(5)
sales = pd.Series(
np.random.randint(100, 300, 20),
index=pd.date_range('2024-01-01', periods=20),
name='daily_sales'
)
df = pd.DataFrame({'sales': sales})
df['all_time_max'] = df['sales'].cummax()
df['new_record'] = df['sales'] == df['all_time_max']
print('Days where a new sales record was set:')
print(df[df['new_record']].drop(columns='new_record'))Practical Example: Cumulative Returns
In finance, cumulative returns show how much an investment has grown from the start date to each subsequent date. Starting from daily percentage returns, the cumulative product of (1 + daily_return) gives the total growth factor. An expanding product is more natural than a rolling product here — you want to track total growth since inception, not just over the last n days.
import pandas as pd
import numpy as np
np.random.seed(7)
start_price = 100
daily_returns = pd.Series(
np.random.normal(0.0005, 0.015, 252),
index=pd.date_range('2024-01-01', periods=252)
)
# Cumulative return = product of (1 + r_i)
cum_returns = (1 + daily_returns).cumprod()
portfolio_value = start_price * cum_returns
print(f'Start value: ${start_price:.2f}')
print(f'End value: ${portfolio_value.iloc[-1]:.2f}')
print(f'Total return: {(portfolio_value.iloc[-1]/start_price - 1)*100:.1f}%')When to Use Expanding vs Rolling vs cumsum
Guidelines for choosing the right approach:
- Use
cumsum() / cummax() / cummin()for simple cumulative aggregations — they are the fastest option. - Use
expanding().mean() / std()when you need cumulative running averages or variance. - Use
expanding().apply(custom_func)for complex all-time statistics not available as built-ins. - Use
rolling(n)instead of expanding when only recent history should influence the current value.
Quick Check
Test your understanding of expanding windows from this lesson.
Lesson Recap
In this lesson you learned: expanding() computes cumulative statistics over a growing window from the start of the Series, cumsum/cummax/cummin are optimised shortcuts for common cumulative operations, and expanding windows are ideal for all-time records, YTD metrics, and cumulative investment returns. Next up we explore exponentially weighted moving averages (EWMA) — giving more weight to recent observations.
Frequently asked questions
Is the “Expanding Windows” lesson free?
Yes — the full text of “Expanding Windows” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.
What will I learn in “Expanding Windows”?
Calculate cumulative statistics that include all rows from the start up to each point using expanding().sum(). You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Pandas & NumPy Academy?
No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Expanding Windows” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Pandas & NumPy Academy lesson?
Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.