在Python数据分析里,Pandas的时间序列能力并不是简单把日期列读进来就算完成。Pandas内部使用numpy的datetime64[ns]保存时间戳,可获得纳秒级精度;如果时间列仍是object类型,后续的rolling、resample、shift、diff等操作很容易失效。实际数据来源通常包括数据库导出的ISO格式字符串、Unix时间戳、带时区的RFC 3339格式。读CSV时若没有显式处理时间列,Pandas会把它识别为object,这是很多时间序列分析出错的起点。
一、时间列解析与格式控制
pd.to_datetime是处理杂乱时间数据的常用入口。对标准程度较高的字符串,它可以自动识别并统一为Timestamp对象:
- import pandas as pd
- dates = ['2023/01/01', 'Jan 5 2023', '15-06-2023']
- parsed_dates = pd.to_datetime(dates)
复制代码
遇到非标准格式时,应显式传入format,避免自动推断带来的错误和性能损耗:
- custom_dates = ['01-2023-15', '02-2023-28']
- pd.to_datetime(custom_dates, format='%d-%Y-%m')
复制代码
这里%d、%Y、%m分别对应日、年、月。原文强调,CSV读取阶段可以用parse_dates指定时间列,也可以用pd.to_datetime后续转换;但不要省略转换,否则时间列会停留在object类型。
二、时区本地化与转换
时区问题常见于全球化业务。Pandas区分naive time和aware time,前者不带时区,后者带时区信息。基本流程是先tz_localize本地化,再tz_convert转换到目标时区:
- naive_time = pd.Timestamp('2023-07-15 14:30')
- aware_time = naive_time.tz_localize('Asia/Shanghai')
- ny_time = aware_time.tz_convert('America/New_York')
复制代码
如果数据里混合了不同时区,直接参与计算容易产生异常。建议在清洗阶段统一时区,例如先转为UTC再处理:
- df = df.tz_convert('UTC')
复制代码
这一步的核心目的不是“显示时间”,而是让时间索引在比较、对齐和聚合时使用同一基准。
三、DatetimeIndex与重采样
时间序列的高级操作通常依赖DatetimeIndex。重采样相当于按时间桶做聚合,类似SQL按时间分组。下面把秒级数据聚合成5分钟均值:
- import numpy as np
- rng = pd.date_range('2023-01-01', periods=3600, freq='s')
- ts = pd.Series(np.random.randn(len(rng)), index=rng)
- ts.resample('5T').mean()
复制代码
金融场景常按日计算OHLC和成交量,可用agg一次完成多个统计:
- df.resample('1D').agg({
- 'price': ['first', 'max', 'min', 'last'],
- 'volume': 'sum'
- })
复制代码
滑动窗口则用于平滑和趋势分析,Pandas提供rolling、ewm、expanding等窗口:
- ts.rolling(window=30).mean()
- ts.ewm(span=30).mean()
- ts.expanding().std()
复制代码
如果窗口内需要自定义权重,可以结合apply:
- def custom_roll(x):
- return x[-1] * 0.6 + x[-2] * 0.3 + x[-3] * 0.1
- ts.rolling(window=3).apply(custom_roll)
复制代码
四、时间特征工程
把时间戳拆成多维特征,能提升模型对周期性的感知。原文给出的做法包括小时、星期、是否周末,以及小时的正弦余弦编码:
- df['hour'] = df.index.hour
- df['day_of_week'] = df.index.dayofweek
- df['is_weekend'] = df.index.dayofweek > 4
- df['time_sin'] = np.sin(2*np.pi*df.index.hour/24)
- df['time_cos'] = np.cos(2*np.pi*df.index.hour/24)
复制代码
预测任务还常构造滞后特征和差分特征:
- for lag in [1, 3, 7]:
- df[f'lag_{lag}'] = df['value'].shift(lag)
- df['diff_1'] = df['value'].diff(1)
复制代码
shift用于把历史值移到当前行,diff用于消除趋势或获得变化量。它们都要求索引或数据顺序正确,否则滞后关系会错位。
五、非均匀时间序列与多序列对齐
物联网设备常产生不均匀时间间隔。原始记录可能像下面这样:
- raw_data = {
- 'timestamp': ['2023-01-01 08:00', '2023-01-01 08:07', '2023-01-01 08:13'],
- 'temperature': [25.3, 26.1, 25.8]
- }
- df = pd.DataFrame(raw_data)
- df['timestamp'] = pd.to_datetime(df['timestamp'])
复制代码
要转换为均匀频率,可以先构造完整时间范围,再重索引并插值:
- full_range = pd.date_range(
- start=df['timestamp'].min(),
- end=df['timestamp'].max(),
- freq='5T'
- )
- df = df.set_index('timestamp').reindex(full_range)
- df['temperature'] = df['temperature'].interpolate()
复制代码
多时间序列对齐也很常见。例如每日销售数据与每小时天气数据频率不同,可以按销售时间点对天气做向前填充:
- sales = pd.Series(
- [120, 135, 118],
- index=pd.date_range('2023-01-01', periods=3, freq='D')
- )
- weather = pd.Series(
- [22, 23, 21, 20, 19, 18],
- index=pd.date_range('2023-01-01 08:00', periods=6, freq='4H')
- )
- aligned_weather = weather.reindex(sales.index, method='ffill')
复制代码
这里的ffill表示用前一个有效观测值填充对齐后的空隙,适合“低频指标对上高频时间戳”的场景。
六、性能优化与排查要点
大规模时间序列处理中,性能差异主要来自类型转换和格式推断。原文列出以下几项优化:
1. 使用pd.to_datetime(..., format=...)比自动推断快3-5倍。
2. 对固定频率数据,先创建DatetimeIndex再构建DataFrame。
3. 将resample与asfreq结合使用,例如高效下采样:
- ts.resample('1H').asfreq()
复制代码
4. 用pd.read_csv(..., parse_dates=['time_col'])替代读入后再转换。
5. 对历史数据可关闭时区检查,原文示例为:
- pd.to_datetime(..., utc=True, infer_datetime_format=True)
复制代码
原文提到,在处理某电商平台一年期用户行为日志约2.7亿条记录时,通过预定义时间列格式和批量处理,将解析时间从45分钟缩短到3分钟。关键点不是某个单行技巧,而是避免对每个时间戳单独做格式推断。
整体来看,Pandas时间序列处理的排查顺序可以归纳为:先确认时间列类型,再用to_datetime和format解析;有跨时区数据时先localize/convert并统一到UTC;需要聚合时确保DatetimeIndex,再使用resample、rolling、shift、diff;面对非均匀或多频率数据,先reindex对齐,必要时interpolate或ffill;数据量上来后,优先减少自动推断和重复转换。这样处理,才能把原文中的解析、重采样、特征工程和优化技巧落到可复用的代码流程中。 |