在 Pandas 的 GroupBy 后筛选组,同时保留组。

15

在pandas中,我想做到:

df.groupby('A').filter(lambda x: x.name > 0) - 按列 A 进行分组,然后筛选出名称值为非正的组。但是这会取消分组,因为 GroupBy.filter 返回 DataFrame,从而丢失了分组信息。 我想按这个顺序操作,因为这应该是计算效率较低的方式,因为 filter 后跟 groupby 将在数据帧上进行两次迭代(先过滤再分组)?此外,克隆来自分组的组(到字典或其他某些东西)将使我失去无缝返回数据帧的功能(就像在 .filter 的示例中一样,你直接得到 DataFrame

谢谢

示例:

   A  B
1 -1  1
2 -1  2
3  0  2
4  1  1
5  1  2

df.groupby('A')

GroupBy object
-1 : [1, 2]
 0 : [3]
 1 : [4,5]

GroupBy.filter(lambda x: x.name >= 0):

GroupBy object
 0 : [3]
 1 : [4,5]

1
你能否在这里放置一个数据框的样例数据呢? :) - Jacquot
1
为什么不直接在 df[df['A'] > 0] 上进行分组,而不是在 df 上进行分组呢? - Jacquot
2
因为我预计这将比先按组分组再过滤组要花费两倍的时间,因为我将过滤10个组而不是100万行。 - Péťa Poliak
好的,明白了 :) - Jacquot
4个回答

11

我认为之前的回答提出了一些解决方法,这些方法可能对你有用,但并没有回答问题。

你创建了分组,并希望根据分组统计信息扔掉或保留一些分组,然后在这些分组上执行你真正关心的分组统计。这应该是可能的,并且在许多情况下很有用,然而,目前似乎只能使用两个相同的groupbys连续执行才能实现。

让我们来看一个例子:Groupby揭示了一些无法按项级别筛选的特征(因此以前的筛选不是一个选择)。例如,一个组加和。过滤的烦恼在于,它返回一个数据框,而不是保持分组并允许您在分组上执行进一步的计算。

这里有一个例子:

假设你想按'C'进行分组,并过滤'A'的总和在分组中小于700的组,但在过滤后你实际上关心的是组的标准差。如果filter只是对组进行过滤,那么这将起作用:

df.groupby(['C']).filter(lambda x:x['A'].sum()<700, combine=False).std()

这个不起作用(注意filter上不存在的combine=False选项),有效的代码如下:

df.groupby(['C']).filter(lambda x:x['A'].sum()<700).groupby(['C']).std()

过滤器实际上是进行过滤和合并,这遵循了分割-应用-合并的逻辑。


4

让我们运行一些时间测试。

df = pd.DataFrame({'A':np.random.randint(-10,10,1000000),'B':np.random.random(1000000)})

测试两个返回值是否相等

df1 = df.groupby('A').filter(lambda x: x.name >= 0)
df2 = df[df.A >= 0]

all(df1 == df2)
True

时间:

%timeit df1 = df.groupby('A').filter(lambda x: x.name >= 0)

每次循环平均为607毫秒±10.2毫秒,共执行7次循环(1次循环为一个运行周期)。

%timeit df2 = df[df.A >= 0]

59.7毫秒±724微秒每个循环(平均值±7次运行的标准差,每个循环10次)

@jacquot的解决方案似乎比分组然后过滤快了10倍。


3
这并不完全是我说的。我的意思是,根据组名称过滤10个组比过滤整个数据集再分组要快,因为(我假设)过滤和分组都是线性时间操作。然而,如果我尝试你的例子,df[df.A >= 0].groupby('A'){name:group for name,group in df.groupby('A').groups.items() if name >= 0} 更快,所以我想我不必太担心速度问题。 - Péťa Poliak

2

使用groupby实际上不会以任何方式聚合值。它只是创建分组,因此filter基本上是在原始数据帧上进行过滤。我认为,除非name是通过对分组应用函数来实现的值,否则先进行分组并不能节省时间或计算。

因此,我建议像这样做:

df.where(df.name > 0).groupby('A')  # now apply some transformation to the groups

但是为了创建这些组,您需要遍历整个数据框,对吗?我的意思是,如果您使用 groupby,则必须检查每一行,如果筛选数据帧,则同样如此,因此需要两次遍历整个数据框。但是,如果我仅筛选组,则仅遍历整个数据框一次,然后再遍历组。 - Péťa Poliak

1
我理解这个问题与Petsol的理解相同,而不是Scott的理解。因此,Scott给出的例子等价于:
df = d.DataFrame({'A':np.random.randint(-10,10,1000000),'B':np.random.random(1000000)})
df1 = df.groupby('A').filter(lambda x: x['A'].mean()>0).groupby('A').count()

尽管第二种方法显然行不通。

但在我找到这个解决方案之前,我的想法很简单:Groupby对象是否有类似于DataFrames的.iloc的方法?

这样,当您创建一个Groupby对象时,可以通过应用某些聚合函数的条件来创建筛选器(并将其存储为布尔值,其中索引对应于每个组),然后仅对请求的组应用其他函数。

不幸的是,我没有找到任何有用的等效于 iloc 的方法或函数(我检查了“nth”、“take”和“get_group”,但它们都没有起作用)。


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