原贴: 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
也是有同样的问题
1
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) |
2
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 列总和。 大屁眼子! |
3
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) |
4
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 |
5
zone10 2021-03-12 11:09:42 +08:00 1
@necomancer 根据你的思路,
df['D'] = np.where(df.C, df.groupby(df.C.eq(True).cumsum().shift(1, fill_value=0)).B.cumsum(), 0) print(df) |
6
yaleyu OP @necomancer 哈哈哈,莫怪莫怪,第一次需求没说清
|
7
yaleyu OP @necomancer 666
|
8
yaleyu OP @zone10 这个和在 stackoverflow 求助得到的一样了,我把思路套进实际数据比对一下,谢谢
|
9
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) ``` |
10
yaleyu OP @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) |
11
necomancer 2021-03-14 20:35:35 +08:00
@yaleyu 这速度测试……是数据集太小了吧……
|
12
HelloViper 2021-03-15 15:59:05 +08:00
@yaleyu groupby 写的爽但肯定影响性能的,这种需求可以使用标识位通过单层遍历一把梭,o(n),而且可读性强,我回的时候正好有事,边界值 zip 什么的全写漏了哈哈
|