滚动列值的累积和直到满足条件
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”列的内容;
- 直到总和为“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 的评论,一个好的、多样化的样本数据集对于为这些类型的问题找出牢不可破的逻辑大有帮助。我可能会在第一次使用良好的样本数据集时提出更好的解决方案。这是一个克服特伦顿在评论中提到的示例数据集的解决方案。如图所示,由于您必须处理结转,因此有更多条件需要处理。在大型数据集上,这仍然比 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
(迭代器)。
我有一个名为“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 的评论,一个好的、多样化的样本数据集对于为这些类型的问题找出牢不可破的逻辑大有帮助。我可能会在第一次使用良好的样本数据集时提出更好的解决方案。这是一个克服特伦顿在评论中提到的示例数据集的解决方案。如图所示,由于您必须处理结转,因此有更多条件需要处理。在大型数据集上,这仍然比 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
(迭代器)。