滚动列值的累积和直到满足条件

Rolling Cummulative Sum of a Column's Values Until A Condition is Met

我有一个名为“df”的数据框。它看起来像这样:

    a
0   2   
1   3   
2   0   
3   5   
4   1   
5   3   
6   1   
7   2   
8   2   
9   1   

我想生成一个累积总和列,其中:

我希望数据框看起来像这样:

    a   a_cumm_sum
0   2   2
1   3   5
2   0   0
3   5   5
4   1   1
5   3   4
6   1   5
7   2   2
8   2   4
9   1   5

在数据框中,“a_cumm_summ”列包含累计和的结果。

有谁知道我怎样才能做到这一点?我已经通过论坛进行了搜索。并且看到了类似的问题,比如this one,但是不符合我的确切要求。

你可以得到cumsum,floor除以5。然后从下面一行的累计和中减去floor除法乘以5的结果:

c = df['a'].cumsum()
g = 5 * (c // 5)
df['a_cumm_sum'] = (c.shift(-1) - g).shift().fillna(df['a']).astype(int)
df
Out[1]: 
   a  a_cumm_sum
0  2           2
1  3           5
2  0           0
3  5           5
4  1           1
5  3           4
6  1           5
7  2           2
8  2           4
9  1           5

解决方案 #2(更稳健):

根据 Trenton 的评论,一个好的、多样化的样本数据集对于为这些类型的问题找出牢不可破的逻辑大有帮助。我可能会在第一次使用良好的样本数据集时提出更好的解决方案。这是一个克服特伦顿在评论中提到的示例数据集的解决方案。如图所示,由于您必须处理结转,因此有更多条件需要处理。在大型数据集上,这仍然比 for 循环更高效,但矢量化逻辑要困难得多:

df = pd.DataFrame({'a': {0: 2, 1: 4, 2: 1, 3: 5, 4: 1, 5: 3, 6: 1, 7: 2, 8: 2, 9: 1}})
c = df['a'].cumsum()
g = 5 * (c // 5)
df['a_cumm_sum'] = (c.shift(-1) - g).shift().fillna(df['a']).astype(int)
over = (df['a_cumm_sum'].shift(1) - 5)
df['a_cumm_sum'] = df['a_cumm_sum'] - np.where(over > 0, df['a_cumm_sum'] - over, 0).cumsum()
s = np.where(df['a_cumm_sum'] < 0, df['a_cumm_sum']*-1, 0).cumsum()
df['a_cumm_sum'] = np.where((df['a_cumm_sum'] > 0) & (s > 0), s + df['a_cumm_sum'],
                              df['a_cumm_sum'])
df['a_cumm_sum'] = np.where(df['a_cumm_sum'] < 0, df['a_cumm_sum'].shift() + df['a'], df['a_cumm_sum'])
df
Out[2]: 
   a  a_cumm_sum
0  2         2.0
1  4         6.0
2  1         1.0
3  5         6.0
4  1         1.0
5  3         4.0
6  1         5.0
7  2         2.0
8  2         4.0
9  1         5.0

赋值可以与条件相结合。代码如下:

import numpy as np
import pandas as pd

a = [2, 3, 0, 5, 1, 3, 1, 2, 2, 1]
df = pd.DataFrame(a, columns=["a"])
df["cumsum"] = df["a"].cumsum()
df["new"] = df["cumsum"]%5
df["new"][((df["cumsum"]/5)==(df["cumsum"]/5).astype(int)) & (df["a"]!=0)] = 5
df

输出结果如下:

    a   cumsum  new
0   2   2       2
1   3   5       5
2   0   5       0
3   5   10      5
4   1   11      1
5   3   14      4
6   1   15      5
7   2   17      2
8   2   19      4
9   1   20      5

工作:
基本上,对 5 的累计和取余。实际和为 5 的情况下也变为零。因此,对于这些情况,请检查 value/5 == int(value/5)。然后,删除实际值为零的情况。

编辑: 正如 Trenton McKinney 在评论中指出的那样,OP 可能希望在 cumsum 超过 5 时将其重置为 0。这使得定义成为一个循环,这通常很难用 pandas/numpy 来实现(参见 David 的解决方案)。在这种情况下,我建议使用 numba 来加速 for 循环


另一种选择:使用groupby

In [78]: df.groupby((df['a'].cumsum()% 5 == 0).shift().fillna(False).cumsum()).cumsum()
Out[78]:
   a
0  2
1  5
2  0
3  5
4  1
5  4
6  5
7  2
8  4
9  5

您可以尝试使用这个 for 循环:

lastvalue = 0
newcum = []
for i in df['a']:
    if lastvalue >= 5:
        lastvalue = i
    else:
        lastvalue += i
    newcum.append(lastvalue)
df['a_cum_sum'] = newcum
print(df)

输出:

   a  a_cum_sum
0  2          2
1  3          5
2  0          0
3  5          5
4  1          1
5  3          4
6  1          5
7  2          2
8  2          4
9  1          5

上面的for循环遍历了a列,当累计和为5以上时,将其重置为0,然后加上a列的值i,但如果累计和小于 5,它只是添加 a 列的值 i(迭代器)。