
在数据分析中,将连续的数值数据划分到离散的类别(即分箱或离散化)是一种常见的预处理技术。例如,将年龄数据划分为“17岁以下”、“18-25岁”等类别,有助于简化数据、发现模式或满足业务需求。然而,在实际数据中,我们经常面临非数值数据、缺失值以及分箱逻辑的精确控制等挑战。本教程将以一个具体的年龄分箱场景为例,详细阐述如何使用Pandas库克服这些挑战。
我们的目标是将原始年龄数据(可能包含文本或缺失值)转换为以下七个精确定义的类别:unknown、17 and under、18-25、26-35、36-45、46-55、56+。其中,任何非数值或无法识别的年龄都应归入unknown类别。
pd.cut是Pandas中用于将数值数据分箱的关键函数。它允许我们根据提供的分箱边界(bins)将Series或DataFrame列中的值分配到不同的类别。
基本用法:
立即学习“Python免费学习笔记(深入)”;
pd.cut(x, bins, labels=None, right=True, include_lowest=False)
在进行数值分箱之前,确保所有数据都是可处理的数值类型至关重要。原始数据可能包含非数字字符(如“sixty-nine”、“45-55”)或缺失值。pd.cut只能处理数值类型。
我们使用pd.to_numeric函数,配合errors='coerce'参数,将非数值数据转换为NaN(Not a Number)。这是一种简洁而强大的预处理方法。
示例数据准备:
import pandas as pd
import numpy as np
# 模拟原始数据
data = {'Q3: AGE': ['45-55', '20', '56', '35', 'sixty-nine', np.nan, '15', '60 on the day after Halloween', '40']}
candy = pd.DataFrame(data)
print("原始数据:")
print(candy)
# 预处理:将非数值转换为NaN
candy['Q3: AGE_numeric'] = pd.to_numeric(candy['Q3: AGE'], errors='coerce')
print("\n预处理后的数值列:")
print(candy[['Q3: AGE', 'Q3: AGE_numeric']])输出:
原始数据:
Q3: AGE
0 45-55
1 20
2 56
3 35
4 sixty-nine
5 NaN
6 15
7 60 on the day after Halloween
8 40
预处理后的数值列:
Q3: AGE Q3: AGE_numeric
0 45-55 NaN
1 20 20.0
2 56 56.0
3 35 35.0
4 sixty-nine NaN
5 NaN NaN
6 15 15.0
7 60 on the day after Halloween NaN
8 40 40.0可以看到,'45-55'、'sixty-nine'等非数字字符串以及原始NaN都被成功转换为了NaN。
pd.cut函数的一个常见错误是“Bin labels must be one fewer than the number of bin edges”(分箱标签数量必须比分箱边界少一个)。这意味着,如果你有N个类别标签,你就需要提供N+1个分箱边界来定义这些区间。
为了实现我们所有类别,包括unknown,并解决上述错误,我们需要精心构造bins和labels:
为了将unknown类别纳入pd.cut的直接处理范围,我们可以创建一个特殊的区间。例如,我们将所有小于等于-1的数值归为unknown。由于pd.to_numeric会将无效值转换为NaN,而NaN不会被pd.cut直接分箱,所以这个unknown区间主要用于捕获理论上可能存在的负数年龄(尽管在实际年龄数据中不常见)。更重要的是,后续我们会用fillna('unknown')来处理所有因预处理而产生的NaN。
# 定义分箱边界
# 注意:这里有8个边界,对应7个标签
bins = [-float('inf'), -1, 17, 25, 35, 45, 55, float('inf')]
# 定义类别标签
labels = ['unknown', '17 and under', '18-25', '26-35', '36-45', '46-55', '56+']
print(f"分箱边界数量: {len(bins)}")
print(f"类别标签数量: {len(labels)}")这里,len(bins)是8,len(labels)是7,满足了len(bins) = len(labels) + 1的条件。
在pd.to_numeric步骤中,所有非数值数据都已转换为NaN。pd.cut在遇到NaN时,默认也会将其结果设为NaN。为了将这些NaN统一归类到unknown,我们需要在pd.cut操作之后使用fillna()方法。
# 应用pd.cut进行分箱
candy['age_cat'] = pd.cut(candy['Q3: AGE_numeric'], bins=bins, labels=labels, right=True)
# 填充所有剩余的NaN值为'unknown'
# 这些NaN可能来自原始数据中的NaN,或者pd.to_numeric转换后的NaN
candy['age_cat'] = candy['age_cat'].fillna('unknown')
print("\n初步分箱结果(包含unknown填充):")
print(candy[['Q3: AGE', 'Q3: AGE_numeric', 'age_cat']])输出:
初步分箱结果(包含unknown填充):
Q3: AGE Q3: AGE_numeric age_cat
0 45-55 NaN unknown
1 20 20.0 18-25
2 56 56.0 56+
3 35 35.0 26-35
4 sixty-nine NaN unknown
5 NaN NaN unknown
6 15 15.0 17 and under
7 60 on the day after Halloween NaN unknown
8 40 40.0 36-45现在,所有非数值和缺失值都已正确地归类为unknown。
为了确保分类结果的数据类型是Pandas的Categorical类型,并且类别顺序严格按照要求排列,我们需要显式地进行转换。这有助于后续的数据分析和可视化,并确保类别顺序的稳定性。
# 定义最终的类别顺序
final_categories = ['unknown', '17 and under', '18-25', '26-35', '36-45', '46-55', '56+']
# 将age_cat列转换为Categorical类型,并指定类别顺序
candy['age_cat'] = pd.Categorical(candy['age_cat'], categories=final_categories, ordered=False)
print("\n最终分箱结果(Categorical类型及指定顺序):")
print(candy[['Q3: AGE', 'age_cat']])
print("\nage_cat列的类别信息:")
print(candy['age_cat'].cat.categories)输出:
最终分箱结果(Categorical类型及指定顺序):
Q3: AGE age_cat
0 45-55 unknown
1 20 18-25
2 56 56+
3 35 26-35
4 sixty-nine unknown
5 NaN unknown
6 15 17 and under
7 60 on the day after Halloween unknown
8 40 36-45
age_cat列的类别信息:
Index(['unknown', '17 and under', '18-25', '26-35', '36-45', '46-55', '56+'], dtype='object')可以看到,age_cat列现在是Categorical类型,并且其内部的类别顺序与final_categories完全一致。
将上述所有步骤整合,即可得到一个完整且健壮的解决方案:
import pandas as pd
import numpy as np
# 模拟原始数据
data = {'Q3: AGE': ['45-55', '20', '56', '35', 'sixty-nine', np.nan, '15', '60 on the day after Halloween', '40', '-5']}
candy = pd.DataFrame(data)
print("--- 原始数据 ---")
print(candy)
# 1. 数据预处理:将非数值转换为NaN
# 使用pd.to_numeric的errors='coerce'参数处理文本和无效值
candy['Q3: AGE_numeric'] = pd.to_numeric(candy['Q3: AGE'], errors='coerce')
# 2. 定义分箱边界和标签
# 注意:分箱边界数量必须比标签数量多一个
bins = [-float('inf'), -1, 17, 25, 35, 45, 55, float('inf')]
labels = ['unknown', '17 and under', '18-25', '26-35', '36-45', '46-55', '56+']
# 3. 应用pd.cut进行分箱
# pd.cut会将Q3: AGE_numeric中的NaN值对应的age_cat设为NaN
candy['age_cat'] = pd.cut(candy['Q3: AGE_numeric'],
bins=bins,
labels=labels,
right=True) # right=True表示区间右侧闭合 (a, b]
# 4. 填充所有剩余的NaN值为'unknown'
# 这会捕获所有因pd.to_numeric转换失败或原始数据为NaN而产生的NaN
candy['age_cat'] = candy['age_cat'].fillna('unknown')
# 5. 规范化为Categorical类型并指定类别顺序
final_categories = ['unknown', '17 and under', '18-25', '26-35', '36-45', '46-55', '56+']
candy['age_cat'] = pd.Categorical(candy['age_cat'], categories=final_categories, ordered=False)
print("\n--- 最终处理结果 ---")
print(candy[['Q3: AGE', 'age_cat']])
print("\n--- age_cat列的类别顺序 ---")
print(candy['age_cat'].cat.categories)本教程详细演示了如何使用Pandas进行复杂的数据分箱操作。通过结合pd.to_numeric进行数据清洗、精确构造bins和labels解决pd.cut的常见错误、利用fillna处理缺失值,以及最终通过pd.Categorical规范化类别顺序,我们能够高效且准确地将原始数据转换为符合业务需求的分类数据。掌握这些技巧对于任何数据分析师来说都至关重要。
以上就是Python Pandas:如何将数值数据精确分箱并处理非数值与缺失值的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号