Resampling and Frequency Conversion
What resampling means
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().resample() has a similar API to .groupby().groupby(): call resample()resample() to define the
buckets, then call an aggregation like .sum().sum() or .mean().mean().
Downsampling: many points into one bucket
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())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:
closedclosed— which edge of each interval is inclusive ("left""left"or"right""right").labellabel— whether the result is labeled with the left or right edge.
print(ts.resample("5min", closed="right").sum())
print(ts.resample("5min", closed="right", label="right").sum())print(ts.resample("5min", closed="right").sum())
print(ts.resample("5min", closed="right", label="right").sum())OHLC: a finance-flavored aggregation
For price data, a common summary per bucket is open, high, low, close — computed in one call:
print(ts.resample("5min").ohlc())print(ts.resample("5min").ohlc())Upsampling: creating new rows
Going from weekly to daily creates rows that don’t exist yet — they start as NaNNaN
unless you tell pandas how to fill them:
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 forwardweekly = 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 forwardresample vs groupby
Both split data into buckets and apply a function — the difference is what defines a bucket:
groupby("column")groupby("column")groups by the values in a column.resample("2D")resample("2D")groups by fixed time intervals, regardless of how many rows fall into each one.
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())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())Common pitfalls
- Downsampling needs an aggregation (
.sum().sum(),.mean().mean(),.ohlc().ohlc(), …); calling.resample("5min").resample("5min")alone just returns a lazy resampler object, not data. - The default for
closedclosed/labellabelis"left""left"for most frequencies but"right""right"for a handful ("ME""ME","YE""YE","QE""QE","W""W") — always check the output on real data. - Upsampling with
.asfreq().asfreq()introducesNaNNaN; you must explicitly choose.ffill().ffill()or another fill strategy if you don’t want gaps.
Visualize it
flowchart LR A["High-frequency series
(e.g. 1-minute data)"] -->|"resample('5min').sum()"| B["Downsampled
fewer rows, aggregated"] C["Low-frequency series
(e.g. weekly data)"] -->|"resample('D').asfreq()"| D["Upsampled
more rows, mostly NaN"] D -->|".ffill()"| E["Upsampled + filled
no gaps"]
🧪 Try It Yourself
Exercise 1 – Downsample Minute Data to 5-Minute Sums
Exercise 2 – Upsample and Forward-Fill
Exercise 3 – Compute OHLC in One Call
Next
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.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
