优化Pandas for循环:10万行分组回写耗时过长问题
问题原因
你当前代码耗时极高的核心原因是完全没有利用pandas内置的向量化计算能力,反复在Python层做全表扫描、逐行/逐切片赋值:
- 每次循环都要在全量DataFrame中执行等值匹配筛选ID,相当于每个唯一ID都要做一次全表扫描,唯一ID越多扫描开销越大
- 逐索引循环赋值、切片赋值都属于Python层的低效操作,没有用到pandas底层C实现的批量计算逻辑
- 你优化后的切片赋值版本还存在逻辑bug:如果同一个
DICE_SUMMARY_ID对应的行不是连续存储的,indices.min():indices.max()会把中间不属于该ID的行错误赋值
10万行规模的数据用正确的pandas写法处理,耗时可以从3小时压缩到秒级甚至毫秒级。
最优解决方案
完全抛弃显式for循环,用pandas原生的分组统计+映射逻辑实现,推荐两种性能最好的写法:
方案1:groupby + transform(代码最直观)
transform会自动把分组统计的结果按照原表的索引映射回每一行,不需要手动查找索引、赋值:
# 生成各家庭类型的0/1标记列,方便分组统计 df['flag_single'] = (df['FAMILY_TYPE_ID'] == 10001).astype(int) df['flag_extra'] = (df['FAMILY_TYPE_ID'] == 10003).astype(int) df['flag_u16'] = (df['FAMILY_TYPE_ID'] == 20001).astype(int) df['flag_16_18'] = (df['FAMILY_TYPE_ID'] == 20002).astype(int) # 按ID分组,直接将统计结果广播回原表所有行 group_obj = df.groupby('DICE_SUMMARY_ID') df['NO_OF_ADULTS'] = group_obj[['flag_single', 'flag_extra']].transform('sum').sum(axis=1).astype(int) df['NO_OF_DEPENDENTS_U_16'] = group_obj['flag_u16'].transform('sum').astype(int) df['NO_OF_DEPENDENTS_16_TO_18'] = group_obj['flag_16_18'].transform('sum').astype(int) # 删除临时标记列 df.drop(columns=['flag_single', 'flag_extra', 'flag_u16', 'flag_16_18'], inplace=True)
这个方案10万行数据的处理耗时通常在500毫秒以内。
方案2:分组计数 + 映射(性能最高)
先统计每个ID下各家庭类型的总数,再通过join把统计结果映射回原表,数据量越大性能优势越明显:
# 统计每个ID下各家庭类型的行数 type_count = df.groupby(['DICE_SUMMARY_ID', 'FAMILY_TYPE_ID']).size().unstack(fill_value=0) # 计算需要的三个指标 id_stat = pd.DataFrame({ 'NO_OF_ADULTS': type_count.get(10001, 0) + type_count.get(10003, 0), 'NO_OF_DEPENDENTS_U_16': type_count.get(20001, 0), 'NO_OF_DEPENDENTS_16_TO_18': type_count.get(20002, 0) }).astype(int) # 把统计结果关联回原表 df = df.join(id_stat, on='DICE_SUMMARY_ID')
这个方案10万行数据的处理耗时通常在100毫秒以内,且不会出现链式赋值的警告问题。
注意事项
- 除非万不得已,不要在pandas中写显式Python循环处理行/分组,所有统计、转换逻辑优先找内置的向量化、groupby接口,底层C实现的性能比Python层循环高3~4个数量级
- 避免使用
.iloc[索引] = 值的链式赋值写法,不仅性能差,还容易触发SettingWithCopyWarning,出现赋值不生效的问题
内容的提问来源于stack exchange,提问作者bibscy
相关产品推荐
相关产品推荐

