Pandas:高级数据转换与时间序列分析
在上一篇文章中,我们介绍了 Pandas 的基础功能和核心操作。本文将深入探讨 Pandas 的高级数据转换和时间序列分析能力,这些功能对于复杂数据处理和金融、科学等领域的时间序列数据分析至关重要。
1. 高级数据转换技术
1.1 多层索引操作
多层索引(MultiIndex)是 Pandas 的强大特性,允许在行或列上创建多个层次的索引。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 创建多层索引 DataFrame
arrays = [
['北京', '北京', '上海', '上海', '广州', '广州'],
['2020', '2021', '2020', '2021', '2020', '2021']
]
index = pd.MultiIndex.from_arrays(arrays, names=['城市', '年份'])
df = pd.DataFrame({
'收入': [100, 120, 90, 110, 85, 100],
'支出': [80, 90, 70, 85, 65, 75],
'利润': [20, 30, 20, 25, 20, 25]
}, index=index)
print("多层索引DataFrame:")
print(df)
# 多层索引选择
print("\n选择特定城市的数据:")
print(df.loc['上海'])
# 跨层选择
print("\n选择所有城市2020年的数据:")
print(df.xs('2020', level='年份'))
# 多层索引排序
print("\n按索引排序:")
print(df.sort_index())
# 计算分组统计
print("\n按城市分组计算均值:")
print(df.groupby(level='城市').mean())
# 索引操作与转换
# 交换索引级别
print("\n交换索引级别:")
print(df.swaplevel('城市', '年份'))
# 堆叠操作 - 将列转为行多级索引
stacked = df.stack()
print("\n堆叠操作 (stack):")
print(stacked)
# 取消堆叠 - 将行多级索引转回列
unstacked = stacked.unstack()
print("\n取消堆叠 (unstack):")
print(unstacked)
1.2 高级分组操作
Pandas 提供了强大的分组功能,可以实现复杂的数据聚合和转换。
# 创建示例数据
df = pd.DataFrame({
'部门': ['销售', '销售', '销售', '市场', '市场', '技术', '技术', '技术'],
'季度': ['Q1', 'Q2', 'Q3', 'Q1', 'Q2', 'Q1', 'Q2', 'Q3'],
'销售额': [100, 110, 120, 80, 85, 50, 55, 60],
'成本': [70, 75, 80, 50, 55, 30, 35, 40],
'利润率': [0.3, 0.32, 0.33, 0.38, 0.35, 0.4, 0.36, 0.33]
})
print("原始数据:")
print(df)
# 使用多种聚合方法
print("\n分组并使用多种聚合方法:")
result = df.groupby('部门').agg({
'销售额': ['sum', 'mean', 'std'],
'成本': ['sum', 'mean'],
'利润率': ['mean', 'min', 'max']
})
print(result)
# 命名聚合结果
print("\n使用命名聚合:")
result_named = df.groupby('部门').agg(
总销售额=('销售额', 'sum'),
平均销售额=('销售额', 'mean'),
总成本=('成本', 'sum'),
平均利润率=('利润率', 'mean')
)
print(result_named)
# 多级分组
print("\n按部门和季度多级分组:")
multi_grouped = df.groupby(['部门', '季度']).agg({
'销售额': 'sum',
'成本': 'sum',
'利润率': 'mean'
})
print(multi_grouped)
# 分组转换
print("\n分组转换 - 计算每个部门内销售额的Z分数:")
def zscore(x):
return (x - x.mean()) / x.std()
df['销售额_Z分数'] = df.groupby('部门')['销售额'].transform(zscore)
print(df)
# 过滤分组
print("\n过滤分组 - 只保留平均销售额大于90的部门:")
filtered = df.groupby('部门').filter(lambda x: x['销售额'].mean() > 90)
print(filtered)
# 分组应用自定义函数
print("\n分组并应用自定义函数:")
def top_n(group, n=1, column='销售额'):
return group.nlargest(n, column)
top_sales = df.groupby('部门').apply(top_n, n=1, column='销售额')
print(top_sales)
1.3 高级数据透视与重塑
Pandas 的数据重塑功能使复杂数据转换变得简单。
# 创建示例数据
data = {
'日期': pd.date_range('2023-01-01', periods=12),
'城市': ['北京', '上海', '广州'] * 4,
'产品': ['A', 'B', 'A', 'B'] * 3,
'销量': np.random.randint(10, 100, 12),
'价格': np.random.randint(100, 1000, 12)
}
df = pd.DataFrame(data)
df['收入'] = df['销量'] * df['价格']
print("原始数据:")
print(df)
# 创建高级数据透视表
print("\n按城市和产品的销量透视表:")
pivot1 = pd.pivot_table(df, values='销量', index='城市', columns='产品', aggfunc='sum')
print(pivot1)
print("\n多指标透视表 - 同时分析销量和收入:")
pivot2 = pd.pivot_table(df,
values=['销量', '收入'],
index=['城市'],
columns=['产品'],
aggfunc={'销量': 'sum', '收入': 'sum'})
print(pivot2)
print("\n按日期和城市的多层透视表:")
# 提取月份作为新列
df['月份'] = df['日期'].dt.month
pivot3 = pd.pivot_table(df,
values='收入',
index=['月份'],
columns=['城市', '产品'],
aggfunc='sum',
fill_value=0)
print(pivot3)
# 使用margins添加总计
print("\n带总计的透视表:")
pivot4 = pd.pivot_table(df,
values='收入',
index=['城市'],
columns=['产品'],
aggfunc='sum',
margins=True,
margins_name='总计')
print(pivot4)
# 复杂的melt操作
print("\n宽转长格式 - 复杂melt操作:")
# 先创建一个宽格式数据
wide_df = pd.pivot_table(df,
values='销量',
index=['日期'],
columns=['城市', '产品'],
aggfunc='sum').reset_index()
print("宽格式数据:")
print(wide_df.head())
# 多级列转换为长格式
long_df = pd.melt(wide_df,
id_vars=['日期'],
value_vars=wide_df.columns[1:],
var_name=['城市', '产品'],
value_name='销量')
print("\n转换后的长格式数据:")
print(long_df.head())
# 交叉表 (cross tabulation)
print("\n城市和产品的交叉表:")
cross_tab = pd.crosstab(df['城市'], df['产品'],
values=df['收入'],
aggfunc='sum',
normalize=True, # 显示比例而非绝对值
margins=True) # 添加边际总计
print(cross_tab)
1.4 高级Apply与自定义函数
Apply、pipe和transform方法能够将自定义函数应用到DataFrame的不同部分。
# 创建示例数据
df = pd.DataFrame({
'A': np.random.randn(5),
'B': np.random.randn(5),
'C': np.random.randn(5),
'D': np.random.randn(5)
})
print("原始数据:")
print(df)
# 应用函数到每一行
print("\n应用函数到每一行 - 计算每行的标准差:")
row_stds = df.apply(np.std, axis=1)
print(row_stds)
# 应用函数到每一列
print("\n应用函数到每一列 - 计算每列的Z分数:")
def standardize(x):
return (x - x.mean()) / x.std()
df_std = df.apply(standardize)
print(df_std)
# 使用apply返回多个值
print("\n应用函数返回多个值 - 同时计算最小值和最大值:")
def min_max(x):
return pd.Series([x.min(), x.max()], index=['min', 'max'])
min_max_values = df.apply(min_max)
print(min_max_values)
# 使用applymap应用到每个元素
print("\n使用applymap对每个元素进行四舍五入:")
df_rounded = df.applymap(lambda x: round(x, 2))
print(df_rounded)
# 使用pipe构建函数链
print("\n使用pipe构建数据处理管道:")
def add_totals(dataframe):
dataframe['总和'] = dataframe.sum(axis=1)
return dataframe
def add_description(dataframe):
dataframe.loc['列总和'] = dataframe.sum()
return dataframe
result = df.pipe(add_totals).pipe(add_description)
print(result)
# 使用transform应用同样的转换保持索引对齐
print("\n使用transform进行标准化:")
normalized = df.transform(lambda x: (x - x.min()) / (x.max() - x.min()))
print(normalized)
# 复杂apply - 计算每行的加权平均
print("\n计算加权平均:")
weights = {'A': 0.3, 'B': 0.2, 'C': 0.4, 'D': 0.1}
def weighted_mean(row):
return sum(row[col] * weights[col] for col in weights)
df['加权平均'] = df.apply(weighted_mean, axis=1)
print(df)
1.5 数据集成与合并
Pandas 提供了多种方法来合并和连接复杂的数据集。
# 创建示例数据集
df1 = pd.DataFrame({
'客户ID': ['A1', 'A2', 'A3', 'A4'],
'姓名': ['张三', '李四', '王五', '赵六'],
'城市': ['北京', '上海', '广州', '深圳']
})
df2 = pd.DataFrame({
'客户ID': ['A1', 'A2', 'A5', 'A6'],
'年龄': [28, 34, 42, 31],
'职业': ['工程师', '医生', '教师', '律师']
})
df3 = pd.DataFrame({
'城市': ['北京', '上海', '广州', '深圳', '杭州'],
'省份': ['北京', '上海', '广东', '广东', '浙江'],
'人口': [2100, 2400, 1500, 1300, 900]
})
df4 = pd.DataFrame({
'客户ID': ['A1', 'A1', 'A2', 'A2', 'A3'],
'产品': ['电脑', '手机', '电视', '冰箱', '洗衣机'],
'购买日期': pd.date_range('2023-01-01', periods=5),
'金额': [8000, 6000, 5000, 4500, 3000]
})
print("数据集1 - 客户基本信息:")
print(df1)
print("\n数据集2 - 客户详细信息:")
print(df2)
print("\n数据集3 - 城市信息:")
print(df3)
print("\n数据集4 - 购买记录:")
print(df4)
# 高级合并操作
# 使用indicator查看合并来源
print("\n带indicator的合并,显示数据来源:")
merged1 = pd.merge(df1, df2, on='客户ID', how='outer', indicator=True)
print(merged1)
# 使用suffixes处理重复列名
df1_dup = pd.DataFrame({
'客户ID': ['A1', 'A2', 'A3'],
'得分': [85, 92, 78]
})
df2_dup = pd.DataFrame({
'客户ID': ['A1', 'A2', 'A4'],
'得分': [90, 88, 95]
})
print("\n使用suffixes处理重复列名:")
merged2 = pd.merge(df1_dup, df2_dup, on='客户ID', how='outer', suffixes=('_期中', '_期末'))
print(merged2)
# 多表连接
print("\n多表连接 - 客户信息、城市信息和购买记录:")
# 先合并客户基本信息和详细信息
customer_info = pd.merge(df1, df2, on='客户ID', how='outer')
# 再合并城市信息
customer_city = pd.merge(customer_info, df3, on='城市', how='left')
# 最后合并购买记录
full_data = pd.merge(customer_city, df4, on='客户ID', how='left')
print(full_data)
# 连接时使用索引
df1_idx = df1.set_index('客户ID')
df2_idx = df2.set_index('客户ID')
print("\n基于索引的合并:")
merged3 = pd.merge(df1_idx, df2_idx, left_index=True, right_index=True, how='inner')
print(merged3)
# 复杂连接条件
orders = pd.DataFrame({
'订单ID': ['O1', 'O2', 'O3', 'O4', 'O5'],
'客户ID': ['A1', 'A2', 'A3', 'A1', 'A2'],
'订单日期': pd.date_range('2023-01-01', periods=5),
'金额': [1000, 1500, 2000, 1200, 800]
})
payments = pd.DataFrame({
'支付ID': ['P1', 'P2', 'P3', 'P4', 'P5'],
'客户ID': ['A1', 'A2', 'A3', 'A1', 'A2'],
'支付日期': pd.date_range('2023-01-02', periods=5),
'支付金额': [1000, 1500, 2000, 1200, 800]
})
print("\n多条件合并 - 匹配客户和金额:")
complex_merge = pd.merge(orders, payments,
on=['客户ID', '金额'], # 同时匹配客户ID和金额
how='inner')
print(complex_merge)
# 使用pd.concat进行复杂连接
print("\n使用concat进行垂直和水平组合:")
# 垂直连接多个数据框
vertical = pd.concat([df1, df1.assign(客户ID=lambda x: ['A7', 'A8', 'A9', 'A10'])], ignore_index=True)
print("垂直连接结果:")
print(vertical)
# 水平连接多个数据框
horizontal = pd.concat([df1.set_index('客户ID'), df2.set_index('客户ID')], axis=1)
print("\n水平连接结果:")
print(horizontal)
# 使用join连接
print("\nJoin方法连接:")
joined = df1.set_index('客户ID').join(df2.set_index('客户ID'), how='outer')
print(joined)
2. 时间序列分析
2.1 时间序列基础操作
时间序列数据在金融、气象、监测等领域广泛应用,Pandas 提供了强大的时间序列分析工具。
# 创建时间序列数据
dates = pd.date_range('2023-01-01', periods=100, freq='D')
ts = pd.Series(np.random.randn(100).cumsum(), index=dates)
print("基础时间序列数据:")
print(ts.head())
# 时间序列索引和切片
print("\n通过日期索引选择单个值:")
print(ts['2023-02-01'])
print("\n日期范围切片:")
print(ts['2023-01-15':'2023-01-20'])
# 使用时间戳属性
print("\n提取时间戳的各个组成部分:")
df_time = pd.DataFrame({
'日期': dates,
'数据': ts.values
})
df_time['年'] = df_time['日期'].dt.year
df_time['月'] = df_time['日期'].dt.month
df_time['日'] = df_time['日期'].dt.day
df_time['星期'] = df_time['日期'].dt.day_name()
df_time['季度'] = df_time['日期'].dt.quarter
df_time['是否月初'] = df_time['日期'].dt.is_month_start
df_time['是否月末'] = df_time['日期'].dt.is_month_end
print(df_time.head())
# 时间序列重采样
print("\n上采样到小时频率并使用插值:")
hourly = ts.resample('H').interpolate(method='cubic')
print(hourly.head(10))
print("\n下采样到周频率:")
weekly = ts.resample('W').mean()
print(weekly.head())
print("\n下采样到月频率并计算多种统计量:")
monthly = ts.resample('M').agg(['mean', 'median', 'std', 'min', 'max'])
print(monthly.head())
# 移动数据点
print("\n时间序列数据前移2天:")
shifted_forward = ts.shift(2)
print(shifted_forward.head())
print("\n时间序列数据后移3天:")
shifted_backward = ts.shift(-3)
print(shifted_backward.head())
# 计算差分
print("\n时间序列一阶差分:")
diff1 = ts.diff()
print(diff1.head())
print("\n时间序列二阶差分:")
diff2 = ts.diff().diff()
print(diff2.head())
# 计算百分比变化
print("\n百分比变化:")
pct_change = ts.pct_change()
print(pct_change.head())
2.2 高级时间序列处理
时间序列的高级处理包括窗口运算、季节性分解、异常检测等。
# 创建示例时间序列数据
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=730, freq='D') # 两年的日数据
# 生成一个有趋势、季节性和噪声的时间序列
time = np.arange(len(dates))
trend = 0.01 * time # 线性趋势
seasonal = 5 * np.sin(2 * np.pi * time / 365.25) # 季节性成分
noise = np.random.normal(0, 1, len(dates)) # 随机噪声
signal = trend + seasonal + noise
ts = pd.Series(signal, index=dates)
print("带有趋势和季节性的时间序列:")
print(ts.head())
# 绘制时间序列
plt.figure(figsize=(12, 6))
ts.plot()
plt.title('时间序列数据')
plt.xlabel('日期')
plt.ylabel('值')
plt.grid(True)
plt.savefig('time_series_data.png')
plt.close()
# 滚动窗口计算
window_sizes = [7, 30, 90]
plt.figure(figsize=(12, 6))
plt.plot(ts.index, ts.values, label='原始数据', alpha=0.5)
for window in window_sizes:
rolling_mean = ts.rolling(window=window).mean()
plt.plot(rolling_mean.index, rolling_mean.values,
label=f'{window}天移动平均线')
plt.title('不同窗口大小的移动平均线')
plt.xlabel('日期')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.savefig('rolling_means.png')
plt.close()
# 指数加权移动平均
spans = [7, 30, 90]
plt.figure(figsize=(12, 6))
plt.plot(ts.index, ts.values, label='原始数据', alpha=0.5)
for span in spans:
ewm = ts.ewm(span=span).mean()
plt.plot(ewm.index, ewm.values,
label=f'span={span}的EWMA')
plt.title('不同span的指数加权移动平均')
plt.xlabel('日期')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.savefig('ewm_means.png')
plt.close()
# 季节性-趋势分解
try:
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition = seasonal_decompose(ts, model='additive', period=365)
# 创建图形
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, figsize=(12, 16))
decomposition.observed.plot(ax=ax1)
ax1.set_title('原始时间序列')
ax1.grid(True)
decomposition.trend.plot(ax=ax2)
ax2.set_title('趋势成分')
ax2.grid(True)
decomposition.seasonal.plot(ax=ax3)
ax3.set_title('季节性成分')
ax3.grid(True)
decomposition.resid.plot(ax=ax4)
ax4.set_title('残差')
ax4.grid(True)
plt.tight_layout()
plt.savefig('seasonal_decomposition.png')
plt.close()
print("\n季节性分解完成,图表已保存")
except ImportError:
print("\n季节性分解需要安装statsmodels库: pip install statsmodels")
# 时间序列的异常检测
# 使用移动平均和标准差进行简单异常检测
window_size = 30
rolling_mean = ts.rolling(window=window_size).mean()
rolling_std = ts.rolling(window=window_size).std()
# 计算Z分数
z_scores = (ts - rolling_mean) / rolling_std
# 标记异常点 (|Z| > 3)
outliers = ts[abs(z_scores) > 3]
# 绘制结果
plt.figure(figsize=(12, 6))
plt.plot(ts.index, ts.values, label='原始数据')
plt.plot(rolling_mean.index, rolling_mean.values, label=f'{window_size}天移动平均线', color='red')
plt.fill_between(
rolling_mean.index,
rolling_mean - 3 * rolling_std,
rolling_mean + 3 * rolling_std,
color='red', alpha=0.2, label='3σ区间'
)
plt.scatter(outliers.index, outliers.values, color='green', label='异常点', zorder=5)
plt.title('时间序列异常点检测')
plt.xlabel('日期')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.savefig('outlier_detection.png')
plt.close()
print(f"\n检测到 {len(outliers)} 个异常点")
# 时区处理
print("\n时区处理:")
# 创建一个基于UTC的时间序列
utc_dates = pd.date_range('2023-01-01', periods=5, freq='D', tz='UTC')
ts_utc = pd.Series(np.random.randn(5), index=utc_dates)
print("UTC时间序列:")
print(ts_utc)
# 转换到上海时区
ts_shanghai = ts_utc.tz_convert('Asia/Shanghai')
print("\n上海时区时间序列:")
print(ts_shanghai)
# 转换到纽约时区
ts_newyork = ts_utc.tz_convert('America/New_York')
print("\n纽约时区时间序列:")
print(ts_newyork)
# 本地时间转换为带时区的时间
local_dates = pd.date_range('2023-01-01', periods=5, freq='D')
ts_local = pd.Series(np.random.randn(5), index=local_dates)
print("\n本地时间序列:")
print(ts_local)
ts_localized = ts_local.tz_localize('Asia/Shanghai')
print("\n本地化为上海时区:")
print(ts_localized)
2.3 金融时间序列分析
金融时间序列分析包括计算收益率、波动率、风险指标等。
# 假设我们有一支股票的价格数据
# 创建一个模拟的股票价格时间序列
dates = pd.date_range('2020-01-01', periods=252, freq='B') # 一年的工作日
np.random.seed(42)
initial_price = 100
# 生成对数正态分布的价格变动
returns = np.random.normal(0.0005, 0.01, len(dates))
log_returns = np.cumsum(returns)
prices = initial_price * np.exp(log_returns)
stock_data = pd.Series(prices, index=dates)
print("股票价格时间序列:")
print(stock_data.head())
# 计算不同的收益率
# 简单收益率
simple_returns = stock_data.pct_change()
# 对数收益率
log_returns = np.log(stock_data / stock_data.shift(1))
print("\n简单收益率 vs 对数收益率:")
returns_comparison = pd.DataFrame({
'价格': stock_data,
'简单收益率': simple_returns,
'对数收益率': log_returns
})
print(returns_comparison.head())
# 计算累积收益
cumulative_simple_returns = (1 + simple_returns).cumprod() - 1
cumulative_log_returns = log_returns.cumsum()
plt.figure(figsize=(12, 6))
plt.plot(cumulative_simple_returns.index, cumulative_simple_returns.values,
label='累积简单收益率')
plt.plot(cumulative_log_returns.index, cumulative_log_returns.values,
label='累积对数收益率')
plt.title('累积收益率比较')
plt.xlabel('日期')
plt.ylabel('累积收益率')
plt.legend()
plt.grid(True)
plt.savefig('cumulative_returns.png')
plt.close()
# 计算不同时间尺度的收益率
daily_returns = simple_returns
weekly_returns = stock_data.resample('W').last().pct_change()
monthly_returns = stock_data.resample('M').last().pct_change()
print("\n不同时间尺度的平均收益率:")
print(f"日平均收益率: {daily_returns.mean():.6f}")
print(f"周平均收益率: {weekly_returns.mean():.6f}")
print(f"月平均收益率: {monthly_returns.mean():.6f}")
# 计算波动率
# 日度波动率
daily_volatility = daily_returns.std()
# 年化波动率 (假设一年252个交易日)
annualized_volatility = daily_volatility * np.sqrt(252)
print("\n波动率:")
print(f"日度波动率: {daily_volatility:.6f}")
print(f"年化波动率: {annualized_volatility:.6f}")
# 使用不同窗口大小的滚动波动率
window_sizes = [21, 63, 126] # 对应约1个月、3个月、6个月
plt.figure(figsize=(12, 6))
for window in window_sizes:
rolling_vol = daily_returns.rolling(window=window).std() * np.sqrt(252)
plt.plot(rolling_vol.index, rolling_vol.values,
label=f'{window}天滚动年化波动率')
plt.title('不同窗口大小的滚动年化波动率')
plt.xlabel('日期')
plt.ylabel('年化波动率')
plt.legend()
plt.grid(True)
plt.savefig('rolling_volatility.png')
plt.close()
# 计算风险指标
# 风险收益比 (Sharpe Ratio)
risk_free_rate = 0.02 / 252 # 假设年化无风险利率为2%
excess_returns = daily_returns - risk_free_rate
sharpe_ratio = np.sqrt(252) * excess_returns.mean() / excess_returns.std()
# 最大回撤
cumulative_returns = (1 + daily_returns).cumprod()
running_max = cumulative_returns.cummax()
drawdown = (cumulative_returns / running_max) - 1
max_drawdown = drawdown.min()
print("\n风险指标:")
print(f"Sharpe比率: {sharpe_ratio:.4f}")
print(f"最大回撤: {max_drawdown:.4f} ({max_drawdown*100:.2f}%)")
# 绘制回撤图表
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(cumulative_returns.index, cumulative_returns.values, label='累积收益')
plt.plot(running_max.index, running_max.values, label='历史最高点', linestyle='--')
plt.title('累积收益与历史最高点')
plt.legend()
plt.grid(True)
plt.subplot(2, 1, 2)
plt.fill_between(drawdown.index, drawdown.values, 0, color='red', alpha=0.3)
plt.plot(drawdown.index, drawdown.values, color='red', label='回撤')
plt.title('回撤分析')
plt.xlabel('日期')
plt.ylabel('回撤')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig('drawdown_analysis.png')
plt.close()
# 分析收益率分布
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
daily_returns.hist(bins=50)
plt.title('日收益率分布')
plt.xlabel('收益率')
plt.ylabel('频率')
plt.grid(True)
plt.subplot(1, 2, 2)
import scipy.stats as stats
stats.probplot(daily_returns.dropna(), plot=plt)
plt.title('日收益率Q-Q图')
plt.tight_layout()
plt.savefig('returns_distribution.png')
plt.close()
# 计算收益率的统计特性
returns_stats = pd.DataFrame({
'统计量': ['均值', '中位数', '标准差', '偏度', '峰度', '最小值', '最大值'],
'日收益率': [
daily_returns.mean(),
daily_returns.median(),
daily_returns.std(),
daily_returns.skew(),
daily_returns.kurt(),
daily_returns.min(),
daily_returns.max()
],
'年化': [
daily_returns.mean() * 252,
daily_returns.median() * 252,
daily_returns.std() * np.sqrt(252),
'-',
'-',
'-',
'-'
]
})
print("\n收益率统计特性:")
print(returns_stats)
2.4 时间序列预测与平滑
# 使用简单的时间序列预测方法
# 注意:高级时间序列预测通常使用专门的库,如statsmodels、prophet或机器学习方法
# 创建示例数据 - 带季节性的时间序列
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=365*2, freq='D') # 两年数据
time = np.arange(len(dates))
trend = 0.05 * time # 线性趋势
seasonal = 10 * np.sin(2 * np.pi * time / 365) # 季节性成分
noise = np.random.normal(0, 3, len(dates)) # 随机噪声
signal = trend + seasonal + noise
ts = pd.Series(signal, index=dates)
print("带有趋势和季节性的时间序列:")
print(ts.head())
# 简单指数平滑 (Single Exponential Smoothing)
def simple_exponential_smoothing(series, alpha):
"""简单指数平滑方法"""
result = [series[0]] # 初始化结果数组,第一个值使用原始值
for n in range(1, len(series)):
result.append(alpha * series[n] + (1 - alpha) * result[n-1])
return pd.Series(result, index=series.index)
# 应用简单指数平滑
alphas = [0.1, 0.3, 0.6]
plt.figure(figsize=(12, 6))
plt.plot(ts.index, ts.values, label='原始数据', alpha=0.5)
for alpha in alphas:
smoothed = simple_exponential_smoothing(ts, alpha)
plt.plot(smoothed.index, smoothed.values, label=f'α={alpha}')
plt.title('简单指数平滑')
plt.xlabel('日期')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.savefig('simple_exponential_smoothing.png')
plt.close()
# 使用Pandas的ewm进行指数平滑
alpha = 0.3
ewm_smoothed = ts.ewm(alpha=alpha).mean()
# 移动平均预测 - 使用过去n天的移动平均来预测未来一天
def moving_average_forecast(series, window):
"""使用移动平均进行一步预测"""
forecast = series.rolling(window=window).mean()
return forecast
# 使用不同窗口大小的移动平均预测
windows = [7, 30, 90]
plt.figure(figsize=(12, 6))
plt.plot(ts.index, ts.values, label='原始数据', alpha=0.5)
for window in windows:
forecast = moving_average_forecast(ts, window)
plt.plot(forecast.index, forecast.values, label=f'{window}天移动平均预测')
plt.title('移动平均预测')
plt.xlabel('日期')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.savefig('moving_average_forecast.png')
plt.close()
# 简单的预测示例:使用过去一年相同日期的值进行季节性预测
# 使用第一年的数据预测第二年
first_year = ts['2020-01-01':'2020-12-31']
second_year = ts['2021-01-01':]
# 创建预测值
forecast_dates = second_year.index
forecast_values = first_year.values
seasonal_forecast = pd.Series(forecast_values, index=forecast_dates)
# 计算预测误差
forecast_error = second_year - seasonal_forecast
mae = np.abs(forecast_error).mean()
rmse = np.sqrt((forecast_error ** 2).mean())
print("\n简单季节性预测误差:")
print(f"平均绝对误差 (MAE): {mae:.4f}")
print(f"均方根误差 (RMSE): {rmse:.4f}")
# 绘制原始数据与预测值
plt.figure(figsize=(12, 6))
plt.plot(second_year.index, second_year.values, label='实际值', alpha=0.7)
plt.plot(seasonal_forecast.index, seasonal_forecast.values, label='季节性预测值', linestyle='--')
plt.title('简单季节性预测')
plt.xlabel('日期')
plt.ylabel('值')
plt.legend()
plt.grid(True)
plt.savefig('seasonal_forecast.png')
plt.close()
# 绘制预测误差
plt.figure(figsize=(12, 6))
plt.plot(forecast_error.index, forecast_error.values)
plt.axhline(y=0, color='r', linestyle='-')
plt.title('预测误差')
plt.xlabel('日期')
plt.ylabel('误差')
plt.grid(True)
plt.savefig('forecast_error.png')
plt.close()
结论
Pandas 的高级数据转换和时间序列分析功能使它成为数据科学家和分析师强大的工具。通过掌握这些高级技术,你可以更有效地处理和分析复杂的数据集,特别是在金融、科学研究和商业智能等领域。
高级数据转换技术使你能够重塑和操作复杂的数据结构,而时间序列分析功能则提供了处理和分析时序数据的强大能力。结合这两方面的知识,你可以构建更复杂、更有洞察力的数据分析流程。
要进一步提高时间序列分析能力,可以探索专业的时间序列库,如 statsmodels、prophet 或通过机器学习方法实现更复杂的预测模型。