如何在Numpy与Pandas中逐行正确使用np.select()方法?
问题:基于Target逐行为零售商计算分数时的np.select报错解决
我需要根据零售商标记的target(分为balanced多维度通用分数、nmv聚焦高NMV零售商两类),为每个零售商生成聚类用的分数。尝试用df.iterrows()逐行循环结合np.select()赋值时,抛出错误:
ValueError: Must have equal len keys and value when setting with an iterable
原代码实现
targets = ['balanced','nmv'] day_of_month = date.today().day df['Score'] = 0 if day_of_month > 10: # 每月10号后执行动态打分逻辑,否则执行月初计划 for index, row in df.iterrows(): target = row['target'] if target == 'balanced': conditions = [ (df['retailer_id'].isin(droppers['retailer_id'])), # 已从MP移除的零售商 (df['months_sr'] > 0.4) | (df['historical_sr'] > 0.4) & (df['orders_this_month_total'] >= 1), (df['wallet_amount'] > 0) & (df['orders_this_month_total'] > 0), # 有余额但本月无订单 (df['orders_this_month_total'] == 1), # 本月仅下单一次 ( (df[['nmv_this_month_total','nmv_one_month_ago_total','nmv_two_months_ago_total','nmv_three_months_ago_total']].fillna(0).pct_change(axis = 1).mean(axis = 1) ) > 0), # NMV呈增长趋势 (df['skus_pct_change_q_cut'].isin(['med','high','extreme'])), # 订单SKU数量更可能超过3个 (df['orders_one_month_ago_total'] >= 1) & (df['orders_this_month_total'] <= 1), # 上月有下单,本月下单≤1次 (df[['orders_one_month_ago_total','orders_two_months_ago_total','orders_three_months_ago_total']].sum(axis = 1) > 0) & (df['orders_this_month_total'] >= 1), # 近三月至少有一个月下单,且本月有下单 (df[['orders_one_month_ago_total','orders_two_months_ago_total','orders_three_months_ago_total']].sum(axis = 1) > 0) & (df['orders_this_month_total'] <= 1), # 近三月至少有一个月下单,但本月下单≤1次 (df['sessions_this_month'] > 0) & (df['visits_this_month'] == 0), # 打开过APP但未被拜访 (df['visits_this_month'] == 0) & (df['peak_week'] == wom) & ((df['months_sr'] >= 0.4) & (df['months_sr'] <= 1)) & (df['orders_this_month_total'] < 4), # 本周是高峰周且下单量<4 (df['peak_week'] < wom) & (df['orders_this_month_total'] == 0), # 错过高峰周且本月无订单 (df['wallet_amount'] > 0), True ] results = list(range(len(conditions) - 1, -1, -1)) # balanced目标的分数映射 elif target == 'nmv': conditions = [ (df['retailer_id'].isin(droppers['retailer_id'])), # 已从MP移除的零售商 (df['visits_this_month'] == 0) & (df['peak_week'] == wom) & ((df['months_sr'] >= 0.4) & (df['months_sr'] <= 1)) & (df['orders_this_month_total'] == 0), # 本周是高峰周且无订单 (df['visits_this_month'] == 0) & (df['historical_sr'] >= 0.4) & (df['orders_this_month_total'] == 0), # 整体转化率≥40%且本月无订单 (df['nmv_q_cut_total'].isin(['high','extreme'])), (df['nmv_q_cut_total'].isin(['high','extreme'])) & ( (df['wallet_amount'] > 0) | (df['n_offers'] > 0) ), (df['months_nmv'].median() >= df['polygon_average_nmv']), (df['orders_one_month_ago'] > 0), (df['months_sessions_q_cut'] > 0), True ] results = list(range(len(conditions) - 1, -1, -1)) # nmv目标的分数映射 df.loc[index, 'Score'] = np.select(conditions, results) df['Score'] = df['Score'].astype(int) else: conditions = [ (df['retailer_id'].isin(droppers['retailer_id'])), # 已从MP移除的零售商 (df['visits_this_month'] == 0) & (df['peak_week'] == wom) & ((df['months_sr'] >= 0.4) & (df['months_sr'] <= 1)), # 本周是高峰周 (df['historical_sr'] >= 0.4), # 整体转化率≥40% (df['orders_one_month_ago'].isin([1,2,3,4])) & (df['nmv_one_month_ago'] >= 1500), (df['orders_one_month_ago'].isin([1,2,3,4])), (df['orders_two_months_ago'].isin([1,2,3,4])), (df['orders_three_months_ago'].isin([1,2,3,4])), (df['last_visit_date'].dt.year == 2022) & (df['last_order_date'].dt.year == 2022), # 最后拜访和下单都在2022年 (df['last_visit_date'].dt.year == 2023) & (df['last_order_date'].dt.year == 2023), True ] results = list(range(len(conditions) - 1, -1, -1)) df['Score'] = np.select(conditions, results)
错误原因
你在iterrows()循环中,conditions是基于整个DataFrame生成的布尔数组(长度等于df总行数),但试图将np.select()返回的整个数组结果赋值给单行df.loc[index, 'Score'],导致长度不匹配,触发报错。
正确解决方法
放弃逐行循环,利用Pandas的向量化操作,按target分组处理所有对应行,效率更高且避免错误:
import pandas as pd import numpy as np from datetime import date targets = ['balanced','nmv'] day_of_month = date.today().day df['Score'] = 0 if day_of_month > 10: # 处理balanced目标的所有行 balanced_mask = df['target'] == 'balanced' if balanced_mask.any(): # 预计算重复使用的统计量 nmv_pct_mean = df[['nmv_this_month_total','nmv_one_month_ago_total','nmv_two_months_ago_total','nmv_three_months_ago_total']].fillna(0).pct_change(axis=1).mean(axis=1) prev_orders_sum = df[['orders_one_month_ago_total','orders_two_months_ago_total','orders_three_months_ago_total']].sum(axis=1) balanced_conditions = [ df['retailer_id'].isin(droppers['retailer_id']), (df['months_sr'] > 0.4) | (df['historical_sr'] > 0.4) & (df['orders_this_month_total'] >= 1), (df['wallet_amount'] > 0) & (df['orders_this_month_total'] > 0), df['orders_this_month_total'] == 1, nmv_pct_mean > 0, df['skus_pct_change_q_cut'].isin(['med','high','extreme']), (df['orders_one_month_ago_total'] >= 1) & (df['orders_this_month_total'] <= 1), (prev_orders_sum > 0) & (df['orders_this_month_total'] >= 1), (prev_orders_sum > 0) & (df['orders_this_month_total'] <= 1), (df['sessions_this_month'] > 0) & (df['visits_this_month'] == 0), (df['visits_this_month'] == 0) & (df['peak_week'] == wom) & ((df['months_sr'] >= 0.4) & (df['months_sr'] <= 1)) & (df['orders_this_month_total'] < 4), (df['peak_week'] < wom) & (df['orders_this_month_total'] == 0), df['wallet_amount'] > 0, True ] balanced_results = list(range(len(balanced_conditions)-1, -1, -1)) df.loc[balanced_mask, 'Score'] = np.select(balanced_conditions, balanced_results)[balanced_mask] # 处理nmv目标的所有行 nmv_mask = df['target'] == 'nmv' if nmv_mask.any(): nmv_conditions = [ df['retailer_id'].isin(droppers['retailer_id']), (df['visits_this_month'] == 0) & (df['peak_week'] == wom) & ((df['months_sr'] >= 0.4) & (df['months_sr'] <= 1)) & (df['orders_this_month_total'] == 0), (df['visits_this_month'] == 0) & (df['historical_sr'] >= 0.4) & (df['orders_this_month_total'] == 0), df['nmv_q_cut_total'].isin(['high','extreme']), df['nmv_q_cut_total'].isin(['high','extreme']) & ((df['wallet_amount'] > 0) | (df['n_offers'] > 0)), df['months_nmv'].median() >= df['polygon_average_nmv'], df['orders_one_month_ago'] > 0, df['months_sessions_q_cut'] > 0, True ] nmv_results = list(range(len(nmv_conditions)-1, -1, -1)) df.loc[nmv_mask, 'Score'] = np.select(nmv_conditions, nmv_results)[nmv_mask] df['Score'] = df['Score'].astype(int) else: # 月初逻辑保持不变,本身就是向量化操作 conditions = [ df['retailer_id'].isin(droppers['retailer_id']), (df['visits_this_month'] == 0) & (df['peak_week'] == wom) & ((df['months_sr'] >= 0.4) & (df['months_sr'] <= 1)), df['historical_sr'] >= 0.4, (df['orders_one_month_ago'].isin([1,2,3,4])) & (df['nmv_one_month_ago'] >= 1500), df['orders_one_month_ago'].isin([1,2,3,4]), df['orders_two_months_ago'].isin([1,2,3,4]), df['orders_three_months_ago'].isin([1,2,3,4]), (df['last_visit_date'].dt.year == 2022) & (df['last_order_date'].dt.year == 2022), (df['last_visit_date'].dt.year == 2023) & (df['last_order_date'].dt.year == 2023), True ] results = list(range(len(conditions)-1, -1, -1)) df['Score'] = np.select(conditions, results)
关键优化点
- 用布尔掩码(
balanced_mask/nmv_mask)筛选对应target的行,一次性处理所有符合条件的记录,避免逐行循环的性能损耗和错误 - 预计算需要重复使用的统计量(如
nmv_pct_mean),提升代码效率 - 保持
np.select()的向量化特性,确保返回结果与目标行长度匹配
内容的提问来源于stack exchange,提问作者Esmael Awad
相关产品推荐
相关产品推荐

