Iterrows向量化优化:基于预测日期生成客户购买特征
优化方案:用向量化操作替代iterrows循环
原代码通过iterrows逐行遍历prediction_df,每次都对purchase_info_df做过滤查询,时间复杂度为O(N*M)(N是预测记录数,M是购买记录数),当数据量达到10万级时会非常低效。以下是两种向量化优化方案,能大幅提升运行速度:
方案一:交叉合并后分组聚合
先将两个DataFrame按客户ID关联,生成所有预测日期与对应客户购买记录的组合,再通过分组聚合计算所需特征,全程避免循环:
import pandas as pd import numpy as np # 1. 按ID交叉合并两个数据集 merged = prediction_df.merge(purchase_info_df, on='id', how='left') # 2. 计算购买日期与预测日期的时间差,标记符合条件的记录 merged['days_diff'] = merged['prediction_date'] - merged['purchase_date'] merged['is_before_pred'] = merged['days_diff'] >= pd.Timedelta(days=0) # 标记各时间窗口内的购买记录 merged['in_15d'] = merged['days_diff'].between(pd.Timedelta(days=0), pd.Timedelta(days=15)) merged['in_30d'] = merged['days_diff'].between(pd.Timedelta(days=0), pd.Timedelta(days=30)) merged['in_45d'] = merged['days_diff'].between(pd.Timedelta(days=0), pd.Timedelta(days=45)) merged['in_60d'] = merged['days_diff'].between(pd.Timedelta(days=0), pd.Timedelta(days=60)) # 3. 按ID和预测日期分组,聚合计算特征 agg_result = merged.groupby(['id', 'prediction_date']).agg( amount_sum=('purchase_amount', lambda x: x[merged['is_before_pred']].sum()), amount_mean=('purchase_amount', lambda x: x[merged['is_before_pred']].mean()), purchase_count=('purchase_amount', lambda x: x[merged['is_before_pred']].count()), last_15_days_dep_amount=('purchase_amount', lambda x: x[merged['in_15d']].sum()), last_30_days_dep_amount=('purchase_amount', lambda x: x[merged['in_30d']].sum()), last_45_days_dep_amount=('purchase_amount', lambda x: x[merged['in_45d']].sum()), last_60_days_dep_amount=('purchase_amount', lambda x: x[merged['in_60d']].sum()), last_15_days_dep_count=('is_before_pred', lambda x: x[merged['in_15d']].sum()), last_30_days_dep_count=('is_before_pred', lambda x: x[merged['in_30d']].sum()), last_45_days_dep_count=('is_before_pred', lambda x: x[merged['in_45d']].sum()), last_60_days_dep_count=('is_before_pred', lambda x: x[merged['in_60d']].sum()) ).reset_index() # 4. 处理空值(与原逻辑保持一致:无购买记录时均值为NaN,其余统计量为0) agg_result[['amount_mean']] = agg_result[['amount_mean']].fillna(value=np.nan) agg_result.fillna(0, inplace=True)
方案二:分组+Numpy广播(内存友好型)
如果交叉合并后数据量过大(比如每个客户有大量预测/购买记录),可以按客户ID分组,用Numpy广播实现向量化计算,避免生成超大中间表:
import pandas as pd import numpy as np def process_customer_group(group): # 提取当前客户的所有预测日期、购买日期和金额 pred_dates = group['prediction_date'].values pur_dates = group['purchase_date'].values pur_amounts = group['purchase_amount'].values # 生成布尔矩阵:每行对应一个预测日期,每列对应一个购买记录,标记是否<=预测日期 mask_before = pur_dates <= pred_dates[:, np.newaxis] # 计算累积统计量 amount_sum = mask_before @ pur_amounts purchase_count = mask_before.sum(axis=1) amount_mean = np.where(purchase_count > 0, amount_sum / purchase_count, np.nan) # 计算各时间窗口的统计量 windows = [15, 30, 45, 60] window_amounts = [] window_counts = [] for window_days in windows: # 标记购买日期是否在预测日期的前N天内 mask_window = (pur_dates >= pred_dates[:, np.newaxis] - np.timedelta64(window_days, 'D')) & mask_before window_amounts.append(mask_window @ pur_amounts) window_counts.append(mask_window.sum(axis=1)) # 组装结果DataFrame return pd.DataFrame({ 'id': group['id'].iloc[0], 'prediction_date': pred_dates, 'amount_sum': amount_sum, 'amount_mean': amount_mean, 'purchase_count': purchase_count, 'last_15_days_dep_amount': window_amounts[0], 'last_30_days_dep_amount': window_amounts[1], 'last_45_days_dep_amount': window_amounts[2], 'last_60_days_dep_amount': window_amounts[3], 'last_15_days_dep_count': window_counts[0], 'last_30_days_dep_count': window_counts[1], 'last_45_days_dep_count': window_counts[2], 'last_60_days_dep_count': window_counts[3], }) # 合并数据集后按ID分组处理 merged = prediction_df.merge(purchase_info_df, on='id', how='left') output = merged.groupby('id').apply(process_customer_group).reset_index(drop=True)
方案对比
- 方案一:代码简洁,实现快速,适合客户平均预测/购买记录数较少的场景;
- 方案二:内存占用更低,适合客户平均记录数较多的场景,避免交叉合并产生超大中间表。
两种方案均能完全复现原代码的计算逻辑,且运行速度比iterrows快10~100倍(取决于数据规模)。
内容的提问来源于stack exchange,提问作者sara
相关产品推荐
相关产品推荐

