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。在分群评分沙盘中,建议对每个连续特征分别检测,并记录异常值比例供后续评估。

异常值检测流程对比 Z-score 检测流程 计算均值 μ 与标准差 σ 计算每个点的 Z = (x-μ)/σ |Z| > 3 标记为异常值 剔除或截断异常值 IQR 检测流程 计算 Q1、Q3 与 IQR 计算上下界:Q1-1.5IQR / Q3+1.5IQR 超出上下界标记为异常值 剔除或截断异常值 适用场景:Z-score 适合正态分布数据;IQR 适合偏态分布,不受极端值影响 分群评分沙盘中建议对每个连续特征分别检测,并记录异常值比例

4.3 数据类型转换:让数据“说对的话”

原始数据中常见的问题包括:数值列被读为字符串、日期列未解析、分类变量未编码。类型转换是后续标准化和建模的前提。

原始类型目标类型转换方法常见场景
object(字符串)float/intpd.to_numeric()金额、年龄列含逗号或空格
object(日期)datetimepd.to_datetime()交易时间、注册日期
int/floatcategorydf['col'].astype('category')性别编码、等级标签
categoryint(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]稀疏数据或中心化数据中
数据标准化流程与选择策略 原始数值特征 含异常值? 是 否 RobustScaler StandardScaler 标准化后特征 MinMaxScaler 适用于有明确边界或神经网络输入;MaxAbsScaler 适用于稀疏数据

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()}")

最佳实践: 在分群评分沙盘中,建议将清洗流水线封装为独立模块,并记录每一步删除的行数和异常值比例。这样既能保证可复现性,也便于在模型效果不佳时回溯数据质量。