使用GroupBy计算某个值的数量并过滤Pandas DataFrame

4

我想筛选一个pandas DataFrame,只保留那些特定分组中具有特定列值最小计数的行。

例如,仅返回df中['c2','c3']组至少有2行具有'c1'值为1的行/组:

df = pd.DataFrame({'c1':[0,1,0,1,1,0], 'c2':[0,0,0,1,1,1], 'c3':[0,0,0,1,1,1]})

结果应该只返回索引为3、4、5的行,因为只有[c2,c3] = [1,1]的分组中至少有2行的'c1'值为1。
df.groupby(['c2','c3']).filter(lambda x: x['c1'].count() >= 2)

不返回所需结果。 我需要的计数仅适用于 1 的计数,而不仅仅是任何 'c1' 值的计数。

以下代码可以工作,但我不确定如何使其更具 Pythonic 风格:

s = df.groupby(['c2','c3']).apply(lambda x: x[x['c1']==1].count() >= 2).all(axis=1)
df = df.reset_index().set_index(['c2','c3']).loc[s[s].index].reset_index().set_index(['index'])    
2个回答

3
使用 groupby + transform 对布尔序列求和,然后用它来屏蔽原始的 DataFrame。
m = df['c1'].eq(1).groupby([df['c2'], df['c3']]).transform('sum').ge(2)

# Alterntively assign the column
#m = df.assign(to_sum = df.c1.eq(1)).groupby(['c2', 'c3']).to_sum.transform('sum').ge(2) 

df.loc[m]
#   c1  c2  c3
#3   1   1   1
#4   1   1   1
#5   0   1   1

使用过滤器时,count 不是正确的逻辑。请使用 ==(或 .eq())来检查 'c1' 是否等于特定值。总结布尔序列,并检查每个组中是否至少有 2 个这样的出现。

df.groupby(['c2','c3']).filter(lambda x: x['c1'].eq(1).sum() >= 2)
#   c1  c2  c3
#3   1   1   1
#4   1   1   1
#5   0   1   1

虽然对于小DataFrame来说可能没有明显的差别,但随着组数增加,使用lambda进行filter的速度变得非常慢。 使用transform则速度较快:

import numpy as np
np.random.seed(123)
df = pd.DataFrame({'c1':np.random.randint(1,100,1000), 'c2':np.random.randint(1,100,1000), 
                   'c3':np.random.choice([1,0], 1000)})

%%timeit
m = df['c1'].eq(1).groupby([df.c3, df.c3]).transform('sum').ge(2)
df.loc[m]
#5.21 ms ± 15.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df.groupby(['c2','c3']).filter(lambda x: x['c1'].eq(1).sum() >= 2)
#124 ms ± 714 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
非常有趣的方法。我不知道即使您将“c1”中的1的值更改为“a”,'df ['c1'] .eq('a').sum()'也会“计算”True值的数量。谢谢! - user7101631
是的,.eq() 返回一个布尔序列,当你应用像 .sum 这样的操作时,它会将 True 强制转换为 1,False 强制转换为 0。 - ALollz
1
在这种情况下,我正在通过DataFrame列对Series进行分组,因此语法有点不同,并且需要单独传递分组列。如果您需要在原始DataFrame中使用许多列,则应使用.assign创建您需要求和的列,然后传递要分组的列的列表。m = df.assign(to_sum = df.c1.eq(1)).groupby(['c2', 'c3']).to_sum.transform('sum').ge(2) - ALollz

1
可能使用 groupby + merge
s=df.groupby(['c2','c3']).c1.sum().ge(2)
s[s].index.to_frame().reset_index(drop=True).merge(df,how='left')
   c2  c3  c1
0   1   1   1
1   1   1   1
2   1   1   0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接