原贴: 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 PRO 试试换个思路加辅助列呢 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 ![]() @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 什么的全写漏了哈哈 |