Skip to content

Resampling and Frequency Conversion

Resampling converts a time series from one frequency to another:

  • Downsampling — high frequency → low frequency (minute data → daily totals). This always needs an aggregation, because many points collapse into one bucket.
  • Upsampling — low frequency → high frequency (weekly data → daily rows). This creates new rows, which start out empty unless you fill them.

.resample() has a similar API to .groupby(): call resample() to define the buckets, then call an aggregation like .sum() or .mean().

Minute data into 5-minute buckets
import pandas as pd
 
idx = pd.date_range("2024-01-01", periods=12, freq="min")
ts = pd.Series(range(12), index=idx)
 
print(ts.resample("5min").sum())

Two choices control how buckets are formed:

  • closed — which edge of each interval is inclusive ("left" or "right").
  • label — whether the result is labeled with the left or right edge.
closed and label options
print(ts.resample("5min", closed="right").sum())
print(ts.resample("5min", closed="right", label="right").sum())

For price data, a common summary per bucket is open, high, low, close — computed in one call:

OHLC resampling
print(ts.resample("5min").ohlc())

Going from weekly to daily creates rows that don’t exist yet — they start as NaN unless you tell pandas how to fill them:

Upsampling with asfreq vs ffill
weekly = pd.DataFrame(
    {"a": [1, 2]},
    index=pd.date_range("2024-01-03", periods=2, freq="W-WED"),
)
 
print(weekly.resample("D").asfreq())  # new rows are NaN
print(weekly.resample("D").ffill())   # new rows carry the last known value forward

Both split data into buckets and apply a function — the difference is what defines a bucket:

  • groupby("column") groups by the values in a column.
  • resample("2D") groups by fixed time intervals, regardless of how many rows fall into each one.
A resample call, side by side with what groupby does
sales = pd.DataFrame(
    {"amount": [10, 20, 30, 40, 50]},
    index=pd.date_range("2024-01-01", periods=5, freq="D"),
)
 
print(sales.resample("2D").sum())
  • Downsampling needs an aggregation (.sum(), .mean(), .ohlc(), …); calling .resample("5min") alone just returns a lazy resampler object, not data.
  • The default for closed/label is "left" for most frequencies but "right" for a handful ("ME", "YE", "QE", "W") — always check the output on real data.
  • Upsampling with .asfreq() introduces NaN; you must explicitly choose .ffill() or another fill strategy if you don’t want gaps.
diagram Downsampling vs upsampling mermaid
Downsampling aggregates many high-frequency points into fewer buckets; upsampling creates new, empty rows that need an explicit fill strategy.
sketch High-frequency points collapsing into buckets p5.js
Twelve minute-level points (amber) are grouped by resample into three 5-minute buckets, each collapsed to one summary bar (blue).

Exercise 1 – Downsample Minute Data to 5-Minute Sums

Section titled “Exercise 1 – Downsample Minute Data to 5-Minute Sums”

Resampling collapses data into buckets. Rolling and Moving Window Functions instead slides a window across the data without collapsing rows — great for smoothing noisy series.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading