分箱处理(bin Division)是将连续值除以任意边界值,将其划分为类别,再将其转换为离散值的处理。它通常作为机器学习的预处理完成。
比如有一个过程,比如将年龄数据分为十几岁和二十几岁。
根据值拆分:cut()
按数量拆分:qcut()
它们是有区别的。
在这里,下面的内容将讲解如何使用pandas.cut()和pandas.qcut()。
以下面的 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
在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))
#
(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 = 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. ]
#
如上所述,默认情况下,右边缘包含在 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=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 指定边界值的精度(小数点后的位数)。
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]]
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]]
如果从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))
#
print(counts['M'])
# 3
有关 value_counts() 的更多信息,请参阅以下文章。
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
在到目前为止的示例中,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))
#
返回类型 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
作为一个具体的例子,我们将使用泰坦尼克号的生存信息数据。你可以从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。