问题描述
TLDR:我想基于滚动窗口对一系列值进行归一化。我使用.apply()
部分完成了此操作,但运行时间太慢,我正在寻找更好的方法(从性能角度考虑)。
我有一个非平稳数据的时间序列。我正在尝试消除趋势,并希望通过更改上一个期间的每个百分比值来消除这种趋势。例如,使用24小时窗口:获取当前值并检查最近24小时获得的百分比,这应该是新值。我希望这有道理。
更具体地说,我将使用以下示例:
df
0 2.00
1 3.00
2 2.50
3 3.10
4 4.00
5 3.20
6 3.80
7 3.00
8 4.20
9 4.10
10 3.90
11 4.12
12 4.30
13 4.11
dtype: float64
我做了什么(窗口大小为4):
output
0 NaN
1 NaN
2 NaN
3 0.75
4 0.75
5 0.50
6 0.50
7 0.00
8 0.75
9 0.50
10 0.25
11 0.50
12 0.75
13 0.25
dtype: float64
具有索引3的行的值为0.75,因为在相关窗口([2,3,2.5,3.1])中,有3个值比3.1小,因此为75%。我知道这并不是100%的技术,但这是我能够实现的。索引为9(值4.1)的行更改为0.5,因为在窗口[3.8、3、4.2、4.1]中有两个小于4.1的值。
我通过apply
进行了以下操作:
df.rolling(4).apply(lambda x: len(x[x < x.iloc[-1]]) / float(x.shape[0]))
它可以工作,但是太慢了。我的真实数据是每小时采样一次的时间序列,跨越了几年。在我的实际应用程序中,我对去年进行了标准化,这是指窗口大小为365 * 24值的百分比。这就是为什么我不介意75%或100%-分母为8760,它没有任何区别。 虽然,如果有人“顺便”建议同时允许1和0,这是很大的好处。我还想以某种方式对“起始值”进行操作,也就是将nan值替换为0、0.5、0.333(2变为0,3变为0.5,因为它较大,然后2和2.5变为0.3,因为它仅较大然后2)
总结我的问题:
- 我正在寻找我所解释的规范化的更快实现
- 一种改进的解决方案,它将考虑不带最后一个值的相关窗口,从而对于索引9而言,该窗口将仅是5、6、7、8(将允许输入0.0和1.0值)
- 如何正确处理nans-从较小的窗口开始,该窗口逐渐增大到四个,然后开始移动。
我要复制的小例子:
import pandas as pd
df = pd.Series(data=[2,3,2.5,3.1,4,3.2,3.8,4.2,4.1,3.9,4.12,4.3,4.11])
df.rolling(4).apply(lambda x: len(x[x < x.iloc[-1]]) / float(x.shape[0]))
真正想要的输出:
0 0.00
1 1.00
2 0.50
3 1.00
4 1.00
5 0.75
6 0.75
7 0.00
8 1.00
9 0.75
10 0.50
11 0.75
12 1.00
13 0.75
dtype: float64
作为旁注:我正在以一种非熊猫的方式来考虑这一点,方法是在当前窗口中保存一个排序的值数组,然后取出最旧的值,添加一个新值,依此类推。对于大小为m的窗口,它需要2 * logm(二进制搜索),因此我需要对数据帧中的所有n个值进行处理。我可以对此解决方案进行编码,并且在渐近时间方面可能非常有效,但是它不使用任何矢量化功能。
解决方法
稍快一些,至少可读性强:
df.rolling(4).apply(lambda x: np.mean(x < x.iloc[-1]))
但是对于最佳解决方案,也许可以在此post中使用性能分析。您也许可以使用scipy.convolve
来解决问题:)
滚动通常很慢。您可以执行for
循环:
window = 4
s = 0
for i in np.arange(1,window+1):
s = s+ (df > df.shift(i))
s/window
输出:
0 0.00
1 0.25
2 0.25
3 0.75
4 1.00
5 0.75
6 0.75
7 0.00
8 1.00
9 0.75
10 0.50
11 0.75
12 1.00
13 0.50
Name: 1,dtype: float64