在数据分析领域,"三分分析,七分清洗"是公认的准则。原始数据往往夹杂缺失值、重复值、异常值等问题,直接使用会导致分析结果失真。而Python的pandas库凭借强大的数据处理能力,成为数据清洗的首选工具。本文结合实际工作场景,总结一套基于pandas的数据清洗标准流程,每个环节都附带可直接复用的简洁代码。
一、核心流程概览
基于pandas的数据清洗遵循"先探索诊断,后针对性处理"的逻辑,核心流程可概括为:数据加载与初步探索 → 数据质量问题诊断 → 针对性清洗处理 → 清洗后验证 → 数据保存。每个环节层层递进,确保清洗工作有序、高效且不遗漏关键问题。
前置准备:首先导入必要库(pandas为核心,matplotlib/seaborn用于可视化诊断),代码如下:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文字体,避免可视化中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
二、各环节详细操作(附Pandas代码)
环节1:数据加载与初步探索
核心目标:将原始数据导入pandasDataFrame,快速了解数据基本信息(维度、数据类型、数值分布等),为后续诊断奠定基础。
1.1 数据加载
根据原始数据格式(CSV、Excel、JSON等)选择对应加载函数,常见场景代码如下:
# 加载CSV文件(最常用)
df = pd.read_csv("原始数据.csv", encoding="utf-8") # 若遇编码错误,尝试encoding="gbk"
# 加载Excel文件
df = pd.read_excel("原始数据.xlsx", sheet_name="Sheet1")
# 加载JSON文件
df = pd.read_json("原始数据.json")
小贴士:若数据量较大,可通过nrows=1000参数先加载1000行数据进行初步探索,提升效率:df = pd.read_csv("原始数据.csv", nrows=1000)。
1.2 初步探索核心操作
通过以下代码快速掌握数据概况:
# 1. 查看数据维度(行数、列数)
print("数据维度:", df.shape) # 输出格式:(行数, 列数)
# 2. 查看数据类型(重点关注:日期字段是否为object类型、数值字段是否异常)
print("\n数据类型:")
print(df.dtypes)
# 3. 查看前5行和后5行数据,直观了解数据结构
print("\n前5行数据:")
print(df.head())
print("\n后5行数据:")
print(df.tail())
# 4. 查看数值型字段的统计摘要(均值、中位数、最值、标准差等)
print("\n数值型字段统计摘要:")
print(df.describe())
# 5. 查看字段名称(避免后续操作字段名写错)
print("\n字段名称:")
print(df.columns.tolist())
# 6. 查看数据表的基本数据格式
df.info()
环节2:数据质量问题诊断
核心目标:用pandas工具系统性排查数据中的核心问题------缺失值、重复值、异常值、数据不一致,为后续清洗提供明确方向。
2.1 缺失值诊断
统计各字段缺失值数量和缺失率,判断缺失程度:
# 统计各字段缺失值数量
missing_count = df.isnull().sum()
print("各字段缺失值数量:")
print(missing_count)
# 计算各字段缺失率(更直观,缺失率=缺失值数量/总行数)
missing_rate = (df.isnull().sum() / len(df)) * 100
print("\n各字段缺失率(%):")
print(missing_rate.round(2)) # 保留2位小数
#可通过df.info()查看确实率
2.2 重复值诊断
分为"完全重复行"和"指定字段重复"(如用户ID+日期),两种场景均需诊断:
# 1. 诊断完全重复行(所有字段值都相同)
duplicate_total = df.duplicated().sum()
print("完全重复行数量:", duplicate_total)
# 2. 诊断指定字段重复(如:用户ID+交易日期,判断同一用户同日是否多笔重复交易)
duplicate_specify = df.duplicated(subset=["用户ID", "交易日期"]).sum()
print("用户ID+交易日期重复数量:", duplicate_specify)
2.3 异常值诊断
异常值指偏离正常范围的值(如年龄200、收入负数),常用"统计方法"和"可视化方法"结合诊断:
# 方法1:统计方法(基于四分位法IQR)
def detect_outliers_iqr(df, column):
q1 = df[column].quantile(0.25) # 25分位数
q3 = df[column].quantile(0.75) # 75分位数
iqr = q3 - q1
# 异常值范围:小于Q1-1.5*IQR 或 大于Q3+1.5*IQR
outliers = df[(df[column] < q1 - 1.5*iqr) | (df[column] > q3 + 1.5*iqr)]
return outliers
# 示例:诊断"年龄"字段的异常值
age_outliers = detect_outliers_iqr(df, "年龄")
print("年龄字段异常值数量:", len(age_outliers))
print("年龄异常值详情:")
print(age_outliers[["用户ID", "年龄"]])
# 方法2:可视化方法(箱线图,直观展示异常值)
plt.figure(figsize=(8, 4))
sns.boxplot(x=df["收入"])
plt.title("收入字段箱线图(圆点为异常值)")
plt.show()
小贴士:业务逻辑诊断不可少!比如用df[df["订单金额"] < 0]可快速筛选出"订单金额为负数"的异常记录。
2.4 数据不一 致诊断
指格式/命名不统一(如日期格式混乱、性别字段有"男""男性""M"),代码诊断如下:
# 1. 诊断分类字段命名不一致(如"性别")
print("性别字段唯一值:")
print(df["性别"].value_counts()) # 若输出包含"男""男性""M",则存在不一致
# 2. 诊断日期格式不一致
# 尝试将日期字段转换为datetime类型,无法转换的会变为NaT(缺失值)
df["交易日期_转换"] = pd.to_datetime(df["交易日期"], errors="coerce")
# 统计转换失败的数量(即格式异常的日期数量)
date_error = df["交易日期_转换"].isnull().sum()
print(f"\n日期格式异常数量:{date_error}")
环节3:针对性清洗处理(核心环节)
针对诊断出的问题,用pandas实施精准清洗,原则是"最小改动、保留真实",避免过度清洗导致数据失真。
3.1 缺失值清洗
根据缺失率和字段重要性选择方案,核心方案及代码如下:
# 方案1:删除法(适用于缺失率>50%的无关字段,或少量缺失记录)
# 删除缺失率高的字段(如"备注"字段缺失率60%)
df = df.drop(columns=["备注"], inplace=False) # inplace=True直接修改原DataFrame
# 删除"年龄"字段缺失的记录(缺失率低且重要)
df = df.dropna(subset=["年龄"], inplace=False)
# 方案2:填充法(适用于缺失率<20%的重要字段)
# 数值型字段:用中位数填充(抗异常值干扰)
df["收入"] = df["收入"].fillna(df["收入"].median())
# 或用均值填充(适用于分布均匀的数据)
df["年龄"] = df["年龄"].fillna(df["年龄"].mean())
# 分类字段:用众数(最频繁值)或"未知"填充
df["性别"] = df["性别"].fillna(df["性别"].mode()[0]) # 众数填充
df["职业"] = df["职业"].fillna("未知") # 固定值填充
# 日期字段:前向填充(用前一条记录的日期填充)
df["交易日期"] = df["交易日期"].fillna(method="ffill")
3.2 重复值清洗
# 1. 删除完全重复行(保留第一条)
df = df.drop_duplicates(inplace=False, keep="first")
# 2. 删除指定字段重复行(如用户ID+交易日期,保留金额最大的有效记录)
# 先按金额降序排序,再删除重复,确保保留金额最大的记录
df = df.sort_values("订单金额", ascending=False)
df = df.drop_duplicates(subset=["用户ID", "交易日期"], inplace=False, keep="first")
3.3 异常值清洗
# 方案1:删除法(适用于明确异常且数量少的情况)
# 删除年龄>120或<0的记录
df = df[(df["年龄"] > 0) & (df["年龄"] < 120)]
# 删除订单金额<0的记录
df = df[df["订单金额"] >= 0]
# 方案2:修正法(适用于录入错误,如将25输为250)
df.loc[df["年龄"] == 250, "年龄"] = 25 # 将年龄250修正为25
# 方案3:替换法(适用于异常值多,无法删除,用边界值替换)
def replace_outliers_iqr(df, column):
q1 = df[column].quantile(0.25)
q3 = df[column].quantile(0.75)
iqr = q3 - q1
# 用Q1-1.5*IQR替换小于该值的异常值,Q3+1.5*IQR替换大于该值的异常值
df.loc[df[column] < q1 - 1.5*iqr, column] = q1 - 1.5*iqr
df.loc[df[column] > q3 + 1.5*iqr, column] = q3 + 1.5*iqr
return df
# 示例:处理"收入"字段异常值
df = replace_outliers_iqr(df, "收入")
3.4 数据不一致清洗
# 1. 分类字段命名统一(如"性别"字段)
df["性别"] = df["性别"].replace({
"男性": "男",
"M": "男",
"女性": "女",
"F": "女"
})
# 2. 日期格式统一(转为YYYY-MM-DD)
# 先删除转换失败的日期记录(或先修正再转换)
df = df.dropna(subset=["交易日期_转换"])
# 统一格式
df["交易日期"] = df["交易日期_转换"].dt.strftime("%Y-%m-%d")
# 删除临时转换字段
df = df.drop(columns=["交易日期_转换"])
# 3. 数值格式统一(如去除金额字段的"¥"和逗号)
df["订单金额"] = df["订单金额"].astype(str).str.replace("¥", "").str.replace(",", "").astype(float)
环节4:清洗后验证
清洗完成后,需再次验证数据质量,确保问题已解决:
# 1. 验证缺失值:确认无遗漏
print("清洗后各字段缺失率:")
print((df.isnull().sum() / len(df) * 100).round(2))
# 2. 验证重复值:确认无重复
print(f"\n清洗后完全重复行数量:{df.duplicated().sum()}")
# 3. 验证异常值:确认无明显异常
print("\n清洗后年龄字段统计:")
print(df["年龄"].describe())
# 4. 验证一致性:确认格式统一
print("\n清洗后性别字段唯一值:")
print(df["性别"].value_counts())
环节5:数据保存
将清洗后的高质量数据保存,便于后续分析使用,同时保留原始数据(避免回溯无门):
# 保存为CSV文件(最常用,体积小、兼容性好)
df.to_csv("清洗后数据.csv", index=False, encoding="utf-8") # index=False不保存行索引
# 保存为Excel文件
df.to_excel("清洗后数据.xlsx", index=False, sheet_name="清洗后数据")
# 若数据量较大,保存为Parquet格式(读取速度快、压缩比高)
df.to_parquet("清洗后数据.parquet", index=False)
三、实战关键技巧
-
避免修改原始数据 :清洗时优先创建副本 (如
df_clean = df.copy()),在副本上操作,原始数据留存备查。 -
封装重复逻辑 :将常用操作封装为函数(如前文的异常值检测/替换函数),后续可直接调用,提升效率。
-
结合业务场景:清洗无标准答案,需贴合业务。例如分析"有效订单"时,可删除"取消状态"的订单;但分析"订单取消率"时,这些记录必须保留。
-
处理大型数据集 :若数据量超过内存,可使用
chunksize分块处理(如for chunk in pd.read_csv("大文件.csv", chunksize=10000):),逐块清洗后合并。
四、总结
基于pandas的数据清洗核心是"流程化+工具化":先通过shape、dtypes、isnull()等函数完成探索诊断,再针对缺失值、重复值等问题用dropna()、drop_duplicates()、replace()等方法精准处理,最后验证保存。本文的代码示例可直接复用于大多数实际场景,你只需根据具体数据字段调整参数即可。
如果遇到特殊场景(如文本数据清洗、时间序列数据缺失值处理),欢迎在评论区留言交流,一起完善数据清洗工具箱~
五、特定类型数据清洗补充(附代码)
实际工作中,除了通用结构化数据,文本数据和时间序列数据的清洗需求也极为常见。以下补充两类数据的专属清洗流程和pandas代码示例,覆盖高频应用场景。
5.1 文本数据清洗(以用户评论数据为例)
文本数据常见问题:含特殊符号、冗余空格、表情/emoji、无关停用词(如"的""了")、大小写不统一等。核心目标是去除无效信息,标准化文本格式,为后续分词、建模做准备。
5.1.1 文本数据常见清洗操作
import re # 用于正则表达式处理特殊字符
# 假设df包含"用户评论"字段,先查看原始文本示例
print("原始评论示例:")
print(df["用户评论"].head(3).tolist())
# 1. 去除特殊符号、表情/emoji(保留中文、字母、数字)
def clean_special_chars(text):
if pd.isna(text): # 处理缺失值
return ""
# 正则表达式:只保留中英文、数字和空格
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', str(text))
return text
df["评论_清洗后"] = df["用户评论"].apply(clean_special_chars)
# 2. 去除冗余空格(多空格转单空格、去除前后空格)
df["评论_清洗后"] = df["评论_清洗后"].str.replace(r'\s+', ' ').str.strip()
# 3. 统一大小写(针对英文文本)
df["评论_清洗后"] = df["评论_清洗后"].str.lower() # 转为小写(大写用str.upper())
# 4. 去除停用词(需提前准备停用词列表)
# 加载停用词(可从网上下载中文停用词表,保存为txt文件)
with open("stopwords.txt", 'r', encoding="utf-8") as f:
stopwords = set(f.read().splitlines())
def remove_stopwords(text):
words = text.split() # 按空格分词
words = [word for word in words if word not in stopwords]
return " ".join(words)
df["评论_无停用词"] = df["评论_清洗后"].apply(remove_stopwords)
# 查看清洗效果
print("\n清洗后评论示例:")
print(df[["用户评论", "评论_无停用词"]].head(3).to_string())
5.1.2 关键说明
- 停用词列表可根据业务场景自定义(如电商评论可添加"商品""购买"等无关词);2. 若需更精细的文本处理(如分词、词形还原),可结合jieba(中文分词)、nltk(英文处理)库;3. 对于极短文本(如长度<2字),可视为无效评论删除:
df = df[df["评论_清洗后"].str.len() >= 2]。
5.2 时间序列数据清洗(以 hourly 气温数据为例)
时间序列数据常见问题:时间格式不统一、缺失值(如某小时数据缺失)、重复时间戳、异常波动值。核心目标是标准化时间索引,补全缺失数据,保证时序连续性。
5.2.1 时间序列数据常见清洗操作
# 假设df包含"时间戳"和"气温"字段,先查看原始数据
print("原始时序数据示例:")
print(df[["时间戳", "气温"]].head())
# 1. 标准化时间格式,转换为datetime类型并设为索引
df["时间戳"] = pd.to_datetime(df["时间戳"], errors="coerce") # 无法转换的设为NaT
df = df.dropna(subset=["时间戳"]) # 删除时间格式错误的记录
df = df.set_index("时间戳") # 设时间为索引,便于时序操作
# 2. 处理重复时间戳(保留第一个值)
df = df[~df.index.duplicated(keep="first")]
# 3. 补全缺失时间(按hour频率补全,缺失值用前后值插值)
# 生成完整的时间序列(从数据起始到结束,每小时一个时间点)
full_time_index = pd.date_range(start=df.index.min(), end=df.index.max(), freq="H")
# 重新索引,缺失时间对应的气温设为NaN
df = df.reindex(full_time_index)
# 4. 填充缺失的气温值(时序数据常用插值法)
df["气温"] = df["气温"].interpolate(method="linear") # 线性插值(适用于平稳时序)
# 备选方案:前向填充(用前一小时数据)
# df["气温"] = df["气温"].fillna(method="ffill")
# 5. 处理异常波动值(用3σ原则)
def remove_outliers_3sigma(series):
mean = series.mean()
std = series.std()
# 异常值范围:小于mean-3*std 或 大于mean+3*std
outliers = (series < mean - 3*std) | (series > mean + 3*std)
# 异常值用均值填充
series[outliers] = mean
return series
df["气温"] = remove_outliers_3sigma(df["气温"])
# 查看清洗效果(时序连续性)
print("\n清洗后时序数据信息:")
print(f"时间范围:{df.index.min()} 至 {df.index.max()}")
print(f"缺失值数量:{df["气温"].isnull().sum()}")
print(f"重复时间戳数量:{df.index.duplicated().sum()}")
5.2.2 关键说明
- 时间频率(freq参数)需根据数据类型调整(如日频用"D"、周频用"W");2. 插值方法选择:平稳时序用线性插值,趋势明显的时序用多项式插值(method="polynomial", order=2);3. 异常值处理需结合业务逻辑(如气温不可能低于-50℃或高于60℃,可先通过业务规则过滤)。