Pandas merge合并后表头带_x后缀、KeyError问题排查
问题场景
- 现有按月拆分的CSV源文件,每月对应7个结构完全一致的分片文件
- 处理目标:先按月用
concat拼接生成分月数据集,再基于唯一id字段合并各月数据集,生成JAN_COUNT、FEB_COUNT这类带月份标识的统计字段,最终汇总计算2019年全年各类型指标总和 - 运行时出现两类异常:
- 即使指定
id为关联键,合并结果反复出现Unnamed、Unnamed_x、Unnamed_x.1这类异常列名 - 汇总全年指标时触发
KeyError,提示JAN_COUNT_CARGO字段不存在,实际字段被自动添加_x后缀变为JAN_COUNT_CARGO_x
- 即使指定
核心问题排查
代码共存在5处直接导致异常的逻辑错误:
- DataFrame操作未生效:
set_index()、sort_values()都是非原地操作,会返回处理后的新DataFrame,未将返回值重新赋值给变量时,这两行代码实际不产生任何效果。 - 文件匹配范围错误:合并年度数据时用
*2019.csv匹配文件,会把脚本运行过程中生成的2019.csv本身也纳入待合并列表,等于反复把未完成的合并结果重新读入参与merge,直接导致列重复、结构混乱。 - 列名冲突未提前处理:所有月度数据集除
id外的列名完全一致,merge时pandas会自动给重复列名加_x/_y后缀区分,自然不会生成预期的带月份前缀的列名,还会触发后续字段找不到的KeyError。 - CSV写入参数错误:
to_csv()的index参数仅接受布尔值(控制是否将行索引导出到文件),传入列名字符串会被判定为True,导致无列名的索引列被写入CSV,下次读取时pandas会自动将这类无名列命名为Unnamed: 0,多次读写后就会出现一堆Unnamed_x类的异常列。 - 列选择语法错误:按列筛选子集时直接传入列名列表即可,在列表前多写的冒号属于pandas切片语法错误,会导致列选择逻辑不符合预期。
修正后代码
print("Importing modules") import os, glob import pandas as pd from functools import reduce print('Modules imported... Setting variables') opath = r'dir of csvs' mlist = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] unique_gridid = 'id' os.chdir(opath) print('Variables set... Merging csv files into monthly datasets') month_df_list = [] for m in mlist: print(f'Running merge for {m} collecting csv files...') csvlist = [i for i in glob.glob(f'{m}2019_G*.csv')] # 拼接当月7个分片文件 month_csv = pd.concat([pd.read_csv(csv) for csv in csvlist]) # 按唯一id去重,避免重复id导致merge时产生笛卡尔积 month_csv = month_csv.drop_duplicates(subset=unique_gridid, keep='last') # 给非id列添加月份前缀,从根源避免merge时列名冲突 month_csv = month_csv.rename(columns={ col: f'{m}_{col}' for col in month_csv.columns if col != unique_gridid }) month_df_list.append(month_csv) print(f'Saving {m}2019.csv') # 修正to_csv参数,不导出行索引 month_csv.to_csv(f'{m}2019.csv', index=False) print('Merging the monthly into yearly dataset...') # 直接合并内存中的月度DataFrame,避免重复读写磁盘和错误匹配文件 ALL2019DF = reduce( lambda left,right: pd.merge(left, right, on=unique_gridid, how='outer'), month_df_list ) clist = ALL2019DF.columns.values.tolist() # 修正列选择语法,移除多余冒号 ALL2019DF['2019_CARGO_COUNT'] = ALL2019DF[[s for s in clist if "CARGO_COUNT" in s]].sum(axis=1) ALL2019DF['2019_CARGO_VDAYS'] = ALL2019DF[[s for s in clist if "CARGO_VDAYS" in s]].sum(axis=1) ALL2019DF['2019_FISHING_COUNT'] = ALL2019DF[[s for s in clist if "FISHING_COUNT" in s]].sum(axis=1) ALL2019DF['2019_FISHING_VDAYS'] = ALL2019DF[[s for s in clist if "FISHING_VDAYS" in s]].sum(axis=1) ALL2019DF['2019_OTHER_COUNT'] = ALL2019DF[[s for s in clist if "OTHER_COUNT" in s]].sum(axis=1) ALL2019DF['2019_OTHER_VDAYS'] = ALL2019DF[[s for s in clist if "OTHER_VDAYS" in s]].sum(axis=1) ALL2019DF['2019_PASSENGER_COUNT'] = ALL2019DF[[s for s in clist if "PASSENGER_COUNT" in s]].sum(axis=1) ALL2019DF['2019_PASSENGER_VDAYS'] = ALL2019DF[[s for s in clist if "PASSENGER_VDAYS" in s]].sum(axis=1) ALL2019DF['2019_TANKER_COUNT'] = ALL2019DF[[s for s in clist if "TANKER_COUNT" in s]].sum(axis=1) ALL2019DF['2019_TANKER_VDAYS'] = ALL2019DF[[s for s in clist if "TANKER_VDAYS" in s]].sum(axis=1) ALL2019DF['2019_ALL_COUNT'] = ALL2019DF[[s for s in clist if "ALL_COUNT" in s]].sum(axis=1) ALL2019DF['2019_ALL_VDAYS'] = ALL2019DF[[s for s in clist if "ALL_VDAYS" in s]].sum(axis=1) print('Saving final 2019 csv with totals') ALL2019DF.to_csv('2019.csv', index=False) print('Done')
内容的提问来源于stack exchange,提问作者MrKingsley
相关产品推荐
相关产品推荐

