
第六章 数据清洗
数据清洗(Data Cleaning) 是数据分析中最关键、最耗时的环节,通常占整个分析流程的 60%~80% 时间。Pandas 提供了强大而灵活的工具来高效完成这项任务。
本文将从 缺失值、重复值、异常值、数据类型、格式标准化、文本清洗、列/行操作 等维度,系统、详细、实战化地介绍 Pandas 数据清洗的核心方法与技巧。
真实世界的数据往往存在以下问题:
NaN, NULL, 空字符串)✅ 目标:将“脏数据”转化为结构清晰、类型正确、逻辑合理、可直接用于分析的干净数据。
在动手清洗前,先全面了解数据:
import pandas as pd
import numpy as np
df = pd.read_csv('dirty_data.csv')
# 基本信息
print(df.shape) # (行数, 列数)
print(df.dtypes) # 每列类型
print(df.info()) # 非空计数 + 内存
print(df.describe()) # 数值列统计
print(df.head()) # 查看前几行
# 缺失值分布
print(df.isnull().sum()) # 每列缺失数量
print((df.isnull().sum() / len(df)) * 100) # 缺失百分比
# 唯一值 & 频次(用于发现异常类别)
for col in df.select_dtypes(include='object').columns:
print(f"{col}:\n{df[col].value_counts(dropna=False)}\n")Pandas 将以下视为缺失:
np.nanNonepd.NaT(时间缺失)na_values 指定的值(如 'N/A', '-')df.isnull() # 返回布尔 DataFrame
df.isna() # 同 isnull()策略 | 方法 | 适用场景 |
|---|---|---|
删除 |
| 缺失比例高、无法填补 |
填充 |
| 有合理填充值 |
插值 |
| 时间序列或有序数据 |
标记 | 新增列标识是否缺失 | 保留缺失信息用于建模 |
# 删除含任意缺失的行(默认)
df_clean = df.dropna()
# 删除所有值都缺失的行
df.dropna(how='all')
# 删除某列缺失的行
df.dropna(subset=['age'])
# 删除缺失超过阈值的列
df.dropna(axis=1, thresh=len(df)*0.8) # 保留至少 80% 非空的列# 填充固定值
df.fillna(0) # 数值填 0
df.fillna('Unknown') # 分类填 'Unknown'
# 填充统计量
df['salary'].fillna(df['salary'].mean(), inplace=True)
df['city'].fillna(df['city'].mode()[0], inplace=True) # 众数
# 前向/后向填充(时间序列常用)
df.fillna(method='ffill') # forward fill
df.fillna(method='bfill') # backward fill
# 按组填充(更合理!)
df['salary'] = df.groupby('department')['salary'].transform(
lambda x: x.fillna(x.mean())
)df['temperature'].interpolate(method='linear') # 线性插值
df['price'].interpolate(method='time') # 按时间索引插值💡 建议:避免简单全局均值填充,优先考虑分组填充或模型预测填充(如 KNNImputer)。
# 全行重复
df.duplicated().sum() # 重复行数
df[df.duplicated()] # 查看重复行(保留首次出现)
# 指定列重复
df.duplicated(subset=['user_id'])# 保留首次出现(默认)
df.drop_duplicates(inplace=True)
# 保留最后一次
df.drop_duplicates(keep='last', inplace=True)
# 基于特定列去重
df.drop_duplicates(subset=['email'], keep='first')⚠️ 注意:去重前确认业务逻辑——有些“重复”可能是合法的(如多次购买记录)。
异常值 ≠ 错误值,需结合业务判断。
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
return (series < lower) | (series > upper)
outliers = detect_outliers_iqr(df['salary'])
print(df[outliers])策略 | 方法 |
|---|---|
删除 |
|
截断(Winsorize) | 用上下限值替换 |
转换 | 对数变换 |
保留 | 若为真实极端值(如富豪收入) |
upper_limit = df['salary'].quantile(0.95)
df['salary'] = np.where(df['salary'] > upper_limit, upper_limit, df['salary'])错误的数据类型会导致计算错误或内存浪费。
object(因含非数字字符)datetimefloat64# 强制转数字,错误变 NaN
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# 指定整数类型(支持缺失)
df['user_id'] = df['user_id'].astype('Int64') # 注意大写 Idf['date'] = pd.to_datetime(df['date'], errors='coerce', format='%Y-%m-%d')
# 自动推断格式(慢但灵活)
df['date'] = pd.to_datetime(df['date'], infer_datetime_format=True)# 将低基数字符串转为 category
df['gender'] = df['gender'].astype('category')
df['city'] = df['city'].astype('category')
# 查看内存节省
df.info(memory_usage='deep').str 访问器)# 去除空格
df['name'] = df['name'].str.strip()
# 大小写统一
df['email'] = df['email'].str.lower()
# 替换非法字符
df['phone'] = df['phone'].str.replace(r'\D', '', regex=True) # 只留数字
# 提取模式
df['area_code'] = df['phone'].str[:3]
# 拆分列
df[['first', 'last']] = df['full_name'].str.split(' ', expand=True)# 工资单位统一为“元”
df['salary'] = np.where(
df['salary_unit'] == '千元',
df['salary'] * 1000,
df['salary']
)df.rename(columns={'old_name': 'new_name'}, inplace=True)
# 批量标准化列名
df.columns = df.columns.str.lower().str.replace(' ', '_')df.drop(['temp_col', 'id_backup'], axis=1, inplace=True)df = df[['id', 'name', 'age', 'salary']] # 指定顺序# 年龄合理范围
df = df[(df['age'] >= 0) & (df['age'] <= 120)]
# 邮箱格式校验
df = df[df['email'].str.contains('@', na=False)]清洗不是一次性任务,需验证结果:
# 再次检查缺失
assert df.isnull().sum().sum() == 0, "仍有缺失值!"
# 检查类型
assert df['age'].dtype == 'int64'
assert df['date'].dtype == 'datetime64[ns]'
# 检查唯一性
assert df['user_id'].is_unique, "user_id 不唯一!"
# 保存清洗后数据
df.to_csv('clean_data.csv', index=False)场景 | 工具/方法 |
|---|---|
自动检测数据质量问题 |
|
交互式清洗 |
|
复杂文本清洗 | 正则表达式 + |
批量清洗多文件 |
|
可复现清洗流程 | 封装为函数或使用 |
def clean_employee_data(df):
df = df.copy()
df.columns = df.columns.str.lower().str.replace(' ', '_')
df['salary'] = pd.to_numeric(df['salary'], errors='coerce')
df['age'] = pd.to_numeric(df['age'], errors='coerce')
df = df[(df['age'] >= 18) & (df['age'] <= 65)]
df['email'] = df['email'].str.lower().str.strip()
df = df.drop_duplicates(subset=['email'])
return df
clean_df = clean_employee_data(raw_df)步骤 | 操作 |
|---|---|
1️⃣ 概览 |
|
2️⃣ 缺失值 | 删除 / 填充 / 插值 |
3️⃣ 重复值 |
|
4️⃣ 异常值 | 检测 + 截断/删除 |
5️⃣ 类型修正 |
|
6️⃣ 格式标准化 |
|
7️⃣ 结构调整 | 重命名、删列、重排序 |
8️⃣ 验证 | 断言检查 + 保存 |
💡 记住: “垃圾进,垃圾出”(Garbage In, Garbage Out) 再强大的模型也无法弥补脏数据带来的偏差。
python过渡项目部分代码已经上传至gitee,后续会逐步更新。
公众号:咚咚王
《Python编程:从入门到实践》
《利用Python进行数据分析》
《算法导论中文第三版》
《概率论与数理统计(第四版) (盛骤) 》
《程序员的数学》
《线性代数应该这样学第3版》
《微积分和数学分析引论》
《(西瓜书)周志华-机器学习》
《TensorFlow机器学习实战指南》
《Sklearn与TensorFlow机器学习实用指南》
《模式识别(第四版)》
《深度学习 deep learning》伊恩·古德费洛著 花书
《Python深度学习第二版(中文版)【纯文本】 (登封大数据 (Francois Choliet)) (Z-Library)》
《深入浅出神经网络与深度学习+(迈克尔·尼尔森(Michael+Nielsen)》
《自然语言处理综论 第2版》
《Natural-Language-Processing-with-PyTorch》
《计算机视觉-算法与应用(中文版)》
《Learning OpenCV 4》
《AIGC:智能创作时代》杜雨+&+张孜铭
《AIGC原理与实践:零基础学大语言模型、扩散模型和多模态模型》
《从零构建大语言模型(中文版)》
《实战AI大模型》
《AI 3.0》
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。