Python数据清洗之缺失值处理,重复项去重与异常值检测详解

 更新时间:2026年05月25日 08:36:23   作者:小庄-Python办公  
本文主要为大家详细介绍了Python数据清洗中的核心技能,包括缺失值处理、重复数据去重和异常值检测,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

本节学习目标

  • 理解缺失值的类型与成因
  • 掌握缺失值的检测、填充与删除方法
  • 学会识别和删除重复数据
  • 掌握 IQR(四分位距)法检测异常值
  • 建立数据清洗的标准化流程思维

为什么学这个?

在真实的数据分析工作中,有一句著名的格言:“Garbage in, garbage out”(垃圾进,垃圾出)

无论你的模型多高级、分析多精巧,如果输入的数据质量有问题,得出的结论就一定是错的。

举个真实的例子:某电商公司的数据分析师在分析年度销售额时,忘记处理缺失值,结果系统把缺失值(NaN)当成了 0 参与计算,最终报告显示年度销售额比实际少了 30%。这个错误直接导致公司在年末促销预算上做出了错误的决策。

根据行业统计,数据科学家 80% 的时间都花在数据清洗上。掌握数据清洗技能,是每一个数据分析师的"基本功"。

数据清洗主要解决三大问题:

  1. 缺失值 —— 数据"缺了一块"
  2. 重复值 —— 数据"多了一份"
  3. 异常值 —— 数据"出了圈"

核心知识点讲解

缺失值(Missing Values)

1. 缺失值是什么?

在 Pandas 中,缺失值用 NaN(Not a Number)表示,它是 NumPy 定义的一个特殊浮点值。

常见的缺失值来源

  • 用户填写表单时漏填了某些字段
  • 数据采集设备故障导致数据丢失
  • 数据库迁移过程中数据不完整
  • 某些测量根本无法获取(如"离职员工的离职后绩效")
import pandas as pd
import numpy as np

# 创建包含缺失值的数据
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六', '孙七'],
    '年龄': [25, np.nan, 28, 35, np.nan],        # np.nan 表示缺失值
    '工资': [15000.0, 20000.0, np.nan, 25000.0, 12000.0],
    '城市': ['北京', '上海', None, '深圳', '杭州'],  # None 也会被转为 NaN
    '部门': ['技术', '技术', '销售', '管理', '技术']
})

print(df)
# 输出:
#   姓名    年龄    工资   城市  部门
# 0 张三  25.0  15000.0   北京  技术
# 1 李四   NaN  20000.0   上海  技术
# 2 王五  28.0      NaN  NaN  销售
# 3 赵六  35.0  25000.0   深圳  管理
# 4 孙七   NaN  12000.0   杭州  技术

2. 检测缺失值

# ===== isnull() 和 notnull() =====

# 检测每个单元格是否为缺失值(返回布尔 DataFrame)
print(df.isnull())
# 输出(True 表示缺失):
#      姓名   年龄   工资   城市   部门
# 0  False  False  False  False  False
# 1  False   True  False  False  False
# 2  False  False   True   True  False
# 3  False  False  False  False  False
# 4  False   True  False  False  False

# 检测非缺失值
print(df.notnull())  # 与 isnull() 相反

# ===== 统计每列的缺失值数量(最常用的检查命令)=====
print(df.isnull().sum())
# 输出:
# 姓名    0
# 年龄    2
# 工资    1
# 城市    1
# 部门    0
# dtype: int64

# ===== 统计缺失值占比 =====
missing_pct = df.isnull().mean() * 100
print("缺失值占比:")
print(missing_pct)
# 输出:
# 姓名     0.0
# 年龄    40.0
# 工资    20.0
# 城市    20.0
# 部门     0.0
# dtype: float64

# ===== 查看哪些行有缺失值 =====
rows_with_na = df[df.isnull().any(axis=1)]
print("有缺失值的行:")
print(rows_with_na)

# ===== 查看完整数据(无任何缺失的行)=====
complete_rows = df.dropna()
print("完整数据行:")
print(complete_rows)

3. 缺失值处理策略

处理缺失值有两大类策略:删除法填充法

策略一:删除法(dropna)

# 创建示例数据
df = pd.DataFrame({
    'A': [1, 2, np.nan, 4, 5],
    'B': [np.nan, 2, 3, np.nan, 5],
    'C': [1, 2, 3, 4, 5]
})

# 默认:删除包含任何缺失值的行
print(df.dropna())
# 只保留第 1 行和第 4 行(索引 1 和 4)

# how='all':只删除全部为缺失值的行
df2 = pd.DataFrame({
    'A': [1, np.nan, np.nan],
    'B': [2, np.nan, 3]
})
print(df2.dropna(how='all'))  # 不会删除任何行

# thresh=N:保留至少有 N 个非缺失值的行
print(df.dropna(thresh=2))  # 保留至少有 2 个非缺失值的行

# subset=:只检查指定列的缺失值
df3 = pd.DataFrame({
    '姓名': ['张三', '李四', '王五'],
    '年龄': [25, np.nan, 28],
    '备注': [np.nan, np.nan, '好学生']
})
print(df3.dropna(subset=['年龄']))  # 只看"年龄"列,删除李四

# axis=1:删除包含缺失值的列(纵向删除)
print(df.dropna(axis=1))  # 删除 B 列(因为它有缺失值)

策略二:填充法(fillna)

df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六', '孙七'],
    '年龄': [25, np.nan, 28, np.nan, 22],
    '工资': [15000.0, 20000.0, np.nan, 25000.0, np.nan]
})

# ===== 用固定值填充 =====
print(df.fillna(0))  # 所有缺失值填 0
print(df.fillna('未知'))  # 所有缺失值填"未知"

# ===== 用统计值填充 =====
# 用均值填充(数值列)
df_filled_mean = df.fillna(df.mean(numeric_only=True))
print("均值填充:")
print(df_filled_mean)

# 用中位数填充(更稳健,不受极端值影响)
df_filled_median = df.fillna(df.median(numeric_only=True))
print("\n中位数填充:")
print(df_filled_median)

# 用众数填充(适合分类数据)
df_filled_mode = df.fillna(df.mode().iloc[0])
print("\n众数填充:")
print(df_filled_mode)

# ===== 前向填充和后向填充 =====
df_time = pd.DataFrame({
    '日期': pd.date_range('2024-01-01', periods=5),
    '温度': [20.0, np.nan, np.nan, 25.0, np.nan]
})

# ffill(forward fill):用前一个值填充
print(df_time['温度'].ffill())
# 输出:[20.0, 20.0, 20.0, 25.0, 25.0]

# bfill(backward fill):用后一个值填充
print(df_time['温度'].bfill())
# 输出:[20.0, 25.0, 25.0, 25.0, NaN]

# ===== 按列用不同方式填充 =====
fill_values = {'年龄': df['年龄'].median(), '工资': df['工资'].mean()}
df_filled = df.fillna(fill_values)
print("按列不同方式填充:")
print(df_filled)

# ===== inplace=True:原地修改 =====
# df.fillna(0, inplace=True)  # 直接修改原 DataFrame

4. 缺失值处理的选择指南

缺失比例建议策略说明
< 5%直接删除影响微乎其微
5% ~ 30%填充(均值/中位数/众数/插值)保留样本量
> 30%删除该列或深入研究数据可能不可靠

重复值处理

1. 检测重复值

# 创建包含重复值的数据
df = pd.DataFrame({
    '订单号': ['ORD001', 'ORD002', 'ORD003', 'ORD002', 'ORD004'],
    '客户': ['张三', '李四', '王五', '李四', '赵六'],
    '金额': [500, 300, 800, 300, 200]
})

print("原始数据:")
print(df)
# 输出:
#     订单号  客户  金额
# 0  ORD001  张三   500
# 1  ORD002  李四   300
# 2  ORD003  王五   800
# 3  ORD002  李四   300   ← 与第1行完全相同
# 4  ORD004  赵六   200

# ===== duplicated() —— 检测重复行 =====
print(df.duplicated())
# 输出:[False, False, False, True, False]
# 第 4 行(索引3)被标记为 True,因为它是重复的

# 检测哪些列组合是重复的
print(df.duplicated(subset=['订单号']))
# 只看订单号列,ORD002 出现两次,第二次标记为 True

# ===== 统计重复行数量 =====
print(f"重复行数:{df.duplicated().sum()}")  # 1

# ===== 查看所有重复行 =====
print(df[df.duplicated(keep=False)])
# keep=False 会标记所有重复的行(而不仅是后面的)

2. 删除重复值

# ===== drop_duplicates() —— 删除重复行 =====

# 默认:删除完全相同的行,保留第一条
df_clean = df.drop_duplicates()
print("删除完全重复的行:")
print(df_clean)

# keep='last':保留最后一条
df_clean2 = df.drop_duplicates(keep='last')
print("\n保留最后一条:")
print(df_clean2)

# keep=False:删除所有重复行(一条都不保留)
df_clean3 = df.drop_duplicates(keep=False)
print("\n全部删除:")
print(df_clean3)

# subset=:只根据指定列判断重复
df2 = pd.DataFrame({
    '订单号': ['ORD001', 'ORD002', 'ORD003', 'ORD004'],
    '客户': ['张三', '李四', '张三', '赵六'],
    '金额': [500, 300, 600, 200]  # 金额不同
})

# 只看"客户"列去重
df_unique = df2.drop_duplicates(subset=['客户'])
print("按客户去重:")
print(df_unique)  # 张三只保留第一条

# inplace=True 原地修改
# df.drop_duplicates(inplace=True)

异常值(Outliers)检测与处理

1. 什么是异常值?

异常值是指显著偏离其他观测值的数据点

举例:一个班级的学生年龄大多是 18~22 岁,但有一条记录是 85 岁。这可能是数据录入错误(实际应该是 18 岁),也可能是真实情况(确实有一位 85 岁的学生)。

异常值不一定是"错误数据",但它值得你特别关注

2. IQR(四分位距)法检测异常值

IQR 法是最常用的异常值检测方法,它基于四分位数。

原理

  • Q1(第一四分位数):25% 的数据小于此值
  • Q3(第三四分位数):75% 的数据小于此值
  • IQR = Q3 - Q1
  • 下界 = Q1 - 1.5 × IQR
  • 上界 = Q3 + 1.5 × IQR
  • 超出上下界的数据即为异常值
# 创建示例数据
np.random.seed(42)
salaries = np.concatenate([
    np.random.normal(15000, 3000, 100),  # 正常工资分布
    [50000, 60000, -5000]                # 异常值
])
df = pd.DataFrame({'工资': salaries})

# ===== 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

print(f"Q1 = {Q1:.2f}")
print(f"Q3 = {Q3:.2f}")
print(f"IQR = {IQR:.2f}")
print(f"下界 = {lower_bound:.2f}")
print(f"上界 = {upper_bound:.2f}")

# 找出异常值
outliers = df[(df['工资'] < lower_bound) | (df['工资'] > upper_bound)]
print(f"\n异常值数量:{len(outliers)}")
print("异常值:")
print(outliers)

# ===== 处理异常值 =====

# 方法一:删除异常值
df_clean = df[(df['工资'] >= lower_bound) & (df['工资'] <= upper_bound)]
print(f"删除后数据量:{len(df_clean)}")

# 方法二:用边界值替换(Winsorization/盖帽法)
df_capped = df.copy()
df_capped.loc[df_capped['工资'] > upper_bound, '工资'] = upper_bound
df_capped.loc[df_capped['工资'] < lower_bound, '工资'] = lower_bound
print(f"盖帽后最大值:{df_capped['工资'].max():.2f}")
print(f"盖帽后最小值:{df_capped['工资'].min():.2f}")

# 方法三:用中位数替换
df_median = df.copy()
df_median.loc[
    (df_median['工资'] < lower_bound) | (df_median['工资'] > upper_bound),
    '工资'
] = df_median['工资'].median()

3. Z-Score 法(标准差法)

Z-Score 法适用于近似正态分布的数据。

# Z-Score = (x - 均值) / 标准差
# |Z| > 3 的数据通常被视为异常值

from scipy import stats

# 计算 Z-Score
z_scores = np.abs(stats.zscore(df['工资']))

# 找出异常值
outlier_mask = z_scores > 3
outliers_z = df[outlier_mask]
print(f"Z-Score 法检测到的异常值:{len(outliers_z)} 个")
print(outliers_z)

4. 异常值可视化(箱线图)

import matplotlib.pyplot as plt

# 箱线图是观察异常值最直观的方式
plt.figure(figsize=(8, 6))
df['工资'].plot(kind='box')
plt.title('工资分布箱线图')
plt.ylabel('工资')
plt.grid(True, alpha=0.3)
plt.show()

# 箱线图中,超出"须线"的点就是潜在的异常值

数据清洗的标准化流程

在实际工作中,建议按照以下流程进行数据清洗:

def data_cleaning_pipeline(df):
    """
    标准数据清洗流程
    """
    print("=" * 50)
    print("数据清洗报告")
    print("=" * 50)

    # 第1步:查看数据概况
    print(f"\n1. 数据形状:{df.shape}")
    print(f"   列名:{list(df.columns)}")

    # 第2步:检查缺失值
    missing = df.isnull().sum()
    missing_pct = (missing / len(df)) * 100
    missing_summary = pd.DataFrame({
        '缺失数量': missing,
        '缺失比例(%)': missing_pct.round(2)
    })
    print("\n2. 缺失值统计:")
    print(missing_summary[missing_summary['缺失数量'] > 0])

    # 第3步:检查重复值
    dup_count = df.duplicated().sum()
    print(f"\n3. 重复行数:{dup_count}")

    # 第4步:数值列基本统计
    print("\n4. 数值列统计摘要:")
    print(df.describe())

    # 第5步:数据类型
    print("\n5. 数据类型:")
    print(df.dtypes)

    print("\n" + "=" * 50)
    print("检查完成,请根据报告决定清洗策略")
    print("=" * 50)

    return df

# 使用示例
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六', '孙七', '张三'],  # 张三重复
    '年龄': [25, np.nan, 28, 35, np.nan, 25],
    '工资': [15000.0, 20000.0, np.nan, 25000.0, 12000.0, 15000.0],
    '部门': ['技术', '技术', '销售', '管理', '技术', '技术']
})

data_cleaning_pipeline(df)

实战练习

练习 1:缺失值处理实战

题目:处理以下包含缺失值的学生成绩数据。

# 参考答案
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '学号': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010],
    '姓名': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
    '语文': [85, np.nan, 78, 92, np.nan, 88, 76, np.nan, 91, 84],
    '数学': [90, 85, np.nan, 88, 92, np.nan, 78, 95, np.nan, 86],
    '英语': [78, 92, 85, np.nan, 88, 76, np.nan, 82, 95, np.nan]
})

# 1. 检查缺失值
print("缺失值统计:")
print(df.isnull().sum())

# 2. 各科用中位数填充
for col in ['语文', '数学', '英语']:
    df[col] = df[col].fillna(df[col].median())

print("\n填充后数据:")
print(df)

# 3. 验证填充结果
print("\n验证:还有缺失值吗?", df.isnull().sum().sum() == 0)

练习 2:去重与异常值检测

题目:以下是一份电商交易数据,包含重复记录和异常价格。

# 参考答案
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '订单号': ['A001', 'A002', 'A003', 'A002', 'A004', 'A005', 'A005', 'A006'],
    '商品': ['手机', '电脑', '平板', '电脑', '耳机', '手表', '手表', '充电器'],
    '价格': [3999, 5999, 2999, 5999, 299, 15000, 15000, -50],
    '数量': [1, 1, 2, 1, 3, 1, 1, 5]
})

# 1. 去除完全重复的行
print("原始行数:", len(df))
df_clean = df.drop_duplicates()
print("去重后行数:", len(df_clean))

# 2. 检测异常价格
Q1 = df_clean['价格'].quantile(0.25)
Q3 = df_clean['价格'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

print(f"\n价格正常范围:[{lower:.0f}, {upper:.0f}]")

abnormal = df_clean[(df_clean['价格'] < lower) | (df_clean['价格'] > upper)]
print("异常价格记录:")
print(abnormal)

# 3. 修正异常值:负价格改为绝对值,过高价格用中位数替换
df_clean = df_clean.copy()
df_clean.loc[df_clean['价格'] < 0, '价格'] = df_clean.loc[df_clean['价格'] < 0, '价格'].abs()

median_price = df_clean['价格'].median()
df_clean.loc[
    (df_clean['价格'] < lower) | (df_clean['价格'] > upper),
    '价格'
] = median_price

print("\n修正后数据:")
print(df_clean)

练习 3:完整清洗流程

题目:对以下"脏"数据进行完整清洗。

# 参考答案
import pandas as pd
import numpy as np

# 模拟脏数据
df = pd.DataFrame({
    'ID': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2],  # ID=2 重复
    '年龄': [25, 30, 28, 150, 22, np.nan, 35, 27, -5, 29, 30],  # 150和-5是异常值
    '收入': [5000, 8000, np.nan, 12000, 6000, 7000, np.nan, 9000, 5500, 7500, 8000],
    '性别': ['男', '女', '男', '女', 'M', '男', '女', np.nan, '男', '女', '女'],
    '城市': ['北京', '上海', '北京', '深圳', '上海', np.nan, '上海', '北京', '深圳', '上海', '上海']
})

print("=== 清洗前 ===")
print(f"数据量:{df.shape}")
print(df.describe())

# 第1步:去重
df = df.drop_duplicates()

# 第2步:处理年龄异常值
df = df[(df['年龄'] > 0) & (df['年龄'] < 100)]

# 第3步:收入填充中位数
df['收入'] = df['收入'].fillna(df['收入'].median())

# 第4步:年龄填充中位数
df['年龄'] = df['年龄'].fillna(df['年龄'].median())

# 第5步:性别标准化
df['性别'] = df['性别'].replace({'M': '男'})
df['性别'] = df['性别'].fillna('未知')

# 第6步:城市填充众数
df['城市'] = df['城市'].fillna(df['城市'].mode()[0])

print("\n=== 清洗后 ===")
print(f"数据量:{df.shape}")
print(df)
print("\n缺失值:", df.isnull().sum().sum())

本节总结

本节我们学习了数据清洗的三大核心任务:

缺失值处理

  • 检测方法:isnull()isnull().sum()
  • 删除:dropna()(注意 howthreshsubset 参数)
  • 填充:fillna()(均值/中位数/众数/前向/后向填充)

重复值处理

  • 检测:duplicated()
  • 删除:drop_duplicates()(注意 keepsubset 参数)

异常值检测

  • IQR 法:基于四分位距,适用范围广
  • Z-Score 法:基于标准差,适合正态分布
  • 处理方式:删除、盖帽法(Winsorization)、中位数替换

到此这篇关于Python数据清洗之缺失值处理,重复项去重与异常值检测详解的文章就介绍到这了,更多相关Python数据清洗内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python使用xlwt模块操作Excel的方法详解

    Python使用xlwt模块操作Excel的方法详解

    这篇文章主要介绍了Python使用xlwt模块操作Excel的方法,结合实例形式分析了Python安装xlwt模块及使用xlwt模块针对Excel文件的创建、设置、保存等常用操作技巧,需要的朋友可以参考下
    2018-03-03
  • 怎么用Python识别手势数字

    怎么用Python识别手势数字

    今天给大家带来的文章是怎么用Python识别手势数字,文中有非常详细的图文示例,对正在学习python的小伙伴们很有帮助,需要的朋友可以参考下
    2021-06-06
  • python实现根据图标提取分类应用程序实例

    python实现根据图标提取分类应用程序实例

    这篇文章主要介绍了python实现根据图标提取分类应用程序实例,是非常实用的应用程序技巧,需要的朋友可以参考下
    2014-09-09
  • 如何利用Python爬虫精准获取淘宝商品详情

    如何利用Python爬虫精准获取淘宝商品详情

    淘宝作为中国最大的电商平台之一,拥有海量的商品数据,对于研究市场趋势、分析消费者行为等具有重要意义,本文将详细介绍如何使用Python编写爬虫程序,精准获取淘宝商品详情信息,感兴趣的朋友跟随小编一起看看吧
    2024-12-12
  • python如何利用turtle绘制正方形

    python如何利用turtle绘制正方形

    这篇文章主要介绍了python如何利用turtle绘制正方形,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Python安全隐患最新URL解析漏洞防范措施

    Python安全隐患最新URL解析漏洞防范措施

    这篇文章主要为大家介绍了Python安全隐患,最新URL解析漏洞的防范措施,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • 利用Python判断整数是否是回文数的3种方法总结

    利用Python判断整数是否是回文数的3种方法总结

    这篇文章主要给大家介绍了关于如何利用Python判断整数是否是回文数的3种方总结,回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数,需要的朋友可以参考下
    2021-07-07
  • 删除PyCharm解释器的方法步骤

    删除PyCharm解释器的方法步骤

    这篇文章主要给大家介绍了关于删除PyCharm解释器的方法步骤,PyCharm解释器是指在PyCharm集成开发环境中用于运行和调试Python代码的解释器,需要的朋友可以参考下
    2023-09-09
  • Pytorch+PyG实现GraphSAGE过程示例详解

    Pytorch+PyG实现GraphSAGE过程示例详解

    这篇文章主要为大家介绍了Pytorch+PyG实现GraphSAGE过程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • python定时采集摄像头图像上传ftp服务器功能实现

    python定时采集摄像头图像上传ftp服务器功能实现

    本文程序实现python定时采集摄像头图像上传ftp服务器功能,大家参考使用吧
    2013-12-12

最新评论