一、数据清洗:从理论到实践的全面认知
1.1 数据质量问题的影响矩阵
在真实业务场景中,数据质量问题会导致严重后果:
- 缺失值:降低模型训练效果(准确率下降30%-50%)
- 重复数据:导致统计指标虚高(如用户数虚增)
- 异常值:影响均值计算(单个异常值可导致均值偏移20%)
1.2 数据清洗的完整生命周期
二、环境配置与数据探索
2.1 环境初始化
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# 设置全局样式
plt.style.use('ggplot')
pd.set_option('display.float_format', lambda x: '%.2f' % x)
np.random.seed(42)
2.2 数据加载与元分析
# 加载泰坦尼克数据集
url = "https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv"
titanic = pd.read_csv(url)
# 数据指纹分析
print(f"数据集维度: {titanic.shape}")
print("\n字段类型分布:")
print(titanic.dtypes.value_counts())
# 生成数据快照
titanic.sample(5, random_state=42)
2.3 缺失值三维分析
def missing_value_analysis(df):
# 计算缺失统计
missing_stats = pd.DataFrame({
'缺失数量': df.isnull().sum(),
'缺失占比': df.isnull().mean().round(4)*100,
'数据类型': df.dtypes
}).sort_values('缺失占比', ascending=False)
# 可视化展示
plt.figure(figsize=(10,6))
sns.barplot(x=missing_stats['缺失占比'], y=missing_stats.index, palette='Reds_r')
plt.title('字段缺失比例分布')
plt.xlabel('缺失比例 (%)')
plt.show()
return missing_stats
missing_report = missing_value_analysis(titanic)
三、缺失值处理:策略与实战
3.1 缺失检测:isnull()的底层机制
Pandas使用NaN
(Not a Number)表示缺失值,其特性包括:
np.nan == np.nan
返回Falsenp.isnan()
是唯一检测方法- 在计算中具有传染性(任何涉及NaN的操作结果都是NaN)
isnull()源码解析:
def isnull(obj):
if isinstance(obj, pd.Series):
return obj.isna()
return _isnan(obj) # 调用NumPy的isnan方法
3.2 数据删除:dropna()的进阶用法
# 案例1:动态阈值删除
threshold = int(0.8 * len(titanic)) # 保留至少80%完整的行
titanic_thresh = titanic.dropna(thresh=threshold)
# 案例2:分组删除(按舱位分组处理)
titanic_grouped = titanic.groupby('Pclass').apply(
lambda x: x.dropna(subset=['Age', 'Fare']))
# 案例3:时间序列前后填充
titanic.sort_values('Ticket', inplace=True)
titanic_ffill = titanic.dropna(subset=['Cabin']).ffill()
3.3 数据填充:fillna()的二十种策略
基础填充法
# 均值填充(适合正态分布)
age_mean = titanic['Age'].mean()
titanic['Age'].fillna(age_mean, inplace=True)
# 众数填充(分类变量)
embarked_mode = titanic['Embarked'].mode()[0]
titanic['Embarked'].fillna(embarked_mode, inplace=True)
高级填充法
# 随机森林填充(机器学习方法)
from sklearn.ensemble import RandomForestRegressor
# 拆分有/无缺失的数据
age_data = titanic[['Age', 'Pclass', 'Fare', 'SibSp']]
known_age = age_data[age_data['Age'].notnull()]
unknown_age = age_data[age_data['Age'].isnull()]
# 训练预测模型
X = known_age.drop('Age', axis=1)
y = known_age['Age']
model = RandomForestRegressor(n_estimators=100)
model.fit(X, y)
# 预测并填充
predicted_age = model.predict(unknown_age.drop('Age', axis=1))
titanic.loc[titanic['Age'].isnull(), 'Age'] = predicted_age
填充策略对照表
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
前向填充(ffill) | 时间序列数据 | 保留趋势 | 可能传播错误 |
多重插补(MICE) | 高维数据 | 保持变量关系 | 计算成本高 |
KNN填充 | 局部相关数据 | 利用相似样本 | 需要特征缩放 |
深度学习填充 | 复杂模式数据 | 捕捉非线性关系 | 需要大量数据 |
四、重复数据处理:从检测到根治
4.1 重复检测:duplicated()的运作原理
def duplicated(df, subset=None, keep='first'):
# 生成哈希指纹
if subset is not None:
df = df[subset]
hashed = pd.util.hash_pandas_object(df)
# 标记重复项
duplicated_mask = hashed.duplicated(keep=keep)
return duplicated_mask
4.2 智能去重:drop_duplicates()的工业级应用
# 案例1:保留最新记录
titanic_sorted = titanic.sort_values('Ticket', ascending=False)
titanic_dedup = titanic_sorted.drop_duplicates(subset=['PassengerId'], keep='first')
# 案例2:多条件复合主键
complex_keys = ['Name', 'Age', 'Ticket']
titanic_complex = titanic.drop_duplicates(subset=complex_keys)
# 案例3:基于时间窗口去重
titanic['BookingDate'] = pd.to_datetime(titanic['Ticket'].str[:4], errors='coerce')
window = pd.DateOffset(days=7)
titanic_rolling = titanic.set_index('BookingDate').sort_index()
titanic_clean = titanic_rolling.groupby('PassengerId').apply(
lambda x: x.drop_duplicates(subset=['Cabin'], keep='last'))
五、泰坦尼克数据集全流程清洗实战
5.1 清洗路线图
5.2 分步实施代码
# 步骤1:删除低价值列
cols_to_drop = ['Name', 'Ticket', 'Cabin']
titanic_clean = titanic.drop(cols_to_drop, axis=1)
# 步骤2:分舱位填充年龄
class_age = titanic_clean.groupby('Pclass')['Age'].transform(
lambda x: x.fillna(x.median()))
titanic_clean['Age'] = class_age
# 步骤3:处理票价异常值
Q1 = titanic_clean['Fare'].quantile(0.25)
Q3 = titanic_clean['Fare'].quantile(0.75)
IQR = Q3 - Q1
fare_cap = Q3 + 1.5 * IQR
titanic_clean['Fare'] = titanic_clean['Fare'].clip(upper=fare_cap)
# 步骤4:高级去重
titanic_clean = titanic_clean.sort_values('PassengerId', ascending=False)
titanic_clean = titanic_clean.drop_duplicates(
subset=['PassengerId', 'Embarked'],
keep='first')
# 步骤5:最终验证
assert titanic_clean.duplicated().sum() == 0, "存在重复记录!"
assert titanic_clean.isnull().sum().sum() == 0, "存在缺失值!"
print("数据清洗完成,最终维度:", titanic_clean.shape)
六、工业级数据清洗最佳实践
6.1 内存优化技巧
def optimize_memory(df):
# 类型转换字典
type_map = {
'int64': 'int32',
'float64': 'float32',
'object': 'category'
}
# 遍历转换
for col in df.columns:
col_type = str(df[col].dtype)
if col_type in type_map:
df[col] = df[col].astype(type_map[col_type])
# 时间类型转换
datetime_cols = df.select_dtypes(include=['datetime']).columns
for col in datetime_cols:
df[col] = pd.to_datetime(df[col], format='%Y%m%d')
return df
titanic_opt = optimize_memory(titanic_clean)
print("内存节省比例:",
(1 - titanic_opt.memory_usage().sum() / titanic.memory_usage().sum())*100)
6.2 自动化质量监控
class DataQualityMonitor:
def __init__(self, df):
self.df = df
def check_missing(self, threshold=0.05):
missing_rate = self.df.isnull().mean()
violations = missing_rate[missing_rate > threshold]
return violations.empty
def check_duplicates(self, keys=None):
if keys is None:
return self.df.duplicated().sum() == 0
else:
return self.df.duplicated(subset=keys).sum() == 0
def generate_report(self):
report = {
'missing_pass': self.check_missing(),
'duplicates_pass': self.check_duplicates(['PassengerId']),
'shape': self.df.shape
}
return report
dqm = DataQualityMonitor(titanic_clean)
print("数据质量报告:", dqm.generate_report())
七、扩展:分布式数据清洗实战
7.1 Dask并行处理
import dask.dataframe as dd
# 转换Pandas DataFrame为Dask DataFrame
ddf = dd.from_pandas(titanic, npartitions=4)
# 并行处理函数
def clean_chunk(df):
df = df.dropna(subset=['Age'])
df = df.drop_duplicates()
return df
# 应用并执行
ddf_clean = ddf.map_partitions(clean_chunk)
result = ddf_clean.compute()
7.2 基于Spark的大规模清洗
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("TitanicCleaning").getOrCreate()
df_spark = spark.read.csv("titanic.csv", header=True, inferSchema=True)
# Spark清洗操作
df_clean = df_spark.dropna(how='any', subset=['Age']) \
.dropDuplicates(['PassengerId']) \
.filter("Fare < 500")
df_clean.show(5)
八、总结
8.1 关键知识点回顾
- 缺失值处理三原则:删除高缺失率字段、合理填充关键字段、保留缺失标记
- 重复数据四维度检测:完全重复、业务主键重复、时间窗口重复、相似度重复
- 工业级清洗四要素:可追溯性、可扩展性、自动化验证、文档完整性
8.2 常见问题解答
Q:如何处理非结构化数据中的缺失值?
A:对于文本数据可以使用特殊标记(如[UNK]),图像数据可以使用插值修复
Q:大数据场景下如何加速数据清洗?
A:采用分块处理、Dask/Spark并行计算、GPU加速等技术
Q:数据清洗后如何保持可追溯性?
A:使用数据版本控制工具(如DVC)、记录清洗日志、保存中间结果