Pandas中时间序列分析的核心功能和实战指南
本节学习目标
- 掌握 Pandas 中日期时间类型的创建与操作
- 学会使用
date_range生成日期序列 - 掌握
resample重采样方法 - 理解
rolling滑动窗口计算 - 能够处理时区和日期偏移
为什么学这个?
几乎所有业务数据都和时间相关:
- 销售数据——按天、按周、按月汇总
- 股票数据——计算移动平均线、波动率
- 网站流量——分析用户活跃时段
- 生产数据——监测设备运行趋势
在 Pandas 出现之前,处理时间序列数据非常痛苦。你需要手动解析日期字符串、处理闰年、计算工作日……现在,Pandas 提供了一整套时间序列工具,让时间分析变得优雅而强大。
Pandas 的时间序列优势:
- 自动处理日期格式解析
- 内置丰富的日期范围生成器
- 一行代码完成"按周/月/季度重采样"
- 滑动窗口计算(移动平均、累计求和等)
打个比方:如果说 DataFrame 是一个仓库,那么时间序列工具就是仓库的"时间管理器"——它能告诉你"什么时候进了多少货"、“过去 7 天平均每天出多少货”、“下个月的预期销量”。
核心知识点讲解
datetime 类型与日期解析
1. Python 的 datetime 模块
from datetime import datetime, timedelta
# 创建日期时间对象
dt = datetime(2024, 3, 15, 14, 30, 0)
print(dt) # 2024-03-15 14:30:00
# 获取当前时间
now = datetime.now()
print(f"当前时间:{now}")
# 日期运算
tomorrow = dt + timedelta(days=1)
print(f"明天:{tomorrow}")
last_week = dt - timedelta(weeks=1)
print(f"上周:{last_week}")
# 提取各个部分
print(f"年:{dt.year}")
print(f"月:{dt.month}")
print(f"日:{dt.day}")
print(f"星期:{dt.weekday()}") # 0=周一,6=周日
print(f"时分秒:{dt.hour}:{dt.minute}:{dt.second}")
2. Pandas 的 Timestamp 类型
import pandas as pd
import numpy as np
# Pandas 的时间戳
ts = pd.Timestamp('2024-03-15 14:30:00')
print(ts)
# 从字符串自动解析
ts = pd.Timestamp('2024/3/15')
print(ts)
ts = pd.Timestamp('15 March 2024')
print(ts)
# 获取时间属性
print(f"年:{ts.year}")
print(f"月:{ts.month}")
print(f"日:{ts.day}")
print(f"星期几:{ts.day_name()}") # Friday
print(f"季度:{ts.quarter}") # 1
print(f"年初至今天数:{ts.dayofyear}") # 75
print(f"本月天数:{ts.days_in_month}") # 31
3. to_datetime:批量转换日期列
# 这是处理时间序列数据的第一步:把字符串列转为 datetime 类型
df = pd.DataFrame({
'日期': ['2024-01-01', '2024-02-15', '2024-03-20', '2024-04-10'],
'销售额': [1000, 1500, 1200, 1800]
})
print("转换前:")
print(df.dtypes)
# 日期 object(字符串)
# 转换为 datetime
df['日期'] = pd.to_datetime(df['日期'])
print("\n转换后:")
print(df.dtypes)
# 日期 datetime64[ns]
# ===== 处理不同格式的日期 =====
df2 = pd.DataFrame({
'日期': ['01/15/2024', '02/20/2024', '03/25/2024']
})
df2['日期'] = pd.to_datetime(df2['日期'], format='%m/%d/%Y')
print(df2)
# ===== 处理包含错误的日期 =====
df3 = pd.DataFrame({
'日期': ['2024-01-01', 'invalid', '2024-03-01']
})
# errors='coerce' 将无法解析的值转为 NaT(Not a Time)
df3['日期'] = pd.to_datetime(df3['日期'], errors='coerce')
print(df3)
date_range:生成日期序列
# ===== 基本用法 =====
# 生成从 2024-01-01 开始的 10 天日期序列
dates = pd.date_range(start='2024-01-01', periods=10)
print("10天序列:")
print(dates)
# 生成到 2024-01-31 结束的所有日期
dates = pd.date_range(end='2024-01-31', periods=5)
print("\n5天序列:")
print(dates)
# 指定起止日期
dates = pd.date_range('2024-01-01', '2024-01-10')
print("\n指定起止:")
print(dates)
# ===== 不同频率 =====
# freq 参数控制频率
print("每日:", pd.date_range('2024-01-01', periods=5, freq='D'))
print("每周:", pd.date_range('2024-01-01', periods=5, freq='W'))
print("每月:", pd.date_range('2024-01-01', periods=5, freq='M'))
print("每季:", pd.date_range('2024-01-01', periods=5, freq='Q'))
print("每年:", pd.date_range('2024-01-01', periods=5, freq='Y'))
print("每小时:", pd.date_range('2024-01-01', periods=5, freq='H'))
# ===== 常用频率代码 =====
# D —— 日历日
# B —— 工作日(排除周末)
# W —— 每周
# M —— 月末
# MS —— 月初
# Q —— 季末
# QS —— 季初
# Y —— 年末
# YS —— 年初
# H —— 每小时
# T/min —— 每分钟
# S —— 每秒
# ===== 工作日序列 =====
# 只生成工作日(排除周六日)
workdays = pd.date_range('2024-01-01', periods=10, freq='B')
print("\n工作日序列:")
print(workdays)
# 自定义(如每周三)
wednesdays = pd.date_range('2024-01-01', periods=10, freq='W-WED')
print("\n每周三:")
print(wednesdays)
创建带日期索引的 DataFrame
# 最常用的做法:用 date_range 创建时间序列 DataFrame
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=30, freq='D')
df = pd.DataFrame({
'销售额': np.random.randint(1000, 5000, 30),
'访客数': np.random.randint(100, 500, 30),
'转化率': np.random.uniform(0.02, 0.08, 30)
}, index=dates)
df.index.name = '日期'
print(df.head())
日期访问器(dt accessor)
转换后的 datetime 列可以通过 .dt 访问器提取各种时间属性。
df = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=10, freq='D'),
'销售额': np.random.randint(1000, 5000, 10)
})
# ===== 提取时间属性 =====
df['年'] = df['日期'].dt.year
df['月'] = df['日期'].dt.month
df['日'] = df['日期'].dt.day
df['季度'] = df['日期'].dt.quarter
df['星期'] = df['日期'].dt.day_name()
df['周几'] = df['日期'].dt.dayofweek # 0=周一
df['一年中第几天'] = df['日期'].dt.dayofyear
df['第几周'] = df['日期'].dt.isocalendar().week
df['是否月初'] = df['日期'].dt.is_month_start
df['是否月末'] = df['日期'].dt.is_month_end
print(df)
resample 重采样
resample 是时间序列分析中最核心的方法。它可以把数据从一个时间粒度转换到另一个时间粒度。
类比:就像你把"按天"的数据"放大"为"按月"看,或"缩小"为"按小时"看。
# 创建日粒度数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=90, freq='D')
df = pd.DataFrame({
'日期': dates,
'销售额': np.random.randint(1000, 5000, 90),
'访客数': np.random.randint(100, 500, 90)
}).set_index('日期')
# ===== 降采样(高频 → 低频)=====
# 按月汇总(求和)
monthly = df.resample('M')['销售额'].sum()
print("月度销售额:")
print(monthly)
# 按周求均值
weekly = df.resample('W')['销售额'].mean()
print("\n周均销售额:")
print(weekly)
# 按季度
quarterly = df.resample('Q').agg({
'销售额': 'sum',
'访客数': 'mean'
})
print("\n季度汇总:")
print(quarterly)
# ===== 升采样(低频 → 高频)=====
# 创建月度数据
monthly_df = pd.DataFrame({
'月度目标': [50000, 60000, 55000]
}, index=pd.date_range('2024-01-01', periods=3, freq='MS'))
# 升采样到日粒度(用 NaN 填充)
daily = monthly_df.resample('D').asfreq()
print("\n升采样到日粒度(前5行):")
print(daily.head())
# 用前向填充
daily_ffill = monthly_df.resample('D').ffill()
print("\n升采样 + 前向填充(前5行):")
print(daily_ffill.head())
常用重采样频率
| 代码 | 含义 | 代码 | 含义 |
|---|---|---|---|
D | 天 | W | 周 |
M | 月末 | MS | 月初 |
Q | 季末 | QS | 季初 |
Y | 年末 | YS | 年初 |
H | 小时 | T/min | 分钟 |
S | 秒 | B | 工作日 |
自定义聚合
# 按周重采样,同时做多种聚合
result = df.resample('W').agg({
'销售额': ['sum', 'mean', 'max'],
'访客数': ['sum', 'mean']
})
result.columns = ['周销售额', '周均销售额', '最高日销售', '周访客', '日均访客']
print(result.head())
rolling 滑动窗口
rolling 用于滑动窗口计算,比如计算 7 天移动平均线、30 天累计销量等。
# 继续使用上面的 df
# ===== 基本滑动窗口 =====
# 7天移动平均
df['7日移动平均'] = df['销售额'].rolling(window=7).mean()
# 30天移动平均
df['30日移动平均'] = df['销售额'].rolling(window=30).mean()
print("含移动平均:")
print(df.head(15))
# ===== 其他窗口计算 =====
# 滑动窗口求和(7天累计)
df['7天累计销售'] = df['销售额'].rolling(window=7).sum()
# 滑动窗口标准差(波动率)
df['7日波动率'] = df['销售额'].rolling(window=7).std()
# 滑动窗口最大值/最小值
df['7日最高'] = df['销售额'].rolling(window=7).max()
df['7日最低'] = df['销售额'].rolling(window=7).min()
# 滑动窗口内的排名
df['7日排名'] = df['销售额'].rolling(window=7).apply(
lambda x: pd.Series(x).rank().iloc[-1]
)
print(df.tail(10))
expanding:扩展窗口
# expanding 是"越来越大"的窗口(从开始到当前位置) # 等价于 cumsum、cummean 等累计函数 df['累计销售额'] = df['销售额'].expanding().sum() df['累计均值'] = df['销售额'].expanding().mean() df['累计最大值'] = df['销售额'].expanding().max() print(df.head(10))
ewm:指数加权移动平均
# 指数加权移动平均(最近的数据权重更大)
df['EMA_7'] = df['销售额'].ewm(span=7).mean()
df['EMA_30'] = df['销售额'].ewm(span=30).mean()
print("含 EMA:")
print(df[['销售额', 'EMA_7', 'EMA_30']].head(15))
时区处理
# ===== 设置时区 =====
dates = pd.date_range('2024-01-01', periods=5, freq='D')
print("无时区:")
print(dates)
# 设置为 UTC
dates_utc = pd.date_range('2024-01-01', periods=5, freq='D', tz='UTC')
print("\nUTC时区:")
print(dates_utc)
# ===== 时区转换 =====
dates_utc = pd.date_range('2024-01-01', periods=5, freq='H', tz='UTC')
dates_shanghai = dates_utc.tz_convert('Asia/Shanghai')
print("\n转为上海时间:")
print(dates_shanghai)
# ===== 本地化(无时区 → 有时区)=====
dates_naive = pd.date_range('2024-01-01', periods=5, freq='H')
dates_local = dates_naive.tz_localize('Asia/Shanghai')
print("\n本地化:")
print(dates_local)
日期偏移(DateOffset)
# ===== 日期偏移计算 =====
from pandas.tseries.offsets import Day, Week, MonthEnd, BusinessDay
date = pd.Timestamp('2024-03-15')
# 加 5 天
print(date + Day(5)) # 2024-03-20
# 加 2 周
print(date + Week(2)) # 2024-03-29
# 月末
print(date + MonthEnd(0)) # 2024-03-31(已经在3月,所以回到3月末)
print(date + MonthEnd(1)) # 2024-04-30
# 加 3 个工作日
print(date + BusinessDay(3)) # 2024-03-20(排除周末)
# ===== 在 DataFrame 中使用 =====
df = pd.DataFrame({
'下单日期': pd.date_range('2024-01-01', periods=5)
})
df['预计发货'] = df['下单日期'] + BusinessDay(2) # 2个工作日后
df['预计送达'] = df['下单日期'] + BusinessDay(5) # 5个工作日后
print(df)
实战练习
练习 1:股票数据移动平均分析
题目:模拟股票数据,计算多种技术指标。
# 参考答案
import pandas as pd
import numpy as np
# 模拟 60 天的股价数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=60, freq='B') # 工作日
df = pd.DataFrame({
'日期': dates,
'收盘价': 100 + np.cumsum(np.random.randn(60) * 2) # 随机游走
}).set_index('日期')
# 1. 计算 5日、10日、20日移动平均线
df['MA5'] = df['收盘价'].rolling(5).mean()
df['MA10'] = df['收盘价'].rolling(10).mean()
df['MA20'] = df['收盘价'].rolling(20).mean()
# 2. 计算日收益率
df['日收益率'] = df['收盘价'].pct_change() * 100
# 3. 计算 20日波动率(收益率的标准差)
df['20日波动率'] = df['日收益率'].rolling(20).std()
# 4. 计算月度统计
monthly = df.resample('M')['收盘价'].agg(['first', 'last', 'max', 'min'])
monthly['月涨跌'] = (monthly['last'] - monthly['first']) / monthly['first'] * 100
print("月度统计:")
print(monthly.round(2))
print("\n技术指标(后10行):")
print(df.tail(10))
练习 2:网站流量时间分析
题目:分析网站的小时级流量数据。
# 参考答案
import pandas as pd
import numpy as np
# 模拟 7 天的小时级流量
np.random.seed(42)
hours = pd.date_range('2024-03-01', periods=7*24, freq='H')
df = pd.DataFrame({
'时间': hours,
'访客数': np.random.poisson(lam=100, size=len(hours))
}).set_index('时间')
# 模拟高峰期(9-18点)
df.loc[df.index.hour.between(9, 18), '访客数'] *= 2
# 1. 每日总访客
daily = df.resample('D')['访客数'].sum()
print("每日访客:")
print(daily)
# 2. 每小时平均访客(找出高峰时段)
df['小时'] = df.index.hour
hourly_avg = df.groupby('小时')['访客数'].mean()
print("\n每小时平均访客:")
print(hourly_avg.round(1))
# 3. 24小时移动平均
df['MA24'] = df['访客数'].rolling(24).mean()
# 4. 工作日与周末对比
df['类型'] = np.where(df.index.dayofweek < 5, '工作日', '周末')
weekday_avg = df.groupby('类型')['访客数'].mean()
print("\n工作日vs周末平均:")
print(weekday_avg.round(1))
本节总结
本节我们学习了 Pandas 强大的时间序列处理能力:
日期处理:
pd.to_datetime()—— 字符串转日期.dt访问器 —— 提取年、月、日、季度等属性pd.date_range()—— 生成日期序列
重采样(resample):
- 降采样:高频 → 低频(日 → 月)
- 升采样:低频 → 高频(月 → 日)
- 配合聚合函数:
resample('M').sum()
滑动窗口(rolling):
rolling(window=N).mean()—— 移动平均expanding()—— 累计窗口ewm(span=N)—— 指数加权移动平均
时区与偏移:
tz_localize/tz_convert处理时区DateOffset进行日期偏移计算
以上就是Pandas中时间序列分析的核心功能和实战指南的详细内容,更多关于Pandas时间序列分析的资料请关注脚本之家其它相关文章!


最新评论