为何value_counts与nunique拖慢4000个Excel文件的校验速度?
问题背景
处理约4000个单工作表Excel文件,每个表约200行10列。原本9项简单校验全程耗时约6分钟,新增check_10校验后总耗时骤增至20分钟。
check_10的校验目标:对每个固定capacity和chunks的id,验证两个规则:
- 唯一
components数量不超过capacity components的总行数等于对应id的chunks值
示例代码及测试数据如下:
import pandas as pd df = pd.DataFrame( { 'id': ['381']*10 + ['382']*10, 'components': ['A1']*4 + ['B1']*4 + ['A1']*2 + ['A1']*6 + ['C1']*4, 'capacity': ['1']*10 + ['2']*10, 'chunks': ['4']*10 + ['6']*10 } ) def check_10(df): cols = ['id', 'capacity', 'chunks'] counts = df.groupby(cols, as_index=False)['components'].agg(total_components='value_counts') counts['unique_components'] = counts.groupby(cols)['components'].transform('nunique') # 原代码vc应为df,此处修正笔误 result1 = counts['unique_components'].le(counts['capacity'].astype(int)).all() result2 = counts['total_components'].eq(df['chunks'].astype(int)).all() return result1 and result2 print(check_10(df)) # False
运行后生成的counts数据:
id capacity chunks components total_components unique_components 0 381 1 4 A1 6 2 1 381 1 4 B1 4 2 2 382 2 6 A1 6 2 3 382 2 6 C1 4 2
性能瓶颈原因
1. 重复分组遍历
原代码先通过groupby(cols).agg(total_components='value_counts')做第一次分组统计,随后又对结果再次groupby(cols)计算unique_components,相当于对同一份数据做了两次完整的分组遍历,4000个文件累加后,额外的遍历开销被放大。
2. value_counts导致数据膨胀
agg中使用value_counts会把每个分组内的不同components展开成单独行,比如一个分组有3种components,就会生成3行数据。这会让临时处理的数据量翻倍甚至更多,增加内存占用和后续计算的时间成本。
3. 重复类型转换
代码中多次对capacity和chunks执行astype(int)转换,每次转换都要遍历整列,4000次重复操作积累了可观的额外耗时。
4. 逻辑冗余(附带逻辑错误)
原代码中result2的判断逻辑有误:total_components是每个components的出现频次,而chunks是id对应的总数值,直接对比两者逻辑不成立;同时这段代码还错误引用了未定义的vc变量,即使修正为df['chunks'],也会再次遍历原数据,增加不必要的计算。
优化方案
将两次分组合并为一次,直接计算所需的聚合指标,减少遍历次数和临时数据量:
def check_10_optimized(df): # 提前转换列类型,避免重复操作 df['capacity'] = df['capacity'].astype(int) df['chunks'] = df['chunks'].astype(int) # 一次分组完成所有需要的聚合计算 agg_stats = df.groupby(['id', 'capacity', 'chunks'], as_index=False).agg( unique_components=('components', 'nunique'), total_components_count=('components', 'count') ) # 验证两个规则 rule1_pass = agg_stats['unique_components'].le(agg_stats['capacity']).all() rule2_pass = agg_stats['total_components_count'].eq(agg_stats['chunks']).all() return rule1_pass and rule2_pass
优化效果说明
- 一次分组完成全部聚合计算,减少一半的数据遍历次数
- 避免
value_counts导致的数据行膨胀,临时数据量大幅降低 - 提前转换列类型,消除重复转换的开销
- 修正原逻辑中
result2的错误,确保校验逻辑正确
内容的提问来源于stack exchange,提问作者VERBOSE
相关产品推荐
相关产品推荐

