Pandas数据清洗之缺失值处理和重复值处理详解

阿麦
阿麦
发布时间:2026-10-10 | 3人浏览过
原创

面对包含大量缺失值与重复记录的原始表格,直接使用固定数值填充或盲目去重极易扭曲业务结论。本文演示如何通过 Pandas 全面评估数据质量,针对分类变量、数值指标与时序记录采用中位数、众数、分组均值及前后向填充等精准策略,并结合业务字段封装自动化清洗流程,在保障数据结构完整的前提下高效完成数据标准化。

评估数据质量全貌

进行任何清洗操作前,必须先掌握原始数据的分布特征。直接查看类型与内存占用无法揭示实质问题,需通过统计函数一次性提取缺失比例、重复行数及极端空缺状态。

import pandas as pd
import numpy as np

data = {
    'name': ['张三', '李四', '王五', '张三', '赵六', np.nan, '钱七'],
    'age': [25, 30, np.nan, 25, '-', 35, 28],
    'salary': [8000, np.nan, 12000, 8000, 15000, 'N/A', 9000],
    'department': ['技术部', '销售部', '技术部', '技术部', '人事部', '财务部', '技术部']
}
df = pd.DataFrame(data)

print("数据缺失情况全貌:")
print(df.isnull().sum())
print("\n缺失值比例:")
print(df.isnull().sum() / len(df) * 100)

print("\n数据质量报告:")
print(f"总行数:{len(df)}")
print(f"完全重复的行数:{df.duplicated().sum()}")
print(f"至少有一个缺失值的行数:{df.isnull().any(axis=1).sum()}")
print(f"全部是缺失值的行数:{df.isnull().all(axis=1).sum()}")

执行上述代码后,可直观获得各字段的绝对缺失量与占比,同时快速定位完全重复的行数与存在空缺的行范围,为后续针对性清洗提供依据。

缺失值处理策略

第1步:统一识别特殊占位符

目的:将非空但代表缺失的字符转为标准的 NaN,避免后续分析被静默忽略。入口:文件读取或 DataFrame 替换操作。动作:定义缺失标识列表并执行映射转换。重要参数:na_values 接收自定义字符串列表或 replace() 指定源值。可观察的完成状态:isnull().sum() 输出的数量较修改前显著上升,覆盖原占位符位置。

# 现实中的数据,缺失值花样百出:"-", "N/A", "暂无", "null"
df = pd.read_excel('messy_data.xlsx', na_values=['-', 'N/A', '暂无数据', 'null', 'na'])
# 或者事后补救
df = df.replace(['-', 'N/A', '暂无数据', 'null', 'na'], np.nan)

第2步:按列类型执行差异化填充

目的:避免业务常识错误(如年龄填0或薪资归零),确保填充值符合数据分布规律。入口:逐列遍历 DataFrame。动作:对象类型采用众数填充,数值类型根据业务关联度选择全局中位数或分组均值。重要参数:df[col].mode()、.median()、mask 条件掩码。可观察的完成状态:isnull().sum() 对应列归零,且填充结果未偏离原始统计区间。

def smart_fillna(df):
    df_filled = df.copy()
    for col in df.columns:
        if df[col].dtype == 'object':
            mode_value = df[col].mode()
            if len(mode_value) > 0:
                df_filled[col] = df[col].fillna(mode_value[0])
        elif df[col].dtype in ['int64', 'float64']:
            if 'age' in col.lower():
                df_filled[col] = df[col].fillna(df[col].median())
            elif 'salary' in col.lower():
                for dept in df['department'].unique():
                    dept_avg = df[df['department'] == dept][col].mean()
                    mask = (df[col].isnull()) & (df['department'] == dept)
                    df_filled.loc[mask, col] = dept_avg
            else:
                df_filled[col] = df[col].fillna(df[col].median())
    return df_filled

df_smart_filled = smart_fillna(df)

第3步:应用前后向填充与插值法

目的:保持时间序列或连续观测数据的平稳过渡,防止断层。入口:带有时间索引或有序排列的 DataFrame。动作:调用前向/后向传播填充或数学插值算法。重要参数:method='ffill'/'bfill'`、limit 控制最大传播步数、interpolate(method='linear'/'time')。可观察的完成状态:连续空缺区域被合理平滑填补,未出现跨越多行的大跨度跳变。

time_series_data = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=10),
    'temperature': [20, 21, np.nan, 23, np.nan, np.nan, 26, 25, np.nan, 24]
})

# 前向填充与后向填充组合
result = time_series_data.fillna(method='ffill').fillna(method='bfill')
# 限制填充范围(最多传递1个缺失)
limited = time_series_data.fillna(method='ffill', limit=1)
# 线性与时间加权插值
print(time_series_data.interpolate(method='linear'))
print(time_series_data.set_index('date').interpolate(method='time'))

重复值处理逻辑

盲目使用默认去重会丢失关键业务差异。应首先明确判定重复的核心字段组合,再根据实际场景选择保留策略或直接合并数值字段。

duplicate_data = pd.DataFrame({
    'name': ['张三', '李四', '张三', '张三', '王五'],
    'age': [25, 30, 25, 26, 35],
    'salary': [8000, 9000, 8000, 8000, 12000],
    'department': ['技术部', '销售部', '技术部', '技术部', '技术部']
})

# 基于关键列去重
print(duplicate_data.drop_duplicates(subset=['name', 'department']))
# 保留最后出现的记录
print(duplicate_data.drop_duplicates(subset=['name'], keep='last'))
# 标记所有参与重复的行
print(duplicate_data[duplicate_data.duplicated(subset=['name'], keep=False)])

# 智能聚合处理(字符串取首次,数值型取均值)
def smart_handle_duplicates(df, key_columns, strategy='first'):
    if strategy in ['first', 'last']:
        return df.drop_duplicates(subset=key_columns, keep=strategy)
    numeric_cols = df.select_dtypes(include=[np.number]).columns
    non_numeric_cols = df.select_dtypes(exclude=[np.number]).columns
    result = df.groupby(key_columns, as_index=False).agg({
        **{col: 'first' for col in non_numeric_cols if col not in key_columns},
        **{col: strategy for col in numeric_cols if col not in key_columns}
    })
    return result

print(smart_handle_duplicates(duplicate_data, ['name'], 'mean'))

自动化清洗流程封装

将常用预处理动作固化为流水线函数,可大幅降低重复劳动成本。以下实现支持按需切换去重依据、填充策略与索引重置。

def auto_clean_data(df,
                   remove_duplicates=True,
                   duplicate_subset=None,
                   fill_missing=True,
                   fill_strategy='smart'):
    df_clean = df.copy()
    # 1. 去重
    if remove_duplicates:
        df_clean = df_clean.drop_duplicates(subset=duplicate_subset, keep='first') if duplicate_subset else df_clean.drop_duplicates(keep='first')
    # 2. 处理缺失值
    if fill_missing:
        if fill_strategy == 'smart':
            df_clean = smart_fillna(df_clean)
        elif fill_strategy == 'mean':
            df_clean = df_clean.fillna(df_clean.mean())
        elif fill_strategy == 'median':
            df_clean = df_clean.fillna(df_clean.median())
        elif fill_strategy == 'mode':
            df_clean = df_clean.fillna(df_clean.mode().iloc[0])
        elif fill_strategy == 'ffill':
            df_clean = df_clean.fillna(method='ffill')
        elif fill_strategy == 'bfill':
            df_clean = df_clean.fillna(method='bfill')
    # 3. 重置索引
    df_clean = df_clean.reset_index(drop=True)
    return df_clean

df_auto_cleaned = auto_clean_data(df, remove_duplicates=True, fill_strategy='smart')

大数据量性能验证

在处理十万级常规表格时,向量化运算结合分组聚合仍能保持较高效率。以下为模拟基准测试参考,实际耗时受硬件配置与数据集密度影响。

import time

def create_large_dirty_data(rows=100000):
    np.random.seed(42)
    departments = ['技术部', '销售部', '人事部', '财务部', '市场部']
    names = [f'员工{i}' for i in range(1000)]
    data = {
        '员工ID': range(1, rows + 1),
        '姓名': np.random.choice(names, rows, replace=True),
        '部门': np.random.choice(departments, rows),
        '年龄': np.random.normal(35, 8, rows),
        '薪资': np.random.normal(8000, 2000, rows),
        '绩效评分': np.random.uniform(60, 100, rows)
    }
    df = pd.DataFrame(data)
    for col in ['年龄', '薪资', '绩效评分']:
        missing_indices = np.random.choice(rows, int(rows * 0.15), replace=False)
        df.loc[missing_indices, col] = np.nan
    duplicate_indices = np.random.choice(rows, int(rows * 0.05), replace=False)
    df = pd.concat([df, df.iloc[duplicate_indices]], ignore_index=True)
    outlier_indices = np.random.choice(rows, int(rows * 0.02), replace=False)
    df.loc[outlier_indices, '年龄'] = df.loc[outlier_indices, '年龄'] * 10
    return df

large_dirty_df = create_large_dirty_data(100000)
start_time = time.time()
cleaned_large_df = auto_clean_data(large_dirty_df, remove_duplicates=True, duplicate_subset=['员工ID'], fill_strategy='smart')
end_time = time.time()

print(f"原始数据:{len(large_dirty_df)} 行")
print(f"清洗后数据:{len(cleaned_large_df)} 行")
print(f"处理时间:{end_time - start_time:.2f} 秒")
print(f"数据质量提升:{(len(large_dirty_df) - len(cleaned_large_df)) / len(large_dirty_df) * 100:.1f}%")

核心处理原则总结

  • 先诊断后干预:始终先用 isnull().sum() 与 duplicated() 确认分布形态。
  • 区分数据类型:分类变量优先众数,连续数值优先中位数或业务关联均值。
  • 服从业务常识:严禁对年龄、金额等敏感字段使用全局零值或极值填充。
  • 重视时序特性:带时间戳的记录首选前向/后向传播或线性插值。
  • 谨慎移除重复:务必界定主键字段,必要时采用分组聚合而非直接丢弃。

以上就是 Pandas 数据清洗中缺失值与重复值处理的详细内容,更多关于 Python 数据分析与 DataFrame 标准化操作的技术资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表