Pandas 时间序列入门

时间序列数据是数据分析中非常常见的数据类型——股票价格、传感器读数、销售记录等。Pandas 提供了强大的时间序列处理工具,让你能够轻松地进行日期范围生成、重采样、滑动窗口计算等操作。本章将带你入门时间序列分析的基础知识。

📌 时间日期数据类型

Pandas 中使用 Timestamp 表示单个时间点,DatetimeIndex 作为一系列时间点的索引,以及 Series/DataFrame 中的 datetime64[ns] 类型。

import pandas as pd
import numpy as np

# 创建单个时间戳
ts = pd.Timestamp('2024-01-01 12:30:00')
print(ts)

# 创建日期范围
dates = pd.date_range('2024-01-01', periods=5, freq='D')
print(dates)

# 将列转换为 datetime 类型
df = pd.DataFrame({'date': ['2024-01-01', '2024-01-02', '2024-01-03']})
df['date'] = pd.to_datetime(df['date'])
print(df.dtypes)

📅 创建日期范围:pd.date_range()

date_range() 是生成时间序列索引的核心函数。

常用参数
  • start: 起始时间
  • end: 结束时间
  • periods: 时间点数量
  • freq: 频率(如 'D' 天, 'H' 小时, 'M' 月末, 'B' 工作日)
  • tz: 时区
  • normalize: 是否归一化到午夜
示例
pd.date_range('2024-01-01', '2024-01-10', freq='3D')
pd.date_range('2024-01-01', periods=10, freq='H')
pd.date_range('2024-01-01', periods=5, freq='W-MON')  # 每周一

🔍 设置时间索引

将日期列设置为索引,可以享受时间序列的便捷功能。

# 创建带日期的数据
df = pd.DataFrame({
    'value': np.random.randn(100)
}, index=pd.date_range('2024-01-01', periods=100, freq='D'))

print(df.head())

# 按年份、月份等切片
print(df['2024-01'])        # 一月份数据
print(df['2024-03-01':'2024-03-10'])  # 时间区间切片

⏳ 时间序列的索引与切片

当 DataFrame 的索引为 DatetimeIndex 时,可以使用字符串进行非常灵活的切片。

# 创建示例
df = pd.DataFrame({'sales': np.random.randint(100, 200, 365)},
                  index=pd.date_range('2024-01-01', periods=365, freq='D'))

# 切片
print(df['2024-02'])               # 整个二月
print(df['2024-03-15':'2024-04-10'])  # 区间
print(df['2024-05'])                # 五月

# 利用 .loc 进行精确选择
print(df.loc['2024-06-01'])

📈 重采样:resample()

重采样是将时间序列从一个频率转换到另一个频率的过程,常用于降采样(如日数据转月数据)或升采样(如日数据转小时数据)。

# 降采样:从日数据聚合成月数据总和
monthly = df.resample('M').sum()
print(monthly.head())

# 不同的聚合函数
df.resample('M').mean()     # 月平均
df.resample('Q').max()      # 季度最大值
df.resample('Y').ohlc()     # 年度的 OHLC(开盘、高、低、收盘)

# 升采样:从日数据插值到小时数据
hourly = df.resample('H').asfreq()  # 空值填充
hourly = df.resample('H').ffill()   # 前向填充

📊 移动窗口计算:rolling()

滑动窗口计算用于计算滚动统计量,如移动平均。

# 7天移动平均
df['rolling_mean_7'] = df['sales'].rolling(window=7).mean()

# 30天移动总和
df['rolling_sum_30'] = df['sales'].rolling(window=30).sum()

# 可以自定义窗口函数
df['rolling_max_14'] = df['sales'].rolling(window=14).max()

print(df.head(20))

🔧 时间序列常用操作

📅 提取时间成分

# 从 DatetimeIndex 中提取信息
df['year'] = df.index.year
df['month'] = df.index.month
df['day'] = df.index.day
df['dayofweek'] = df.index.dayofweek   # 周一=0
df['quarter'] = df.index.quarter

# 对于 Series/DataFrame 的 datetime 列类似
df['date'] = df.index
df['weekday_name'] = df['date'].dt.day_name()

📈 滞后与差分

# 滞后(shift)
df['sales_lag1'] = df['sales'].shift(1)   # 前一天的值
df['sales_lag7'] = df['sales'].shift(7)   # 一周前的值

# 差分(计算相邻差值)
df['sales_diff1'] = df['sales'].diff(1)   # 与前一天的差值
df['sales_diff7'] = df['sales'].diff(7)   # 与7天前的差值

🧪 综合示例:股票交易数据分析

import pandas as pd
import numpy as np

# 模拟股票数据
dates = pd.date_range('2024-01-01', periods=100, freq='B')  # 工作日
stock = pd.DataFrame({
    'price': 100 + np.cumsum(np.random.randn(100)),  # 随机游走
    'volume': np.random.randint(1000, 10000, 100)
}, index=dates)

print("原始数据:")
print(stock.head())

# 1. 重采样:月度最高价和总成交量
monthly_high = stock['price'].resample('M').max()
monthly_volume = stock['volume'].resample('M').sum()
print("\n月度统计:")
print(pd.DataFrame({'high': monthly_high, 'volume': monthly_volume}))

# 2. 计算20日移动平均和20日价格标准差
stock['ma20'] = stock['price'].rolling(window=20).mean()
stock['std20'] = stock['price'].rolling(window=20).std()

# 3. 计算布林带上轨/下轨
stock['upper'] = stock['ma20'] + 2 * stock['std20']
stock['lower'] = stock['ma20'] - 2 * stock['std20']

# 4. 计算每日收益率
stock['return'] = stock['price'].pct_change()  # 百分比变化

print("\n技术指标:")
print(stock.tail(10))

⚠️ 注意事项

频率字符串:常用的频率如 'D'(日历日)、'B'(工作日)、'H'(小时)、'T'(分钟)、'S'(秒)、'M'(月末)、'Q'(季末)、'A'(年末)等,可以组合数字,如 '3D' 表示3天。
时区处理:如果数据包含时区信息,建议使用 tz_localize()tz_convert() 处理。
缺失时间点:当使用 asfreq() 升采样时,会引入缺失值,需要填充处理。
性能:对于超长时间序列,尽量使用整数索引加偏移的方式,或使用 resamplekind='period' 参数。
最佳实践:
  • 确保时间列是 datetime64 类型,可以用 pd.to_datetime() 转换。
  • 将时间列设置为索引(set_index)能发挥 Pandas 时间序列的最大威力。
  • 熟悉常用的频率别名,能极大简化代码。
  • 绘图前将时间序列索引排序,确保曲线正确。