时间序列的核心操作包括 重采样(resample)、滚动窗口(rolling)、扩展窗口(expanding)、指数加权移动(ewm) 以及 时区处理。
1. 重采样 resample()
将时间序列从一种频率转换为另一种频率,支持降采样和升采样。
1.1 降采样(高频 → 低频)
ts = pd.Series(
range(10),
index=pd.date_range('2024-01-01', periods=10, freq='D')
)
# 按周聚合(默认周日结束)
ts.resample('W').sum()
# 按月聚合
ts.resample('ME').mean()
# 按季度取最大值
ts.resample('QE').max()1.2 自定义聚合
ts.resample('ME').agg(['sum', 'mean', 'max'])
# 多列不同聚合
df.resample('ME').agg({'A': 'sum', 'B': 'mean'})1.3 升采样(低频 → 高频)
ts = pd.Series([100, 200, 300],
index=pd.to_datetime(['2024-01-01', '2024-02-01', '2024-03-01']))
# 按日升采样,默认产生 NaN
ts.resample('D').asfreq()
# 前向填充
ts.resample('D').ffill()
# 后向填充
ts.resample('D').bfill()
# 插值填充
ts.resample('D').interpolate()1.4 resample() 参数
| 参数 | 说明 |
|---|---|
rule | 目标频率(字符串或 DateOffset) |
label | 聚合标签:'right'(默认,区间右端点)或 'left' |
closed | 区间闭合:'right'(默认)或 'left' |
origin | 对齐起点 |
on | 若索引非时间,指定时间列 |
axis | 重采样轴(默认 0) |
# 左端点标签 + 左闭合
ts.resample('W', label='left', closed='left').sum()1.5 与 groupby 结合
# 按组 + 时间重采样
df.groupby('地区').resample('ME')['销售额'].sum()2. 滚动窗口 rolling()
计算固定窗口内的滚动统计量,常用于移动平均、波动率等。
2.1 基本用法
ts = pd.Series(range(100), index=pd.date_range('2024-01-01', periods=100, freq='D'))
ts.rolling(window=7).mean() # 7 日移动平均
ts.rolling(7).sum()
ts.rolling(7).std()
ts.rolling(7).max()2.2 rolling() 参数
| 参数 | 说明 |
|---|---|
window | 窗口大小(整数或时间偏移) |
min_periods | 最小非空观测数(默认等于窗口) |
center | 窗口是否居中(默认 False) |
win_type | 加权窗口类型(如 'gaussian') |
on | 指定列作为时间 |
closed | 区间闭合方向 |
# 按天数窗口
ts.rolling('7D').mean()
# 最小非空观测数
ts.rolling(7, min_periods=3).mean()
# 居中窗口
ts.rolling(7, center=True).mean()2.3 自定义聚合函数
ts.rolling(7).apply(lambda x: x.max() - x.min())
ts.rolling(7).agg(['mean', 'std', 'skew', 'kurt'])2.4 多列滚动
df.rolling(7).mean()3. 扩展窗口 expanding()
从数据起点到当前点的累积窗口,用于计算累积统计量。
ts = pd.Series([1, 2, 3, 4, 5])
ts.expanding().sum() # 累积和(等价于 cumsum)
ts.expanding().mean() # 累积均值
ts.expanding().max() # 累积最大值
ts.expanding().std() # 累积标准差expanding() 参数
| 参数 | 说明 |
|---|---|
min_periods | 最小非空观测数(默认 1) |
axis | 计算轴 |
ts.expanding(min_periods=3).sum() # 前两个值为 NaN4. 指数加权移动 ewm()
对历史数据赋予指数衰减权重,近期数据权重更高。
4.1 基本用法
ts = pd.Series([1, 2, 3, 4, 5])
ts.ewm(span=3).mean() # 指数加权均值
ts.ewm(span=3).std() # 指数加权标准差4.2 ewm() 参数
| 参数 | 说明 |
|---|---|
span | 衰减因子(span = 2/alpha - 1) |
alpha | 平滑系数(0~1,直接指定) |
com | 质心(com = (1 - alpha)/alpha) |
halflife | 半衰期 |
min_periods | 最小非空观测数 |
adjust | 是否调整权重(默认 True) |
ignore_na | 是否忽略 NaN(默认 False) |
ts.ewm(alpha=0.3).mean()
ts.ewm(halflife=3).mean()5. 时区处理
5.1 本地化(添加时区信息)
ts = pd.Timestamp('2024-01-01 12:00:00')
ts_local = ts.tz_localize('UTC')
# 2024-01-01 12:00:00+00:005.2 转换时区
ts_local.tz_convert('Asia/Shanghai')
# 2024-01-01 20:00:00+08:005.3 Series / DataFrame 时区操作
s = pd.Series(
[1, 2, 3],
index=pd.date_range('2024-01-01', periods=3, freq='D', tz='UTC')
)
s.tz_convert('Asia/Shanghai')
s.index.tz # <UTC>
s.tz_localize(None) # 移除时区信息5.4 处理不明确/不存在的时间
# 夏令时切换时,本地化可能出现歧义
ts_ambiguous = pd.date_range('2024-03-31 01:30', periods=2, freq='h')
ts_ambiguous.tz_localize('Europe/Berlin', ambiguous='infer')
# 不存在的本地时间(如春令时跳过的时段)
ts_nonexistent = pd.date_range('2024-03-31 02:30', periods=1, freq='h')
ts_nonexistent.tz_localize('Europe/Berlin', nonexistent='shift_forward')| 参数 | 说明 |
|---|---|
ambiguous | 'raise'、'infer'、'NaT'、Bool 数组 |
nonexistent | 'raise'、'shift_forward'、'shift_backward'、'NaT' |
6. 其他时间序列操作
差分与变化率
ts.diff() # 一阶差分(当前-前值)
ts.pct_change() # 百分比变化滞后与超前
ts.shift(1) # 前一期
ts.shift(-1) # 后一期时间序列重排
# 转成 DataFrame
ts.to_frame()
# 转成周期索引
ts.to_period('M')缺失时间点处理
# 构建完整索引
full_idx = pd.date_range(ts.index.min(), ts.index.max(), freq='D')
ts = ts.reindex(full_idx).ffill()时间序列填充与插值
ts.asfreq('D') # 创建 NaN
ts.ffill() # 前向填充
ts.bfill() # 后向填充
ts.interpolate(method='time') # 时间插值本章小结
- 时间戳:
Timestamp单点时间,属性与运算丰富- 时间差:
Timedelta持续时间,支持复合运算- 周期:
Period时间段,适合月度/季度统计- 时间索引:
DatetimeIndex、TimedeltaIndex、PeriodIndex支持灵活切片- 重采样:
resample实现频率转换与聚合- 窗口计算:
rolling、expanding、ewm完成动态统计- 时区处理:
tz_localize/tz_convert管理时区,处理缺失/歧义时间