47_Pandas使用cut和qcut函数进行分箱处理

分箱处理(bin Division)是将连续值除以任意边界值,将其划分为类别,再将其转换为离散值的处理。它通常作为机器学习的预处理完成。
比如有一个过程,比如将年龄数据分为十几岁和二十几岁。

根据值拆分:cut()
按数量拆分:qcut()

它们是有区别的。

在这里,下面的内容将讲解如何使用pandas.cut()和pandas.qcut()。

  • 等分或任意边界值的分箱过程:cut()
  • 以相等的间隔除以最大值和最小值
  • 通过指定边界值拆分
  • 获取边界值列表:retbins
  • 指定是否包含左边缘或右边缘:right
  • 指定标签:labels
  • 指定边界值的精度(小数点后的位数):precision
  • 对bin中元素个数进行相等分箱:qcut()
  • 通过指定拆分次数拆分
  • 重复值时的注意事项
  • 统计bin中元素的个数:value_counts()
  • Python 列表、NumPy 数组 ndarray 的分箱处理
  • 具体例子:Binning泰坦尼克号生存信息的年龄

以下面的 pandas.Series 为例。

import pandas as pd

s = pd.Series(data=[x**2 for x in range(11)],
              index=list('abcdefghijk'))

print(s)
# a      0
# b      1
# c      4
# d      9
# e     16
# f     25
# g     36
# h     49
# i     64
# j     81
# k    100
# dtype: int64

等分或任意边界值的分箱过程:cut()

在pandas.cut()函数中,第一个参数x中指定为原始数据的一维数组(Python列表,numpy.ndarray,pandas.Series),第二个参数bins中指定bin划分设置。

以相等的间隔除以最大值和最小值

如果为第二个参数 bin 指定了整数值,则将指定分割数(bin 数)。以相等的间隔除以最大值和最小值。如果使用pandas.Series 作为原始数据,将返回pandas.Series。

s_cut = pd.cut(s, 4)
print(s_cut)
# a     (-0.1, 25.0]
# b     (-0.1, 25.0]
# c     (-0.1, 25.0]
# d     (-0.1, 25.0]
# e     (-0.1, 25.0]
# f     (-0.1, 25.0]
# g     (25.0, 50.0]
# h     (25.0, 50.0]
# i     (50.0, 75.0]
# j    (75.0, 100.0]
# k    (75.0, 100.0]
# dtype: category
# Categories (4, interval[float64]): [(-0.1, 25.0] < (25.0, 50.0] < (50.0, 75.0] < (75.0, 100.0]]

print(type(s_cut))
# <class 'pandas.core.series.Series'>

(a, b]表示a < x <= b。默认情况下不包括左边缘(较小)的值,最左边(最小边界值)比最大值小0.1%。

通过指定边界值拆分

如果在第二个参数 bin 中指定了列表,则列表的元素将被划分为边界值。范围外的值为NaN。

print(pd.cut(s, [0, 10, 50, 100]))
# a          NaN
# b      (0, 10]
# c      (0, 10]
# d      (0, 10]
# e     (10, 50]
# f     (10, 50]
# g     (10, 50]
# h     (10, 50]
# i    (50, 100]
# j    (50, 100]
# k    (50, 100]
# dtype: category
# Categories (3, interval[int64]): [(0, 10] < (10, 50] < (50, 100]]

获取边界值列表:retbins

如果参数retbins = True,则可以同时获取binned数据和边界值列表。边界值列表是 numpy.ndarray。

s_cut, bins = pd.cut(s, 4, retbins=True)
print(s_cut)
# a     (-0.1, 25.0]
# b     (-0.1, 25.0]
# c     (-0.1, 25.0]
# d     (-0.1, 25.0]
# e     (-0.1, 25.0]
# f     (-0.1, 25.0]
# g     (25.0, 50.0]
# h     (25.0, 50.0]
# i     (50.0, 75.0]
# j    (75.0, 100.0]
# k    (75.0, 100.0]
# dtype: category
# Categories (4, interval[float64]): [(-0.1, 25.0] < (25.0, 50.0] < (50.0, 75.0] < (75.0, 100.0]]

print(bins)
print(type(bins))
# [ -0.1  25.   50.   75.  100. ]
# <class 'numpy.ndarray'>

指定是否包含左边缘或右边缘:right

如上所述,默认情况下,右边缘包含在 bin 中,左边缘不包含在 bin 中,但使用参数 right = False,右边缘不包含在 bin 中。

print(pd.cut(s, 4, right=False))
# a      [0.0, 25.0)
# b      [0.0, 25.0)
# c      [0.0, 25.0)
# d      [0.0, 25.0)
# e      [0.0, 25.0)
# f     [25.0, 50.0)
# g     [25.0, 50.0)
# h     [25.0, 50.0)
# i     [50.0, 75.0)
# j    [75.0, 100.1)
# k    [75.0, 100.1)
# dtype: category
# Categories (4, interval[float64]): [[0.0, 25.0) < [25.0, 50.0) < [50.0, 75.0) < [75.0, 100.1)]

最右边(最大边界值)比最大值大 0.1%。

指定标签:labels

可以使用参数标签指定标签。默认值为labels=None,如前面的示例 (a, b]. 如果labels = False,索引将是整数值的索引(从0开始的序列号)。

print(pd.cut(s, 4, labels=False))
# a    0
# b    0
# c    0
# d    0
# e    0
# f    0
# g    1
# h    1
# i    2
# j    3
# k    3
# dtype: int64

还可以指定列表中的任何标签。在这种情况下,如果 bin 数量和列表中的元素数量不匹配,则会发生错误。

print(pd.cut(s, 4, labels=['small', 'medium', 'large', 'x-large']))
# a      small
# b      small
# c      small
# d      small
# e      small
# f      small
# g     medium
# h     medium
# i      large
# j    x-large
# k    x-large
# dtype: category
# Categories (4, object): [small < medium < large < x-large]

指定边界值的精度(小数点后的位数):precision

可以使用参数 precision 指定边界值的精度(小数点后的位数)。

print(pd.cut(s, 3))
# a      (-0.1, 33.333]
# b      (-0.1, 33.333]
# c      (-0.1, 33.333]
# d      (-0.1, 33.333]
# e      (-0.1, 33.333]
# f      (-0.1, 33.333]
# g    (33.333, 66.667]
# h    (33.333, 66.667]
# i    (33.333, 66.667]
# j     (66.667, 100.0]
# k     (66.667, 100.0]
# dtype: category
# Categories (3, interval[float64]): [(-0.1, 33.333] < (33.333, 66.667] < (66.667, 100.0]]

print(pd.cut(s, 3, precision=1))
# a     (-0.1, 33.3]
# b     (-0.1, 33.3]
# c     (-0.1, 33.3]
# d     (-0.1, 33.3]
# e     (-0.1, 33.3]
# f     (-0.1, 33.3]
# g     (33.3, 66.7]
# h     (33.3, 66.7]
# i     (33.3, 66.7]
# j    (66.7, 100.0]
# k    (66.7, 100.0]
# dtype: category
# Categories (3, interval[float64]): [(-0.1, 33.3] < (33.3, 66.7] < (66.7, 100.0]]

对bin中元素个数进行相等分箱:qcut()

qcut()是一个binning处理(bin Division)的函数,使每个bin中包含的个数(元素个数)相等,而不是像cut()那样等分值或指定边界值。

在第一个参数 x 中指定作为原始数据的一维数组(Python 列表、numpy.ndarray、pandas.Series),并在第二个参数 q 中指定分割数。

有labels和 retbins 作为与 cut() 相同的参数。

通过指定拆分次数拆分

在第二个参数 q 中指定分割数。 如果 q = 2,则除以中位数。

print(pd.qcut(s, 2))
# a    (-0.001, 25.0]
# b    (-0.001, 25.0]
# c    (-0.001, 25.0]
# d    (-0.001, 25.0]
# e    (-0.001, 25.0]
# f    (-0.001, 25.0]
# g     (25.0, 100.0]
# h     (25.0, 100.0]
# i     (25.0, 100.0]
# j     (25.0, 100.0]
# k     (25.0, 100.0]
# dtype: category
# Categories (2, interval[float64]): [(-0.001, 25.0] < (25.0, 100.0]]

重复值时的注意事项

当原始数据的元素值重复时要小心。 例如,如果值被复制到中位数。

s_duplicate = pd.Series(data=[0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6],
                        index=list('abcdefghijk'))

print(s_duplicate)
# a    0
# b    0
# c    0
# d    0
# e    0
# f    1
# g    2
# h    3
# i    4
# j    5
# k    6
# dtype: int64

可以用 q = 2 除以中位数,但如果除数大于这个数,则会出现错误。

print(pd.qcut(s_duplicate, 2))
# a    (-0.001, 1.0]
# b    (-0.001, 1.0]
# c    (-0.001, 1.0]
# d    (-0.001, 1.0]
# e    (-0.001, 1.0]
# f    (-0.001, 1.0]
# g       (1.0, 6.0]
# h       (1.0, 6.0]
# i       (1.0, 6.0]
# j       (1.0, 6.0]
# k       (1.0, 6.0]
# dtype: category
# Categories (2, interval[float64]): [(-0.001, 1.0] < (1.0, 6.0]]

# print(pd.qcut(s_duplicate, 4))
# ValueError: Bin edges must be unique: array([0. , 0. , 1. , 3.5, 6. ]).
# You can drop duplicate edges by setting the 'duplicates' kwarg

例如在四分位数的情况下,将最小值、1/4 分位数(25%)、中位数(50%)、3/4 分位数(75%)和最大值设置为边界值。重叠元素喜欢,错误的原因是最小值和1/4分位数是相同的值。

如果参数duplicates=‘drop’,则排除并划分重复的边界值。

print(pd.qcut(s_duplicate, 4, duplicates='drop'))
# a    (-0.001, 1.0]
# b    (-0.001, 1.0]
# c    (-0.001, 1.0]
# d    (-0.001, 1.0]
# e    (-0.001, 1.0]
# f    (-0.001, 1.0]
# g       (1.0, 3.5]
# h       (1.0, 3.5]
# i       (3.5, 6.0]
# j       (3.5, 6.0]
# k       (3.5, 6.0]
# dtype: category
# Categories (3, interval[float64]): [(-0.001, 1.0] < (1.0, 3.5] < (3.5, 6.0]]

统计bin中元素的个数:value_counts()

如果从pandas.Series中调用value_counts()方法,通过cut()或qcut()可以得到的划分为bin的标记,可以得到bin中包含的个数(元素个数)。

counts = pd.cut(s, 3, labels=['S', 'M', 'L']).value_counts()
print(counts)
# S    6
# M    3
# L    2
# dtype: int64

print(type(counts))
# <class 'pandas.core.series.Series'>

print(counts['M'])
# 3

有关 value_counts() 的更多信息,请参阅以下文章。

  • 15_Pandas计算元素的数量和频率(出现的次数)

value_counts() 不仅作为pandas.Series 的一个方法提供,而且作为一个函数pandas.value_counts() 提供。也可以传递pandas.Series,它可以通过cut()或qcut()获得,作为它的参数。

print(pd.value_counts(pd.cut(s, 3, labels=['S', 'M', 'L'])))
# S    6
# M    3
# L    2
# dtype: int64

Python 列表、NumPy 数组 ndarray 的分箱处理

在到目前为止的示例中,pandas.Series 被用作原始数据,但如果它是一维的,也可以指定 Python 列表或 NumPy 数组 ndarray 作为 cut() 或 qcut() 的第一个参数 x大批。

l = [x**2 for x in range(11)]
print(l)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

l_cut = pd.cut(l, 3, labels=['S', 'M', 'L'])
print(l_cut)
# [S, S, S, S, S, ..., M, M, M, L, L]
# Length: 11
# Categories (3, object): [S < M < L]

print(type(l_cut))
# <class 'pandas.core.categorical.Categorical'>

返回类型 pandas.Categorical。可以通过索引(下标)获取元素,通过list()将其转换为Python列表类型。

print(l_cut[0])
# S

print(list(l_cut))
# ['S', 'S', 'S', 'S', 'S', 'S', 'M', 'M', 'M', 'L', 'L']

如果要计算 bin 中包含的数量(元素数),请使用函数 pandas.value_counts()。

print(pd.value_counts(l_cut))
# S    6
# M    3
# L    2
# dtype: int64

具体例子:Binning泰坦尼克号生存信息的年龄

作为一个具体的例子,我们将使用泰坦尼克号的生存信息数据。你可以从Kaggle下载它。

df_titanic = pd.read_csv('data/src/titanic_train.csv').drop(['Name', 'Ticket', 'Cabin', 'Embarked'], axis=1)

print(df_titanic.head())
#    PassengerId  Survived  Pclass     Sex   Age  SibSp  Parch     Fare
# 0            1         0       3    male  22.0      1      0   7.2500
# 1            2         1       1  female  38.0      1      0  71.2833
# 2            3         1       3  female  26.0      0      0   7.9250
# 3            4         1       1  female  35.0      1      0  53.1000
# 4            5         0       3    male  35.0      0      0   8.0500

使用 cut() 函数对 age’Age’ 列执行分箱。

print(df_titanic['Age'].describe())
# count    714.000000
# mean      29.699118
# std       14.526497
# min        0.420000
# 25%       20.125000
# 50%       28.000000
# 75%       38.000000
# max       80.000000
# Name: Age, dtype: float64

print(pd.cut(df_titanic['Age'], 5, precision=0).value_counts(sort=False, dropna=False))
# (0.0, 16.0]     100
# (16.0, 32.0]    346
# (32.0, 48.0]    188
# (48.0, 64.0]     69
# (64.0, 80.0]     11
# NaN             177
# Name: Age, dtype: int64

要将结果作为新列添加到原始 DataFrame:如果要覆盖(分配)现有列,可以将左侧的列名称更改为现有列名称。

df_titanic['Age_bin'] = pd.cut(df_titanic['Age'], 5, labels=False)

print(df_titanic.head())
#    PassengerId  Survived  Pclass     Sex   Age  SibSp  Parch     Fare  Age_bin
# 0            1         0       3    male  22.0      1      0   7.2500      1.0
# 1            2         1       1  female  38.0      1      0  71.2833      2.0
# 2            3         1       3  female  26.0      0      0   7.9250      1.0
# 3            4         1       1  female  35.0      1      0  53.1000      2.0
# 4            5         0       3    male  35.0      0      0   8.0500      2.0

在这种情况下,为了说明起见,立即执行分箱处理,但最初,在分箱之前通过某种方法补充缺失值 NaN。