开新帖求教 pandas 大拿,关于 groupby 和 cumsum 和 rolling

2021-03-11 18:49:28 +08:00
 yaleyu

原贴: https://www.v2ex.com/t/760567 求助问题:对每个 A 列里面的值,当 C 列为 False 时候,D 列为 0,当 C 列为 True 时候,D 列为上一个 True 之后的第一个 False 到当前行的 B 列总和。

更改了一下数据,更加接近原始数据

df = pd.DataFrame([['S1', 10, False], ['S1', 10, True],
    ['S2', 20, False], ['S2', 10, False], ['S2', 10, True],
    ['S3', 200, False], ['S3', 100, False], ['S3', 100, True]],
    columns=list('ABC'))
print(df)
    A    B      C
0  S1   10  False
1  S1   10   True
2  S2   20  False
3  S2   10  False
4  S2   10   True
5  S3  200  False
6  S3  100  False
7  S3  100   True

用 for 循环来切片然后再处理,能得到希望的结果:

codes = df.A.unique()
dfs = []
for code in codes:
    subdf = df[df.A == code].reset_index()
    slices = subdf[subdf.C].index
    slices = slices.insert(0, -1)
    for i in range(len(slices) - 1):
        tempdf = subdf.loc[slices[i]+1: slices[i+1]].copy()
        tempdf['D'] = np.where(tempdf.C, tempdf.groupby('A').B.sum(), 0)
        dfs.append(tempdf)
df_with_d = pd.concat(dfs).reset_index()
print(df_with_d[list('ABCD')])
    A    B      C    D
0  S1   10  False    0
1  S1   10   True   20
2  S2   20  False    0
3  S2   10  False    0
4  S2   10   True   40
5  S3  200  False    0
6  S3  100  False    0
7  S3  100   True  400

觉得效率不高,求更有效的方法!

按原贴 @necomancer 的方法

df['D'] = np.where(df.C, df.groupby(df.C.eq(False).cumsum()).B.cumsum(), 0)
print(df)
    A    B      C    D
0  S1   10  False    0
1  S1   10   True   20
2  S2   20  False    0
3  S2   10  False    0
4  S2   10   True   20
5  S3  200  False    0
6  S3  100  False    0
7  S3  100   True  200

第 4 行 D 列的结果不对,应该是 40 (20+10+10),第 7 行 D 列应该是 400

按 @cassidyhere 的方法

class CustomIndexer(BaseIndexer):
    def get_window_bounds(self, num_values, min_periods, center, closed):
        start = np.empty(num_values, dtype=np.int64)
        end = np.empty(num_values, dtype=np.int64)
        for i in range(num_values):
            end[i] = i + 1
            j = i
            while j > 0 and self.use_expanding[j]:
                j -= 1
                start[i] = j
        return start, end
    
window_size = df.C.groupby((df.C != df.C.shift(1)).cumsum()).agg('sum').max() # 最大连续次数
indexer = CustomIndexer(window_size=window_size, use_expanding=df.C)
df['D'] = np.where(df.C, df.B.rolling(indexer, min_periods=2).sum().fillna(0), 0)
print(df)
    A    B      C      D
0  S1   10  False    0.0
1  S1   10   True   20.0
2  S2   20  False    0.0
3  S2   10  False    0.0
4  S2   10   True   20.0
5  S3  200  False    0.0
6  S3  100  False    0.0
7  S3  100   True  200.0

也是有同样的问题

1762 次点击
所在节点    Python
12 条回复
HelloViper
2021-03-12 10:08:58 +08:00
个人认为不要在 pandas 上做处理,应当吧 b 列和 c 列单独 tolist,通过单层遍历就算出 d 列的 list,在组装回去

随手写点,没细想边界值之类的:

d=[]
last_false = 0
for i,(x,y) in enumerate(b,c):
if y:
d.append(sum(b[last_false:i+1])
last_false=i+1
else:
d.append(0)
necomancer
2021-03-12 10:39:24 +08:00
你上个帖子里说
如下一个表,想每当 C 列为 False 时候,D 列为 0,为 True 时候,D 列为 B 列的上一次 C 列为 False 到当前列的加总

这次就变成
求助问题:对每个 A 列里面的值,当 C 列为 False 时候,D 列为 0,当 C 列为 True 时候,D 列为上一个 True 之后的第一个 False 到当前行的 B 列总和。

大屁眼子!
TimePPT
2021-03-12 10:43:17 +08:00
试试换个思路加辅助列呢

df = pd.DataFrame([['S1', 10, False], ['S1', 10, True], ['S2', 20, False], ['S2', 10, False], ['S2', 10, True], ['S3', 200, False], ['S3', 100, False], ['S3', 100, True]], columns=list('ABC'))

df['D'] = df['B'].cumsum()
df_tmp = df[df['C']]
df_tmp['X'] = df_tmp['D'].diff()
df = pd.merge(left=df, right=df_tmp, on=['A', 'B', 'C', 'D'], how='left')
df['D'] = np.where(df['C']==False, 0, df['D'])
df['D'] = np.where(((df['C'] == True) & (df['X'].isna() == False)), df['X'], df['D'])
df = df[['A', 'B', 'C', 'D']]

print(df)
necomancer
2021-03-12 10:52:48 +08:00
df['D'] = np.where(df.C, df.groupby(pd.Series(np.diff(df.C, prepend=0)).eq(-1).cumsum()).B.cumsum(),0)

df

A B C D
0 S1 10 False 0
1 S1 10 True 20
2 S2 20 False 0
3 S2 10 False 0
4 S2 10 True 40
5 S3 200 False 0
6 S3 100 False 0
7 S3 100 True 400
zone10
2021-03-12 11:09:42 +08:00
@necomancer 根据你的思路,

df['D'] = np.where(df.C, df.groupby(df.C.eq(True).cumsum().shift(1, fill_value=0)).B.cumsum(), 0)
print(df)
yaleyu
2021-03-12 11:49:07 +08:00
@necomancer 哈哈哈,莫怪莫怪,第一次需求没说清
yaleyu
2021-03-12 11:50:22 +08:00
yaleyu
2021-03-12 11:52:05 +08:00
@zone10 这个和在 stackoverflow 求助得到的一样了,我把思路套进实际数据比对一下,谢谢
princelai
2021-03-12 14:35:32 +08:00
想到一个思路不太一样的方法

```
import pandas as pd
import numpy as np

df.loc[df.C == True, 'Z'] =range(df.C.sum())
df.Z.bfill(inplace=True)
df['D'] = np.where(df.C,df.groupby('Z')['B'].transform('sum'),0)
df.drop(columns='Z',inplace=True)
```
yaleyu
2021-03-13 22:11:46 +08:00
@HelloViper 原来的代码跑出来有错,没考虑到 A,稍微改了一下
```
a = df.A.to_list()
b = df.B.to_list()
c = df.C.to_list()
d = []
first_false = 0
for i, (x, y, z) in enumerate(zip(a, b, c)):
if a[i] != a[i-1] and not z:
first_false = i
if z:
d.append(sum(b[first_false: i+1]))
else:
d.append(0)
df['D'] = d
print(df)
```
居然性能是最快的:
226 µs ± 4.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
necomancer
2021-03-14 20:35:35 +08:00
@yaleyu 这速度测试……是数据集太小了吧……
HelloViper
2021-03-15 15:59:05 +08:00
@yaleyu groupby 写的爽但肯定影响性能的,这种需求可以使用标识位通过单层遍历一把梭,o(n),而且可读性强,我回的时候正好有事,边界值 zip 什么的全写漏了哈哈

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/760789

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX