Python处理百万行CSV速度过慢,求高效优化方案
高效处理大CSV文件修正SPECIAL_ID字段的优化方案
你的问题核心是处理超大规模CSV时的性能瓶颈,原来的两种方案都因为大量Python层面的循环和低效操作(比如DataFrame.append()、多次筛选子DataFrame)导致速度极慢。下面给你两个层级的优化方案,从快速解决当前问题到适配未来1-2GB级别的超大文件:
一、当前200万行文件的最优快速方案(基于Pandas向量化操作)
Pandas的groupby + cumcount()是专门解决分组内递增计数的向量化工具,完全避免Python循环,性能提升至少一个数量级。
核心思路:
- 按
day, month, year分组,用cumcount()生成每个分组内的递增序号(从0开始),加1后格式化为三位补零的字符串。 - 提取
SPECIAL_ID的前缀(__-之前的部分),拼接上生成的三位序号,替换原字段。 - 直接写入CSV,全程避免逐行操作和低效的DataFrame拼接。
代码实现:
import pandas as pd # 读取文件(内存吃紧可加low_memory=False) df = pd.read_csv(filename, delimiter=';') # 1. 按日期分组生成递增序号,格式化为三位补零 df['seq'] = df.groupby(['day', 'month', 'year']).cumcount() + 1 df['seq_str'] = df['seq'].apply(lambda x: f"{x:03d}") # 2. 替换SPECIAL_ID:提取前缀 + 拼接序号 df['SPECIAL_ID'] = df['SPECIAL_ID'].str.replace('__-', '') + df['seq_str'] # 3. 保存结果(删除临时列,或直接指定要保存的列) df.drop(columns=['seq', 'seq_str']).to_csv(filename_fixed, sep=';', encoding='utf-8', index=False)
为什么这个方案快?
groupby.cumcount()是Pandas底层实现的向量化操作,用C语言循环,比Python的iterrows()快几十倍。- 全程没有逐行修改和DataFrame拼接,避免了
append()带来的O(n²)时间复杂度。
二、适配未来1-2GB超大文件的分块处理方案
如果文件大到内存无法一次性加载,可以用分块读取+全局计数的方式,保证跨块的日期分组序号连续:
核心步骤:
- 先遍历所有块,统计每个日期分组的总数量,记录每个分组的起始序号。
- 再次分块读取,对每个块内的日期分组,计算当前块内的序号范围(起始序号到起始序号+块内数量-1),生成序号后替换SPECIAL_ID。
- 逐块追加写入CSV,避免内存溢出。
代码实现:
import pandas as pd from collections import defaultdict filename = "your_large_file.csv" filename_fixed = "fixed_large_file.csv" chunksize = 100000 # 按10万行一块调整,根据内存情况修改 # 第一步:全局统计每个日期分组的总数量 date_counts = defaultdict(int) for chunk in pd.read_csv(filename, delimiter=';', chunksize=chunksize): chunk_counts = chunk.groupby(['day', 'month', 'year']).size() for (d, m, y), cnt in chunk_counts.items(): date_counts[(d, m, y)] += cnt # 计算每个日期分组的起始序号(前一个分组的累计和) current_start = 1 date_start = {} for key in sorted(date_counts.keys()): date_start[key] = current_start current_start += date_counts[key] # 第二步:分块处理并写入 first_chunk = True for chunk in pd.read_csv(filename, delimiter=';', chunksize=chunksize): # 给当前块的每个行生成序号 chunk['seq'] = chunk.groupby(['day', 'month', 'year']).cumcount() + 1 chunk['start_seq'] = chunk.apply(lambda row: date_start[(row['day'], row['month'], row['year'])], axis=1) chunk['final_seq'] = chunk['start_seq'] + chunk['seq'] - 1 chunk['seq_str'] = chunk['final_seq'].apply(lambda x: f"{x:03d}") # 替换SPECIAL_ID chunk['SPECIAL_ID'] = chunk['SPECIAL_ID'].str.replace('__-', '') + chunk['seq_str'] # 写入文件,第一块写表头,后续块追加 chunk.drop(columns=['seq', 'start_seq', 'final_seq', 'seq_str']).to_csv( filename_fixed, sep=';', encoding='utf-8', index=False, mode='w' if first_chunk else 'a', header=first_chunk ) first_chunk = False
注意事项:
chunksize的大小根据你的内存情况调整,比如8GB内存可以设为20万行。- 如果日期分组的顺序不影响序号连续性,可以不用排序,直接累计计数。
验证结果
用你给出的示例数据测试,上述方案会生成完全符合预期的输出:
原行8的SPECIAL_ID会从
21102011505__-变为21102011505002,和你预期的一致。
内容的提问来源于stack exchange,提问作者Kristjan
相关产品推荐
相关产品推荐

