在数据分析和数据处理任务中,我们经常需要将连续的数值列转换为离散的区间类别。Pandas 提供的 pd.cut 函数就是专门用来做这种“数据分箱”操作的工具。它可以把一列数值按照我们设定的边界切分成不同的区间,并返回每个值所属的区间类别,方便做分组统计、用户分层或特征工程。
pd.cut 函数的基本用法
pd.cut 最核心的参数是 x 和 bins。x 是待切割的一维数组或 Series,bins 可以是整数(表示均匀分成几份),也可以是具体的边界列表或数组。当 bins 为整数时,Pandas 会先计算数据的最大值和最小值,然后在范围内均匀切分;当 bins 为序列时,序列中的每一个值都会被当作区间的边界。
下面是一段最基础的代码示例,我们将一组学生考试成绩按照自定义边界分成不及格、及格、良好、优秀四个区间:
import pandas as pd scores = pd.Series([58, 72, 85, 90, 45, 66, 78, 99]) # 自定义边界,注意边界数量比区间多1 bins = [0, 60, 75, 90, 100] labels = ['不及格', '及格', '良好', '优秀'] result = pd.cut(scores, bins=bins, labels=labels, include_lowest=True) print(result) print(result.value_counts())
在上面的代码中,include_lowest 设置为 True,表示第一个区间包含左端点(即 0 分也被算作不及格)。如果不加这个参数,默认第一个区间是不包含左端点的,可能导致最小值找不到归属。labels 参数让输出的结果不再是原始的区间对象,而是我们指定的中文类别,可读性更好。
bins 为整数与序列的区别
很多初学者会混淆 bins 传整数和传序列的差异。当传入整数 n 时,pd.cut 会根据数据的极值做等宽分箱,也就是每个区间的长度相同。这种方式简单,但容易受极端值影响,例如某一个数值特别大,会导致大部分数据挤在前面几个区间里。
相比之下,传入序列可以完全由我们控制每个区间的边界,适合业务上已经定义好的规则,比如电商价格带、年龄分段等。以下示例展示等宽分箱的效果:
import pandas as pd ages = pd.Series([5, 12, 17, 23, 35, 48, 60, 72, 80]) # 等宽分成3个区间 age_cut = pd.cut(ages, bins=3, precision=0) print(age_cut)
这段代码中,Pandas 自动计算出年龄的最小值为 5,最大值为 80,然后将其均分为三段。precision 参数用于控制边界显示的小数精度。可以看出,等宽分箱完全不考虑实际业务含义,仅从数学上做均匀切割。
右开左闭与端点处理
pd.cut 默认生成的区间是“左开右闭”的,即 (a, b] 的形式,意味着右侧边界属于该区间,左侧边界不属于。这在处理连续变量时一般不会出问题,但如果我们的数据里包含确切的边界值(例如刚好等于 60 分),就需要注意归属。
我们可以通过 right 参数将其改为左闭右开,用 include_lowest 保证最左侧边界被包含。下面用表格总结常见参数组合的效果:
| 参数设置 | 区间形式 | 适用场景 |
|---|---|---|
| right=True(默认) | (a, b] | 常规连续数值,避免重复计数 |
| right=False | [a, b) | 离散整数且左端点重要时 |
| include_lowest=True | 首个区间含左端点 | 最小值刚好等于边界 |
理解这些开关,能避免“明明有数据却返回 NaN”的情况。比如最左边界的值在默认设置下若不被包含,就会变成空类别,影响后续统计。
结合 groupby 做区间统计
分箱的最终目的通常是做聚合分析。我们可以将 pd.cut 的结果作为分组键,配合 groupby 计算各区间的数量、均值等指标。这种方式在用户消费分层、成绩分布分析中非常实用。
以下示例演示按销售金额分箱后统计每组的订单数和平均金额:
import pandas as pd
orders = pd.DataFrame({
'amount': [20, 150, 300, 450, 80, 1200, 600, 90],
'user': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
})
bins = [0, 100, 500, 1000, 2000]
labels = ['低消', '中消', '高消', '超高消']
orders['level'] = pd.cut(orders['amount'], bins=bins, labels=labels, include_lowest=True)
summary = orders.groupby('level', observed=True).agg(
order_count=('amount', 'size'),
avg_amount=('amount', 'mean')
)
print(summary)
这里 observed=True 确保只显示实际出现的区间,不会列出没有任何数据的空类别。通过这一步骤,原本零散的金额数据被转化成了清晰的业务层级,可以直接用于报表或可视化。
与 pd.qcut 的差别
除了 pd.cut,Pandas 还有 pd.qcut 函数,二者容易搞混。pd.cut 是按“值”的区间宽度来切,而 pd.qcut 是按“样本量”来切,保证每个区间里的数据个数大致相等。如果你的目标是做分位数分析,例如把用户按消费额分成人数均等的四组,就应该用 pd.qcut。
简单对比如下:
- pd.cut:关注数值边界,区间宽度由我们或等宽规则决定
- pd.qcut:关注样本分布,每个区间数据量尽量一致
- 选择依据:业务已有明确分段用 cut,需平衡各组样本量用 qcut
掌握 pd.cut 的边界控制、标签设置和端点包含逻辑,基本就能应对绝大多数连续变量离散化的需求。在写代码时,建议先打印出分箱结果确认边界归属,再接入后续分析流程,可减少很多隐蔽的数据偏差。