1. Pandas时间序列基础概念
时间序列数据是数据分析中最常见的数据类型之一,它按照时间顺序记录观测值。在金融、物联网、气象等领域都有广泛应用。Pandas作为Python数据分析的核心库,提供了强大的时间序列处理能力。
时间序列数据通常包含两个关键要素:
- 时间戳(timestamp):记录数据产生的时间点
- 观测值(observation):对应时间点的测量值
Pandas中主要使用两种数据类型处理时间序列:
Timestamp:表示单个时间点,相当于Python的datetime但功能更强大DatetimeIndex:时间戳的索引集合,用于作为DataFrame的索引
提示:在Pandas中,将普通列转换为时间序列类型后,可以解锁大量时间相关的操作方法,这是高效处理时间序列数据的第一步。
2. 时间序列数据的创建与转换
2.1 从字符串创建时间序列
实际工作中,时间数据常常以字符串形式存储。Pandas提供了to_datetime()函数进行转换:
python复制import pandas as pd
# 单个时间字符串转换
date_str = '2023-05-15 14:30:00'
timestamp = pd.to_datetime(date_str)
print(timestamp) # 输出: 2023-05-15 14:30:00
# 批量转换时间序列
date_list = ['2023-01-01', '2023-01-02', '2023-01-03']
date_series = pd.to_datetime(date_list)
print(date_series)
2.2 处理不同格式的时间字符串
当时间字符串格式不统一时,可以指定格式参数:
python复制dates = ['15/05/2023', '16/05/2023', '17/05/2023']
pd.to_datetime(dates, format='%d/%m/%Y')
常见格式符号:
%Y:4位数年份%m:月份(01-12)%d:日(01-31)%H:小时(00-23)%M:分钟(00-59)%S:秒(00-59)
2.3 从组件创建时间序列
当数据中的年、月、日等组件分开存储时,可以直接组装:
python复制df = pd.DataFrame({
'year': [2023, 2023, 2023],
'month': [5, 6, 7],
'day': [15, 20, 25],
'hour': [14, 10, 8]
})
df['datetime'] = pd.to_datetime(df[['year', 'month', 'day', 'hour']])
3. 时间序列索引操作
3.1 设置时间索引
将时间列设为索引后,可以方便地进行时间相关的切片和重采样:
python复制# 创建示例数据
data = {
'value': [10, 20, 30, 40, 50],
'date': pd.date_range('2023-01-01', periods=5, freq='D')
}
df = pd.DataFrame(data)
# 设置时间索引
df.set_index('date', inplace=True)
print(df.index) # 显示DatetimeIndex
3.2 时间索引的切片操作
时间索引支持直观的切片操作:
python复制# 选择特定日期
print(df.loc['2023-01-03'])
# 选择日期范围
print(df.loc['2023-01-02':'2023-01-04'])
# 选择特定年份/月份
print(df.loc['2023-01']) # 选择1月数据
3.3 时间范围生成
Pandas可以生成规则的时间序列:
python复制# 生成每日数据
daily = pd.date_range('2023-01-01', '2023-01-31', freq='D')
# 生成工作日数据
biz_days = pd.date_range('2023-01-01', '2023-01-31', freq='B')
# 生成每小时数据
hourly = pd.date_range('2023-01-01', periods=24, freq='H')
4. 时间序列的重采样与频率转换
4.1 降采样(低频化)
将高频数据聚合为低频数据,如日数据转为月数据:
python复制# 创建示例数据
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=90, freq='D')
data = np.random.randn(90)
ts = pd.Series(data, index=dates)
# 按月重采样(求均值)
monthly = ts.resample('M').mean()
print(monthly)
4.2 升采样(高频化)
将低频数据转为高频数据,需要注意缺失值处理:
python复制# 创建季度数据
quarterly = pd.Series([10, 20, 30, 40],
index=pd.date_range('2023-01-01', periods=4, freq='Q'))
# 转为月度数据(前向填充)
monthly = quarterly.resample('M').ffill()
4.3 常用聚合方法
重采样时可使用多种聚合方法:
mean():均值sum():求和max()/min():最大值/最小值first()/last():第一个/最后一个值ohlc():开盘、最高、最低、收盘(金融常用)
5. 时间序列的移动窗口操作
5.1 滚动统计
计算滚动窗口统计量是时间序列分析的常见操作:
python复制# 7天滚动平均值
ts.rolling(window=7).mean()
# 30天滚动标准差
ts.rolling(window=30).std()
5.2 扩展窗口统计
计算从时间序列开始到当前点的统计量:
python复制# 累计平均值
ts.expanding().mean()
# 累计最大值
ts.expanding().max()
5.3 指数加权移动平均
对近期数据给予更大权重:
python复制# 跨度20天的EWMA
ts.ewm(span=20).mean()
6. 时间序列的差分与变化率
6.1 计算差分
分析时间序列的变化量:
python复制# 一阶差分
ts.diff()
# 二阶差分
ts.diff().diff()
# 7天差分
ts.diff(periods=7)
6.2 计算百分比变化
python复制# 日变化率
ts.pct_change()
# 周变化率
ts.pct_change(periods=7)
6.3 累计变化
python复制# 累计和
ts.cumsum()
# 累计乘积
(1 + ts.pct_change()).cumprod()
7. 时区处理
7.1 时区本地化
python复制# 创建无时区时间
ts = pd.date_range('2023-01-01', periods=3, freq='D')
# 本地化为UTC
ts_utc = ts.tz_localize('UTC')
print(ts_utc)
# 转换为其他时区
ts_ny = ts_utc.tz_convert('America/New_York')
print(ts_ny)
7.2 处理夏令时
Pandas可以正确处理夏令时转换:
python复制# 跨越夏令时的日期范围
dt_range = pd.date_range('2023-03-10', '2023-03-15', freq='D', tz='America/New_York')
print(dt_range)
8. 时间序列的缺失值处理
8.1 检测缺失值
python复制# 创建有缺失值的时间序列
ts = pd.Series([1, np.nan, 3, np.nan, 5],
index=pd.date_range('2023-01-01', periods=5, freq='D'))
# 检测缺失值
print(ts.isna())
8.2 填充缺失值
python复制# 前向填充
ts.fillna(method='ffill')
# 后向填充
ts.fillna(method='bfill')
# 线性插值
ts.interpolate()
# 指定值填充
ts.fillna(0)
9. 时间序列的合并与对齐
9.1 时间序列的连接
python复制ts1 = pd.Series([1, 2, 3], index=pd.date_range('2023-01-01', periods=3, freq='D'))
ts2 = pd.Series([4, 5, 6], index=pd.date_range('2023-01-04', periods=3, freq='D'))
# 直接连接
pd.concat([ts1, ts2])
# 按时间排序后连接
pd.concat([ts1, ts2]).sort_index()
9.2 时间序列的对齐操作
python复制ts1 = pd.Series([1, 2, 3], index=pd.date_range('2023-01-01', periods=3, freq='D'))
ts2 = pd.Series([4, 5, 6], index=pd.date_range('2023-01-03', periods=3, freq='D'))
# 内连接
ts1 + ts2 # 默认是内连接,只保留共同时间点
# 外连接
ts1.add(ts2, fill_value=0) # 保留所有时间点,缺失值填0
10. 时间序列的批量操作技巧
10.1 按时间属性分组
python复制# 创建带时间索引的DataFrame
df = pd.DataFrame({
'value': np.random.randn(100),
'date': pd.date_range('2023-01-01', periods=100, freq='D')
}).set_index('date')
# 按年份分组
df.groupby(df.index.year).mean()
# 按月份分组
df.groupby(df.index.month).mean()
# 按周分组
df.groupby(df.index.week).mean()
10.2 应用自定义时间函数
python复制# 判断是否为季度末
def is_quarter_end(date):
return date.month in [3, 6, 9, 12] and date.day == date.days_in_month
df['is_q_end'] = df.index.map(is_quarter_end)
10.3 时间序列的批量计算
python复制# 计算过去7天的滚动总和
df['7d_sum'] = df['value'].rolling(7).sum()
# 计算月累计值
df['month_cumsum'] = df.groupby(pd.Grouper(freq='M'))['value'].cumsum()
11. 时间序列可视化
11.1 基础时间序列图
python复制import matplotlib.pyplot as plt
ts.plot(figsize=(12, 6), title='Time Series Plot')
plt.xlabel('Date')
plt.ylabel('Value')
plt.grid(True)
plt.show()
11.2 滚动统计可视化
python复制# 计算并绘制滚动平均值
rolling_mean = ts.rolling(window=30).mean()
rolling_std = ts.rolling(window=30).std()
plt.figure(figsize=(12, 6))
ts.plot(label='Original')
rolling_mean.plot(label='30-Day Rolling Mean', color='red')
plt.fill_between(rolling_mean.index,
rolling_mean - 2*rolling_std,
rolling_mean + 2*rolling_std,
color='red', alpha=0.1)
plt.legend()
plt.show()
11.3 季节性分解可视化
python复制from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(ts, model='additive', period=30)
result.plot()
plt.show()
12. 时间序列预测基础
12.1 简单移动平均预测
python复制# 使用过去7天的平均值预测明天
forecast = ts.rolling(window=7).mean().iloc[-1]
12.2 指数平滑预测
python复制from statsmodels.tsa.holtwinters import SimpleExpSmoothing
model = SimpleExpSmoothing(ts).fit()
forecast = model.forecast(7) # 预测未来7天
12.3 ARIMA模型基础
python复制from statsmodels.tsa.arima.model import ARIMA
model = ARIMA(ts, order=(1,1,1))
results = model.fit()
forecast = results.forecast(steps=7)
13. 实际案例:股票数据分析
13.1 加载股票数据
python复制import yfinance as yf
# 下载苹果公司股票数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-01-01')
13.2 计算技术指标
python复制# 计算20日移动平均
aapl['MA20'] = aapl['Close'].rolling(20).mean()
# 计算MACD
aapl['EMA12'] = aapl['Close'].ewm(span=12).mean()
aapl['EMA26'] = aapl['Close'].ewm(span=26).mean()
aapl['MACD'] = aapl['EMA12'] - aapl['EMA26']
aapl['Signal'] = aapl['MACD'].ewm(span=9).mean()
13.3 分析收益率
python复制# 计算日收益率
aapl['Return'] = aapl['Close'].pct_change()
# 计算累计收益率
aapl['CumReturn'] = (1 + aapl['Return']).cumprod()
14. 性能优化技巧
14.1 使用dt访问器加速
python复制# 慢速方法
df['year'] = df['date'].apply(lambda x: x.year)
# 快速方法
df['year'] = df['date'].dt.year
14.2 避免重复转换
python复制# 不好的做法 - 每次操作都转换
df[df['date'] > pd.to_datetime('2023-01-01')]
# 好的做法 - 先转换
df['date'] = pd.to_datetime(df['date'])
df[df['date'] > '2023-01-01']
14.3 使用resample替代groupby
python复制# 较慢的方法
df.groupby(pd.Grouper(key='date', freq='M')).mean()
# 较快的方法(如果date是索引)
df.resample('M').mean()
15. 常见问题与解决方案
15.1 处理不规则时间序列
python复制# 创建规则时间索引
full_index = pd.date_range(start=ts.index.min(), end=ts.index.max(), freq='D')
# 重新索引并填充缺失值
ts_reindexed = ts.reindex(full_index, method='ffill')
15.2 处理大时间跨度数据
python复制# 只加载特定时间范围
df = pd.read_csv('big_data.csv', parse_dates=['timestamp'])
df = df[(df['timestamp'] >= '2022-01-01') & (df['timestamp'] < '2023-01-01')]
15.3 处理时区混合数据
python复制# 统一时区
df['timestamp'] = df['timestamp'].dt.tz_localize('UTC').dt.tz_convert('America/New_York')
16. 高级时间序列操作
16.1 自定义工作日历
python复制from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import USFederalHolidayCalendar
us_cal = CustomBusinessDay(calendar=USFederalHolidayCalendar())
pd.date_range('2023-01-01', '2023-12-31', freq=us_cal)
16.2 时间序列的特征工程
python复制# 创建时间特征
df['hour'] = df.index.hour
df['day_of_week'] = df.index.dayofweek
df['is_weekend'] = df.index.dayofweek >= 5
df['time_sin'] = np.sin(2 * np.pi * df.index.hour/23)
df['time_cos'] = np.cos(2 * np.pi * df.index.hour/23)
16.3 多时间尺度分析
python复制# 创建多尺度聚合
agg_df = pd.DataFrame({
'daily': ts.resample('D').mean(),
'weekly': ts.resample('W').mean(),
'monthly': ts.resample('M').mean()
})
17. 时间序列数据库集成
17.1 从数据库读取时间序列
python复制import sqlalchemy
engine = sqlalchemy.create_engine('postgresql://user:pass@localhost/db')
query = """
SELECT timestamp, value
FROM measurements
WHERE timestamp BETWEEN '2023-01-01' AND '2023-12-31'
"""
df = pd.read_sql(query, engine, parse_dates=['timestamp'])
df = df.set_index('timestamp')
17.2 写入时间序列到数据库
python复制df.to_sql('processed_measurements', engine, if_exists='replace', index=True)
17.3 使用Pandas处理InfluxDB数据
python复制from influxdb import DataFrameClient
client = DataFrameClient(host='localhost', port=8086, username='user', password='pass', database='db')
query = "SELECT * FROM measurements WHERE time > now() - 30d"
df = client.query(query)['measurements']
18. 时间序列的异常检测
18.1 基于统计的异常检测
python复制# 计算Z-score
df['zscore'] = (df['value'] - df['value'].rolling(30).mean()) / df['value'].rolling(30).std()
# 标记异常值
df['is_anomaly'] = df['zscore'].abs() > 3
18.2 基于移动窗口的异常检测
python复制# 计算上下限
window = 30
df['upper'] = df['value'].rolling(window).mean() + 3 * df['value'].rolling(window).std()
df['lower'] = df['value'].rolling(window).mean() - 3 * df['value'].rolling(window).std()
# 标记异常值
df['is_anomaly'] = (df['value'] > df['upper']) | (df['value'] < df['lower'])
18.3 使用机器学习检测异常
python复制from sklearn.ensemble import IsolationForest
model = IsolationForest(contamination=0.01)
df['is_anomaly'] = model.fit_predict(df[['value']].values) == -1
19. 时间序列的相似性分析
19.1 计算时间序列距离
python复制from scipy.spatial.distance import euclidean
from fastdtw import fastdtw
distance, path = fastdtw(ts1.values, ts2.values, dist=euclidean)
19.2 时间序列聚类
python复制from sklearn.cluster import KMeans
# 创建特征矩阵
features = pd.DataFrame({
'mean': ts.rolling(7).mean(),
'std': ts.rolling(7).std()
}).dropna()
# 聚类
kmeans = KMeans(n_clusters=3)
features['cluster'] = kmeans.fit_predict(features)
19.3 模式匹配
python复制from scipy.signal import find_peaks
peaks, _ = find_peaks(ts.values, height=0, distance=7)
20. 时间序列的实时处理
20.1 流式时间序列处理
python复制class StreamingProcessor:
def __init__(self, window_size=10):
self.window = []
self.window_size = window_size
def update(self, new_value):
self.window.append(new_value)
if len(self.window) > self.window_size:
self.window.pop(0)
return np.mean(self.window)
processor = StreamingProcessor()
for value in real_time_data:
current_avg = processor.update(value)
20.2 使用Pandas处理实时数据
python复制# 初始化空的DataFrame
stream_df = pd.DataFrame(columns=['timestamp', 'value'])
# 模拟实时数据到达
for new_data in data_stream:
new_row = pd.DataFrame([{'timestamp': pd.Timestamp.now(), 'value': new_data}])
stream_df = pd.concat([stream_df, new_row], ignore_index=True)
# 实时分析
if len(stream_df) > 10:
current_avg = stream_df['value'].tail(10).mean()
20.3 与消息队列集成
python复制import pika
def callback(ch, method, properties, body):
timestamp = pd.Timestamp.now()
value = float(body)
# 处理实时数据
process_realtime_data(timestamp, value)
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.basic_consume(queue='timeseries', on_message_callback=callback, auto_ack=True)
channel.start_consuming()
