24. 数据分箱
1. 概述
数据分箱(Binning)是将连续变量离散化的过程,将数值范围划分为多个区间,每个区间称为一个"箱"。分箱常用于将连续变量转换为分类变量,便于分析和建模。
importpandasaspdimportnumpyasnp# 创建示例数据np.random.seed(42)df=pd.DataFrame({'姓名':[f'用户_{i}'foriinrange(1,21)],'年龄':np.random.randint(18,70,20),'收入':np.random.randint(3000,30000,20),'分数':np.random.randint(0,100,20),'消费次数':np.random.randint(1,100,20)})print("原始数据:")print(df.head())2. cut() - 等宽分箱
2.1 基本用法
cut()将数据按指定的区间边界进行分箱。
# 定义年龄区间age_bins=[0,30,50,100]age_labels=['青年','中年','老年']df['年龄段']=pd.cut(df['年龄'],bins=age_bins,labels=age_labels)print("年龄段分布:")print(df[['年龄','年龄段']].head())# 查看各区间计数print("\n年龄段统计:")print(df['年龄段'].value_counts())2.2 自动生成区间
# 指定区间数量,自动等宽分箱score_bins=4# 分成4等份score_labels=['低','中低','中高','高']df['分数等级']=pd.cut(df['分数'],bins=score_bins,labels=score_labels)print("分数等级(等宽):")print(df[['分数','分数等级']].head(10))print("\n区间分布:")print(df['分数等级'].value_counts().sort_index())2.3 查看区间边界
# 不指定 labels,查看区间范围age_groups=pd.cut(df['年龄'],bins=[0,30,50,100])print("年龄区间:")print(age_groups.head())# 查看区间类别print("\n区间类别:")print(age_groups.cat.categories)3. qcut() - 等频分箱
qcut()根据数据的分位数进行分箱,使每个箱包含大致相同数量的样本。
# 等频分箱(4等份)df['收入等级_q']=pd.qcut(df['收入'],q=4,labels=['低','中低','中高','高'])print("收入等级(等频):")print(df[['收入','收入等级_q']].head())# 查看各区间样本数(应该大致相等)print("\n等频分箱统计:")print(df['收入等级_q'].value_counts().sort_index())4. cut() vs qcut() 对比
# 创建极端数据np.random.seed(42)data=pd.DataFrame({'值':np.random.exponential(10,100)# 指数分布数据})# 等宽分箱data['等宽']=pd.cut(data['值'],bins=5,labels=['箱1','箱2','箱3','箱4','箱5'])# 等频分箱data['等频']=pd.qcut(data['值'],q=5,labels=['箱1','箱2','箱3','箱4','箱5'])print("等宽 vs 等频对比:")print("等宽分箱分布:")print(data['等宽'].value_counts().sort_index())print("\n等频分箱分布:")print(data['等频'].value_counts().sort_index())5. 自定义分箱规则
5.1 使用自定义函数
defcustom_binning(x):ifx<30:return'青年'elifx<50:return'中年'else:return'老年'df['年龄段_自定义']=df['年龄'].apply(custom_binning)print("自定义分箱:")print(df[['年龄','年龄段_自定义']].head())5.2 使用字典映射
# 先使用 cut,再映射age_bins=[0,30,50,100]df['年龄段代码']=pd.cut(df['年龄'],bins=age_bins,labels=False)print("年龄段代码:")print(df[['年龄','年龄段代码']].head())# 代码映射为中文code_map={0:'青年',1:'中年',2:'老年'}df['年龄段_映射']=df['年龄段代码'].map(code_map)6. 分箱后的聚合分析
# 按年龄段统计平均收入print("各年龄段平均收入:")print(df.groupby('年龄段')['收入'].mean().round(0))# 按分数等级统计平均收入print("\n各分数等级平均收入:")print(df.groupby('分数等级')['收入'].mean().round(0))# 多维度分组print("\n年龄段 × 分数等级 平均收入:")pivot=df.pivot_table(values='收入',index='年龄段',columns='分数等级',aggfunc='mean').round(0)print(pivot)7. 完整示例:客户价值分群
# 创建客户数据np.random.seed(42)customers=pd.DataFrame({'客户ID':[f'CUST{i:04d}'foriinrange(1,101)],'年消费金额':np.random.exponential(5000,100).round(0),'购买次数':np.random.poisson(15,100),'客单价':np.random.normal(300,100,100).round(0),'会员天数':np.random.randint(1,1000,100)})# 修正负值customers['客单价']=customers['客单价'].clip(lower=0)print("="*60)print("客户价值分群分析")print("="*60)print("\n原始数据统计:")print(customers[['年消费金额','购买次数','客单价','会员天数']].describe())# 1. 消费金额分箱(等频)customers['消费等级']=pd.qcut(customers['年消费金额'],q=4,labels=['低消费','中低消费','中高消费','高消费'])print("\n1. 消费等级分布:")print(customers['消费等级'].value_counts())# 2. 购买频次分箱freq_bins=[0,5,10,20,200]freq_labels=['低频','中低频','中高频','高频']customers['频次等级']=pd.cut(customers['购买次数'],bins=freq_bins,labels=freq_labels)print("\n2. 频次等级分布:")print(customers['频次等级'].value_counts())# 3. 客单价分箱price_bins=[0,200,300,400,1000]price_labels=['低单价','中单价','高单价','超高单价']customers['单价等级']=pd.cut(customers['客单价'],bins=price_bins,labels=price_labels)print("\n3. 单价等级分布:")print(customers['单价等级'].value_counts())# 4. 会员时长分箱days_bins=[0,30,90,180,365,1000]days_labels=['新客','活跃','忠诚','资深','元老']customers['会员等级']=pd.cut(customers['会员天数'],bins=days_bins,labels=days_labels)print("\n4. 会员等级分布:")print(customers['会员等级'].value_counts())# 5. 客户价值评分defcalculate_score(row):# 消费金额得分(0-40分)amount_score=min(row['年消费金额']/20000*40,40)# 频次得分(0-30分)freq_score=min(row['购买次数']/30*30,30)# 单价得分(0-20分)price_score=min(row['客单价']/500*20,20)# 忠诚度得分(0-10分)loyalty_score=min(row['会员天数']/365*10,10)returnround(amount_score+freq_score+price_score+loyalty_score,2)customers['价值得分']=customers.apply(calculate_score,axis=1)# 6. 客户分群score_bins=[0,25,50,75,100]score_labels=['低价值','中低价值','中高价值','高价值']customers['客户分群']=pd.cut(customers['价值得分'],bins=score_bins,labels=score_labels)print("\n5. 客户分群分布:")print(customers['客户分群'].value_counts())# 7. 分群统计print("\n6. 各客户群平均指标:")segment_stats=customers.groupby('客户分群').agg({'年消费金额':'mean','购买次数':'mean','客单价':'mean','会员天数':'mean','价值得分':'mean'}).round(2)print(segment_stats)# 8. 交叉分析print("\n7. 消费等级 × 会员等级 分布:")cross_tab=pd.crosstab(customers['消费等级'],customers['会员等级'])print(cross_tab)8. 分箱方法对比
| 方法 | 特点 | 适用场景 |
|---|---|---|
cut()等宽 | 区间宽度相同 | 数据均匀分布 |
cut()自定义边界 | 灵活控制区间 | 有业务含义的边界 |
qcut()等频 | 每箱样本数相同 | 数据倾斜、需要平衡样本 |
apply()自定义 | 完全自定义 | 复杂逻辑 |
9. 总结
| 函数 | 说明 | 示例 |
|---|---|---|
cut(x, bins) | 等宽分箱 | pd.cut(df['age'], bins=[0,30,60,100]) |
cut(x, bins, labels) | 带标签分箱 | pd.cut(df['age'], bins=4, labels=['A','B','C','D']) |
qcut(x, q) | 等频分箱 | pd.qcut(df['income'], q=4) |
cut(..., labels=False) | 返回区间代码 | pd.cut(df['age'], bins=3, labels=False) |
value_counts() | 统计分布 | df['bin'].value_counts() |