Turning event streams into daily, weekly or monthly series is the standard first step of time-series analysis.
`resample(rule)` is a groupby over time buckets. It requires a DatetimeIndex (or on="ts" for a column) and a frequency alias.
Downsampling (to a coarser frequency) aggregates:
daily = events.resample("D")["amount"].sum()
monthly = events.resample("ME").agg({"amount": "sum", "user": "nunique"}).ohlc() gives open/high/low/close per bucket, useful for prices. Empty buckets appear as well (sum gives 0, mean gives NaN), which a groupby on the date wouldn't do. That gap-filling is often exactly what you want for charts and models.
Upsampling (to a finer frequency) creates new empty slots that you fill: resample("h").ffill(), .bfill(), .interpolate(), or .asfreq() to just insert NaN.
Bucket edges matter. Defaults depend on the frequency: for "D" and "h" buckets are closed and labelled on the left; for end-anchored rules like "ME", "QE", "W" they're closed and labelled on the right. closed= and label= override this, and origin=/offset= shift where buckets start (e.g. days starting at 06:00). In pandas 2.2+ month-end is "ME" and month-start "MS"; old "M" is deprecated.
Two alternatives with the same machinery:
df.groupby(["store", pd.Grouper(key="ts", freq="W")])["sales"].sum()resamples per store.df.groupby(df.ts.dt.to_period("M"))groups by calendar periods, giving period labels like2024-03.
Time zones and daylight saving apply: resampling a tz-aware series to days uses local days, so some days have 23 or 25 hours. Convert to UTC first if you need uniform buckets.
Example
import pandas as pd
events = pd.DataFrame({
"ts": pd.to_datetime(["2024-01-01 09:00", "2024-01-01 17:30", "2024-01-03 08:00",
"2024-02-10 12:00", "2024-02-11 13:00"]),
"store": ["a", "b", "a", "a", "b"],
"amount": [20, 35, 10, 50, 5],
}).set_index("ts")
daily = events["amount"].resample("D").sum()
print(daily.loc["2024-01-01":"2024-01-03"].tolist()) # [55, 0, 10] empty day kept
monthly = events.resample("ME").agg({"amount": "sum", "store": "nunique"})
print(monthly)
# amount store
# ts
# 2024-01-31 65 2
# 2024-02-29 55 2
hourly = pd.Series([1.0, 4.0], index=pd.to_datetime(["2024-01-01 00:00", "2024-01-01 03:00"]))
print(hourly.resample("h").interpolate().tolist()) # [1.0, 2.0, 3.0, 4.0]
per_store = events.groupby(["store", pd.Grouper(freq="MS")])["amount"].sum()
print(per_store.tolist()) # [30, 50, 35, 5] (a Jan, a Feb, b Jan, b Feb)Remember
resample(rule)groups by time buckets on a DatetimeIndex- Downsample with aggregations; upsample with ffill, interpolate or asfreq
- Empty buckets are included, unlike a plain groupby
- Use
pd.Grouper(freq=...)inside groupby to resample per entity
Watch out for
- Using the deprecated 'M' alias or assuming left-labelled buckets for month-end rules, then misreading which period a value belongs to.