Pandas 数值离散化:从连续数据到有序区间的完整指南

一句话总结: pd.cut 将连续数值切分为固定区间,配合标签和虚拟编码,实现数值特征到类别特征的转换。

📋 环境要求

  • Python 3.14+
  • scikit-learn 1.9+
  • pandas 3.0+
  • numpy 2.4+
  • jupyter 1.1+
  • notebook 7.5+
  • nbconvert 7.17+

一、为什么需要数值离散化?

连续型数值在某些场景下不如离散区间直观:

  • 业务解释:年龄段比具体年龄更易理解(青年/中年/老年)
  • 降低噪声:平滑异常值对模型的影响
  • 适配算法:决策树、朴素贝叶斯等算法对类别型更友好
  • 规则表达:"体重超过 80kg 为超重"比回归系数更直观

核心工具: pandas 的 cut() 函数,基于区间边界做等宽划分。

二、等宽区间划分

场景: 将数据自动划分为 N 个等宽区间。

import pandas as pd

df = pd.DataFrame(data={'weight': [75., 78.5, 85., 91., 84.5, 83., 68.]})

# bins=3: 自动计算区间边界,将数据划分为 3 个等宽区间
# 区间宽度 = (最大值 91 - 最小值 68) / 3 ≈ 7.67
df["weight_cut"] = pd.cut(df["weight"], bins=3)

划分结果:

weightweight_cut
75.0(67.977, 75.667]
78.5(75.667, 83.333]
85.0(83.333, 91.0]
91.0(83.333, 91.0]
84.5(83.333, 91.0]
83.0(75.667, 83.333]
68.0(67.977, 75.667]

关键点:

  • 区间左开右闭:(67.977, 75.667] 表示大于 67.977 且小于等于 75.667
  • 自动生成边界:覆盖数据范围并适当外延
  • 返回 category 类型,节省内存

三、指定区间边界

场景: 业务有明确区间定义(如 BMI 分级),需自定义边界。

df = pd.DataFrame(data={'weight': [75., 78.5, 85., 91., 84.5, 83., 68.]})

# bins=[60, 75, 80, 95]: 自定义三个区间 (60,75], (75,80], (80,95]
# 注意:边界值 75 归入 (60,75],边界值 80 归入 (75,80]
df["weight_cut"] = pd.cut(df["weight"], bins=[60, 75, 80, 95])

与自动划分的区别:

方式区间适用场景
自动划分(67.977, 75.667]探索性分析,无先验知识
指定边界(60, 75]业务规则明确,如体重分级

关键点:

  • 自定义边界更贴合业务语义
  • 数据超出边界范围会产生 NaN
  • 边界值归属遵循右闭原则

四、添加区间标签

场景: 将区间转换为易读的标签(如轻/中/重)。

df = pd.DataFrame(data={'weight': [75., 78.5, 85., 91., 84.5, 83., 68.]})

# labels: 为每个区间指定可读标签
# bins 数量 = labels 数量 + 1
df["weight_cut"] = pd.cut(
    df["weight"],
    bins=[60, 75, 80, 95],
    labels=["light", "normal", "heavy"]  # 对应三个区间
)

结果:

weightweight_cut
75.0light
78.5normal
85.0heavy
91.0heavy
84.5heavy
83.0heavy
68.0light

关键点:

  • labels 长度必须等于 bins 数量减 1
  • 标签顺序与区间顺序一一对应
  • 标签使数据更具业务可读性

五、虚拟编码(One-Hot 编码)

场景: 将类别型特征转换为数值型,供机器学习模型使用。

df = pd.DataFrame(data={'weight': [75., 78.5, 85., 91., 84.5, 83., 68.]})
df["weight_cut"] = pd.cut(
    df["weight"],
    bins=(60, 75, 80, 95),
    labels=['light', 'normal', 'heavy']
)
# pd.get_dummies: 将类别列展开为多列布尔值
df = pd.get_dummies(df)

编码结果:

weightweight_cut_lightweight_cut_normalweight_cut_heavy
75.0TrueFalseFalse
78.5FalseTrueFalse
85.0FalseFalseTrue
91.0FalseFalseTrue
84.5FalseFalseTrue
83.0FalseFalseTrue
68.0TrueFalseFalse

关键点:

  • 每行只有一个 True,其余为 False
  • 列名格式:原列名_类别标签
  • 适用于线性模型、神经网络等需要数值输入的算法
  • 决策树类算法可直接使用 category 类型,无需虚拟编码

六、常见问题

Q1: 区间边界值归属哪一侧?

pd.cut 默认右闭左开:(a, b] 表示 a < x <= b

# 75.0 归入 (60, 75],而非 (75, 80]
pd.cut([75.0], bins=[60, 75, 80])  # [(60, 75.0]]

如需左闭右开,使用 right=False

pd.cut([75.0], bins=[60, 75, 80], right=False)  # [[60, 75)]

Q2: 数据超出边界怎么办?

# 55 和 100 超出 [60, 95] 范围
df["weight_cut"] = pd.cut([55, 70, 100], bins=[60, 75, 95])
# 结果: [NaN, (60, 75], NaN]

解决: 扩大边界或添加 include_lowest=True

pd.cut([55, 70], bins=[50, 75, 100])  # 扩大边界

Q3: 等宽 vs 等频划分?

方法函数特点适用场景
等宽pd.cut区间宽度相等数据均匀分布
等频pd.qcut每区间样本数相等数据偏态分布
# 等频划分:每个区间约 1/3 的数据
pd.qcut(df["weight"], q=3, labels=["low", "mid", "high"])

七、速查表

pd.cut 参数

参数说明示例
x待划分的数组/Seriesdf["weight"]
bins区间数量或边界列表3[60, 75, 95]
labels区间标签["light", "heavy"]
right是否右闭区间True(默认) / False
include_lowest是否包含最小值False(默认)

完整流程模板

# 1. 离散化
df["category"] = pd.cut(df["value"], bins=[0, 50, 100, 150], labels=["低", "中", "高"])

# 2. 虚拟编码(模型需要时)
df_encoded = pd.get_dummies(df, columns=["category"])

八、总结

数值离散化是数据预处理中的重要步骤,pd.cut 提供了灵活且强大的区间划分能力:

  1. 等宽划分:适用于探索性分析,无先验知识时使用
  2. 指定边界:适用于业务规则明确的场景
  3. 添加标签:提升数据的可读性和业务解释性
  4. 虚拟编码:为机器学习模型准备数值输入

通过合理使用离散化技术,可以将连续数值转换为更具业务意义的类别特征,提升模型的可解释性和稳定性。


网络取材来源于: ant-exercises-sklearn: scikit-learn 编程练习 100例

刚开始学习数据集的处理,在这里同步学习成果、激励自己坚持到底。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值