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

问题描述

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

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

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

  • 累加“a”列的内容
  • 直到总和为“5”;
  • 当总和达到“5”时,将总和重置为 0,并继续求和过程;

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

    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 的评论,一个好的、多样化的样本数据集对于找出这些类型问题的牢不可破的逻辑大有帮助。我可能会在第一次使用好的样本数据集时提出更好的解决方案。这是一个克服 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,5,1,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 在评论中指出的那样,每当 cumsum 超过 5 时,OP 可能希望将其重置为 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 列的值 {{1} },但如果累积总和小于 5,它只会添加 i 列的值 a(迭代器)。