Pandas 滑动窗口 .rolling().apply() 详细教程与高级特性介绍
1. 基本概念
.rolling().apply()
是pandas中用于在滚动窗口上应用自定义函数的方法。它由两部分组成:
.rolling()
: 创建一个滚动窗口对象.apply()
: 在滚动窗口上应用一个函数
2. 基本语法
df.rolling(window).apply(func)
window
: 滚动窗口的大小func
: 要应用的函数
3. 参数详解
.rolling() 的主要参数:
window
: 窗口大小(必需)min_periods
: 窗口中所需的最小观察次数(默认为window)center
: 是否将标签放在窗口的中心(默认为False)
.apply() 的主要参数:
func
: 要应用的函数(必需)raw
: 如果为True,传递原始ndarray而不是Series(默认为False)engine
: 计算引擎(‘cython’或’numba’,默认为None)
4. 使用示例
让我们通过一些示例来深入了解 .rolling().apply()
的使用:
示例1:计算移动平均
import pandas as pd
import numpy as np
# 创建一个示例DataFrame
df = pd.DataFrame({'A': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
# 计算3天移动平均
df['3D_MA'] = df['A'].rolling(window=3).apply(lambda x: np.mean(x))
print(df)
输出:
A 3D_MA
0 1 NaN
1 2 NaN
2 3 2.0
3 4 3.0
4 5 4.0
5 6 5.0
6 7 6.0
7 8 7.0
8 9 8.0
9 10 9.0
示例2:自定义函数 - 计算窗口内的最大值与最小值之差
def max_min_diff(x):
return x.max() - x.min()
df['3D_Range'] = df['A'].rolling(window=3).apply(max_min_diff)
print(df)
输出:
A 3D_MA 3D_Range
0 1 NaN NaN
1 2 NaN NaN
2 3 2.0 2.0
3 4 3.0 2.0
4 5 4.0 2.0
5 6 5.0 2.0
6 7 6.0 2.0
7 8 7.0 2.0
8 9 8.0 2.0
9 10 9.0 2.0
5. 高级用法
使用不同的窗口类型
除了固定大小的窗口,pandas还支持其他类型的窗口:
# 指数加权移动平均
df['EWMA'] = df['A'].ewm(span=3).mean()
# 扩展窗口(累积)
df['Cumulative_Mean'] = df['A'].expanding().mean()
处理时间序列数据
对于时间序列数据,可以使用基于时间的窗口:
import pandas as pd
# 创建一个时间序列DataFrame
dates = pd.date_range('20230101', periods=10)
df = pd.DataFrame({'Date': dates, 'Value': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
df.set_index('Date', inplace=True)
# 使用7天滚动窗口
df['7D_MA'] = df['Value'].rolling('7D').mean()
print(df)
6. 性能考虑
- 对于简单操作(如均值、和等),使用内置方法(如
.mean()
、.sum()
等)比.apply()
更快。 - 对于复杂操作,考虑使用
numba
引擎来提高性能。
7. 注意事项
- 滚动窗口操作可能会产生
NaN
值,特别是在数据开始的地方。 - 确保自定义函数能够处理
NaN
值,以避免意外错误。 - 大型数据集上的滚动窗口操作可能会很慢,需要考虑性能优化。
8. 窗口大小不足时的行为
当窗口大小不足时,.apply(func)
中的func
函数是否被调用取决于min_periods
参数的设置:
-
如果没有指定
min_periods
,默认值等于窗口大小。在这种情况下,当窗口中的有效数据少于窗口大小时,func
不会被调用,结果将是NaN
。 -
如果指定了
min_periods
,只要窗口中的有效数据数量达到或超过min_periods
,func
就会被调用。
让我们通过一个例子来说明这一点:
import pandas as pd
import numpy as np
def custom_func(x):
print(f"Function called with data: {x}")
return x.mean()
# 创建一个示例DataFrame
df = pd.DataFrame({'A': [1, 2, 3, 4, 5]})
print("Case 1: Without min_periods")
df['Rolling_1'] = df['A'].rolling(window=3).apply(custom_func)
print("\nCase 2: With min_periods=1")
df['Rolling_2'] = df['A'].rolling(window=3, min_periods=1).apply(custom_func)
print("\nFinal DataFrame:")
print(df)
输出:
Case 1: Without min_periods
Function called with data: [1. 2. 3.]
Function called with data: [2. 3. 4.]
Function called with data: [3. 4. 5.]
Case 2: With min_periods=1
Function called with data: [1.]
Function called with data: [1. 2.]
Function called with data: [1. 2. 3.]
Function called with data: [2. 3. 4.]
Function called with data: [3. 4. 5.]
Final DataFrame:
A Rolling_1 Rolling_2
0 1 NaN 1.000000
1 2 NaN 1.500000
2 3 2.000000 2.000000
3 4 3.000000 3.000000
4 5 4.000000 4.000000
从这个例子中,我们可以观察到:
-
在没有指定
min_periods
的情况下(Case 1),custom_func
只在窗口大小达到3时才被调用。 -
当指定
min_periods=1
时(Case 2),即使窗口中只有一个或两个值,custom_func
也会被调用。 -
结果显示,Case 1的前两行是
NaN
,而Case 2从第一行开始就有值。
这个行为对于理解和使用.rolling().apply()
非常重要,尤其是在处理数据集开始部分或存在缺失值的情况下。通过适当设置min_periods
,您可以控制函数在数据不足时的行为。
9. step 参数对 .apply() 的影响
rolling()
函数中的step
参数允许你控制滚动窗口的步进大小。当使用step
参数时,apply()
的行为会发生以下变化:
-
窗口移动频率:
step
参数决定了窗口在每次操作后向前移动的行数。默认情况下,step=1
,意味着窗口每次向前移动一行。 -
输出频率:
apply()
函数将只在每个步进点上被调用,而不是对每一行都调用。这会减少输出的行数。 -
性能影响:使用更大的
step
值可以显著提高处理大数据集时的性能,因为它减少了apply()
函数被调用的次数。
让我们通过一个例子来说明step
参数的作用:
import pandas as pd
import numpy as np
def custom_func(x):
print(f"Function called with data: {x}")
return x.mean()
# 创建一个示例DataFrame
df = pd.DataFrame({'A': range(1, 11)})
print("Case 1: Without step parameter")
df['Rolling_1'] = df['A'].rolling(window=3).apply(custom_func)
print("\nCase 2: With step=2")
df['Rolling_2'] = df['A'].rolling(window=3, step=2).apply(custom_func)
print("\nFinal DataFrame:")
print(df)
输出:
Case 1: Without step parameter
Function called with data: [1. 2. 3.]
Function called with data: [2. 3. 4.]
Function called with data: [3. 4. 5.]
Function called with data: [4. 5. 6.]
Function called with data: [5. 6. 7.]
Function called with data: [6. 7. 8.]
Function called with data: [7. 8. 9.]
Function called with data: [8. 9. 10.]
Case 2: With step=2
Function called with data: [1. 2. 3.]
Function called with data: [3. 4. 5.]
Function called with data: [5. 6. 7.]
Function called with data: [7. 8. 9.]
Final DataFrame:
A Rolling_1 Rolling_2
0 1 NaN NaN
1 2 NaN NaN
2 3 2.000000 2.000000
3 4 3.000000 NaN
4 5 4.000000 4.000000
5 6 5.000000 NaN
6 7 6.000000 6.000000
7 8 7.000000 NaN
8 9 8.000000 8.000000
9 10 9.000000 NaN
从这个例子中,我们可以观察到:
-
在没有
step
参数的情况下(Case 1),custom_func
在每一行都被调用(从第3行开始)。 -
当使用
step=2
时(Case 2),custom_func
只在每隔一行被调用。这导致输出结果只在每隔一行有值,其他行为NaN
。 -
使用
step
参数显著减少了函数调用的次数(从8次减少到4次)。 -
在输出结果列中,两个有具体值的元素之间
NaN
的数量为step-1
。
注意事项:
- 使用
step > 1
时,输出将包含更多的NaN
值。在处理这些结果时需要特别注意。 step
参数对于大数据集的性能优化特别有用,但可能会影响结果的精度或频率。- 当使用
step
参数时,确保你的分析不会因为跳过中间值而产生偏差。