pandas从0.25.1升级到2.1.4后,分组按月前向填充速度骤降
Pandas升级后数据填充性能暴跌的优化方案
背景与问题
我有总计约1000万行的月度.csv格式数据。在重新安装Anaconda并将pandas版本从0.25.1升级到2.1.4后,生成目标DataFrame的运行时间从约20分钟增至8小时。虽不确定是否与升级直接相关,但问题在升级后出现。
核心需求:若用户某月份未登录(无对应记录),需为其添加该行,并填充最近有登录月份的login_counter和last_login_timestamp值。
示例数据
Jan-26
customer;user_id;user_name;status;user_type;last_login_timestamp;login_counter;month 1111;1;A;1;Normal;11.01.2026 11:38;100;202601 1111;2;B;1;Normal;04.01.2026 11:11;50;202601 2222;4;P;1;Normal;04.01.2026 21:16;40;202601 2222;5;Q;1;Admin;12.01.2026 04:54;55;202601 2222;6;R;1;Normal;23.01.2026 21:06;20;202601 3333;9;X;2;Normal;04.01.2026 06:56;70;202601
Feb-26
customer;user_id;user_name;status;user_type;last_login_timestamp;login_counter;month 1111;1;A;1;Normal;21.02.2026 06:38;120;202602 2222;4;P;1;Normal;13.02.2026 18:11;50;202602 2222;5;Q;1;Admin;19.02.2026 14:51;60;202602 2222;6;R;1;Normal;11.02.2026 16:41;25;202602
Mar-26
customer;user_id;user_name;status;user_type;last_login_timestamp;login_counter;month 1111;1;A;1;Normal;05.03.2026 03:30;150;202603 1111;2;B;1;Normal;04.03.2026 21:51;60;202603 2222;4;P;1;Normal;12.03.2026 15:16;80;202603 2222;5;Q;1;Admin;24.03.2026 04:54;90;202603 2222;6;R;1;Normal;03.03.2026 21:06;45;202603 3333;9;X;2;Normal;21.03.2026 13:03;95;202603
每行对应某客户下的特定用户,例如客户1111有用户1和2;若用户某月份未登录则无对应记录,如客户3333的用户9在2026年2月无记录。
原处理代码
1. 合并所有数据文件
import pandas as pd import os df_all = pd.DataFrame() for i in ['Data_202601.csv', 'Data_202602.csv', 'Data_202603.csv']: df = pd.read_csv(os.path.join(path, i), sep=';') df['last_login_timestamp'] = pd.to_datetime( df['last_login_timestamp'], format='%d.%m.%Y %H:%M') df['month'] = pd.to_datetime(df['month'], format='%Y%m') df_all = df_all._append(df) df_all = df_all.sort_values( ['customer', 'user_id', 'month'], ascending=(True, True, True) ).reset_index(drop=True)
2. 缺失月份填充(性能瓶颈代码)
df_all = (df_all.set_index('month') .groupby(['customer', 'user_id']) .apply(lambda x: x.asfreq('MS', method='ffill')) .drop(['customer', 'user_id'], axis=1)).reset_index()
这段代码在Pandas升级后运行时间从20分钟暴涨至8小时,核心原因是groupby.apply的逐组处理在新版本中效率大幅降低,尤其是处理百万级数据时。
优化方案
优化思路
放弃低效的逐组apply,改用全量索引生成+左连接+矢量化填充的方式,利用Pandas的矢量化操作提升效率,同时修复原方法的填充漏洞(原方法无法将仅出现在早期月份的用户填充至后续月份)。
优化代码
import pandas as pd import os # 1. 批量读取并合并数据(用pd.concat替代循环append,提升合并效率) file_list = ['Data_202601.csv', 'Data_202602.csv', 'Data_202603.csv'] dfs = [] for f in file_list: df = pd.read_csv(os.path.join(path, f), sep=';') df['last_login_timestamp'] = pd.to_datetime(df['last_login_timestamp'], format='%d.%m.%Y %H:%M') df['month'] = pd.to_datetime(df['month'], format='%Y%m') dfs.append(df) df_all = pd.concat(dfs, ignore_index=True) df_all = df_all.sort_values(['customer', 'user_id', 'month']).reset_index(drop=True) # 2. 生成全量的(customer, user_id, month)组合,确保每个用户在所有月份都有记录 # 获取所有唯一的客户-用户组合 unique_users = df_all[['customer', 'user_id']].drop_duplicates() # 获取数据覆盖的所有连续月份 min_month = df_all['month'].min() max_month = df_all['month'].max() all_months = pd.date_range(start=min_month, end=max_month, freq='MS') # 生成笛卡尔积:每个用户对应所有月份 unique_users['temp_key'] = 1 months_df = pd.DataFrame({'month': all_months, 'temp_key': 1}) full_index = pd.merge(unique_users, months_df, on='temp_key').drop('temp_key', axis=1) # 3. 左连接原数据,填充缺失值 # 左连接保留全量索引,缺失的月份会显示NaN result = full_index.merge(df_all, on=['customer', 'user_id', 'month'], how='left') # 按客户-用户分组,向前填充所有缺失字段 result = result.groupby(['customer', 'user_id']).ffill().reset_index(drop=True)
优化效果说明
- 性能提升:矢量化操作避免了
groupby.apply的逐组循环,处理1000万级数据的时间可恢复至接近升级前的水平。 - 修复填充漏洞:原方法仅能填充有起始和结束记录的用户(如3333/9),新方法会将仅出现在早期月份的用户(比如仅在2026年1月出现的4444/8)自动填充至所有后续月份,保持其最近的登录数据。
内容的提问来源于stack exchange,提问作者cph_sto
相关产品推荐
相关产品推荐

