Pandas数据清洗之缺失值处理和重复值处理详解
面对包含大量缺失值与重复记录的原始表格,直接使用固定数值填充或盲目去重极易扭曲业务结论。本文演示如何通过 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 标准化操作的技术资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













