Time Series
Time-indexed data needs calendar-aware grouping, rolling statistics, and explicit timezone rules - pandas DatetimeIndex provides the machinery.
Recipe
Quick-reference recipe card - copy-paste ready.
import pandas as pd
ts = df.set_index("ordered_at").sort_index()
daily = ts["revenue"].resample("D").sum()
roll = daily.rolling(7, min_periods=3).mean()
local = ts.index.tz_convert("America/New_York")When to reach for this:
- Sales or metrics indexed by event timestamps
- Downsampling high-frequency logs to hourly/daily KPIs
- Rolling anomaly detection on sensor streams
- Aligning multi-region data to a reporting timezone
Working Example
import pandas as pd
idx = pd.date_range("2025-01-01", periods=48, freq="h", tz="UTC")
events = pd.DataFrame(
{"revenue": range(48), "region": ["East"] * 24 + ["West"] * 24},
index=idx,
)
events.index.name = "ts"
# Daily totals per region (wide)
daily = (
events.groupby([pd.Grouper(freq="D"), "region"])["revenue"]
.sum()
.unstack(fill_value=0)
)
# 7-day rolling mean on East column
daily["East_roll7"] = daily["East"].rolling(7, min_periods=3).mean()
# Business-day reindex with forward fill for reporting gaps
biz = daily.asfreq("B").ffill()
# Display in US Eastern
eastern = events.index.tz_convert("America/New_York")
print(daily.head())
print("first Eastern hour:", eastern[0])What this demonstrates:
- UTC hourly index construction
Grouper(freq="D")for calendar daily buckets- Rolling mean with
min_periodsto avoid tiny windows asfreq+ffillfor business-day alignment
Deep Dive
How It Works
- A
DatetimeIndexstores int64 nanoseconds plus timezone metadata. resamplegroups into regular periods (rule strings likeD,ME,h).rollingslides a fixed window;expandinggrows from the start.- Timezone-naive vs aware comparisons raise in pandas 2.x - standardize at ingest.
Frequency Aliases
| Rule | Meaning |
|---|---|
h | Hourly |
D | Calendar day |
B | Business day |
ME | Month end |
W-MON | Weekly Monday |
Python Notes
import pandas as pd
# Parse mixed formats, force UTC
pd.to_datetime(series, format="mixed", utc=True)
# Shift for lag features
df["revenue_lag1"] = df["revenue"].shift(1)Gotchas
- Unsorted index -
resampleandrollingassume sorted time. Fix:df.sort_index()first. - Timezone-naive local times - DST gaps and duplicates. Fix: ingest UTC; convert with
tz_convertfor display. - Month-end vs month-start -
MSvsMEchanges KPI boundaries. Fix: document reporting calendar with stakeholders. - Rolling on sparse data - default
min_periods=windowyields many NA. Fix: lowermin_periodsknowingly or impute gaps. - Mixing PeriodIndex and Timestamp - deprecated patterns break in pandas 2.x. Fix: stick to
DatetimeIndexend-to-end.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Polars group_by_dynamic | Large lazy time buckets | Small pandas-only notebooks |
| statsmodels | ARIMA/seasonal decomposition | Simple resample KPIs |
DuckDB date_trunc | SQL-native warehouse aggregates | Single-machine Series ops |
np.convolve | Fixed FIR smoothing on ndarray | Irregular timestamps |
FAQs
Why UTC at ingest?
- Avoids DST ambiguities and eases multi-region joins.
- Convert to local timezone only in presentation layer.
What is the difference between resample and groupby Grouper?
resampleworks on DatetimeIndex directly.Grouperattaches to column keys ingroupby.
How do I fill missing dates with zero?
ts.resample("D").sum().asfreq("D", fill_value=0)How do I compute week-over-week change?
weekly = ts.resample("W").sum()
wow = weekly.pct_change()Can rolling windows be time-based?
ts.rolling("7D").mean()- Requires sorted DatetimeIndex.
How do I handle irregular events?
- Resample to regular grid, then aggregate.
- Or use
merge_asoffor as-of joins.
What does closed and label mean in resample?
closed="left"includes left bin edge.labelsets which timestamp names the bucket.
How do I detect duplicate timestamps?
ts.index.duplicated().any()- Aggregate duplicates before resample.
How do I export to Excel with dates?
- Keep datetime dtype; avoid converting to strings before
to_excel.
Does Polars replace pandas time series?
- Polars excels at scale; pandas still leads for interactive EDA.
- Both use Arrow under the hood in modern stacks.
Related
- GroupBy & Aggregation - Grouper patterns
- Cleaning & Transforming Data - parse dates at load
- Joins & Merges -
merge_asof - Performance & Memory - downsampling large series
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.