如何计算滚动窗口内的最大值索引?

10

考虑 pd.Series s

import pandas as pd
import numpy as np

np.random.seed([3,1415])
s = pd.Series(np.random.randint(0, 10, 10), list('abcdefghij'))
s

a    0
b    2
c    7
d    3
e    8
f    7
g    0
h    6
i    8
j    6
dtype: int64

我想获取滑动窗口大小为3时最大值的索引。
s.rolling(3).max()

a    NaN
b    NaN
c    7.0
d    7.0
e    8.0
f    8.0
g    8.0
h    7.0
i    8.0
j    8.0
dtype: float64

What I want is

a    None
b    None
c       c
d       c
e       e
f       e
g       e
h       f
i       i
j       i
dtype: object

我做过的事情

s.rolling(3).apply(np.argmax)

a    NaN
b    NaN
c    2.0
d    1.0
e    2.0
f    1.0
g    0.0
h    0.0
i    2.0
j    1.0
dtype: float64

这显然不是我想要的。

6个回答

15

这并不是一件简单的事情,因为传递给滚动应用函数的参数是一个普通的numpy数组,而不是pandas Series,所以它不知道索引。此外,滚动函数必须返回一个浮点结果,因此如果它们不是浮点数,它们不能直接返回索引值。

以下是一种方法:

>>> s.index[s.rolling(3).apply(np.argmax)[2:].astype(int)+np.arange(len(s)-2)]
Index([u'c', u'c', u'e', u'e', u'e', u'f', u'i', u'i'], dtype='object')

该想法是取argmax值并通过添加一个指示我们在序列中走多远的值来将它们与系列对齐。(也就是说,对于第一个argmax值,我们添加零,因为它给出了从原始序列中索引为0的子序列开始的索引; 对于第二个argmax值,我们添加1,因为它给出了从原始序列中索引为1的子序列开始的索引; 以此类推。)

这样可以得到正确的结果,但不包括开头的两个“None”值;如果您想要它们,您需要手动添加。

一个开放的pandas问题,可以添加滚动idxmax。


很好,那很聪明。 - thomas.mac
计算滚动idxmax这么罕见吗?我本以为现在已经有高效的实现了...我甚至找不到一个像样的Julia实现,它也不需要外部C帮助。这里是否有我所忽略的使其变得非常困难/不必要的东西? - Harel Rozental

4
我使用了一个生成器。
def idxmax(s, w):
    i = 0
    while i + w <= len(s):
        yield(s.iloc[i:i+w].idxmax())
        i += 1

pd.Series(idxmax(s, 3), s.index[2:])

c    c
d    c
e    e
f    e
g    e
h    f
i    i
j    i
dtype: object

3
这里有一个使用broadcasting的方法 -
maxidx = (s.values[np.arange(s.size-3+1)[:,None] + np.arange(3)]).argmax(1)
out = s.index[maxidx+np.arange(maxidx.size)]

这将生成所有与滚动窗口对应的索引,使用这些索引来提取数组版本并获得每个窗口的最大索引。为了更有效率地进行索引,我们可以使用NumPy strides,具体操作如下 -
arr = s.values
n = arr.strides[0]
maxidx = np.lib.stride_tricks.as_strided(arr, \
                   shape=(s.size-3+1,3), strides=(n,n)).argmax(1)

3

我认为这是最简单的方法,只需使用如下lambda函数:

rolling_max_index=df.rolling(period).apply(lambda x: x.idxmax())

好极了。这个完美地运行了。 - Lumber Jack
这样做的问题在于,它只会给出与每个窗口(而非整个数据帧)中最高值相比较的索引。因此,例如,如果您使用的窗口大小为10,则无论您的数据框有多大,它都将给您介于0到9之间的数字,这在我的情况下是一个问题。 - Masih Bahmani

2

我来分享一下我是如何解决类似问题的。我不想找到确切的索引,而是想知道最大值发生了多久。但这也可以用来找到索引。

我基本上使用了移位策略,但我正在迭代几个具有可配置长度的移位。这可能很慢,但对我来说足够好用。

import pandas as pd


length = 5

data = [1, 2, 3, 4, 5, 4, 3, 4, 5, 6, 7, 6, 5, 4, 5, 4, 3]
df = pd.DataFrame(data, columns=['number'])
df['helper_max'] = df.rolling(length).max()

for i in range(length, -1, -1):
    # Set the column to what you want. You may grab the index 
    # if you wish, I wanted number of rows since max happened
    df.loc[df['number'].shift(i) == df['helper_max'], 'n_rows_ago_since_max'] = i

print(df)

输出:

    number  helper_max  n_rows_ago_since_max
0        1         NaN                   NaN
1        2         NaN                   NaN
2        3         NaN                   NaN
3        4         NaN                   NaN
4        5         5.0                   0.0
5        4         5.0                   1.0
6        3         5.0                   2.0
7        4         5.0                   3.0
8        5         5.0                   0.0
9        6         6.0                   0.0
10       7         7.0                   0.0
11       6         7.0                   1.0
12       5         7.0                   2.0
13       4         7.0                   3.0
14       5         7.0                   4.0
15       4         6.0                   4.0
16       3         5.0                   2.0

1
您可以通过创建一个DataFrame并使用idxmax来模拟滚动窗口,如下所示:
window_values = pd.DataFrame({0: s, 1: s.shift(), 2: s.shift(2)})
s.index[np.arange(len(s)) - window_values.idxmax(1)]

Index(['a', 'b', 'c', 'c', 'e', 'e', 'e', 'f', 'i', 'i'], dtype='object', name=0)

正如你所看到的,前两个术语是应用于长度为1和2的初始窗口而不是空值的idxmax。这并不像被接受的答案那样高效,对于大窗口可能不是一个好主意,但这只是另一种观点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接