Pandas:如何在组内计算有条件的滚动/累积最大值?

4
我希望在不使用缓慢的for循环的情况下,能够在列condrolmax(基于列close)中实现以下结果(条件滚动/累积最大值)。
Index    close    bool       condrolmax
0        1        True       1
1        3        True       3
2        2        True       3
3        5        True       5
4        3        False      5
5        3        True       3 --> rolling/accumulative maximum reset (False cond above)
6        4        True       4
7        5        False      4
8        7        False      4
9        5        True       5 --> rolling/accumulative maximum reset (False cond above)
10       7        False      5
11       8        False      5
12       6        True       6 --> rolling/accumulative maximum reset (False cond above)
13       8        True       8
14       5        False      8
15       5        True       5 --> rolling/accumulative maximum reset (False cond above)
16       7        True       7
17       15       True       15
18       16       True       16

创建此数据帧的代码:
# initialise data of lists.
data = {'close':[1,3,2,5,3,3,4,5,7,5,7,8,6,8,5,5,7,15,16],
        'bool':[True, True, True, True, False, True, True, False, False, True, False,
                False, True, True, False, True, True, True, True],
        'condrolmax': [1,3,3,5,5,3,4,4,4,5,5,5,6,8,8,5,7,15,16]}
 
# Create DataFrame
df = pd.DataFrame(data)

我相信可以对其进行向量化处理(一行代码解决)。有什么建议吗?

再次感谢!


所选答案并未按照您的要求执行,因此可能是答案本身或者您的问题出现了错误,请您澄清一下? - mozway
请注意,问题是要在“condrolmax”列中实现所需的结果,这在问题中已经清楚地显示出来。术语“rolling”可能无法精确描述要求,并且不需要在解决方案中使用“rolling()”函数。这只是一个问题措辞的问题。 - SeaBean
rolling()函数没有提及窗口大小(如果适用),实际上窗口大小可以根据列bool的模式而变化。因此,如果要使用滚动函数,最好使用expanding()而不是rolling()。在这种情况下,在组内使用cummax()会更直接。这就是我们选择cummax()而不是rolling.max()的原因。 - SeaBean
3个回答

3
您可以设置分组,然后使用cummax()函数,如下所示:
# Set group: New group if current row `bool` is True and last row `bool` is False
g = (df['bool'] & (~df['bool']).shift()).cumsum()   

# Get cumulative max of column `close` within the group 
df['condrolmax'] = df.groupby(g)['close'].cummax()

结果:

print(df)

    close   bool  condrolmax
0       1   True           1
1       3   True           3
2       2   True           3
3       5   True           5
4       3  False           5
5       3   True           3
6       4   True           4
7       5  False           5
8       7  False           7
9       5   True           5
10      7  False           7
11      8  False           8
12      6   True           6
13      8   True           8
14      5  False           8
15      5   True           5
16      7   True           7
17     15   True          15
18     16   True          16

cummaxrolling+max不是同一件事,cummax相当于expanding+max。@plonfat,如果这是您想要的,请编辑问题,因为它是不正确的。 - mozway
@mozway 我认为 OP 使用了错误的术语来描述需求。但是从期望的结果可以看出他/她想要什么。 - SeaBean
@mozway 我同意问题的措辞有点令人困惑。让我们等一会儿看看原帖作者是否会编辑它。如果不行,我会编辑它。别担心。 - SeaBean
@plonfat 问题提问者和回答者都需要合作,才能得到良好的解决方案:提问者需要清晰地阐述问题。回答者需要仔细阅读问题,以理解每一个细节。虽然我同意一个清晰的问题也可以帮助回答者更容易理解,但我们双方都需要做好自己的部分。 - SeaBean
@plonfat 没问题,我只是进行了小的编辑,以突出已经在问题的第一行清晰写明的关键陈述,并淡化了“rolling”这个词。使其更加清晰的主要解释在于我所做的评论中,bool的模式实际上可以变化,从而导致窗口大小的变化,因此,这不是真正的“rolling”,可能会被误解。 - SeaBean
显示剩余2条评论

2

首先使用你的条件(bool从False变为True)和cumsum创建分组,然后在groupby之后应用rolling

group = (df['bool']&(~df['bool']).shift()).cumsum()
df.groupby(group)['close'].rolling(2, min_periods=1).max()

输出:

0     0      1.0
      1      3.0
      2      3.0
      3      5.0
      4      5.0
1     5      3.0
      6      4.0
      7      5.0
      8      7.0
2     9      5.0
      10     7.0
      11     8.0
3     12     6.0
      13     8.0
      14     8.0
4     15     5.0
      16     7.0
      17    15.0
      18    16.0
Name: close, dtype: float64

将其作为列插入:
df['condrolmax'] = df.groupby(group)['close'].rolling(2, min_periods=1).max().droplevel(0)

输出:

    close   bool  condrolmax
0       1   True         1.0
1       3   True         3.0
2       2   True         3.0
3       5   True         5.0
4       3  False         5.0
5       3   True         3.0
6       4   True         4.0
7       5  False         5.0
8       7  False         7.0
9       5   True         5.0
10      7  False         7.0
11      8  False         8.0
12      6   True         6.0
13      8   True         8.0
14      5  False         8.0
15      5   True         5.0
16      7   True         7.0
17     15   True        15.0
18     16   True        16.0

注意:如果您希望边界也包含在滚动计算中,请在rolling函数中使用min_periods=1


0

我不确定如何使用线性代数和向量化来加速这个函数,但是使用列表推导式,我们可以编写一个更快的算法。首先,将函数定义为:

def faster_condrolmax(df):
    df['cond_index'] = [df.index[i] if df['bool'][i]==False else 0 for i in 
    df.index]
    df['cond_comp_index'] = [np.max(df.cond_index[0:i]) for i in df.index]
    df['cond_comp_index'] = df['cond_comp_index'].fillna(0).astype(int)
    df['condrolmax'] = np.zeros(len(df.close))
    df['condrolmax'] = [np.max(df.close[df.cond_comp_index[i]:i]) if 
               df.cond_comp_index[i]<i else df.close[i] for 
               i in range(len(df.close))]
    return df

接着,你可以使用:

!pip install line_profiler
%load_ext line_profiler

要添加和加载行分析器,并查看代码的每行需要多长时间,可以使用以下方法:

%lprun -f faster_condrolmax faster_condrolmax(df)

这将产生以下结果: 每行分析结果

或者只是看看整个函数需要多长时间:

%timeit faster_condrolmax(df)

这将会产生如下结果: 总算法分析结果

如果你使用SeaBean的函数,你可以在一半的时间内获得更好的结果,相比于我提出的函数。然而,对于SeaBean的速度估计似乎不够可靠,为了评估他的函数,你应该在更大的数据集上运行它,然后再做决策。这是因为%timeit报告如下: SeaBean的函数分析结果


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