如何在Python DataFrame的groupby与transform中用多条件生成NewRank
需求:基于GroupBy的条件判断生成NewRank列
我们需要结合groupby操作,通过条件判断在DataFrame中生成NewRank新列。
原始数据
创建目标DataFrame的代码如下:
import numpy as np import pandas as pd data = {'User': ['User1', 'User2', 'User3', 'User4', 'User5', 'User6', 'User7','User8', 'User9', 'User10','User11', 'User12', 'User13','User14', 'User15', 'User16'], 'Type': ['A', 'B', 'B', 'A', 'A', 'B', float('nan'),'C','C','C','D','D','D','E','E','E'], 'Usage': [100, 200, 150, 50, 75, 175, float('nan'),0,0,0,-3,-1,0,4,0,0], 'OldRank': [3, 3, 1, 1, 2, 2, float('nan'),1,1,1,1,2,3,3,1,1]} df = pd.DataFrame(data)
期望输出
最终需要得到的结果如下:
User Type Usage OldRank NewRank 0 User1 A 100.0 3.0 3.0 4 User5 A 75.0 2.0 2.0 3 User4 A 50.0 1.0 1.0 1 User2 B 200.0 3.0 3.0 5 User6 B 175.0 2.0 2.0 2 User3 B 150.0 1.0 1.0 7 User8 C 0.0 1.0 0.0 8 User9 C 0.0 1.0 0.0 9 User10 C 0.0 1.0 0.0 12 User13 D 0.0 3.0 3.0 11 User12 D -1.0 2.0 2.0 10 User11 D -3.0 1.0 1.0 13 User14 E 4.0 3.0 3.0 14 User15 E 0.0 1.0 0.0 15 User16 E 0.0 1.0 0.0 6 User7 NaN NaN NaN NaN
遇到的问题
尝试以下代码时触发ValueError,提示Series的真值判断不明确:
df['NewRank'] = df.groupby('Type')['Usage'].transform(lambda x: 0 if ( ((x==0).all()) | ( (x.min()>=0) & (df['OldRank']==0) ) ) else df['OldRank'] )
错误信息:
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
现有可行方案需要新增多个中间列,代码冗长且影响性能:
# 冗长的实现代码 df['R1'] = df.groupby('Type')['Usage'].transform(lambda x: (x==0).all() ) df['R2'] = df.groupby('Type')['Usage'].transform(lambda x: (x.min()>=0)) df['R3'] = df.groupby('Type')['Usage'].transform(lambda x: (x==0)) df['NewRank'] = np.where((df['R2']==True) & (df['R3']==True) | df['R1']==True ,0, df['OldRank']) df = df.sort_values(by=['Type', 'Usage'], ascending=[True, False])
简洁高效的解决方案
可以通过一次分组计算生成所需标记,避免创建额外列,同时确保逻辑清晰:
# 生成分组级别的两个判断条件 grouped = df.groupby('Type')['Usage'] # 条件1:分组内所有Usage都是0 all_zero = grouped.transform(lambda x: (x == 0).all()) # 条件2:分组内Usage最小值≥0,且当前行Usage为0 min_non_neg_and_zero = grouped.transform(lambda x: x.min() >= 0) & (df['Usage'] == 0) # 生成NewRank:满足任一条件则为0,否则保留OldRank df['NewRank'] = np.where(all_zero | min_non_neg_and_zero, 0, df['OldRank']) # 按Type和Usage降序排序,得到目标结果 df = df.sort_values(by=['Type', 'Usage'], ascending=[True, False])
逻辑说明
all_zero:通过transform为每个分组标记是否所有Usage值都是0,结果是与原DataFrame长度一致的Series。min_non_neg_and_zero:先判断分组内Usage的最小值是否≥0(通过transform广播到每一行),再结合当前行Usage是否为0,得到第二个条件的标记。- 生成NewRank:用
np.where判断,只要满足all_zero或min_non_neg_and_zero任一条件,就赋值0,否则保留原OldRank。 - 排序:按照
Type升序、Usage降序排序,匹配期望输出的顺序。
这种方式无需创建多余的中间列,直接通过分组计算和条件组合完成需求,性能更优。
内容的提问来源于stack exchange,提问作者Josue Medrano
相关产品推荐
相关产品推荐

