数据清洗,如何利用pd.cut()给数字数据进行分组处理?
发布于 作者:苏南大叔 来源:程序如此灵动~继续python
里面,对数字数据进行分类处理的方式方法。数字数据一般指的是:年龄、工资、金额 等数据。这类数据由数字组成,每一部分数据都可以使用文字来进行描述,全部数据就可以使用多个文字来进行描述。从而实现对数字类型数据的一个抽象分类化的过程。
苏南大叔的“程序如此灵动”博客,这里讲述苏南大叔和计算机代码之间的故事。测试环境:python@3.12.3
,pandas@2.2.2
,numpy@1.26.4
。
前文回顾
本文所描述的分组过程,和前面描述过dtype
的category
化是有类似的效果的。所以,可以把两者进行对比:
默认情况下,分组后的结果还具有排序功能。这部分内容,对比参考:
pandas.cut()原型
pandas.cut(x, bins, right=True, labels=None, retbins=False,precision=3, include_lowest=False, duplicates='raise', ordered=True)
参数 | 默认值 | 解释 |
---|---|---|
x | 要切分的一维数组,可以是列表,也可以是dataFrame的一列 | |
bins | 切片的方式,列表[a,b,c],表示按照a-b,b-c的区间来切分,也可以是数值n,直接指定分为n组 | |
labels | 标签参数,比如[低、中、高]。数量上来说,是bins 数组数量减一 | |
right | True | True/False,为True时,表示分组区间包含右边,不包含左边,即(]。False,代表[) |
retbins | False | True/False, True返回bin的具体范围值 |
precision | 3 | 存储和显示标签的精度。 |
include_lowest | False | True/False, 第一个区间是否应该是左包含的 |
duplicates | raise | raise/drop, 如果bin列表里有重复,报错/直接删除至保留一个 |
ordered | True | 标签是否有序 |
最简单用法【推荐】
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
c = pd.cut(df.kg, [0, 6, 9, 11], labels=['light(0,6]', 'normal(6,9]', 'heavy(9,11]'])
print(c)
输出:
0 light(0,6]
1 normal(6,9]
2 light(0,6]
3 normal(6,9]
4 NaN
Name: kg, dtype: category
Categories (3, object): ['light(0,6]' < 'normal(6,9]' < 'heavy(9,11]']
这里labels
里面的左开右闭区间,是后面的主要内容。而且,要注意到:这个默认返回值是个带有顺序的series
。但是,这里的结果其实并不是很好理解。所以,如果把返回的结果,放在原来的dataframe
里面,是个更好的选择。例如:
df["w"] = pd.cut(df.kg, [0, 6, 9, 11], labels=['light(0,6]', 'normal(6,9]', 'heavy(9,11]'])
print(df)
输出:
name kg w
0 老许 2 light(0,6]
1 小黑 9 normal(6,9]
2 虎子 6 light(0,6]
3 黑猫 9 normal(6,9]
4 老白 12 NaN
目前为止,就是最基础常见的用法了。还可以看到:bins
设定,并不是一定会覆盖所有的数据的,允许产生遗漏的NaN
数据。本文后面的内容,基本上也用不到。
极简不设置标签
如果不设置labels
,也是可以的。但是,这么操作似乎没有什么意义。
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
c = pd.cut(df.kg, [0, 6, 9, 11])
print(c)
输出:
0 (0.0, 6.0]
1 (6.0, 9.0]
2 (0.0, 6.0]
3 (6.0, 9.0]
4 NaN
Name: kg, dtype: category
Categories (3, interval[int64, right]): [(0, 6] < (6, 9] < (9, 11]]
边界特殊设置
正如上面的例子所示,bins
就是设置数字边界的,默认情况下是个list
数组(必须从左到右依次增加,否则报错),而且范围是属于左开右闭。允许有不被这个bins
覆盖到的数据。
特殊情况一
然而,bins
也可以设置为一个数字。这种情况下,是自动平均划分范围的意思。这同时面临的问题是:均分的精度问题。可以使用参数precision
设置。
例如:
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
c = pd.cut(df.kg, 3)
print(c)
c = pd.cut(df.kg, 3, precision=1)
print(c)
输出:
0 (1.99, 5.333]
1 (8.667, 12.0]
2 (5.333, 8.667]
3 (8.667, 12.0]
4 (8.667, 12.0]
Name: kg, dtype: category
Categories (3, interval[float64, right]): [(1.99, 5.333] < (5.333, 8.667] < (8.667, 12.0]]
0 (2.0, 5.3]
1 (8.7, 12.0]
2 (5.3, 8.7]
3 (8.7, 12.0]
4 (8.7, 12.0]
Name: kg, dtype: category
Categories (3, interval[float64, right]): [(2.0, 5.3] < (5.3, 8.7] < (8.7, 12.0]]
特殊情况二
retbins
参数,返回真正的bins
。这个情况很特殊,有两个返回值。一个是正常的分组后的数据,另外一个是分组的标准。为啥要再次返回分组标准?是因为确实有没有明确说明bins
的情况,或者存在bins
设置,被duplicates='drop'
修改的情况。
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
c,r = pd.cut(df.kg, 3, retbins = True)
print(r)
c, r = pd.cut(df.kg, [0, 6, 6, 9, 12], retbins=True, duplicates="drop")
print(r)
输出:
[ 1.99 5.33333333 8.66666667 12. ]
[ 0 6 9 12]
边界是否包含处理
这个小节的内容是:左开右闭区间的问题。有两个相关的参数,
- 参数
right
,默认True
,左开右闭。False
则是左闭右开。 - 参数
include_lowest
,默认False
不包含最小值。改成True
,则最左边的范围会变小一些[或者改成左闭右开],使得最小值被包含。值得特别注意的是:这个lowest
指的是bins
里面的最小值,并不是实际被切分的数据中的最小值。
为了更好的展示效果,这里不设置labels
,直接看结果。
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
c = pd.cut(df.kg, [0, 6, 9, 12], right = False)
print(c)
c = pd.cut(df.kg, [0, 6, 9, 12], include_lowest=True)
print(c)
c = pd.cut(df.kg, [3, 6, 9, 12], include_lowest=True)
print(c)
c = pd.cut(df.kg, [3, 6, 9, 12], include_lowest=True, right=False)
print(c)
输出:
0 [0.0, 6.0)
1 [9.0, 12.0)
2 [6.0, 9.0)
3 [9.0, 12.0)
4 NaN
Name: kg, dtype: category
Categories (3, interval[int64, left]): [[0, 6) < [6, 9) < [9, 12)]
0 (-0.001, 6.0]
1 (6.0, 9.0]
2 (-0.001, 6.0]
3 (6.0, 9.0]
4 (9.0, 12.0]
Name: kg, dtype: category
Categories (3, interval[float64, right]): [(-0.001, 6.0] < (6.0, 9.0] < (9.0, 12.0]]
0 NaN
1 (6.0, 9.0]
2 (2.999, 6.0]
3 (6.0, 9.0]
4 (9.0, 12.0]
Name: kg, dtype: category
Categories (3, interval[float64, right]): [(2.999, 6.0] < (6.0, 9.0] < (9.0, 12.0]]
0 NaN
1 [9.0, 12.0)
2 [6.0, 9.0)
3 [9.0, 12.0)
4 NaN
Name: kg, dtype: category
Categories (3, interval[int64, left]): [[3, 6) < [6, 9) < [9, 12)]
边界是否合理处理
这里指的就是参数duplicates
,指的是bins
里面如果写了重复的值,该如何处理。说实话,这个参数就是伪命题。bins
这个人为设置的参数,为啥非要设置个非法的重复值呢?默认是raise
报错。
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
c = pd.cut(df.kg, [0, 6, 6, 12])
print(c)
输出:
ValueError: Bin edges must be unique: Index([0, 6, 6, 12], dtype='int64').
You can drop duplicate edges by setting the 'duplicates' kwarg
是否排序处理
这个ordered
参数就略微烧脑,因为从pd.cut()
的返回值上来说,是不是ordered
区别不大。返回值类型都是Series
,其dtype
都是category
。区别仅仅是这个category
是不是有序的。如何测试其有序性呢?参考文章:
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
df["w"] = pd.cut(df.kg, [0, 6, 9, 12], labels = ["轻", "不轻不重", "重"])
print(df["w"])
print(df["w"]>="不轻不重")
输出:
0 轻
1 不轻不重
2 轻
3 不轻不重
4 重
Name: w, dtype: category
Categories (3, object): ['轻' < '不轻不重' < '重']
0 False
1 True
2 False
3 True
4 True
Name: w, dtype: bool
如果改成无序的,就会得到报错信息。
import pandas as pd
df = pd.DataFrame({
'name': ['老许', '小黑', '虎子', '黑猫', '老白'],
'kg': [2, 9, 6, 9, 12]})
df["w"] = pd.cut(df.kg, [0, 6, 9, 12], labels = ["轻", "不轻不重", "重"], ordered = False)
print(df["w"])
print(df["w"]>="不轻不重")
输出:
0 轻
1 不轻不重
2 轻
3 不轻不重
4 重
Name: w, dtype: category
Categories (3, object): ['轻', '不轻不重', '重']
raise TypeError(
TypeError: Unordered Categoricals can only compare equality or not
pd.cut()
后续操作
效果对比:
pd.cut()
的效果上来看,是大量数字变成了有限的category
文字数组。.astype('category')
的效果,是大量的重复文本变成了有限的category
文字数组。
所以,后续的操作,可以是df.groupby()
。参考文章:
结语
数据清洗是对数据集进行预测的必要步骤。更多苏南大叔的python
文章,请参考下面的链接:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。