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() has a similar API to .groupby(): call resample() to define the
buckets, then call an aggregation like .sum() or .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())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.
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())Upsampling: creating new rows
Going from weekly to daily creates rows that don't exist yet — they start as NaN
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 forwardresample vs groupby
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.
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(),.mean(),.ohlc(), ...); calling.resample("5min")alone just returns a lazy resampler object, not data. - The default for
closed/labelis"left"for most frequencies but"right"for a handful ("ME","YE","QE","W") — always check the output on real data. - Upsampling with
.asfreq()introducesNaN; you must explicitly choose.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
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – Upsample and Forward-Fill
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Compute OHLC in One Call
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Check your understanding
Which statement describes downsampling?
Downsampling collapses many points into one bucket, such as minute data into daily totals. Upsampling creates new rows.
Show answer
A — Converting high-frequency data to a lower frequency, which needs an aggregation — Downsampling collapses many points into one bucket, such as minute data into daily totals. Upsampling creates new rows.
For ts = pd.Series(range(12)) with a minute index, what are the three values of ts.resample("5min").sum()?
The buckets hold 0 to 4 (sum 10), 5 to 9 (sum 35) and 10 to 11 (sum 21).
Show answer
A — 10, 35, 21 — The buckets hold 0 to 4 (sum 10), 5 to 9 (sum 35) and 10 to 11 (sum 21).
What is the difference between weekly.resample("D").asfreq() and weekly.resample("D").ffill()?
Upsampling creates rows that start empty. You must choose a fill such as ffill if you do not want gaps.
Show answer
A — asfreq leaves the new rows as NaN while ffill carries the last value forward — Upsampling creates rows that start empty. You must choose a fill such as ffill if you do not want gaps.
For daily sales of 10, 20, 30, 40 and 50, what does sales.resample("2D").sum() return?
The two-day buckets are 10 + 20, 30 + 40 and the leftover 50.
Show answer
A — 30, 70, 50 — The two-day buckets are 10 + 20, 30 + 40 and the leftover 50.
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.