4. 数据清洗基础:缺失值与重复值处理
在构建分群评分沙盘样本时,原始数据几乎总是包含不完整或冗余的记录。缺失值会破坏统计计算的连续性,重复值则会导致样本偏差和过拟合风险。本章系统讲解 Pandas 中缺失值与重复值的检测与处理方法,为后续样本构建打下干净的数据基础。
4.1 缺失值检测:isnull 与 notnull
缺失值在 Pandas 中通常表示为 NaN(Not a Number)。isnull() 返回布尔型 DataFrame,标记每个元素是否为缺失值;notnull() 是其反操作。
| 方法 | 返回值 | 典型用途 |
|---|---|---|
df.isnull() |
布尔 DataFrame | 定位缺失值位置 |
df.notnull() |
布尔 DataFrame | 定位非缺失值位置 |
df.isnull().sum() |
每列缺失计数 | 快速评估数据完整性 |
df.isnull().any(axis=1) |
每行布尔值 | 筛选包含缺失值的行 |
关键指标: 在分群评分场景中,若某特征的缺失率超过 40%,通常建议直接删除该特征,而非填充。
import pandas as pd
import numpy as np
# 模拟含缺失值的评分数据
data = {
'用户ID': ['U001', 'U002', 'U003', 'U004'],
'年龄': [25, np.nan, 30, 22],
'消费分': [85, 92, np.nan, 78],
'活跃分': [90, 88, 95, np.nan]
}
df = pd.DataFrame(data)
# 检测缺失值
print(df.isnull())
print("\n每列缺失数量:")
print(df.isnull().sum())
4.2 缺失值删除:dropna
dropna() 用于删除包含缺失值的行或列。在沙盘样本构建中,删除操作需谨慎,避免损失关键样本。
| 参数 | 说明 | 示例 |
|---|---|---|
axis=0 |
删除行(默认) | df.dropna() |
axis=1 |
删除列 | df.dropna(axis=1) |
how='any' |
行/列中任意缺失即删除 | df.dropna(how='any') |
how='all' |
行/列全部缺失才删除 | df.dropna(how='all') |
thresh=n |
保留至少有 n 个非空值的行/列 | df.dropna(thresh=3) |
subset |
只在指定列中检查缺失 | df.dropna(subset=['年龄']) |
# 删除包含任何缺失值的行
df_clean = df.dropna()
print("删除后行数:", len(df_clean))
# 只保留至少有3个非空值的行
df_thresh = df.dropna(thresh=3)
print("thresh=3 保留行数:", len(df_thresh))
# 仅在'年龄'和'消费分'列检查缺失
df_subset = df.dropna(subset=['年龄', '消费分'])
print("subset 保留行数:", len(df_subset))
4.3 缺失值填充:fillna
fillna() 用指定值或统计量替换缺失值。在分群评分场景中,填充策略直接影响聚类结果,需根据业务含义选择。
填充策略选择建议:
• 连续型特征(如消费分):优先使用中位数填充,对异常值不敏感。
• 离散型特征(如等级):使用众数填充。
• 时间序列特征:使用前向填充(ffill)或后向填充(bfill)。
• 评分类特征:若缺失比例低(<5%),可用均值填充。
• 连续型特征(如消费分):优先使用中位数填充,对异常值不敏感。
• 离散型特征(如等级):使用众数填充。
• 时间序列特征:使用前向填充(ffill)或后向填充(bfill)。
• 评分类特征:若缺失比例低(<5%),可用均值填充。
# 用固定值填充
df_filled = df.fillna(0)
# 用列均值填充
df_mean = df.copy()
df_mean['年龄'] = df_mean['年龄'].fillna(df_mean['年龄'].mean())
df_mean['消费分'] = df_mean['消费分'].fillna(df_mean['消费分'].mean())
# 用列中位数填充
df_median = df.copy()
df_median['年龄'] = df_median['年龄'].fillna(df_median['年龄'].median())
# 前向填充(用上一行值填充)
df_ffill = df.fillna(method='ffill')
print("均值填充结果:")
print(df_mean)
4.4 重复值检测:duplicated
duplicated() 返回布尔 Series,标记重复行(除第一次出现外)。在沙盘样本中,重复记录可能来自数据采集异常或多次录入。
| 参数 | 说明 | 示例 |
|---|---|---|
subset |
指定列判断重复 | df.duplicated(subset=['用户ID']) |
keep='first' |
标记除第一次外的重复(默认) | df.duplicated(keep='first') |
keep='last' |
标记除最后一次外的重复 | df.duplicated(keep='last') |
keep=False |
标记所有重复行 | df.duplicated(keep=False) |
# 模拟含重复值的评分数据
data_dup = {
'用户ID': ['U001', 'U002', 'U002', 'U003', 'U003', 'U004'],
'消费分': [85, 92, 92, 78, 78, 88],
'活跃分': [90, 88, 88, 95, 95, 82]
}
df_dup = pd.DataFrame(data_dup)
# 检测完全重复行
print("完全重复行标记:")
print(df_dup.duplicated())
# 基于用户ID检测重复
print("\n基于用户ID重复:")
print(df_dup.duplicated(subset=['用户ID']))
# 查看所有重复行
print("\n所有重复行:")
print(df_dup[df_dup.duplicated(keep=False)])
4.5 重复值删除:drop_duplicates
drop_duplicates() 删除重复行,返回去重后的 DataFrame。参数与 duplicated() 对应。
注意: 在分群评分沙盘中,若重复记录的业务含义是“同一用户多次评分”,应保留最新评分(按时间戳排序后使用
keep='last'),而非简单删除。
# 删除完全重复行
df_unique = df_dup.drop_duplicates()
print("完全去重后行数:", len(df_unique))
# 基于用户ID去重,保留第一次出现
df_unique_first = df_dup.drop_duplicates(subset=['用户ID'], keep='first')
print("基于用户ID去重(保留首次):", len(df_unique_first))
# 基于用户ID去重,保留最后一次出现
df_unique_last = df_dup.drop_duplicates(subset=['用户ID'], keep='last')
print("基于用户ID去重(保留末次):", len(df_unique_last))
# 原地修改
df_dup.drop_duplicates(inplace=True)
4.6 综合实战:沙盘样本清洗流水线
在真实的分群评分项目中,缺失值和重复值处理通常组合成一条清洗流水线。以下是一个完整的示例流程:
def clean_sandbox_data(df):
"""
沙盘样本清洗函数
步骤:
1. 删除全部为空的列
2. 删除用户ID重复的记录(保留最新)
3. 对数值型特征用中位数填充缺失值
4. 对类别型特征用众数填充缺失值
"""
df_clean = df.copy()
# 步骤1:删除全空列
df_clean = df_clean.dropna(axis=1, how='all')
# 步骤2:基于用户ID去重,保留最后一条
if '用户ID' in df_clean.columns:
df_clean = df_clean.drop_duplicates(subset=['用户ID'], keep='last')
# 步骤3:数值型特征中位数填充
num_cols = df_clean.select_dtypes(include=[np.number]).columns
for col in num_cols:
df_clean[col] = df_clean[col].fillna(df_clean[col].median())
# 步骤4:类别型特征众数填充
cat_cols = df_clean.select_dtypes(include=['object']).columns
for col in cat_cols:
if col != '用户ID':
mode_val = df_clean[col].mode()
if not mode_val.empty:
df_clean[col] = df_clean[col].fillna(mode_val[0])
return df_clean
# 应用清洗函数
df_raw = pd.DataFrame({
'用户ID': ['U001', 'U002', 'U002', 'U003', None],
'年龄': [25, np.nan, 30, 22, 28],
'消费分': [85, 92, 92, np.nan, 80],
'等级': ['A', 'B', 'B', None, 'A']
})
df_cleaned = clean_sandbox_data(df_raw)
print("清洗后数据:")
print(df_cleaned)
4.7 本章小结
- 缺失值检测:
isnull()/notnull()定位缺失,sum()统计缺失量。 - 缺失值删除:
dropna()支持行/列删除,thresh参数控制非空阈值。 - 缺失值填充:
fillna()支持固定值、统计量、前向/后向填充。 - 重复值检测:
duplicated()标记重复,subset指定判断列。 - 重复值删除:
drop_duplicates()去重,keep参数控制保留策略。 - 实战要点: 构建清洗流水线时,先处理结构性问题(全空列),再处理业务逻辑(去重),最后处理数值填充。