承接《Pandas 透视表 pivot_table》,本篇讲解
pd.cut和pd.qcut分箱函数,用于连续数值离散化,对比两者原理、边界设置、标签、异常处理、业务场景,是特征工程、用户分层、报表区间统计高频工具,保持系列教程统一风格。
前置导入与示例数据
import pandas as pd
import numpy as np
df = pd.DataFrame({
"name": ["张三","李四","王五","赵六","孙七","周八","吴九","郑十"],
"age": [18, 22, 25, 30, 35, 42, 50, 61],
"income": [4500, 7200, 9800, 12000, 15600, 18000, 24000, 32000]
})
print(df)
一、cut 与 qcut 一句话区别
pd.cut:按自定义区间边界分箱(等距分箱),区间宽度固定,每个箱子样本数量不一定均匀pd.qcut:按分位数分箱(等频分箱),每个箱子里面样本数量尽量一样,区间宽度不固定
适用场景:连续数字 → 转换成分类标签(年龄段、收入档位、风险等级)
二、pd.cut 基础用法
2.1 等距分箱,直接指定箱子数量
# age 分成3个区间
df["age_bin_cut"] = pd.cut(df["age"], bins=3)
print(df[["name","age","age_bin_cut"]])
输出会自动生成区间,默认左开右闭 (a, b]。
2.2 手动指定边界(业务最常用)
bins = [0, 25, 35, 45, 100]
labels = ["青年","壮年","中年","老年"]
df["age_level"] = pd.cut(
df["age"],
bins=bins,
labels=labels,
right=True # right=True:左开右闭;right=False:左闭右开
)
print(df[["name","age","age_level"]])
right=True(默认):(0,25],包含25right=False:[0,25),不包含25
2.3 include_lowest 包含最小值
边界最小值默认是开区间,如果原始数据刚好等于最小值,会归为NaN,开启 include_lowest=True 解决:
df["age_level"] = pd.cut(
df["age"],
bins=[18,30,45,100],
labels=["青年","中年","老年"],
include_lowest=True
)
三、pd.qcut 分位数分箱(等频)
qcut 依靠分位数,保证每个分组里面样本数量基本一致。
# 分成4组,每组人数尽量相等
df["income_qbin"] = pd.qcut(df["income"], q=4)
# 自定义标签
df["income_level"] = pd.qcut(
df["income"],
q=4,
labels=["低收入","中低收入","中高收入","高收入"]
)
print(df[["name","income","income_level"]])
q 可以传入分位数列表,自定义分割位置:
# 20%、50%、80% 分位点
df["income_q2"] = pd.qcut(df["income"], q=[0,0.2,0.5,0.8,1], labels=["A","B","C","D"])
⚠️ qcut 坑:如果大量重复值,无法均分,会报错,设置
duplicates="drop"跳过重复分位数
pd.qcut(df["income"], q=4, duplicates="drop")
四、返回值:retbins=True 获取分割边界
有时候需要拿到分割阈值,用于后续新数据预测:
s_bin, edges = pd.cut(df["age"], bins=3, retbins=True)
print(edges)
qcut 同样支持 retbins。
五、参数汇总
pd.cut 参数
x:一维Series/数组bins:int(箱子数)或者列表(自定义边界)labels:分组标签,None则返回区间right:是否右闭区间include_lowest:是否把最小值纳入第一个区间retbins:是否返回边界precision:小数精度
pd.qcut 参数
q:int(分组数量)或者分位数列表 [0,0.25,0.5,0.75,1]duplicates:drop用来处理大量重复值导致分位数重合报错
六、cut 和 qcut 对比
| 函数 | 分割依据 | 箱子宽度 | 样本数量 | 适合场景 |
|---|---|---|---|---|
| cut | 固定区间边界 | 相等 | 不一定均匀 | 年龄、金额区间、固定档位报表 |
| qcut | 分位数 | 不相等 | 尽量均匀 | 分群、排名分层、特征分箱 |
七、搭配 value_counts 统计各箱数量
# 统计cut分箱数量
df["age_level"].value_counts().sort_index()
# qcut分箱统计
df["income_level"].value_counts().sort_index()
八、分组 + 分箱(业务实战)
# 每个地区内部,收入做等频分层
df2 = pd.DataFrame({
"area":["A","A","A","A","B","B","B","B"],
"income":[3000,5000,8000,12000,4000,6500,9500,15000]
})
df2["income_tier"] = df2.groupby("area")["income"].transform(lambda x: pd.qcut(x, q=2, labels=["低","高"]))
print(df2)
九、高频坑清单
- cut 手动bins时,数据超出区间范围 → 返回NaN,需要把边界拉大覆盖全部数据
- right=True 容易搞混边界,比如 25 到底属于上一组还是下一组
- qcut 遇到大量相同值,分位数重复,直接抛出 ValueError,加
duplicates="drop" - 标签labels长度必须和箱子数量一致,数量不对直接报错
- 分箱后得到的是Categorical类型,排序会按照区间顺序,不是字符串字典序
- 分箱结果可以直接用于 groupby 分组统计
十、综合实战模板
def bin_by_custom(s: pd.Series, bins, labels):
"""自定义区间分箱"""
return pd.cut(
s,
bins=bins,
labels=labels,
right=True,
include_lowest=True
)
def bin_by_quantile(s: pd.Series, n_group):
"""等频分箱"""
return pd.qcut(
s,
q=n_group,
duplicates="drop"
)
df["age_tier"] = bin_by_custom(df["age"], bins=[0,25,35,45,100], labels=["青年","壮年","中年","老年"])
df["income_tier"] = bin_by_quantile(df["income"], 4)
print(df[["name","age","age_tier","income","income_tier"]])
十一、速查表
| 需求 | 选择 |
|---|---|
| 固定档位,如 0-25,25-35 | cut + 列表bins |
| 平均分成N等宽区间 | cut + bins=N |
| 每组人数尽量一样 | qcut |
| 存在大量重复值做等频分箱 | qcut + duplicates=”drop” |
0 条笔记