在 Pandas 中进行分组后,使用条件聚合选择多个第 n 个值

3

我有一个含有四列的pd.DataFrame

    df = pd.DataFrame({'id':[1,1,1,1,1,2,2,2,2] 
                      , 'A':['H','H','E','E','H','E','E','H','H']
                      , 'B':[4,5,2,7,6,1,3,1,0]
                      , 'C':['M','D','M','D','M','M','M','D','D']})

   id  A  B  C
0   1  H  4  M
1   1  H  5  D
2   1  E  2  M
3   1  E  7  D
4   1  H  6  M
5   2  E  1  M
6   2  E  3  M
7   2  H  1  D
8   2  H  0  D

我想按id分组,并获取每个id中A='H'的第n个(比如第二个)出现的B值,以及C='M'的第n个(比如第一个)出现的B值:

desired output:
   id  agg_B1  agg_B2
0   1      5      4  
1   2      0      1

desired_output = df.groupby('id').agg(
      agg_B1= ('B',lambda x:x[df.loc[x.index].loc[df.A== 'H'][1]])
   ,  agg_B2= ('B',lambda x:x[df.loc[x.index].loc[df.C== 'M'][0]])

    ).reset_index()

TypeError: Indexing a Series with DataFrame is not supported, use the appropriate DataFrame column

显然,我在索引方面做错了些什么。


编辑:如果可能的话,我想使用带有lambda函数的聚合函数,因为我还想同时提取其他类型的多个聚合输出。

2个回答

3

如果需要的话,您可以使用 GroupBy.agg 来改变您的解决方案:

desired_output = df.groupby('id').agg(
      agg_B1= ('B',lambda x:x[df.loc[x.index, 'A']== 'H'].iat[1]),
      agg_B2= ('B',lambda x:x[df.loc[x.index, 'C']== 'M'].iat[0])
    ).reset_index()


print (desired_output)
   id  agg_B1  agg_B2
0   1       5       4
1   2       0       1

但如果性能很重要,并且不确定是否始终存在第二个符合第一个条件的值 H,我建议分别处理每个条件,然后将其添加到原始聚合值中:

#some sample aggregations
df0 = df.groupby('id').agg({'B':'sum', 'C':'last'})

df1 = df[df['A'].eq('H')].groupby("id")['B'].nth(1).rename('agg_B1')
df2 = df[df['C'].eq('M')].groupby("id")['B'].first().rename('agg_B2')

desired_output = pd.concat([df0, df1, df2], axis=1)
print (desired_output)
     B  C  agg_B1  agg_B2
id                       
1   24  M       5       4
2    5  D       0       1

编辑1:如果需要 GroupBy.agg,可以测试是否出现索引错误,然后添加缺失值:

#for second value in sample working nice
def f1(x):
    try:
        return x[df.loc[x.index, 'A']== 'H'].iat[1]
    except:
        return np.nan

desired_output = df.groupby('id').agg(
      agg_B1= ('B',f1),
      agg_B2= ('B',lambda x:x[df.loc[x.index, 'C']== 'M'].iat[0])
    ).reset_index()

print (desired_output)
   id  agg_B1  agg_B2
0   1       5       4
1   2       0       1

#third value not exist so added missing value NaN
def f1(x):
    try:
        return x[df.loc[x.index, 'A']== 'H'].iat[2]
    except:
        return np.nan

desired_output = df.groupby('id').agg(
      agg_B1= ('B',f1),
      agg_B2= ('B',lambda x:x[df.loc[x.index, 'C']== 'M'].iat[0])
    ).reset_index()

print (desired_output)
   id  agg_B1  agg_B2
0   1     6.0       4
1   2     NaN       1

工作原理相同的内容:

df1 = df[df['A'].eq('H')].groupby("id")['B'].nth(2).rename('agg_B1')
df2 = df[df['C'].eq('M')].groupby("id")['B'].first().rename('agg_B2')

desired_output = pd.concat([df1, df2], axis=1)
print (desired_output)
    agg_B1  agg_B2
id                
1      6.0       4
2      NaN       1

哇,我的索引真的搞砸了!非常感谢,这正是我正在寻找的。 - Ankhnesmerira
1
@Ankhnesmerira - 我添加了一些编辑,请在真实数据中测试是否正确(因为如果第一个.iat[1]的组没有第二个值,解决方案也会失败) - jezrael
1
所以,在我的真实数据集中,这个操作失败了(我只使用了第一次出现),并显示了“IndexError: index 0 is out of bounds for axis 0 with size 0.”。我该如何确保对于那些没有任何出现的数据,我只获取Null而不是错误信息? - Ankhnesmerira
1
@Ankhnesmerira - 刚刚为这种情况添加了编辑。 - jezrael

2

筛选出 A 等于 H 的行,然后使用 nth 函数获取第二行:

df.query("A=='H'").groupby("id").nth(1)

    A   B
id      
1   H   5
2   H   0

Python使用从0开始的标记法,因此第2行将是nth(1)


感谢您提供的解决方案。这个方法是可行的,但我的问题是我实际上想使用聚合函数,因为我对多个输出感兴趣。我想在示例中过于简化了我的问题。如果我想在同一聚合操作中使用另一个聚合函数,但只针对A=='E'怎么办?使用初始查询将无法实现此目标。我该如何在聚合函数中使用它? - Ankhnesmerira
没问题,我已经编辑了这个问题,因为原来的简化问题并不是我的实际问题。<:} - Ankhnesmerira
没问题,很高兴有人已经解决了它。 - sammywemmy

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