4. 数据清洗实战:重复值处理、异常值检测(Z-score/IQR)、数据类型转换与标准化
数据清洗是分群评分沙盘建模中最耗时但最关键的一步。原始数据通常包含重复记录、异常离群点、错误的数据类型以及量纲不一致的特征,这些问题会直接扭曲聚类结果和评分权重。本章将系统讲解重复值处理、异常值检测、数据类型转换与标准化四大核心操作,并提供可复用的 Python 代码模板。
4.1 重复值处理:识别与去重策略
重复值指数据集中完全一致或部分关键字段一致的多条记录。在分群场景中,重复记录会放大特定样本的权重,导致聚类中心偏移。
| 重复类型 | 定义 | 处理策略 |
|---|---|---|
| 完全重复 | 所有字段值完全相同 | 直接删除,保留第一条 |
| 关键字段重复 | 用户ID、时间戳等主键重复 | 按业务规则保留最新或最完整记录 |
| 近似重复 | 文本或数值存在微小差异 | 使用模糊匹配或编辑距离合并 |
实战要点: 使用 df.duplicated() 标记重复行,df.drop_duplicates() 执行删除。对于关键字段重复,建议先按时间排序再保留最后一条,避免丢失最新行为数据。
import pandas as pd
# 加载示例数据
df = pd.read_csv('customer_data.csv')
# 查看重复行数量
print(f"重复行数: {df.duplicated().sum()}")
# 删除完全重复行
df_clean = df.drop_duplicates()
# 按用户ID去重,保留最近一次记录
df_dedup = df.sort_values('timestamp').drop_duplicates(subset='user_id', keep='last')
4.2 异常值检测:Z-score 与 IQR 方法
异常值会严重干扰分群算法的距离计算和评分区间划分。本节介绍两种最常用的统计检测方法:Z-score 适用于近似正态分布的数据,IQR 适用于偏态分布或含极端值的场景。
4.2.1 Z-score 方法
Z-score 衡量数据点与均值的偏离程度,以标准差为单位。通常将 |Z| > 3 的点视为异常值。
from scipy import stats
import numpy as np
# 计算 Z-score
z_scores = np.abs(stats.zscore(df_clean['amount']))
threshold = 3
# 标记异常值
df_clean['amount_zscore'] = z_scores
outliers_z = df_clean[z_scores > threshold]
print(f"Z-score 检测异常值数量: {len(outliers_z)}")
# 剔除异常值
df_z_clean = df_clean[z_scores <= threshold]
4.2.2 IQR 方法
IQR(四分位距)基于数据分布的分位数,不受极端值影响。通常将低于 Q1-1.5×IQR 或高于 Q3+1.5×IQR 的点视为异常值。
# 计算四分位数
Q1 = df_clean['amount'].quantile(0.25)
Q3 = df_clean['amount'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
df_clean['amount_iqr_outlier'] = (df_clean['amount'] < lower_bound) | (df_clean['amount'] > upper_bound)
outliers_iqr = df_clean[df_clean['amount_iqr_outlier']]
print(f"IQR 检测异常值数量: {len(outliers_iqr)}")
# 截断处理(可选)
df_iqr_clean = df_clean[(df_clean['amount'] >= lower_bound) & (df_clean['amount'] <= upper_bound)]
选择建议: 数据量较大且近似正态分布时优先使用 Z-score;数据偏态明显或存在业务阈值时优先使用 IQR。在分群评分沙盘中,建议对每个连续特征分别检测,并记录异常值比例供后续评估。
4.3 数据类型转换:让数据“说对的话”
原始数据中常见的问题包括:数值列被读为字符串、日期列未解析、分类变量未编码。类型转换是后续标准化和建模的前提。
| 原始类型 | 目标类型 | 转换方法 | 常见场景 |
|---|---|---|---|
| object(字符串) | float/int | pd.to_numeric() | 金额、年龄列含逗号或空格 |
| object(日期) | datetime | pd.to_datetime() | 交易时间、注册日期 |
| int/float | category | df['col'].astype('category') | 性别编码、等级标签 |
| category | int(one-hot) | pd.get_dummies() | 城市、渠道等无序分类 |
# 数值类型转换(处理含逗号的金额)
df_clean['amount'] = pd.to_numeric(df_clean['amount'].str.replace(',', ''), errors='coerce')
# 日期类型转换
df_clean['signup_date'] = pd.to_datetime(df_clean['signup_date'], format='%Y-%m-%d')
# 分类变量独热编码
df_encoded = pd.get_dummies(df_clean, columns=['city', 'channel'], drop_first=True)
# 检查转换后数据类型
print(df_encoded.dtypes)
注意: errors='coerce' 会将无法转换的值设为 NaN,务必在转换后检查缺失值比例。对于日期列,建议指定 format 参数以提高解析速度和准确性。
4.4 数据标准化:消除量纲影响
分群算法(如 K-Means)和评分模型对特征的尺度敏感。标准化使不同量纲的特征具有可比性,避免大数值特征主导聚类结果。
4.4.1 Z-score 标准化(StandardScaler)
将数据转换为均值为 0、标准差为 1 的分布,适用于数据近似正态分布的场景。
from sklearn.preprocessing import StandardScaler
# 选择需要标准化的数值列
num_cols = ['amount', 'frequency', 'recency']
scaler = StandardScaler()
# 拟合并转换
df_scaled = df_encoded.copy()
df_scaled[num_cols] = scaler.fit_transform(df_encoded[num_cols])
print(f"标准化后均值: {df_scaled[num_cols].mean().round(2)}")
print(f"标准化后标准差: {df_scaled[num_cols].std().round(2)}")
4.4.2 Min-Max 标准化(MinMaxScaler)
将数据缩放到 [0, 1] 区间,适用于有明确边界或需要保留原始分布形状的场景。
from sklearn.preprocessing import MinMaxScaler
minmax_scaler = MinMaxScaler(feature_range=(0, 1))
df_minmax = df_encoded.copy()
df_minmax[num_cols] = minmax_scaler.fit_transform(df_encoded[num_cols])
print(f"Min-Max 后最小值: {df_minmax[num_cols].min().round(2)}")
print(f"Min-Max 后最大值: {df_minmax[num_cols].max().round(2)}")
| 标准化方法 | 输出范围 | 适用场景 | 对异常值敏感度 |
|---|---|---|---|
| StandardScaler | 无固定范围(均值0,标准差1) | 数据近似正态分布 | 高 |
| MinMaxScaler | [0, 1] 或自定义区间 | 有明确边界、神经网络输入 | 高 |
| RobustScaler | 基于中位数和IQR | 数据含较多异常值 | 低 |
| MaxAbsScaler | [-1, 1] | 稀疏数据或中心化数据 | 中 |
4.5 完整清洗流水线实战
将上述步骤整合为一个可复用的清洗函数,便于在分群评分沙盘中批量处理多个数据集。
def data_cleaning_pipeline(df, num_cols, cat_cols, date_col=None,
outlier_method='iqr', scale_method='standard'):
"""
数据清洗完整流水线
:param df: 原始DataFrame
:param num_cols: 数值列列表
:param cat_cols: 分类列列表
:param date_col: 日期列名(可选)
:param outlier_method: 'zscore' 或 'iqr'
:param scale_method: 'standard' 或 'minmax'
:return: 清洗后的DataFrame
"""
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler, MinMaxScaler
df_clean = df.copy()
# 1. 去重
df_clean = df_clean.drop_duplicates()
# 2. 日期转换
if date_col:
df_clean[date_col] = pd.to_datetime(df_clean[date_col], errors='coerce')
# 3. 数值列类型转换
for col in num_cols:
df_clean[col] = pd.to_numeric(df_clean[col], errors='coerce')
# 4. 异常值处理
for col in num_cols:
if outlier_method == 'zscore':
z = np.abs((df_clean[col] - df_clean[col].mean()) / df_clean[col].std())
df_clean = df_clean[z <= 3]
elif outlier_method == 'iqr':
Q1 = df_clean[col].quantile(0.25)
Q3 = df_clean[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
df_clean = df_clean[(df_clean[col] >= lower) & (df_clean[col] <= upper)]
# 5. 分类变量编码
df_clean = pd.get_dummies(df_clean, columns=cat_cols, drop_first=True)
# 6. 标准化
if scale_method == 'standard':
scaler = StandardScaler()
elif scale_method == 'minmax':
scaler = MinMaxScaler()
df_clean[num_cols] = scaler.fit_transform(df_clean[num_cols])
return df_clean
# 使用示例
cleaned_df = data_cleaning_pipeline(
df=df,
num_cols=['amount', 'frequency', 'recency'],
cat_cols=['city', 'channel'],
date_col='signup_date',
outlier_method='iqr',
scale_method='standard'
)
print(f"清洗后数据形状: {cleaned_df.shape}")
print(f"清洗后数据列: {cleaned_df.columns.tolist()}")
最佳实践: 在分群评分沙盘中,建议将清洗流水线封装为独立模块,并记录每一步删除的行数和异常值比例。这样既能保证可复现性,也便于在模型效果不佳时回溯数据质量。