数据清洗是数据分析过程中至关重要的一步,它确保数据的准确性、一致性和完整性。这不仅有助于提高分析结果的可靠性和有效性,还能为算法建模决策提供高质量的数据基础。在进行数据分析和建模的过程中,大量的时间花在数据准备上:加载、清理、转换和重新排列,这样的工作占用了工程师 80% 以上的时间。所以掌握常用的数据清洗方法,将帮助我们能更高效、更高质量完成数据清洗工作。
我们将从易到难来讲述数据清洗系列三篇章,本文为第二篇章:数据转换处理,包括数据映射、数据替换、数据离散化、数据标准化、数据归一化,我们将理论和实践结合,层层递进一步一步掌握缺数据转换的处理方法。
1、数据映射
在对数据进行清洗时,我们经常需要将数据进行分类,例如根据年龄划分为儿童/青年/中年/老年,根据分数划分为不及格/及格/良好/优秀等。在 pandas 中,我们可以使用 map 方法用于对 DataFrame 中的元素进行映射。如下所示,我们使用字典进行配置映射,我们根据姓名-性别配置表,基于 name 映射出 sex 列
import pandas as pd
frame_data = pd.DataFrame({'name': ['tom','paz','leo','tom','leo'],
'age': [11, 10, 10, 11, 12]})
# 创建映射字典
mapping = {'tom':'男', 'paz':'女', 'leo':'男'}
# 使用 map 进行替换
frame_data['sex'] = frame_data['name'].map(mapping)
frame_data
# ----输出----
name age sex
0 tom 11 男
1 paz 10 女
2 leo 10 男
3 tom 11 男
4 leo 12 男
当被映射的属性比较多时,单纯的配置表就无法满足我们的需求了。此时,我们可以使用函数进行转换,如下所示,配置表变成映射函数,函数中编写相应的映射逻辑
import pandas as pd
frame_data = pd.DataFrame({'name': ['tom','paz','leo','tom','leo'],
'height': [112, 100, 120, 101, 122]})
# 定义一个简单的映射函数
def height_type(height):
if height >= 120:
return 'high'
elif height < 110:
return 'low'
else:
return 'middle'
# 使用 map 进行替换
frame_data['type'] = frame_data['height'].map(height_type)
frame_data
# ----输出----
name height type
0 tom 112 middle
1 paz 100 low
2 leo 120 high
3 tom 101 low
4 leo 122 high
2、数据替换
在数据预处理中,我们有时候要对数据进行替换处理。此时,我们可以使用 replace 方法用于替换 DataFrame 或 Series 中的特定值。它可以用于替换单个值、列表、字典或使用正则表达式进行复杂的替换。我们可以进行进行简单的单个值替换,如下所示
import pandas as pd
frame_data = pd.DataFrame({'name': ['tom','paz','leo','tom','leo'],
'age': [1, 10, 10, 1, 12]})
# 将 1 替换为 11
frame_data.replace(1, 11)
# ----输出----
name age
0 tom 11
1 paz 10
2 leo 10
3 tom 11
4 leo 12
当然我们还可以使用字典配置来进行多行多列的替换
import pandas as pd
frame_data = pd.DataFrame({'name': ['tom','paz','leo','tom','leo'],
'age': [1, 10, 10, 1, 12]})
# 将paz替换为jim,将1替换为11
frame_data.replace({'name': {'paz':'jim'}, 'age': {1:11}})
# ----输出----
name age
0 tom 11
1 jim 10
2 leo 10
3 tom 11
4 leo 12
如果有更复杂的替换场景,我们还可以使用正则表达式进行替换
import pandas as pd
frame_data = pd.DataFrame({'name': ['tom','paz','leo','tom','leo'],
'age': [1, 10, 10, 1, 12]})
# 使用正则表达式替换
frame_data.replace(to_replace=r'^t', value='T', regex=True)
# ----输出----
name age
0 Tom 1
1 paz 10
2 leo 10
3 Tom 1
4 leo 12
replace 替换默认都是生成一个新的数据,我们如果想直接在原 DataFrame 上进行替换,可以设置 inplace=True
frame_data.replace(1, 10, inplace=True)
3、数据离散化
在数据预处理中连续值经常需要离散化(也叫分箱)进行分析,在 pandas 中可以使用 cut 进行数据的切分成组
import pandas as pd
series_data = pd.Series([10, 20, 30, 40, 50, 60, 70, 80, 90])
# 定义区间边界
bins = [0, 20, 50, 80, 100]
# 应用cut函数
categories = pd.cut(series_data, bins)
categories
# ----输出----
0 (0, 20]
1 (0, 20]
2 (20, 50]
3 (20, 50]
4 (20, 50]
5 (50, 80]
6 (50, 80]
7 (50, 80]
8 (80, 100]
dtype: category
Categories (4, interval[int64, right]): [(0, 20] < (20, 50] < (50, 80] < (80, 100]]
可以看到 pandas 返回的对象是一个特殊的 Categorical 对象,其值为由 pandas.cut 计算出的箱,并且内部包含一个 categories(类别)数组。从结果中可以看到,区间的左边是小括号、右边是中括号,其与数学中的区间符号表示含义一致,小括号表示边是开放的,中括号表示它是封闭的(包括边)。我们可以通过传递 right=False 来改变哪一边是封闭的
pd.cut(series_data, bins, right=False)
# ----输出----
0 [0, 20)
1 [20, 50)
2 [20, 50)
3 [20, 50)
4 [50, 80)
5 [50, 80)
6 [50, 80)
7 [80, 100)
8 [80, 100)
dtype: category
Categories (4, interval[int64, left]): [[0, 20) < [20, 50) < [50, 80) < [80, 100)]
如果我们向语义化显示集合,可以通过向 labels 选项传递一个列表或数组来传入自定义的箱名也进行语义化显示
# 定义箱名
age_groups = ['young', 'middle', 'senior', 'old']
pd.cut(series_data, bins, labels=age_groups)
# ----输出----
0 young
1 young
2 middle
3 middle
4 middle
5 senior
6 senior
7 senior
8 old
dtype: category
Categories (4, object): ['young' < 'middle' < 'senior' < 'old']
将数据分箱后,我们经常需要统计相关分箱的数量。此时,我们可以通过 value_counts 方法统计每个箱子中的数量,其默认是按数量从多到少进行
categories.value_counts()
# ----输出----
(20, 50] 3
(50, 80] 3
(0, 20] 2
(80, 100] 1
Name: count, dtype: int64
其默认是按数量从多到少进行排序的,如果我们不想进行排序,而是直接按箱子的顺序进行输出,则可以通过设置属性 sort=False 不进行排序
categories.value_counts(sort=False)
# ----输出----
(0, 20] 2
(20, 50] 3
(50, 80] 3
(80, 100] 1
Name: count, dtype: int64
如果我们想等量划分每个箱子中的数量,可以在 cut 方法中传入整数(表示箱子数量)来代替显式的箱边,pandas 将根据数据中的最小值和最大值计算出等长的箱
pd.cut(series_data, 4)
# ----输出----
0 (9.92, 30.0]
1 (9.92, 30.0]
2 (9.92, 30.0]
3 (30.0, 50.0]
4 (30.0, 50.0]
5 (50.0, 70.0]
6 (50.0, 70.0]
7 (70.0, 90.0]
8 (70.0, 90.0]
dtype: category
Categories (4, interval[float64, right]): [(9.92, 30.0] < (30.0, 50.0] < (50.0, 70.0]
以上介绍了 cut 常用的特性,如果我们想通过样本分位数进行分箱,这时我们就需要使用 qcut 方法,通过定义每个分箱数量的占比,自动进行分箱边界值的划分,如下所示
import pandas as pd
series_data = pd.Series([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
pd.qcut(series_data, [0, 0.2, 0.5, 0.9, 1])
# ----输出----
0 (9.999, 28.0]
1 (9.999, 28.0]
2 (28.0, 55.0]
3 (28.0, 55.0]
4 (28.0, 55.0]
5 (55.0, 91.0]
6 (55.0, 91.0]
7 (55.0, 91.0]
8 (55.0, 91.0]
9 (91.0, 100.0]
dtype: category
Categories (4, interval[float64, right]): [(9.999, 28.0] < (28.0, 55.0] < (55.0, 91.0] < (91.0, 100.0]]
4、数据标准化
在数据预处理中,标准化是常用的技术,标准化是将数据转换为均值为0、标准差为1的分布,常用于需要满足正态分布假设的算法中。
import pandas as pd
data = {'value': [10, 20, 30, 40, 50]}
df = pd.DataFrame(data)
# 计算均值和标准差
mean = df['value'].mean()
std = df['value'].std()
# 计算标准化,应用公式 ((x - mean) / std)
df['standardized'] = (df['value'] - mean) / std
df
# ----输出----
value standardized
0 10 -1.264911
1 20 -0.632456
2 30 0.000000
3 40 0.632456
4 50 1.264911
5、数据归一化
在数据预处理中,归一化是常用的技术,归一化是将数据缩放到一个特定的范围(通常是0到1)。适用于特征值范围差异较大的情况
import pandas as pd
data = {'value': [10, 20, 30, 40, 50]}
df = pd.DataFrame(data)
# 计算最小值和最大值
min_value = df['value'].min()
max_value = df['value'].max()
# 归一化计算,应用公式 ((x - min) / (max - min))
df['normalized'] = (df['value'] - min_value) / (max_value - min_value)
df
# ----输出----
value normalized
0 10 0.00
1 20 0.25
2 30 0.50
3 40 0.75
4 50 1.00
本文,我们详细介绍了在数据清洗中我们可以如何进行数据转换,包括数据映射、数据替换、数据离散化、数据标准化、数据归一化,希望对阅读本文的读者有一定的学习提升和借鉴启发,不足之处也欢迎留言指出。
如果你喜欢本文,欢迎点赞,并且关注我们的微信公众号:Python数据挖掘分析,我们会持续更新数据挖掘分析领域的好文章,让大家在数据挖掘分析领域持续精进提升,成为更好的自己!
同时可以扫描以下二维码,加入 Python数据挖掘分析 群,在群内与众多业界大牛互动,了解行业发展前沿~