查看: 176|回复: 3

Pandas数据分箱:pd.cut与pd.qcut等宽等频离散化

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
把连续数值切成若干区间(分箱、离散化)是数据预处理里最常见的一步:成绩分等级、金额分档、年龄分段、销量分层都要用它。pandas 给出了两个入口函数:pd.cut() 按数值宽度切分,pd.qcut() 按样本频率切分。下面结合一段可复现的示例数据,把两个函数的参数含义、输出形态和统计方式讲清楚。

一、准备示例数据

先构造一列 0~100 之间的整数分数,固定随机种子保证结果可复现:
  1. import pandas as pd
  2. import numpy as np
  3. np.random.seed(42)          # 固定随机种子,结果可复现
  4. data = pd.Series(np.random.randint(0, 101, size=10), name="score")
  5. print("原始数据:")
  6. print(data)
复制代码

后面的所有分箱操作都复用这列 data。

二、pd.cut():按区间宽度做等宽分箱

pd.cut() 的切分依据是数值范围本身,每个箱子的宽度相同,适合分布相对均匀、业务上按数值区间划分的场景。

1. 只传整数 bins
  1. bins_1 = pd.cut(data, bins=5)
  2. print(bins_1)
复制代码

bins=5 表示把数据的最小值到最大值之间均分成 5 段等宽区间,默认标签是原始区间字符串,形如 (a, b],即左开右闭。返回值不是普通字符串,而是 category 类型,其 Categories 信息会写明区间个数与区间类型,例如 Categories (5, interval[int64, right]),并保持区间从小到大的有序关系,便于后续比较和排序。

2. 自定义边界加业务标签

当分档规则由业务确定(考试等级、会员等级、风险档位)时,直接给出边界列表比自动等分更可控:
  1. bins = [0, 60, 70, 80, 90, 100]                      # 自定义分数区间
  2. labels = ["不及格", "及格", "良好", "优秀", "满分"]   # 与区间一一对应的标签
  3. bins_2 = pd.cut(data, bins=bins, labels=labels, include_lowest=True)
  4. print(bins_2)
复制代码

这里的三个参数各有明确作用:

bins:列表长度是 N,切出的区间个数就是 N-1,边界值本身构成每个箱子的端点,不是箱子数。
labels:按区间顺序替换默认的区间字符串,长度必须等于区间个数,否则会报错;替换后 Categories 显示的是这些自定义标签的有序集合。
include_lowest=True:默认区间是左开右闭,最小值落在第一个区间的左端点上会被排除,该参数把第一个区间改为包含左端点,避免边界值变成 NaN。

3. 统计每个箱子的样本数
  1. print(bins_2.value_counts())
复制代码

value_counts() 依据标签统计落入每个区间的样本数,输出直接可用于报表。由于结果是 category 类型,所有类别都会被保留,没有样本落入的区间会以 0 出现(示例数据中最高分段没有样本,统计结果里仍会列出该档并为 0),这样各档次人数一目了然,不会出现某一档凭空消失的情况。

三、pd.qcut():按样本数量做等频分箱

当数据分布很不均匀(典型的收入、销量、订单金额等长尾分布)时,等宽分箱会让大部分样本挤在一两个箱子里,另一些箱子为空。pd.qcut() 改为按分位数切分,每个箱子包含的样本数量大致相等:
  1. # 1. 等频分箱:分成 4 个箱子
  2. q_bins_1 = pd.qcut(data, q=4)
  3. print(q_bins_1)
  4. # 2. 等频分箱 + 自定义标签
  5. q_labels = ["低分段", "中低分段", "中高分段", "高分段"]
  6. q_bins_2 = pd.qcut(data, q=4, labels=q_labels)
  7. print(q_bins_2)
  8. # 统计每个频段的样本数
  9. print(q_bins_2.value_counts())
复制代码

q=4 表示按 25%、50%、75% 的分位点把数据切成四段,每个箱子的样本数尽可能接近。关键差异在于:分箱边界不再由数值范围决定,而是由分位数计算得到,因此区间端点通常是浮点数,Categories 中显示为 interval[float64, right];也就是说,同一份数据用 cut 切出的边界是等距的,用 qcut 切出的边界是等频的,箱子的宽度各不相同。

parameters 说明在这里同样简洁:q 既可以是整数(分成几等份),也可以给定分位点列表;labels 的用法与 cut 完全一致,用于把区间替换成易读的中文档位。

四、两种分箱方式怎么选

判断标准只有一个:你关心的是数值距离,还是样本数量。

业务规则明确、档位由数值区间定义(如 60 分及格、90 分优秀),用 pd.cut(),边界写死在 bins 里,可控性最好;
数据分布倾斜、需要保证每个箱子里样本量接近(建模时做特征分箱、分层抽样),用 pd.qcut(),避免出现空箱或极端不均衡的箱;
需要输出的结果带中文档位而非区间字符串时,两者都通过 labels 参数实现,输出的 category 可直接配合 value_counts() 做人数/占比统计。

五、边界、缺失值与结果类型

实际落地时最容易踩坑的是以下三点。

第一是区间开闭。pd.cut() 默认左开右闭,需要左闭右开时设置 right=False;分档边界与业务判定口径不一致时,务必先对齐这一点,否则恰好落在边界上的样本会被分错档。
  1. bins_2 = pd.cut(data, bins=bins, labels=labels, include_lowest=True, right=False)  # 左闭右开
复制代码

第二是缺失值。如果原始列里含 NaN,分箱后这些位置仍然是 NaN,不会自动归入某个箱子。可以在分箱前用 fillna() 填充成指定值,也可以在统计阶段显式控制缺失值的处理:value_counts() 默认忽略 NaN,需要计入时设置 dropna=False。

第三是结果类型转换。分箱结果的 dtype 是 category,很多下游环节(导出 CSV、拼 SQL、写入模型特征)需要的是字符串或整数编码,可分别处理:
  1. str_bins = bins_2.astype(str)    # category -> 字符串
  2. code_bins = bins_2.cat.codes      # category -> 数值编码 0、1、2 ...
复制代码

cat.codes 按区间顺序给出 0 开始的整数编码,正好可以把有序分箱结果直接喂给模型;astype(str) 则保留标签文本,适合做展示和落库。

六、小结

pd.cut() 按数值宽度分箱,区间等距,适合分布均匀或业务规则明确的场景,配合 bins、labels、include_lowest、right 四个参数即可覆盖绝大多数分档需求;pd.qcut() 按样本数量分箱,区间等频,适合长尾分布数据,避免空箱。两者输出都是有序的 category,用 value_counts() 可以直接统计各箱样本量,再按需 astype(str) 或 cat.codes 转换类型,就能接上后续的报表或建模流程。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Pandas数据分箱:pd.cut与pd.qcut等宽等频离散化

讲得很清楚,尤其是把“区间宽度”和“样本频率”这两个判断标准提炼出来,实际用的时候基本就是按这个来决定用 cut 还是 qcut 的。补充几点我踩过的坑,供参考。 cat.codes 那里有个细节值得留意:如果原始数据里有 NaN,分箱后对应位置是缺失值,cat.codes 会把它编码成 -1,而不是报错或者变成某个正常类别的编号。下游如果直接把这个编码喂给模型,等于凭空多出一个“-1 类”,最好在编码前先决定这些缺失样本是剔除、单独归为一档还是填充,别让它悄悄混进去。 qcut 在样本量小或者重复值特别多的时候容易出问题:分位点落在完全相同的数值上,边界不唯一,会直接抛 Bin edges must be unique 的错。这时可以加 duplicates=“drop” 让重复边界被丢弃,箱数会少于设定的 q,也可以用 rank(method=“first”) 先给重复值打破平局再分。另外等频只是“尽可能”等,整数分数这种重复值密集的数据,四个箱子的人数往往不会严格相等,这点做分层抽样时要留意比例是否真的对齐了。 cut 传整数 bins 时是按 min 到 max 均分的,只要有一个极端离群值,其余样本就会全挤在一两个箱子里,所以这种场景下要么先看分布、要么直接给自定义边界,会比自动等分稳得多。 还有 value_counts 在 category 上默认会把所有类别都列出来
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Pandas数据分箱:pd.cut与pd.qcut等宽等频离散化

感谢分享,写得很系统,尤其是把 cut 和 qcut 的适用场景和边界开闭这种容易踩的坑讲清楚了。我自己在这块也踩过几个坑,补充几点供参考: 一是 cat.codes 转整数编码时,如果原数据里有 NaN,对应位置的 code 会是 -1,直接拿去当类别特征喂模型会莫名其妙多出一个“类别”,最好先确认缺失情况或者显式替换掉。 二是 qcut 在数据有大量重复值时会报边界重复的错误,因为分位点重合了,这时候可以传 duplicates=“drop” 让它自动丢弃重复边界,箱数会少于预期,需要自己检查一下最终分了几箱。 三是切分边界被截断的问题,比如用浮点数算出来的边界再拿去做比较,可能出现恰好等于边界值却落不进任何区间的情况,这种时候思路和帖子里的 include_lowest、right 参数是一样的,先统一口径再统计。 另外楼主帖子最后一段好像没贴完,结果类型转换那里停在 cat.codes 后面,不知道是不是后面还有转换成整数区间或者还原边界的写法,如果有的话期待补全。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Pandas数据分箱:pd.cut与pd.qcut等宽等频离散化

感谢分享,讲得很清楚。我之前一直分不清 cut 和 qcut,看到“关心的是数值距离,还是样本数量”这句就一下明白了,尤其是长尾数据用等宽分箱会把样本挤在一两个箱子里这个坑,我也踩过。 有个小问题想提一下:帖子第三部分“结果类型转换”最后一行好像被截断了,cat.codes 后面的内容没显示出来,方便的话能补全吗?正好我也想看看 category 转整数编码那部分。 另外想请教一下,qcut 在数据里重复值特别多的时候(比如一列里大量 0 或者同一个金额),分位点会重合导致报错,这种情况一般怎么处理?我隐约记得有个参数可以处理重复边界,但一直没弄清楚具体用法。还有楼主提到 value_counts 默认忽略 NaN,这个在实际做报表时确实容易被忽略,感谢提醒。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-11 05:39 , Processed in 0.022206 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部