一、引言:时间序列分析的四大核心能力
在数据分析领域,80%的业务数据都包含时间维度。掌握以下四大核心能力,你将能处理90%的时间序列场景:
- 日期格式转换:处理多源异构时间数据
- 重采样聚合:实现多粒度数据分析
- 时区智能处理:全球化数据对齐
- 滑动窗口计算:揭示时间维度规律
本文以股票数据为案例,手把手带你掌握Pandas时间序列处理的完整方法论。
二、日期转换:从混沌到秩序
2.1 pd.to_datetime()
基础应用
功能:将各类日期表示统一转换为Pandas的DateTime对象
import pandas as pd
# 示例1:标准日期字符串
date_list = ['2023-01-01', '2023/02/15', '15-Mar-2023']
datetime_series = pd.to_datetime(date_list)
print(datetime_series)
# 输出:
# DatetimeIndex(['2023-01-01', '2023-02-15', '2023-03-15'], dtype='datetime64[ns]', freq=None)
2.2 高级参数详解
参数 | 类型 | 说明 |
---|---|---|
format |
str | 指定日期格式,如"%d-%m-%Y %H:%M:%S" |
errors |
str | ‘coerce’(无效转NaT),‘ignore’(保留原值) |
dayfirst |
bool | 优先解析日为前,适用于欧洲日期格式 |
yearfirst |
bool | 优先解析年为前,适用于包含年份的复杂格式 |
混合格式处理技巧:
mixed_dates = ['01-02-2023', '2023年3月15日', '12/Apr/2023']
df = pd.DataFrame({
'raw_date': mixed_dates})
# 分步解析策略
df['date'] = pd.to_datetime(
df['raw_date'],
format='mixed', # 自动检测常见格式
dayfirst=False, # 禁用日优先
errors='coerce' # 无法解析的设为NaT
)
# 处理残留异常值
df = df.dropna(subset=['date'])
2.3 原理深度解析
Pandas的DateTime对象基于numpy.datetime64
实现,其内部存储为64位整数,表示从1970-01-01至今的纳秒数。这种设计带来两个优势:
- 高效计算:日期运算转换为整数运算
- 高精度:支持纳秒级时间精度
数学表达式:
DateTime = T e p o c h + N × 1 0 − 9 \text{DateTime} = T_{epoch} + N \times 10^{-9} DateTime=Tepoch+N×10−9
其中 T e p o c h T_{epoch} Tepoch为Unix纪元时间, N N N为存储的整数值
三、重采样:时间维度的降维与升维
3.1 resample()
核心机制
概念:将时间序列从一个频率转换为另一个频率的过程
- 降采样(Downsampling):高频→低频(如日数据→周数据)
- 上采样(Upsampling):低频→高频(如月数据→日数据)
# 创建示例数据
date_rng = pd.date_range(start='2023-01-01', end='2023-03-31', freq='D')
stock_data = pd.DataFrame({
'date': date_rng,
'price': np.random.rand(len(date_rng)) * 100 + 100,
'volume': np.random.randint(1000, 10000, len(date_rng))
}).set_index('date')
# 周级重采样
weekly_data = stock_data.resample('W-MON').agg({
'price': 'mean',
'volume': 'sum'
})
3.2 金融专用重采样:OHLC模式
ohlc_data = stock_data.resample('W-MON').agg({
'price': {
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last'
},
'volume': 'sum'
})
# 扁平化列名
ohlc_data.columns = ['open', 'high', 'low', 'close', 'volume']
3.3 重采样中的对齐艺术
时间箱(Bin)对齐方式:
- 左闭合(默认):[start, end)
- 右闭合:(start, end]
# 右闭合示例
weekly_right = stock_data.resample('W-MON', closed='right').mean()
3.4 上采样中的插值方法
方法 | 说明 | 适用场景 |
---|---|---|
ffill |
前向填充 | 趋势延续型数据 |
bfill |
后向填充 | 实时数据流 |
interpolate |
线性插值 | 平稳变化数据 |
asfreq |
不填充,保留NaN | 稀疏数据处理 |
# 日数据上采样为小时数据
hourly_data = daily_data.resample('H').asfreq().interpolate(method='time')
四、时区处理:跨越时空的数据对齐
4.1 时区本地化三部曲
- 识别时区:确定原始数据的时区
- 本地化:将时区信息附加到时间戳
- 转换:切换到目标时区
# 原始数据假设为纽约时间
ny_data = stock_data.tz_localize('America/New_York')
# 转换为上海时间
sh_data = ny_data.tz_convert('Asia/Shanghai')
4.2 处理夏令时陷阱
# 处理模糊时间(夏令时切换时可能重复)
ambiguous_times = pd.date_range(
'2023-11-05 01:30:00',
periods=3,
freq='H',
tz='America/New_York'
)
# 指定模糊时间处理方式
df.tz_localize('America/New_York', ambiguous='infer')
4.3 全球市场时间对齐
# 定义各市场交易时间
market_tz = {
'NYSE': 'America/New_York',
'LSE': 'Europe/London',
'TSE': 'Asia/Tokyo'
}
# 统一转换为UTC时间
def convert_to_utc(df, market):
return df.tz_localize(market_tz[market]).tz_convert('UTC')
nyse_data = convert_to_utc(nyse_raw, 'NYSE')
lse_data = convert_to_utc(lse_raw, 'LSE')
五、滑动窗口:时间维度的特征工程
5.1 滚动窗口 vs 扩展窗口
类型 | 窗口大小 | 特点 |
---|---|---|
滚动窗口 | 固定长度 | 反映近期趋势 |
扩展窗口 | 随时间增长 | 反映长期累积效应 |
# 滚动窗口示例
stock_data['30D_MA'] = stock_data['price'].rolling(window=30).mean()
# 扩展窗口示例
stock_data['Expanding_MA'] = stock_data['price'].expanding().mean()
5.2 指数加权窗口
EMA公式:
E M A t = α ⋅ p r i c e t + ( 1 − α ) ⋅ E M A t − 1 EMA_t = \alpha \cdot price_t + (1-\alpha) \cdot EMA_{t-1} EMAt=α⋅pricet+(1−α)⋅EMAt−1
其中 α = 2 s p a n + 1 \alpha = \frac{2}{span + 1} α=span+12
# 计算12日EMA
stock_data['EMA_12'] = stock_data['price'].ewm(span=12, adjust=False).mean()
5.3 高级窗口控制
# 时间偏移窗口(处理非固定频率)
rolling_30D = stock_data['price'].rolling('30D') # 自动处理日期间隔
# 最小样本数控制
robust_ma = stock_data['price'].rolling(30, min_periods=10).mean()
六、股票数据分析实战
6.1 数据获取与清洗
import yfinance as yf
# 获取苹果公司历史数据
aapl = yf.download('AAPL',
start='2020-01-01',
end='2023-12-31',
progress=False,
auto_adjust=True) # 自动调整价格
# 清洗数据
aapl_clean = aapl.dropna().rename(columns={
'Open': 'open',
'High': 'high',
'Low': 'low',
'Close': 'close',
'Volume': 'volume'
})
6.2 多周期分析流水线
def multi_period_analysis(df):
# 日线指标
df['MA20'] = df['close'].rolling(20).mean()
df['Volatility'] = df['close'].pct_change().rolling(30).std() * np.sqrt(252)
# 周线指标
weekly = df.resample('W-MON').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
})
weekly['WMA5'] = weekly['close'].rolling(5).mean()
# 月线指标
monthly = df.resample('M').agg({
'close': 'last',
'volume': 'mean'
})
monthly['MOM'] = monthly['close'].pct_change(3)
return df, weekly, monthly
daily, weekly, monthly = multi_period_analysis(aapl_clean)
6.3 可视化分析示例
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2, 1, figsize=(12, 8))
# 价格与移动平均线
aapl_clean[['close', 'MA20']].plot(ax=ax[0], title='Price Trend')
ax[0].set_ylabel('Price (USD)')
# 波动率分析
aapl_clean['Volatility'].plot(ax=ax[1], color='red', title='Volatility')
ax[1].set_ylabel('Annualized Volatility')
plt.tight_layout()
plt.show()
七、避坑指南:常见问题解决方案
7.1 缺失日期处理
# 生成完整日期范围
full_index = pd.date_range(start=df.index.min(),
end=df.index.max(),
freq='D')
# 重新索引并填充
df = df.reindex(full_index)
df['volume'] = df['volume'].fillna(0) # 交易量填0
df['close'] = df['close'].ffill() # 价格前向填充
7.2 非等间隔数据处理
# 方法1:重采样到固定频率
regular_data = df.resample('10T').asfreq().interpolate()
# 方法2:使用时间加权窗口
df['MA_time'] = df['value'].rolling('2H').mean()
7.3 性能优化技巧
# 1. 使用数值型时间戳计算
df['timestamp'] = df.index.view('int64') // 10**9 # 转换为秒级时间戳
# 2. 禁用索引自动对齐
result = pd.merge(df1, df2, left_index=True, right_index=True, validate='one_to_one')
# 3. 使用numba加速
from numba import jit
@jit(nopython=True)
def custom_ema(values, alpha):
result = np.empty_like(values)
result[0] = values[0]
for i in range(1, len(values)):
result[i] = alpha * values[i] + (1 - alpha) * result[i-1]
return result
八、总结
8.1 技术全景图
8.2 进阶学习路径
- 时间序列预测:Prophet、ARIMA、LSTM
- 事件驱动分析:财报日期、分红除权处理
- 高频数据处理:Tick数据清洗、订单簿分析
- 多时间序列分析:协整分析、配对交易