Day 3: 数据清洗与预处理
学习目标
完成今天的学习后,你将能够:
- 识别和处理缺失值
- 检测和处理异常值
- 进行数据类型转换
- 掌握数据标准化和归一化
技术原理
缺失值处理
缺失值类型
- 完全随机缺失(MCAR):缺失与任何变量无关
- 随机缺失(MAR):缺失与其他变量相关
- 非随机缺失(MNAR):缺失与缺失值本身相关
处理方法
- 删除:删除包含缺失值的行或列
- 填充:使用均值、中位数、众数等填充
- 插值:使用插值方法估计缺失值
- 模型预测:使用机器学习模型预测缺失值
异常值处理
异常值检测方法
- 3σ原则:超过3个标准差的值
- IQR方法:基于四分位距
- Z-score:标准化后的值
- 可视化:箱线图、散点图
处理方法
- 删除:删除异常值
- 替换:用边界值替换
- 转换:对数转换、平方根转换
- 分箱:将连续变量分箱
数据类型转换
- 数值类型:int、float
- 字符串类型:str
- 日期类型:datetime
- 类别类型:category
- 布尔类型:bool
数据标准化
- Min-Max标准化:缩放到[0,1]区间
- Z-score标准化:均值为0,标准差为1
- 小数定标标准化:缩放到[-1,1]区间
- 对数转换:处理偏态分布
案例:电商用户数据清洗
假设我们有一个电商用户数据集,包含用户ID、年龄、收入、购买金额等信息。数据存在以下问题:
- 年龄列有缺失值
- 收入列有异常值(负值或极大值)
- 购买金额数据类型不一致
- 需要标准化处理
应用场景
- 数据预处理:为机器学习准备干净的数据
- 数据质量提升:提高数据准确性和一致性
- 异常检测:识别欺诈、故障等异常情况
- 数据集成:合并来自不同来源的数据
代码案例
缺失值处理
python
# 缺失值处理示例
import pandas as pd
import numpy as np
# 创建包含缺失值的示例数据
data = {
'姓名': ['张三', '李四', '王五', '赵六', '钱七'],
'年龄': [25, 30, np.nan, 35, 28],
'收入': [5000, 8000, 6000, np.nan, 7000],
'城市': ['北京', '上海', '广州', np.nan, '深圳']
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 1. 检测缺失值
print("\n缺失值检测:")
print(df.isna()) # 显示缺失值布尔矩阵
print("\n每列缺失值数量:")
print(df.isna().sum()) # 统计每列缺失值数量
print("\n缺失值比例:")
print(df.isna().mean() * 100) # 计算缺失值比例
# 2. 删除缺失值
# 删除包含缺失值的行
df_dropped = df.dropna()
print("\n删除缺失值后的数据:")
print(df_dropped)
# 3. 填充缺失值
# 使用均值填充数值列
df_filled = df.copy()
df_filled['年龄'].fillna(df['年龄'].mean(), inplace=True)
df_filled['收入'].fillna(df['收入'].median(), inplace=True)
df_filled['城市'].fillna('未知', inplace=True)
print("\n填充缺失值后的数据:")
print(df_filled)
# 4. 使用前向填充
df_ffill = df.fillna(method='ffill')
print("\n前向填充后的数据:")
print(df_ffill)
# 5. 使用后向填充
df_bfill = df.fillna(method='bfill')
print("\n后向填充后的数据:")
print(df_bfill)异常值处理
python
# 异常值处理示例
import pandas as pd
import numpy as np
# 创建包含异常值的示例数据
np.random.seed(42) # 设置随机种子,确保结果可重现
normal_data = np.random.normal(100, 10, 100) # 生成正态分布数据
outliers = [200, 250, 300, -50, -100] # 添加异常值
data = np.concatenate([normal_data, outliers])
df = pd.DataFrame({'数值': data})
print("原始数据统计:")
print(df.describe())
# 1. 使用3σ原则检测异常值
mean = df['数值'].mean()
std = df['数值'].std()
lower_bound = mean - 3 * std
upper_bound = mean + 3 * std
outliers_3sigma = df[(df['数值'] < lower_bound) | (df['数值'] > upper_bound)]
print(f"\n3σ原则检测到的异常值:")
print(outliers_3sigma)
# 2. 使用IQR方法检测异常值
Q1 = df['数值'].quantile(0.25)
Q3 = df['数值'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_iqr = df[(df['数值'] < lower_bound) | (df['数值'] > upper_bound)]
print(f"\nIQR方法检测到的异常值:")
print(outliers_iqr)
# 3. 处理异常值
# 删除异常值
df_cleaned = df[(df['数值'] >= lower_bound) & (df['数值'] <= upper_bound)]
print(f"\n删除异常值后的数据形状: {df_cleaned.shape}")
# 替换异常值为边界值
df_replaced = df.copy()
df_replaced.loc[df_replaced['数值'] < lower_bound, '数值'] = lower_bound
df_replaced.loc[df_replaced['数值'] > upper_bound, '数值'] = upper_bound
print(f"\n替换异常值后的数据统计:")
print(df_replaced.describe())
# 4. 可视化异常值
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 箱线图
axes[0].boxplot(df['数值'])
axes[0].set_title('原始数据箱线图')
axes[0].set_ylabel('数值')
# 直方图
axes[1].hist(df['数值'], bins=20, edgecolor='black')
axes[1].set_title('原始数据直方图')
axes[1].set_xlabel('数值')
axes[1].set_ylabel('频数')
# 清洗后数据的箱线图
axes[2].boxplot(df_cleaned['数值'])
axes[2].set_title('清洗后数据箱线图')
axes[2].set_ylabel('数值')
plt.tight_layout()
plt.show()数据类型转换
python
# 数据类型转换示例
import pandas as pd
import numpy as np
# 创建示例数据
data = {
'ID': ['001', '002', '003', '004', '005'],
'年龄': ['25', '30', '35', '28', '32'],
'入职日期': ['2020-01-15', '2019-06-20', '2021-03-10', '2020-11-05', '2018-08-25'],
'薪资': ['5000.50', '8000.75', '6000.25', '7000.00', '9000.50'],
'是否在职': ['是', '是', '否', '是', '是']
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
print("\n原始数据类型:")
print(df.dtypes)
# 1. 转换为数值类型
df['年龄'] = pd.to_numeric(df['年龄'], errors='coerce')
df['薪资'] = pd.to_numeric(df['薪资'], errors='coerce')
print("\n转换数值类型后:")
print(df.dtypes)
# 2. 转换为日期类型
df['入职日期'] = pd.to_datetime(df['入职日期'])
print("\n转换日期类型后:")
print(df.dtypes)
print("\n日期列信息:")
print(df['入职日期'])
# 3. 转换为类别类型
df['是否在职'] = df['是否在职'].astype('category')
print("\n转换类别类型后:")
print(df.dtypes)
print("\n类别信息:")
print(df['是否在职'].cat.categories)
# 4. 转换为字符串类型
df['ID'] = df['ID'].astype('str')
print("\n转换字符串类型后:")
print(df.dtypes)
# 5. 数据类型转换的实际应用
# 从日期中提取年份、月份、星期
df['入职年份'] = df['入职日期'].dt.year
df['入职月份'] = df['入职日期'].dt.month
df['入职星期'] = df['入职日期'].dt.day_name()
print("\n提取日期信息后:")
print(df[['入职日期', '入职年份', '入职月份', '入职星期']])数据标准化
python
# 数据标准化示例
import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# 创建示例数据
data = {
'姓名': ['张三', '李四', '王五', '赵六', '钱七'],
'年龄': [25, 30, 35, 28, 32],
'收入': [5000, 8000, 6000, 7000, 9000],
'消费': [2000, 3500, 2500, 3000, 4000]
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 1. Min-Max标准化
scaler_minmax = MinMaxScaler()
df_minmax = df.copy()
df_minmax[['年龄', '收入', '消费']] = scaler_minmax.fit_transform(df[['年龄', '收入', '消费']])
print("\nMin-Max标准化后的数据:")
print(df_minmax)
# 2. Z-score标准化
scaler_standard = StandardScaler()
df_standard = df.copy()
df_standard[['年龄', '收入', '消费']] = scaler_standard.fit_transform(df[['年龄', '收入', '消费']])
print("\nZ-score标准化后的数据:")
print(df_standard)
# 3. 手动Min-Max标准化
def manual_minmax(series):
return (series - series.min()) / (series.max() - series.min())
df_manual = df.copy()
df_manual['年龄_normalized'] = manual_minmax(df['年龄'])
df_manual['收入_normalized'] = manual_minmax(df['收入'])
print("\n手动Min-Max标准化:")
print(df_manual)
# 4. 对数转换
df_log = df.copy()
df_log['收入_log'] = np.log1p(df['收入']) # log1p = log(1 + x)
print("\n对数转换后的数据:")
print(df_log)
# 5. 可视化标准化效果
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 原始数据
axes[0, 0].bar(df['姓名'], df['收入'])
axes[0, 0].set_title('原始收入数据')
axes[0, 0].set_ylabel('收入')
# Min-Max标准化
axes[0, 1].bar(df['姓名'], df_minmax['收入'])
axes[0, 1].set_title('Min-Max标准化后的收入')
axes[0, 1].set_ylabel('标准化值')
# Z-score标准化
axes[1, 0].bar(df['姓名'], df_standard['收入'])
axes[1, 0].set_title('Z-score标准化后的收入')
axes[1, 0].set_ylabel('标准化值')
# 对数转换
axes[1, 1].bar(df['姓名'], df_log['收入_log'])
axes[1, 1].set_title('对数转换后的收入')
axes[1, 1].set_ylabel('对数值')
plt.tight_layout()
plt.show()课后练习
练习1:缺失值处理
- 创建一个包含缺失值的数据集
- 检测缺失值的位置和数量
- 使用不同的方法填充缺失值
- 比较不同填充方法的效果
练习2:异常值处理
- 生成一个包含异常值的数据集
- 使用3σ原则和IQR方法检测异常值
- 删除异常值并比较处理前后的统计量
- 绘制箱线图可视化异常值
练习3:数据类型转换
- 创建一个混合数据类型的数据集
- 将字符串转换为数值类型
- 将字符串转换为日期类型
- 提取日期中的年、月、日信息
练习4:数据标准化
- 创建一个数值范围差异较大的数据集
- 使用Min-Max标准化
- 使用Z-score标准化
- 比较两种方法的适用场景
常见问题
Q1: 应该删除还是填充缺失值?
A: 取决于缺失值的比例和数据特点:
- 缺失比例小(<5%):可以删除
- 缺失比例大:考虑填充
- 数据重要:尽量填充而非删除
Q2: 如何选择异常值处理方法?
A: 根据业务场景选择:
- 数据错误:删除或修正
- 真实异常:保留或单独分析
- 业务异常:根据业务规则处理
Q3: 什么时候需要数据标准化?
A: 以下情况需要标准化:
- 不同量纲的特征
- 距离计算算法(如KNN、SVM)
- 梯度下降优化
- 正则化处理
Q4: 如何处理类别型数据?
A: 可以使用以下方法:
- 标签编码:有序类别
- 独热编码:无序类别
- 目标编码:高基数类别
Q5: 数据预处理顺序是什么?
A: 推荐顺序:
- 数据类型转换
- 缺失值处理
- 异常值处理
- 数据标准化
- 特征工程
下一步学习
完成今天的学习后,建议你:
- 练习处理真实数据中的缺失值和异常值
- 掌握不同数据类型转换方法
- 理解数据标准化的应用场景
- 准备进入Day 4的学习:数据可视化基础
明天我们将学习数据可视化,这是数据分析中展示结果的重要技能。