时间序列:日期处理与重采样实战详解(十)

一、引言:时间序列分析的四大核心能力

在数据分析领域,80%的业务数据都包含时间维度。掌握以下四大核心能力,你将能处理90%的时间序列场景:

  1. 日期格式转换:处理多源异构时间数据
  2. 重采样聚合:实现多粒度数据分析
  3. 时区智能处理:全球化数据对齐
  4. 滑动窗口计算:揭示时间维度规律

本文以股票数据为案例,手把手带你掌握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至今的纳秒数。这种设计带来两个优势:

  1. 高效计算:日期运算转换为整数运算
  2. 高精度:支持纳秒级时间精度

数学表达式:
DateTime = T e p o c h + N × 1 0 − 9 \text{DateTime} = T_{epoch} + N \times 10^{-9} DateTime=Tepoch+N×109
其中 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 时区本地化三部曲

  1. 识别时区:确定原始数据的时区
  2. 本地化:将时区信息附加到时间戳
  3. 转换:切换到目标时区
# 原始数据假设为纽约时间
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α)EMAt1
其中 α = 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 进阶学习路径

  1. 时间序列预测:Prophet、ARIMA、LSTM
  2. 事件驱动分析:财报日期、分红除权处理
  3. 高频数据处理:Tick数据清洗、订单簿分析
  4. 多时间序列分析:协整分析、配对交易