iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Time Series

Pandas treats time-series as a first-class citizen: DatetimeIndex, frequency strings, resampling, rolling windows, offset arithmetic. Get the index right and operations like “weekly average” or “30-day rolling sum” become one-liners.

DatetimeIndex, resample, rolling, tz

EXAMPLE
import pandas as pd
import numpy as np

# 1) Build a time-series DataFrame
idx = pd.date_range(start='2024-01-01', periods=24, freq='h')
df = pd.DataFrame({
    'orders':  np.random.randint(0, 100, 24),
    'revenue': np.random.uniform(0, 10_000, 24),
}, index=idx)

df.index.name = 'ts'
print(df.head())

# 2) Parsing dates
df = pd.read_csv('events.csv', parse_dates=['occurred_at'], index_col='occurred_at')
# OR
df['occurred_at'] = pd.to_datetime(df['occurred_at'], utc=True)
df = df.set_index('occurred_at').sort_index()

# 3) Frequency strings (offsets)
# 'D'      day
# 'B'      business day
# 'W'      week (default Sunday)
# 'W-MON'  week ending Monday
# 'M'      month-end
# 'MS'     month-start
# 'Q'      quarter-end
# 'A'      year-end
# 'h'      hour
# 'min' or 'T' minute
# 's'      second
# '15min'  every 15 min
# 'D'+'h'  combine via offsets

pd.date_range('2024-01-01', '2024-01-10', freq='D')
pd.date_range('2024-01-01', periods=10, freq='15min')

# 4) Slicing by date — partial-string indexing
df['2024-01-15']                       # whole day
df['2024-01']                          # whole month
df['2024-01-15':'2024-01-20']           # range (inclusive both ends)
df.loc['2024-01-15 09:00':'2024-01-15 17:00']

# 5) Resample — change frequency
hourly = df['orders']
daily  = hourly.resample('D').sum()
weekly = hourly.resample('W').mean()
monthly = hourly.resample('MS').sum()

# Multi-aggregation
df.resample('D').agg({
    'orders':  ['sum', 'mean'],
    'revenue': ['sum', 'max'],
})

# OHLC bar (open-high-low-close) — finance idiom
df['revenue'].resample('D').ohlc()

# 6) Rolling windows
df['orders'].rolling(window=24).mean()         # 24-hour moving average
df['orders'].rolling(window='7D').sum()         # time-based window
df['orders'].rolling(24, min_periods=12).mean()  # tolerate partial windows
df['orders'].rolling(24).agg(['mean', 'min', 'max', 'std'])

# Centered window
df['orders'].rolling(24, center=True).mean()

# 7) Expanding windows — from start to current
df['orders'].expanding().sum()                  # cumulative
df['orders'].expanding(min_periods=10).mean()    # cumulative average

# 8) Shifting + diffs
df['orders_lag_1'] = df['orders'].shift(1)       # previous value
df['orders_diff']  = df['orders'].diff()         # x - x_prev
df['orders_pct']   = df['orders'].pct_change()   # (x - x_prev) / x_prev
df['orders_yoy']   = df['orders'].pct_change(periods=365 * 24)   # year-over-year (hourly data)

# 9) Resample + fill missing periods
df2 = df.resample('D').sum()                    # daily sum (zero days = 0)
df3 = df.asfreq('h', fill_value=0)              # force frequency, fill gaps
df4 = df.resample('h').asfreq()                  # resample then leave NaN

# Forward / back fill
df['orders'].resample('h').ffill()
df['orders'].resample('h').bfill()

# 10) Time zones
idx = pd.date_range('2024-01-01', periods=24, freq='h', tz='UTC')
df.tz_localize('UTC')                            # naive -> UTC
df.tz_convert('Australia/Sydney')                # convert to Sydney time
df.tz_localize(None)                              # strip tz info

# Daylight saving: tz-aware operations handle DST transitions correctly.

# 11) Offsets — add business days, month-ends
from pandas.tseries.offsets import BDay, MonthEnd, Week
next_bday = pd.Timestamp('2024-01-05') + BDay(1)
end_of_month = pd.Timestamp('2024-01-15') + MonthEnd(0)
next_friday = pd.Timestamp('2024-01-01') + Week(weekday=4)

# Custom holiday calendar
from pandas.tseries.holiday import USFederalHolidayCalendar
cal = USFederalHolidayCalendar()
biz_days = pd.bdate_range('2024-01-01', '2024-01-31', freq=pd.offsets.CustomBusinessDay(calendar=cal))

# 12) Group by time period
df.groupby(df.index.date)['orders'].sum()        # by date
df.groupby(df.index.hour)['orders'].mean()        # by hour-of-day
df.groupby([df.index.year, df.index.month])['revenue'].sum()

# 13) Truncation + flooring
ts = pd.Timestamp('2024-01-15 13:42:17')
ts.floor('h')                                     # 2024-01-15 13:00:00
ts.ceil('h')                                       # 2024-01-15 14:00:00
ts.round('h')                                      # 2024-01-15 14:00:00

df.index.floor('D')

# 14) Date range patterns
pd.date_range('2024-01-01', '2024-01-31')         # daily
pd.date_range('2024-01-01', periods=12, freq='MS')  # 12 month-starts
pd.bdate_range('2024-01-01', '2024-01-31')        # business days

# 15) Time-series plot
import matplotlib.pyplot as plt
df['orders'].plot(figsize=(12, 4))
df['orders'].rolling('7D').mean().plot()           # overlay 7-day MA
plt.show()

# 16) Statsmodels / Prophet — proper forecasting
# from statsmodels.tsa.arima.model import ARIMA
# model = ARIMA(df['orders'], order=(1, 1, 1)).fit()
# forecast = model.forecast(steps=24)
# 
# from prophet import Prophet
# m = Prophet()
# m.fit(df.reset_index().rename(columns={'index': 'ds', 'orders': 'y'}))

# 17) Common bugs
# • Forgetting to sort index → resample / rolling produce garbage; df.sort_index()
# • Naive vs tz-aware datetimes mixed → TypeError; pick one and convert
# • Resample W defaults to Sunday-ending — use W-MON or W-FRI for business cycles
# • Daylight saving boundary → hour repeats or skips; use UTC for storage, local for display
# • parse_dates with format='%Y/%m/%d' assumed but column has dashes → silent NaT; specify format
# • Index not a DatetimeIndex → resample raises TypeError
# • Rolling on irregular index without time-based window → wrong window size
# • Year arithmetic across leap years — use pd.DateOffset(years=1), not Timedelta(days=365)
# • Storing dates as strings — lose comparison + sort semantics; always to_datetime
# • Doing time math with .dt.year * 12 + .dt.month → fragile; use Period or DateOffset

Why it matters

Make the index a DatetimeIndex, store in UTC, convert to local for display. resample changes frequency, rolling(\"7D\") opens a time-based window, shift/diff/pct_change handle lags, and partial-string indexing makes df["2024-01"] a one-liner for “whole month”. Sort the index before any of this and watch DST boundaries.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import pandas as pd
df = pd.read_csv('sales.csv', parse_dates=['date']).set_index('date')
monthly = df['amount'].resample('M').sum()
last_30 = df.last('30D')
Try it Yourself »

Discussion

Loading…