Pandas窗口函数(rolling/expanding)
数据分析专题 · 滑动窗口与扩展窗口计算
专题:Python数据分析系统学习
关键词:数据分析, Pandas, rolling, expanding, ewm, 移动平均, 窗口函数, 滑动窗口, 布林带
一、窗口函数概述
窗口函数是Pandas中处理时间序列和有序数据的重要工具,它允许我们在一个滑动或扩展的数据子集上执行计算。窗口函数的核心思想是对数据序列的局部区域进行聚合分析,从而揭示数据的局部趋势、波动特征和演变规律。与传统的全局聚合不同,窗口函数输出的结果与输入数据具有相同的长度,每个输出值对应输入序列中某个特定窗口的计算结果。
Pandas提供了三种主要类型的窗口函数:rolling(滚动窗口)使用固定大小的窗口在数据上滑动;expanding(扩展窗口)从序列起点开始逐步累积到当前位置;ewm(指数加权移动平均)为近期的观测值赋予更高的权重。这三种方法各有特点,适用于不同的分析场景。
窗口函数的应用场景:金融时间序列的移动平均线、技术指标计算(布林带、RSI)、信号平滑去噪、波动率估计、累计统计量分析、异常值检测等。在实际工作中,窗口函数是数据分析师处理时间序列数据时使用最频繁的工具之一。
| 函数 | 窗口类型 | 特点 | 适用场景 |
| rolling() | 固定窗口 | 窗口大小固定,在序列上滑动 | 移动平均、技术指标 |
| expanding() | 扩展窗口 | 从起点开始,逐步扩大 | 累计统计、历史回测 |
| ewm() | 指数加权 | 权重指数衰减,近期权重高 | 平滑滤波、趋势跟踪 |
在使用窗口函数前,需要确保数据按照时间顺序正确排序。时间序列数据通常需要先设置DatetimeIndex,然后才能可靠地执行窗口计算。下面我们从一个简单的示例开始,展示如何创建样本数据并应用基本的窗口操作。
import pandas as pd
import numpy as np
# 创建示例时间序列数据
np.random.seed(42)
dates = pd.date_range('2025-01-01', periods=20, freq='D')
df = pd.DataFrame({'value': np.random.randn(20)}, index=dates)
print(df.head(10))
二、rolling滚动窗口基础
rolling函数是Pandas中最常用的窗口函数,它创建一个固定大小的滑动窗口,并支持在窗口上应用各种聚合操作。滚动窗口的计算过程可以理解为:对于一个长度为N的序列,一个大小为k的窗口从第一个元素开始,每次向前滑动一个位置,在每个位置上都对窗口内的k个元素执行指定的计算。
基本用法
创建rolling对象后,需要通过调用聚合方法才能得到最终的计算结果。最基础的用法是计算简单移动平均(Simple Moving Average, SMA),即将窗口内的数据求平均值。
# 计算3日简单移动平均
df['sma_3'] = df['value'].rolling(window=3).mean()
print(df.head(10))
# 计算5日移动求和
df['sum_5'] = df['value'].rolling(window=5).sum()
print(df)
上述代码中,window=3表示窗口大小为3个数据点。 rolling函数默认使用左对齐方式,即窗口包含当前行及其前面2行(共3行)。需要注意的是,序列的前window-1个元素会得到NaN值,因为在此之前窗口尚未填满。
窗口大小与对齐方式
rolling函数的center参数控制输出结果的对齐方式。默认center=False,计算结果对齐到窗口的右侧(最后一个元素);当center=True时,计算结果对齐到窗口的中央位置。这对于某些需要滞后处理的场景(如交易信号生成)尤为重要。
# 不同对齐方式的对比
df['sma_right'] = df['value'].rolling(3, center=False).mean()
df['sma_center'] = df['value'].rolling(3, center=True).mean()
print(df[['value', 'sma_right', 'sma_center']].head(8))
# 输出解释:
# sma_right: 索引201的第一个NaN(窗口[0,1,2]只填了2个)
# sma_center: 索引201的第一个NaN(窗口[0,1,2]只填了2个且对齐到中央)
默认对齐(center=False)
结果向右对齐,适合预测场景。当前时刻的输出值仅依赖历史数据(当前及之前的数据),不会引入未来信息。
居中对齐(center=True)
结果居中对齐,适合平滑展示。当前时刻的输出值依赖前后数据,会产生中心化效果,但会引入未来信息。
min_periods参数
min_periods参数指定一个窗口最少需要多少个非NaN值才能产生有效结果。当数据中存在缺失值或在序列边界时,这个参数非常有用。通过设置min_periods,可以控制前期的数据展示策略。
# min_periods 控制最小观测数
df['sma_1'] = df['value'].rolling(5, min_periods=1).mean()
df['sma_3'] = df['value'].rolling(5, min_periods=3).mean()
df['sma_5'] = df['value'].rolling(5, min_periods=5).mean()
print(df[['sma_1', 'sma_3', 'sma_5']].head(7))
# sma_1: 从第1行就开始有值,但只有1个观测值
# sma_3: 从第3行开始有值,累积3个观测值后计算
# sma_5: 从第5行开始有值,必须满5个才计算
win_type加权窗口
rolling函数支持多种加权窗口类型,通过win_type参数指定。常用的加权窗口包括三角窗(triang)、汉宁窗(hamming)、黑曼窗(blackman)、巴特利特窗(bartlett)等。加权窗口可以为窗口内的数据分配不同的权重,使得靠近窗口中央的数据获得更高的权重。
# 使用不同类型的加权窗口
df['triang'] = df['value'].rolling(5, win_type='triang').mean()
df['hamming'] = df['value'].rolling(5, win_type='hamming').mean()
df['blackman'] = df['value'].rolling(5, win_type='blackman').mean()
print(df[['value', 'triang', 'hamming', 'blackman']].head(10))
# 加权窗口的权重分布可以通过numpy获取
import scipy.signal.windows as w
print(w.triang(5)) # [0.333 0.667 1.0 0.667 0.333]
print(w.hamming(5)) # [0.08 0.54 1.0 0.54 0.08]
三、rolling聚合操作
rolling对象提供了丰富的聚合方法,包括常见的描述性统计量和一些专门的计算方法。这些方法可以直接链式调用,也可以使用agg方法同时应用多个聚合函数。掌握这些聚合操作是灵活运用窗口函数的基础。
常用聚合函数
Pandas的rolling对象支持绝大多数常用的数据聚合操作,包括均值(mean)、标准差(std)、方差(var)、最小值(min)、最大值(max)、求和(sum)、中位数(median)、分位数(quantile)、偏度(skew)和峰度(kurt)等。
# 多种rolling聚合操作
window = 5
df['rolling_mean'] = df['value'].rolling(window).mean()
df['rolling_std'] = df['value'].rolling(window).std()
df['rolling_var'] = df['value'].rolling(window).var()
df['rolling_min'] = df['value'].rolling(window).min()
df['rolling_max'] = df['value'].rolling(window).max()
# 查看汇总结果
print(df[['value', 'rolling_mean', 'rolling_std', 'rolling_min', 'rolling_max']].tail(10))
# 分位数和高级统计量
df['q25'] = df['value'].rolling(window).quantile(0.25)
df['q75'] = df['value'].rolling(window).quantile(0.75)
df['skew'] = df['value'].rolling(window).skew()
df['kurt'] = df['value'].rolling(window).kurt()
print(df[['value', 'q25', 'q75', 'skew', 'kurt']].tail(10))
多聚合组合
使用agg方法可以在一次rolling操作中同时计算多个聚合函数,返回一个包含多列结果的DataFrame。这种方式代码简洁且执行效率更高,尤其是当需要对同一窗口执行大量不同计算时。
# 使用agg一次执行多个聚合
agg_result = df['value'].rolling(5).agg([
'mean', 'std', 'min', 'max',
'median', 'skew'
])
print(agg_result.tail(10))
# 自定义聚合名称
agg_custom = df['value'].rolling(5).agg(
avg='mean',
stdev='std',
iqr=lambda x: x.quantile(0.75) - x.quantile(0.25)
)
print(agg_custom.tail(10))
聚合函数速查表:rolling对象支持的聚合方法包括mean()、sum()、std()、var()、min()、max()、median()、quantile(q)、skew()、kurt()、count()、sem()(标准误)。对于非数值聚合,可以使用apply()方法传入自定义函数。
四、expanding扩展窗口
expanding扩展窗口与rolling滚动窗口的核心区别在于窗口的起始位置固定为序列的第一个元素,终点随索引递增而不断向前扩展。也就是说,扩展窗口从数据序列的起点开始,逐步纳入更多的历史数据,形成一个越来越大的窗口。这种计算方式在计算累计统计量时非常有用。
基本用法与累计统计
expanding的使用方式与rolling非常相似,同样支持mean、sum、std、min、max等多种聚合方法。它在计算累计均值、累计最大值、回测中的累计收益等方面有广泛应用。
# expanding基础用法
df['expanding_mean'] = df['value'].expanding().mean()
df['expanding_sum'] = df['value'].expanding().sum()
df['expanding_std'] = df['value'].expanding().std()
df['expanding_min'] = df['value'].expanding().min()
df['expanding_max'] = df['value'].expanding().max()
print(df[['value', 'expanding_mean', 'expanding_sum', 'expanding_max']].head(12))
# 观察expanding_mean的变化:随着数据点增加,均值逐渐趋于稳定
# 这就是"大数定律"在数据处理中的体现
rolling与expanding的对比
理解rolling和expanding的差异是正确使用它们的关镮。 rolling关注的是局部特征,适合捕捉短期波动和局部趋势;expanding反映的是全局特征,随着数据积累,统计量趋于稳定。在实际应用中,rolling常用于技术指标计算,expanding常用于累计绩效统计。
# 直观对比 rolling vs expanding
df['rolling_mean_5'] = df['value'].rolling(5).mean()
df['expanding_mean_all'] = df['value'].expanding().mean()
# 取出前10行进行对比
compare = df[['value', 'rolling_mean_5', 'expanding_mean_all']].head(10)
compare.columns = ['原始值', '5期滚动平均', '历史累计平均']
print(compare)
# expanding 也可以设置 min_periods
df['expanding_min2'] = df['value'].expanding(min_periods=2).mean()
print(df[['value', 'expanding_mean', 'expanding_min2']].head(5))
expanding的高级应用
expanding窗口在金融数据分析和性能监控中有很多高级用法,例如计算滚动累计收益、累计最大值(创历史新高检测)、以及累计相关系数等。
# 模拟累计收益计算
returns = pd.Series(np.random.randn(100) * 0.02)
cumulative_return = (returns + 1).expanding().apply(lambda x: x.prod())
print(cumulative_return.head(10))
# 检测数据是否创历史新高
data = pd.Series([3, 1, 4, 2, 5, 3, 6, 4])
is_new_high = data.expanding().max()
is_new_high_label = data == is_new_high
print(pd.DataFrame({'data': data, 'cummax': is_new_high, '新高': is_new_high_label}))
五、ewm指数加权移动平均
ewm(Exponentially Weighted Moving Average)是指数加权移动平均,它给较近期的观测值赋予更高的权重,权重随着时间推移呈指数级衰减。这种加权方式尤其适合处理具有时变特征的数据,因为它能更快地响应数据的变化,同时保持一定的平滑性。
参数体系
ewm方法提供了四种参数来指定衰减速度,分别是alpha(平滑因子)、span(跨度)、com(中心质量)和halflife(半衰期)。这四个参数是互斥的,只能指定其中一个。理解它们之间的关系对于正确使用ewm非常关键。
| 参数 | 含义 | 计算公式 | 典型值 |
| alpha | 平滑因子 | 直接指定alpha | 0.1(平滑)、0.5(灵敏) |
| span | 跨度 | alpha = 2/(span+1) | span=20 对应alpha≈0.095 |
| com | 中心质量 | alpha = 1/(1+com) | com=10 对应alpha≈0.091 |
| halflife | 半衰期 | alpha = 1 - exp(-ln(2)/halflife) | halflife=12 对应alpha≈0.056 |
# ewm不同参数的使用
df['ewm_alpha_03'] = df['value'].ewm(alpha=0.3).mean()
df['ewm_span_5'] = df['value'].ewm(span=5).mean()
df['ewm_halflife_3'] = df['value'].ewm(halflife=3).mean()
print(df[['value', 'ewm_alpha_03', 'ewm_span_5', 'ewm_halflife_3']].head(10))
# 验证 alpha 和 span 的等价关系
print('alpha from span=5:', 2/(5+1)) # 0.3333
ewm的扩展功能
除了均值之外,ewm还支持计算指数加权方差(var)和协方差(cov)。这对于金融领域的风险度量非常实用,例如计算指数加权的波动率估计。
# ewm方差和协方差
df['ewm_var'] = df['value'].ewm(span=20).var()
df['ewm_std'] = df['value'].ewm(span=20).std()
print(df[['value', 'ewm_var', 'ewm_std']].head(10))
# 调整:ewm的adjust参数控制是否使用调整公式
# adjust=True(默认):使用无限序列的精确公式
# adjust=False:使用递归公式,初始值为第一个观测值
df['ewm_adjust_false'] = df['value'].ewm(alpha=0.3, adjust=False).mean()
print(df[['value', 'ewm_alpha_03', 'ewm_adjust_false']].head(8))
参数选择建议:在实际应用中,span是最常用的参数,因为它直观地表示"相当于多少期的移动平均"。对于日频数据,常用的span值为12(月均水平)、26(季均水平)。halflife参数在科学计算中使用较多,它指定权重衰减到一半所需的时间步数。
六、窗口函数与groupby组合
在实际的数据分析工作中,我们经常需要对分组数据分别执行窗口计算。Pandas支持在groupby之后链式调用窗口函数,从而实现"分组内滚动计算"的功能。这种组合操作在面板数据、多股票分析、多产品销量跟踪等场景中极为常见。
分组滚动计算
groupby与rolling的组合使用需要注意索引处理。有两种主要的调用方式:先groupby再rolling,会得到MultiIndex结果;或者使用transform方法,可以保持原始索引不变。
# 创建多组数据示例
np.random.seed(42)
dates = pd.date_range('2025-01-01', periods=10, freq='D')
groups = ['A'] * 10 + ['B'] * 10
multi_df = pd.DataFrame({
'group': groups,
'value': np.random.randn(20)
}, index=pd.date_range('2025-01-01', periods=20, freq='D'))
# 方法一:groupby + rolling + 聚合
result1 = multi_df.groupby('group')['value'].rolling(3).mean()
print(result1)
# 结果是一个MultiIndex Series
# 方法二:使用transform保持原始索引
multi_df['rolling_mean_by_group'] = (
multi_df.groupby('group')['value']
.transform(lambda x: x.rolling(3).mean())
)
print(multi_df.head(12))
分组扩展窗口
分组扩展窗口的计算方式与滚动窗口类似,区别在于窗口类型不同。对于需要计算组内累计统计量的场景(如累计收益率、组内排名等),扩展窗口配合groupby是非常有力的工具。
# 组内expanding计算
multi_df['cumulative_mean'] = (
multi_df.groupby('group')['value']
.transform(lambda x: x.expanding().mean())
)
multi_df['cumulative_max'] = (
multi_df.groupby('group')['value']
.transform(lambda x: x.expanding().max())
)
print(multi_df[['group', 'value', 'cumulative_mean', 'cumulative_max']].head(14))
# 组内累积计数
multi_df['cumcount'] = multi_df.groupby('group').cumcount() + 1
print(multi_df[['group', 'value', 'cumcount']].head(14))
注意:在使用groupby+rolling时,务必保证每个组内的数据是已经排好序的。如果原始数据不是按时间排序的,可以使用sort_values先进行排序,否则窗口计算的结果将没有意义。
七、自定义窗口函数
当Pandas内置的聚合方法不能满足需求时,可以使用apply方法传入自定义函数,实现任意复杂的窗口计算逻辑。自定义窗口函数为数据分析提供了极大的灵活性,但由于在Python层面逐窗口执行,当数据量较大时可能会有性能问题。
使用apply进行自定义计算
rolling对象的apply方法接收一个可调用对象(函数),该函数以一个包含窗口内数据的numpy数组为输入,返回一个标量值。自定义函数在每次窗口滑动时被调用,因此代码应当尽量高效。
# 自定义窗口函数:计算窗口内的范围(max - min)
def window_range(x):
return x.max() - x.min()
df['rolling_range'] = df['value'].rolling(5).apply(window_range)
print(df[['value', 'rolling_range']].head(10))
# 自定义函数:计算窗口内的百分位排名
def percentile_rank(x):
return (x[-1] - x.min()) / (x.max() - x.min() + 1e-10)
df['pct_rank'] = df['value'].rolling(5).apply(percentile_rank)
print(df[['value', 'pct_rank']].head(10))
使用lambda和numpy函数
对于简单的自定义逻辑,可以使用lambda表达式,使代码更加简洁。同时,numpy的通用函数也可以直接传入apply,实现特定场景的计算需求。
# 使用lambda和numpy实现自定义计算
import numpy as np
# 窗口内的均方根(RMS)
df['rolling_rms'] = df['value'].rolling(3).apply(lambda x: np.sqrt(np.mean(x**2)))
# 窗口内的绝对偏差中位数(MAD)
df['rolling_mad'] = df['value'].rolling(5).apply(lambda x: np.median(np.abs(x - np.median(x))))
# 窗口内的线性回归斜率(简化版)
def rolling_slope(y):
x = np.arange(len(y))
cov_matrix = np.cov(x, y)
return cov_matrix[0, 1] / cov_matrix[0, 0]
df['rolling_slope'] = df['value'].rolling(5).apply(rolling_slope)
print(df[['value', 'rolling_rms', 'rolling_mad', 'rolling_slope']].head(10))
性能优化建议
对于大数据集,自定义窗口函数的性能可能成为瓶颈。以下是一些优化建议:raw=True参数可以使apply接收numpy数组而不是Pandas Series,略微提升性能;尽量使用numpy向量化操作而非Python循环;对于简单的统计需求,优先使用内置方法。
# 使用raw=True提升性能
# raw=True传入裸numpy数组(无索引),raw=False传入Series(有索引)
df['rolling_range_raw'] = df['value'].rolling(5).apply(window_range, raw=True)
# 验证结果一致
print((df['rolling_range'] == df['rolling_range_raw']).all()) # True
# expanded也可使用apply
df['expanding_custom'] = df['value'].expanding().apply(
lambda x: x.std() / x.mean() if x.mean() != 0 else np.nan
)
print(df[['value', 'expanding_custom']].head(10))
八、金融时间序列应用实例
窗口函数在金融时间序列分析中有着广泛而深入的应用。本节将通过几个经典的金融技术指标实例,展示如何综合运用rolling、expanding和ewm解决实际问题。这些例子既涵盖了基础指标的计算,也包括了一些相对复杂的组合应用。
移动平均线(MA)与交叉策略
移动平均线是最基础也是最重要的技术分析工具之一。通过计算不同周期的移动平均线,可以识别趋势方向和潜在的买卖信号。当短期均线上穿长期均线时形成"金叉"信号,下穿时形成"死叉"信号。
# 模拟股价数据并计算移动平均线
np.random.seed(123)
stock_prices = 100 + np.cumsum(np.random.randn(60) * 0.5)
stock_df = pd.DataFrame({'close': stock_prices},
index=pd.date_range('2025-01-01', periods=60, freq='D'))
# 计算5日均线和20日均线
stock_df['ma_5'] = stock_df['close'].rolling(5).mean()
stock_df['ma_20'] = stock_df['close'].rolling(20).mean()
# 生成交易信号:金叉=1,死叉=-1,否则=0
stock_df['signal'] = np.where(
stock_df['ma_5'] > stock_df['ma_20'], 1,
np.where(stock_df['ma_5'] < stock_df['ma_20'], -1, 0)
)
# 计算交叉点
stock_df['cross'] = stock_df['signal'].diff()
buy_signals = stock_df[stock_df['cross'] == 2] # 从-1到1,金叉
sell_signals = stock_df[stock_df['cross'] == -2] # 从1到-1,死叉
print(stock_df.tail(15))
print(f"买入信号数: {len(buy_signals)}, 卖出信号数: {len(sell_signals)}")
布林带(Bollinger Bands)
布林带由三条线组成:中轨是N期移动平均线,上下轨分别是中轨加减K倍的标准差。布林带不仅可以显示价格的相对高低,还能反映市场的波动性。当布林带收窄时,市场可能即将出现大幅波动(挤压后爆发);当价格触及上轨或下轨时,可能意味着超买或超卖。
# 计算布林带指标
window = 20
num_std = 2
stock_df['middle'] = stock_df['close'].rolling(window).mean()
stock_df['std'] = stock_df['close'].rolling(window).std()
stock_df['upper'] = stock_df['middle'] + num_std * stock_df['std']
stock_df['lower'] = stock_df['middle'] - num_std * stock_df['std']
# 带宽(Bandwidth)指标:衡量波动性
stock_df['bandwidth'] = (stock_df['upper'] - stock_df['lower']) / stock_df['middle']
# %B指标:价格在布林带中的相对位置
stock_df['pct_b'] = (stock_df['close'] - stock_df['lower']) / (stock_df['upper' - stock_df['lower'] + 1e-10)
print(stock_df[['close', 'middle', 'upper', 'lower', 'bandwidth', 'pct_b']].tail(10))
# 检测价格触及上下轨的信号
touch_upper = stock_df[stock_df['close'] >= stock_df['upper']
touch_lower = stock_df[stock_df['close'] <= stock_df['lower']
print(f"触及上轨的次数: {len(touch_upper)}")
print(f"触及下轨的次数: {len(touch_lower)}")
波动率计算
波动率是金融风险管理中的核心指标。最常用的波动率估计方法是对数收益率的年化标准差。通过rolling函数可以计算滚动波动率,反映市场风险水平的动态变化。ewm指数加权波动率能给近期数据更高的权重,对市场变化更加敏感。
# 计算滚动波动率(对数收益率标准差年化)
stock_df['log_return'] = np.log(stock_df['close'] / stock_df['close'].shift(1))
# 20日滚动波动率(年化)
window = 20
trading_days = 252
stock_df['realized_vol'] = (
stock_df['log_return'].rolling(window).std() * np.sqrt(trading_days)
)
# 指数加权波动率(对近期变化更敏感)
stock_df['ewm_vol'] = (
stock_df['log_return'].ewm(span=window).std() * np.sqrt(trading_days)
)
print(stock_df[['close', 'log_return', 'realized_vol', 'ewm_vol']].tail(10))
# 比较两种波动率的差异
print(f"滚动波动率均值: {stock_df['realized_vol'].mean():.4f}")
print(f"EWM波动率均值: {stock_df['ewm_vol'].mean():.4f}")
金融应用小结:窗口函数是金融数据分析的基石。移动平均线识别趋势方向,布林带评估价格相对高低和波动性,波动率计算量化风险水平。这些指标的组合使用可以构建出复杂的交易策略和风险管理系统。
九、窗口函数的进阶技巧
在掌握了窗口函数的基础用法之后,还有一些进阶技巧可以帮助我们更高效地处理实际问题。这些技巧包括对非时间序列使用rolling、处理不规则时间序列、以及多列数据的窗口计算等。
基于时间频率的窗口
当时间序列存在缺失值时,基于固定数据点数的窗口可能不合适。Pandas的rolling支持传入时间偏移量作为窗口大小,这样无论数据点之间的间隔是否均匀,窗口始终覆盖指定长度的时间范围。
# 基于时间偏移量的滚动窗口
# 创建带有缺失时间点的数据
irregular_dates = pd.to_datetime(['2025-01-01', '2025-01-03', '2025-01-04',
'2025-01-07', '2025-01-08', '2025-01-10'])
irregular_series = pd.Series([1, 3, 2, 5, 4, 6], index=irregular_dates)
# 使用"3D"(3天)作为窗口大小
# 这会包括当前时间点往前推3天内的所有数据点
result = irregular_series.rolling('3D').mean()
print(pd.DataFrame({'value': irregular_series, 'rolling_3d': result}))
# 常用时间偏移量:'7D'(7天), '1M'(1个月), '30s'(30秒), '2h'(2小时)
# 注意:使用时间窗口时,index必须是DatetimeIndex
多列滚动计算
当需要在多个列上同时执行窗口计算时,可以直接对整个DataFrame调用rolling方法,它会返回一个DataFrame类型的rolling对象,所有的聚合操作都会逐列执行。此外,还可以在不同列上应用不同的聚合函数。
# 多列同时滚动计算
multi_col = pd.DataFrame({
'col_a': np.random.randn(10),
'col_b': np.random.randn(10),
'col_c': np.random.randn(10)
}, index=pd.date_range('2025-01-01', periods=10, freq='D'))
# 每列分别计算3日滚动均值
rolling_mean_multi = multi_col.rolling(3).mean()
print(rolling_mean_multi)
# 不同列应用不同聚合
# col_a 求均值,col_b 求和,col_c 求标准差
agg_dict = {'col_a': 'mean', 'col_b': 'sum', 'col_c': 'std'}
result_multi = multi_col.rolling(3).agg(agg_dict)
print(result_multi)
时间窗口的特点:基于时间偏移量的窗口(如'7D')会自动适应数据间隔,确保每个窗口覆盖相同长度的时间范围。这对于分析日频、周频甚至更高频的金融数据非常有用,特别是当数据存在缺失值或交易日历不连续时。
十、核心要点总结
窗口函数核心概念回顾:
- rolling滚动窗口:固定大小的窗口在数据序列上依次滑动,适用于局部趋势分析和短期预测
- expanding扩展窗口:窗口从序列起点开始逐步扩大,适用于累计统计量和历史回测
- ewm指数加权:权重随距离指数衰减,近期数据权重大,适合趋势跟踪和实时平滑
- center参数:控制输出对齐方式,center=False(默认)向右对齐,center=True居中对齐
- min_periods:控制窗口最少有效观测数,影响序列边界处的计算结果
- win_type:加权窗口类型,可选用三角窗、汉宁窗等多种信号处理窗函数
- agg方法:一次性应用多个聚合函数,返回多列结果,代码简洁高效
- groupby+窗口:分组内分别执行窗口计算,是面板数据分析的标准模式
- apply自定义:传入自定义函数实现任意窗口计算逻辑,灵活性最高
- 时间偏移窗口:基于时间长度而非数据点数的窗口,适用于不规则时间序列
选择指南:需要局部滑动分析用rolling,需要全局累计分析用expanding,需要对近期数据更敏感用ewm。三者可以组合使用,例如先用ewm生成平滑信号,再用rolling计算平滑信号的移动平均,形成"双重平滑"效果。
十一、进一步思考与实践
掌握窗口函数的基础语法只是第一步,更重要的是在实际问题中灵活运用。金融量化交易中的策略回测、传感器数据的实时滤波、电商平台的滑动转化率统计、网站流量的趋势分析,这些场景都是窗口函数的用武之地。
在进一步的学习中,建议关注以下几个方向:第一,将窗口函数与Pandas的resample频率转换结合,处理不同频率数据的对齐问题;第二,学习使用Numba加速自定义窗口函数,处理大规模数据时的性能优化;第三,将窗口函数应用于多因子模型的特征工程中,构建具有预测力的技术因子。
"窗口函数是连接时间序列中过去与现在的桥梁。它让我们能够在保留数据序列结构的前提下,提取出局部的统计规律。无论是移动平均的平滑之美,还是布林带的波动之韵,其核心都是对数据局部结构的深刻理解。"
推荐练习:用真实的股票日频数据(可从Yahoo Finance获取),依次实现以下功能:计算5日、20日、60日移动平均线;计算布林带指标;计算滚动夏普比率(年化收益率/年化波动率);构建一个基于移动平均线交叉的简单回测系统。通过这些练习,可以全面巩固窗口函数的知识体系。