Python Pandas 处理时间序列的三大核心工具详解
时间序列处理是数据分析中的常见任务,Pandas 为此提供了三大核心工具:Timestamp、Timedelta 和 DatetimeIndex。下面我将详细解析这三个工具的功能、用法和实际应用场景。
1. Timestamp:时间戳对象
基本概念
Timestamp 是 Pandas 的日期时间对象,相当于 Python 标准库 datetime 的增强版。
创建方式
import pandas as pd
import numpy as np
# 创建单个Timestamp
ts1 = pd.Timestamp('2023-01-01')
print(f"ts1: {ts1}")
print(f"类型: {type(ts1)}")
ts2 = pd.Timestamp('2023-12-15 14:30:45')
print(f"\nts2: {ts2}")
# 使用datetime对象创建
from datetime import datetime
dt = datetime(2023, 6, 15, 10, 30, 45)
ts3 = pd.Timestamp(dt)
print(f"\n从datetime创建: {ts3}")
常用属性和方法
ts = pd.Timestamp('2023-06-15 14:30:45')
# 获取各个时间组件
print(f"年: {ts.year}")
print(f"月: {ts.month}")
print(f"日: {ts.day}")
print(f"小时: {ts.hour}")
print(f"分钟: {ts.minute}")
print(f"秒: {ts.second}")
print(f"星期几: {ts.dayofweek} (0=周一)")
print(f"一年中的第几天: {ts.dayofyear}")
print(f"季度: {ts.quarter}")
# 时间格式化
print(f"\n格式化输出: {ts.strftime('%Y-%m-%d %H:%M:%S')}")
# 时间运算
print(f"\n加一天: {ts + pd.Timedelta(days=1)}")
print(f"减一周: {ts - pd.Timedelta(weeks=1)}")
# 时间判断
print(f"\n是否为闰年: {ts.is_leap_year}")
print(f"是否月末: {ts.is_month_end}")
print(f"是否季末: {ts.is_quarter_end}")
print(f"是否年末: {ts.is_year_end}")
时区处理
# 创建带时区的时间戳
ts_utc = pd.Timestamp('2023-06-15 14:30:45', tz='UTC')
print(f"UTC时间: {ts_utc}")
# 时区转换
ts_beijing = ts_utc.tz_convert('Asia/Shanghai')
print(f"北京时间: {ts_beijing}")
# 本地化(添加时区)
ts_naive = pd.Timestamp('2023-06-15 14:30:45')
ts_localized = ts_naive.tz_localize('America/New_York')
print(f"纽约时间: {ts_localized}")
2. Timedelta:时间差对象
基本概念
Timedelta 表示两个时间点之间的差值,用于时间间隔计算。
创建方式
# 创建Timedelta
td1 = pd.Timedelta(days=5, hours=3, minutes=30)
print(f"td1: {td1}")
td2 = pd.Timedelta('2 days 12:30:45')
print(f"\ntd2: {td2}")
td3 = pd.Timedelta(weeks=2)
print(f"\ntd3: {td3}")
# 通过计算得到
start = pd.Timestamp('2023-01-01')
end = pd.Timestamp('2023-01-10')
td4 = end - start
print(f"\n时间差: {td4}")
print(f"类型: {type(td4)}")
常用属性和方法
td = pd.Timedelta('5 days 12:30:45')
# 获取各个时间组件
print(f"天数: {td.days}")
print(f"秒数: {td.seconds}")
print(f"总秒数: {td.total_seconds()}")
print(f"总小时数: {td.total_seconds() / 3600}")
print(f"总天数: {td.total_seconds() / (24*3600)}")
# 分解时间差
print(f"\n分解表示: {td.components}")
print(f"天: {td.components.days}, 小时: {td.components.hours}, 分钟: {td.components.minutes}")
# 时间差运算
td1 = pd.Timedelta('2 days')
td2 = pd.Timedelta('1.5 days')
print(f"\ntd1 + td2: {td1 + td2}")
print(f"td1 * 3: {td1 * 3}")
print(f"td1 / 2: {td1 / 2}")
实际应用
# 时间序列偏移
base_time = pd.Timestamp('2023-01-01')
print(f"基础时间: {base_time}")
print(f"加3天: {base_time + pd.Timedelta(days=3)}")
print(f"减6小时: {base_time - pd.Timedelta(hours=6)}")
# 批量时间计算
dates = pd.date_range('2023-01-01', periods=5, freq='D')
print(f"\n原始日期序列: {dates}")
print(f"加一周: {dates + pd.Timedelta(weeks=1)}")
3. DatetimeIndex:日期时间索引
基本概念
DatetimeIndex 是专门为时间序列数据设计的索引类型,是处理时间序列数据的基础。
创建方式
# 创建DatetimeIndex
# 方法1: 使用date_range
idx1 = pd.date_range('2023-01-01', periods=10, freq='D')
print(f"idx1:\n{idx1}")
print(f"类型: {type(idx1)}")
# 方法2: 从列表/数组创建
dates_list = ['2023-01-01', '2023-01-02', '2023-01-03']
idx2 = pd.DatetimeIndex(dates_list)
print(f"\nidx2:\n{idx2}")
# 方法3: 从Series创建
s = pd.Series(['2023-06-01', '2023-06-02', '2023-06-03'])
idx3 = pd.to_datetime(s)
print(f"\nidx3:\n{idx3}")
# 创建具有特定频率的索引
idx4 = pd.date_range('2023-01-01', periods=5, freq='2H')
print(f"\n2小时频率:\n{idx4}")
idx5 = pd.date_range('2023-01-01', periods=3, freq='MS') # 每月开始
print(f"\n每月开始:\n{idx5}")
常用属性和方法
idx = pd.date_range('2023-01-01', '2023-12-31', freq='M')
# 基本属性
print(f"索引长度: {len(idx)}")
print(f"起始时间: {idx[0]}")
print(f"结束时间: {idx[-1]}")
print(f"频率: {idx.freq}")
# 时间组件提取(向量化操作)
print(f"\n所有年份: {idx.year}")
print(f"所有月份: {idx.month}")
print(f"所有季度: {idx.quarter}")
print(f"所有星期几: {idx.dayofweek}")
print(f"是否闰年: {idx.is_leap_year}")
# 时间范围操作
print(f"\n索引范围: {idx.min()} 到 {idx.max()}")
print(f"时间跨度: {idx.max() - idx.min()}")
实际应用:创建时间序列数据
# 创建时间序列数据
idx = pd.date_range('2023-01-01', periods=100, freq='D')
data = np.random.randn(100).cumsum() # 模拟股票价格
ts = pd.Series(data, index=idx)
print(f"时间序列:\n{ts.head()}")
print(f"\n索引类型: {type(ts.index)}")
# 按时间切片
print(f"\n一月数据:\n{ts['2023-01'].head()}")
print(f"\n1月到3月数据:\n{ts['2023-01':'2023-03'].head()}")
# 按时间段查询
print(f"\n工作日数据:\n{ts[ts.index.dayofweek < 5].head()}")
# 按月分组统计
monthly_mean = ts.resample('M').mean()
print(f"\n月平均值:\n{monthly_mean.head()}")
综合应用示例
示例1:股票数据分析
# 创建模拟股票数据
np.random.seed(42)
date_rng = pd.date_range(start='2023-01-01', end='2023-12-31', freq='B') # 工作日
stock_data = pd.DataFrame({
'price': np.random.randn(len(date_rng)).cumsum() + 100,
'volume': np.random.randint(1000, 10000, len(date_rng))
}, index=date_rng)
print("股票数据:")
print(stock_data.head())
# 计算移动平均
stock_data['MA_5'] = stock_data['price'].rolling(window=5).mean()
stock_data['MA_20'] = stock_data['price'].rolling(window=20).mean()
# 按月统计
monthly_stats = stock_data.resample('M').agg({
'price': ['mean', 'min', 'max'],
'volume': 'sum'
})
print("\n月度统计:")
print(monthly_stats.head())
# 按季度分析
stock_data['quarter'] = stock_data.index.quarter
quarterly_avg = stock_data.groupby('quarter')['price'].mean()
print("\n季度平均价格:")
print(quarterly_avg)
示例2:时间序列预测预处理
# 创建有缺失值的时间序列
idx = pd.date_range('2023-01-01', periods=20, freq='D')
data = np.random.randn(20)
data[[3, 7, 12]] = np.nan # 设置缺失值
ts = pd.Series(data, index=idx)
print("原始时间序列:")
print(ts)
# 填充缺失值(前向填充)
ts_filled = ts.fillna(method='ffill')
print("\n前向填充后:")
print(ts_filled)
# 重采样为周数据
weekly_data = ts.resample('W').mean()
print("\n周平均数据:")
print(weekly_data)
# 计算变化率
returns = ts.pct_change() * 100
print("\n日收益率(%):")
print(returns.head())
示例3:时间序列特征工程
# 创建时间序列数据集
idx = pd.date_range('2023-01-01', periods=365, freq='D')
data = {
'value': np.sin(np.linspace(0, 4*np.pi, 365)) + np.random.randn(365)*0.1
}
df = pd.DataFrame(data, index=idx)
# 提取时间特征
df['year'] = df.index.year
df['month'] = df.index.month
df['day'] = df.index.day
df['dayofweek'] = df.index.dayofweek
df['dayofyear'] = df.index.dayofyear
df['weekofyear'] = df.index.isocalendar().week
df['quarter'] = df.index.quarter
df['is_month_start'] = df.index.is_month_start
df['is_month_end'] = df.index.is_month_end
# 滞后特征
df['value_lag1'] = df['value'].shift(1)
df['value_lag7'] = df['value'].shift(7)
df['value_rolling_mean_7'] = df['value'].rolling(window=7).mean()
df['value_rolling_std_7'] = df['value'].rolling(window=7).std()
print("特征工程后的数据集:")
print(df.head(10))
重要技巧和注意事项
1. 时间频率别名
# 常用频率别名
freq_aliases = {
'B': '工作日',
'D': '日历日',
'W': '周',
'M': '月结束',
'MS': '月开始',
'Q': '季结束',
'QS': '季开始',
'A/Y': '年结束',
'AS/YS': '年开始',
'H': '小时',
'T/min': '分钟',
'S': '秒'
}
2. 性能优化建议
# 使用向量化操作而不是循环
# 不推荐
results = []
for timestamp in df.index:
results.append(timestamp.month)
# 推荐
df['month'] = df.index.month
# 使用resample进行降采样/升采样
# 处理大量数据时更高效
3. 常见问题解决
# 处理时区问题
# 统一时区
df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai')
# 处理时间范围外推
extended_index = pd.date_range(
start=df.index.min() - pd.Timedelta(days=7),
end=df.index.max() + pd.Timedelta(days=7),
freq=df.index.freq
)
# 处理不规则时间序列
# 使用asfreq填充缺失时间点
df_regular = df.asfreq('D', method='pad')
总结
Pandas 时间序列处理的三大核心工具各有其独特作用:
Timestamp:处理单个时间点,提供丰富的时间属性和方法
Timedelta:处理时间间隔,支持时间运算
DatetimeIndex:作为时间序列索引,支持高效的时间切片、重采样和分组操作
熟练掌握这三个工具,可以高效地处理各种时间序列分析任务,包括数据清洗、特征工程、时间序列分析和预测等。在实际应用中,通常需要结合使用这三个工具,充分利用 Pandas 提供的向量化操作和优化功能,以处理大规模时间序列数据。