【问题标题】:Rolling Cummulative Sum of a Column's Values Until A Condition is Met滚动列值的累积总和,直到满足条件
【发布时间】:2021-01-02 04:49:18
【问题描述】:

我有一个名为“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,但它们并不符合我的确切要求。

【问题讨论】:

  • 这样的条件使得向量化的方法在这里很难使用,我可能会考虑使用numba,它可以让您加快速度并将其编写为显式循环。
  • 对于撰写此评论时显示的解决方案,我使用import pandas as pd; import numpy as np; np.random.seed(365); df = pd.DataFrame({'a': [np.random.randint(5) for _ in range(3000000)]}) 测试了每个解决方案。虽然所有解决方案都适用于来自 OP 的测试数据,但当两行的 cumsum 大于 5 时,只有来自 U11-Forwardsolution 正确地将 cumsum 重置为 0。

标签: python pandas rolling-computation cumsum


【解决方案1】:

你可以得到 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 的评论,一个好的、多样化的样本数据集对于找出这类问题的牢不可破的逻辑大有帮助。我可能会在第一次使用一个好的样本数据集时提出一个更好的解决方案。这是克服特伦顿在 cmets 中提到的示例数据集的解决方案。如图所示,有更多条件需要处理,因为您必须处理结转。在大型数据集上,这仍然比 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

【讨论】:

  • 此解决方案似乎适用于测试数据,但是如果连续数字的总和大于 5,它似乎无法正常工作。例如,给定值 [2, 4, 1] 解决方案应该是[2, 6, 1],但是,此解决方案返回 [2, 6, 2],因为超过 5(在本例中为 1)的余数会在累积和中结转。 将累计总和重置为 0 条件未正确满足。
  • 如果您使用np.random.seed(365); df = pd.DataFrame({'a': [np.random.randint(5) for _ in range(1000000)]}) 运行它,您会看到从第4 行开始出现问题。[(2, 2.0), (4, 6.0), (1, 1.0), (3, 4.0), (4, 3.0), (2, 5.0), (2, 0.0), (1, 1.0), (2, 3.0), (0, 3.0)]。总和应该是7(1 + 3 + 4)
  • @TrentonMcKinney Thx,我明天会尝试从另一个角度了解它。
  • 总和应该是 8 (1 + 3 + 4),而不是 7,正如我在之前的评论中所说的那样。抱歉,打错字了。
【解决方案2】:

分配可以与条件组合。代码如下:

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)。然后,删除实际值为零的情况。

【讨论】:

  • 这适用于测试数据,但如果 2 个数字的 cumsum 大于 5,则失败。例如,给定 [2, 4, 1] 解决方案应该是 [2, 6, 1],但是,此解决方案返回[2, 1, 2]
【解决方案3】:

编辑: 正如 Trenton McKinney 在 cmets 中指出的那样,每当 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

【讨论】:

  • 如果连续数字的总和大于 5,则不会产生正确的结果。条件 Resets the cumsum total, to 0 未正确满足。例如,给定[2, 4, 1] 的值,解决方案应该是[2, 6, 1],但是,此解决方案返回[2, 6, 7]
  • 我把 OPs “直到它得到一个总和为“5”” 的意思正好是 5。你的评论更有意义!
【解决方案4】:

您可以尝试使用这个 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(迭代器)。

【讨论】:

  • 我不是投反对票的人,但是,我的猜测是,虽然这可以解决问题,但这不是一个好的pandas 解决方案,因为for-looppandas , 是anti-pattern。正如您从其他解决方案中看到的那样,有更有效的矢量化方法来解决问题。
  • 但是,我不认为David Ericksonsolution 是正确的。
  • @TrentonMcKinney 是的,我刚刚发布了一个使用 for 循环的解决方案,这可能不是最好的。
  • 我支持我对 for-loops 的评论,但 +1 是唯一满足条件 Resets the cumsum total, to 0 的解决方案。其他解决方案仅适用于测试数据,但在 2 个数字的cumsum 大于 5 的情况下,其他解决方案失败。
  • @TrentonMcKinney 感谢您对我的支持 :-) 是的,它不是那么有效,但工作方式最一致 :-)
猜你喜欢
  • 1970-01-01
  • 2021-03-31
  • 2018-11-14
  • 1970-01-01
  • 2020-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多