pandas客户三等分分箱分组不均问题的代码优化咨询
问题根因
你当前代码分组客户数不均的核心原因有两点:
- 现有逻辑先计算
Ea EUR(即入参expo)列的1/3、2/3分位点作为切分阈值,再通过pd.cut()按取值打组,在Ea EUR存在大量重复值时会出现严重偏差:如果分位点位置刚好对应大批同值记录,这些记录会被全部分到同一侧分组,直接打破等人数要求。比如总客户300个,1/3分位点取值为500,但有80个客户的Ea EUR都是500,这80个客户会全被划入Lower组,最终Lower组人数会达到180,远高于100的预期值。 - 现有逻辑本质是按Ea EUR取值区间做等频切分,不是按客户排序后的位次做三等分,只要存在取值重复,两种逻辑的结果就会出现偏差。
修正方案
替换原有按值切分的打组逻辑,改为排序后按位次分组,从根源避免重复值带来的偏移:
- 先按
Ea EUR列对筛选后的客户做升序排序,重置索引保证索引连续 - 以排序后的索引为依据做切分,直接把客户划分为人数完全均等的三个组,最大程度保证同组客户的
Ea EUR取值接近 - 保留原有聚合、指标计算逻辑,仅替换分组打标部分,同时补全原有代码的隐式警告、分组缺失类别的问题
修正后完整代码
import pandas as pd import numpy as np from scipy import stats def f(df, stab, idn, ndef, expo, pdm, segm): # 加copy避免链式赋值警告 df = df.loc[df[stab] == segm].copy() df = df.sort_values(by=[expo]).reset_index(drop=True) total_client = len(df) # 按排序后的索引切分,三组客户数差不超过1,完全满足等人数要求 df['By ex'] = pd.cut( df.index, bins=[-1, total_client//3, total_client*2//3, total_client], labels=['Lower', 'Medium', 'Upper'] ) dfA = df.groupby(['By ex'], observed=False).agg( apd = (pdm, 'mean'), ncl=(idn, 'count'), ndf=(ndef, 'sum'), ex=(expo, 'sum') ) dfA.loc[:, 'dfr'] = dfA['ndf'] / dfA['ncl'] dfA.loc[:, 'p_value'] = stats.beta.cdf( dfA['apd'], dfA['ndf'] + 1/2, dfA['ncl'] - dfA['ndf'] + 1/2 ) dfA = dfA.reset_index() dfA = dfA.rename({ 'ncl':'N', 'ndf':'D', 'ex':'Ea EUR', 'dfr':'DR', 'p_value':'test p-value' }, axis=1) df_temp = pd.DataFrame({ "Ex tertile" : dfA.loc[:, "By ex"].values, "Ea EUR" : dfA.loc[:, "Ea EUR"].values, "AD" : dfA.loc[:, "apd"].values, "N" : dfA.loc[:, 'N'].values, "D" : dfA.loc[:, "D"].values, "DR" : dfA.loc[:, "DR"].values, "test p-value" : dfA.loc[:, "test p-value"].values }) # 原np.vstack逻辑会把所有数值转成字符串,需要保留数值类型可直接return df_temp return pd.DataFrame(np.vstack([df_temp.columns, df_temp]))
可选调整项
- 如果业务要求
Ea EUR取值相同的客户必须分到同一个组,可以把打组逻辑替换为按秩分组:先计算df['ex_rank'] = df[expo].rank(method='min'),再按秩做三等分切分。这种方式会在存在大量同值时允许组间人数有小幅偏差,但能保证同值客户同组。 - 如果不需要兼容原有返回格式,直接返回
df_temp即可,不需要用np.vstack做拼接,避免数值类型被转成字符串影响后续计算。 - 如果需要和原分位点切分逻辑的结果做对比,可以打印两种逻辑下每组的N值,验证人数均等效果。
内容的提问来源于stack exchange,提问作者LinaN
相关产品推荐
相关产品推荐

