如何使用`transform`过滤组的最大和最小行

2 投票
1 回答
32 浏览
提问于 2025-04-13 13:21

我正在做一个任务,需要筛选出包含组内最大值和最小值的行,其他的行都要过滤掉。这样做是为了理解每个十分位的值是如何变化的。

np.random.seed(0)
df = pd.DataFrame({'id' : range(1,31),
                  'score' : np.random.uniform(size = 30)})
df    

    id  score
0   1   0.548814
1   2   0.715189
2   3   0.602763
3   4   0.544883
4   5   0.423655
5   6   0.645894
6   7   0.437587
7   8   0.891773
8   9   0.963663
9   10  0.383442
10  11  0.791725
11  12  0.528895
12  13  0.568045
13  14  0.925597
14  15  0.071036
15  16  0.087129
16  17  0.020218
17  18  0.832620
18  19  0.778157
19  20  0.870012
20  21  0.978618
21  22  0.799159
22  23  0.461479
23  24  0.780529
24  25  0.118274
25  26  0.639921
26  27  0.143353
27  28  0.944669
28  29  0.521848
29  30  0.414662

然后我使用以下代码添加十分位列:

df['decile'] = pd.qcut(df['score'], 10, labels=False)

现在我尝试了这两种方法:

df.transform((df['score'] == df.groupby('decile')['score'].min()) or (df['score'] == df.groupby('decile')['score'].max()))

还有

df.transform(df['score'].eq(df.groupby('decile')['score'].min().values).any() or df['score'].eq(df.groupby('decile')['score'].max().values).any())

但是这两种方法都不奏效,有人能帮我解决这个问题吗?

1 个回答

3

使用 DataFrame.transform 方法,结合两个条件(也叫“掩码”),可以通过 Series.eq 来实现,这两个条件之间用位运算符 OR(也就是 |)连接起来:

g = df.groupby('decile')['score']
out = df[df['score'].eq(g.transform('min')) | df['score'].eq(g.transform('max'))]
print (out)
    id     score  decile
1    2  0.715189       6
2    3  0.602763       5
3    4  0.544883       4
5    6  0.645894       5
6    7  0.437587       2
9   10  0.383442       1
10  11  0.791725       7
11  12  0.528895       3
12  13  0.568045       4
13  14  0.925597       8
15  16  0.087129       0
16  17  0.020218       0
17  18  0.832620       7
19  20  0.870012       8
20  21  0.978618       9
22  23  0.461479       3
23  24  0.780529       6
24  25  0.118274       1
27  28  0.944669       9
29  30  0.414662       2

详细信息

print (df.assign(min=df['score'].eq(g.transform('min')),
                 max=df['score'].eq(g.transform('max')),
                 both = lambda x: x['min'] | x['max']))

    id     score  decile    min    max   both
0    1  0.548814       4  False  False  False
1    2  0.715189       6   True  False   True
2    3  0.602763       5   True  False   True
3    4  0.544883       4   True  False   True
4    5  0.423655       2  False  False  False
5    6  0.645894       5  False   True   True
6    7  0.437587       2  False   True   True
7    8  0.891773       8  False  False  False
8    9  0.963663       9  False  False  False
9   10  0.383442       1  False   True   True
10  11  0.791725       7   True  False   True
11  12  0.528895       3  False   True   True
12  13  0.568045       4  False   True   True
13  14  0.925597       8  False   True   True
14  15  0.071036       0  False  False  False
15  16  0.087129       0  False   True   True
16  17  0.020218       0   True  False   True
17  18  0.832620       7  False   True   True
18  19  0.778157       6  False  False  False
19  20  0.870012       8   True  False   True
20  21  0.978618       9  False   True   True
21  22  0.799159       7  False  False  False
22  23  0.461479       3   True  False   True
23  24  0.780529       6  False   True   True
24  25  0.118274       1   True  False   True
25  26  0.639921       5  False  False  False
26  27  0.143353       1  False  False  False
27  28  0.944669       9   True  False   True
28  29  0.521848       3  False  False  False
29  30  0.414662       2   True  False   True

还有一种用 lambda 函数的方法,但这种解决方案比较慢:

out = df[df.groupby('decile')['score'].transform(lambda x: x.eq(x.min()) | x.eq(x.max()))]

np.random.seed(0)
df = pd.DataFrame({'id' : range(1,30001),
                  'score' : np.random.uniform(size = 30000)})

df['decile'] = pd.qcut(df['score'], 5000, labels=False)


In [44]: %%timeit
    ...: g = df.groupby('decile')['score']
    ...: out = df[df['score'].eq(g.transform('min')) | df['score'].eq(g.transform('max'))]
    ...: 
3.27 ms ± 239 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [45]: %%timeit
    ...: df[df.groupby('decile')['score'].transform(lambda x: x.eq(x.min()) | x.eq(x.max()))]
    ...: 
1.45 s ± 12.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

撰写回答