4. 单变量切片分析:数值型特征切片、类别型特征切片、时间序列切片、文本特征切片
单变量切片,说白了就是一次只看一个特征。你可能会问:「这有什么难的?不就是画个直方图吗?」嗯,没那么简单。我做了这么多年模型诊断,发现很多模型翻车,恰恰就翻在单变量切片这一步没做透。
这一章,咱们把四种最常见的特征类型——数值型、类别型、时间序列、文本——的切片方法挨个过一遍。每种我都会给出具体的操作步骤、代码示例,以及我踩过的坑。
4.1 数值型特征切片
数值型特征是最常见的。年龄、收入、温度、点击次数……这些连续值怎么切?我见过有人直接按等距分桶,结果样本分布不均,模型评估完全失真。
我个人习惯用三种方法:
- 等宽分桶:把值域分成 N 个等距区间。适合分布均匀的特征,比如温度 0-100°C 每 10°C 切一段。
- 等频分桶:每个桶里样本数差不多。适合长尾分布,比如收入——少数人收入极高,等宽分桶会导致前几个桶挤满人,后面桶几乎空着。
- 业务阈值分桶:按业务含义切。比如年龄按 0-18、18-35、35-60、60+ 来分。这是我最推荐的方式,因为可解释性最强。
举个例子,假设我们有个房价预测模型,特征 房屋面积 从 20 到 500 平米。用等频分桶看看模型误差:
import pandas as pd
import numpy as np
# 假设 df 包含 'area' 和 'error'(预测误差)
df['area_bucket'] = pd.qcut(df['area'], q=5, labels=['Q1','Q2','Q3','Q4','Q5'])
# 计算每个桶的平均误差
error_by_bucket = df.groupby('area_bucket')['error'].agg(['mean','std','count'])
print(error_by_bucket)
输出结果可能长这样:
| 面积分桶 | 平均误差 | 误差标准差 | 样本数 |
|---|---|---|---|
| Q1 (20-60) | 0.15 | 0.08 | 2000 |
| Q2 (60-90) | 0.12 | 0.06 | 2000 |
| Q3 (90-130) | 0.11 | 0.05 | 2000 |
| Q4 (130-180) | 0.18 | 0.12 | 2000 |
| Q5 (180-500) | 0.35 | 0.25 | 2000 |
4.2 类别型特征切片
类别型特征,比如城市、职业、产品类别。这类特征切片最容易犯的错误是:只看整体准确率,忽略低频类别。
我曾经在一个信贷风控模型里发现,整体 AUC 高达 0.92,但「自由职业」这个类别的坏账率预测完全不准。为什么?因为样本太少,模型直接把它当成了「平均人」。
类别型切片我一般分三步走:
- 计算每个类别的样本量——少于 100 条的类别要标记为「低频」。
- 计算每个类别的评估指标——准确率、召回率、F1 都算上。
- 对比低频类别与整体表现——如果差距超过 20%,就要警惕了。
代码示例:
# 假设 df 包含 'city' 和 'pred'、'label'
city_stats = df.groupby('city').agg(
count=('label', 'count'),
accuracy=('label', lambda x: (x == df.loc[x.index, 'pred']).mean())
).reset_index()
# 标记低频类别
city_stats['flag'] = city_stats['count'].apply(
lambda x: '低频' if x < 100 else '正常'
)
print(city_stats[city_stats['flag'] == '低频'])
4.3 时间序列切片
时间序列切片,很多人以为就是按年、月、日分组看看。其实没那么简单。时间序列里有个很隐蔽的问题——时间漂移。
什么意思?模型在训练集上表现很好,但上线后效果越来越差。因为数据分布随时间变了。比如用户行为习惯变了、政策变了、季节变了。
我建议按以下维度做时间切片:
- 按时间粒度:年、季度、月、周、日。根据业务周期选。
- 按时间窗口:比如「训练期 vs 验证期 vs 上线后第1周 vs 第2周」。
- 按事件节点:比如「促销前 vs 促销中 vs 促销后」。
代码示例——按月查看模型误差变化:
# 假设 df 包含 'date' 和 'error'
df['month'] = pd.to_datetime(df['date']).dt.to_period('M')
monthly_error = df.groupby('month')['error'].agg(['mean','std']).reset_index()
monthly_error['month'] = monthly_error['month'].astype(str)
# 看看有没有明显上升趋势
print(monthly_error.head(12))
如果发现某个月误差突然飙升,别急着调模型。先看看那个月有没有特殊事件——比如双十一、春节、或者数据采集系统出了故障。
4.4 文本特征切片
文本特征切片,听起来有点抽象。文本怎么切?按长度?按情感?按主题?
其实思路是一样的——找到文本的某个可量化维度,然后看模型在这个维度上的表现差异。我常用的维度有:
- 文本长度:短文本 vs 长文本。模型对短文本(比如标题)和长文本(比如评论)的表现可能完全不同。
- 情感极性:正面 vs 负面 vs 中性。情感分类模型在负面样本上往往更差。
- 主题类别:按 LDA 或 BERT 聚类后的主题分组。
- 语言/方言:如果数据是多语言的,一定要按语言切片。
举个例子,假设我们有个垃圾邮件分类模型,按文本长度切片:
# 假设 df 包含 'text' 和 'pred'、'label'
df['text_len'] = df['text'].apply(len)
df['len_bucket'] = pd.cut(df['text_len'],
bins=[0, 50, 200, 500, 10000],
labels=['极短','短','中','长'])
len_stats = df.groupby('len_bucket').agg(
count=('label', 'count'),
accuracy=('label', lambda x: (x == df.loc[x.index, 'pred']).mean())
)
print(len_stats)
结果可能显示「极短」文本的准确率只有 60%,而整体是 95%。为什么?因为极短文本信息量太少,模型很难判断。这时候就需要对短文本做特征增强,比如加入字符级 n-gram。
4.5 四种切片方法的对比与选择
说了这么多,你可能想问:「那我到底该用哪种?」
我的经验是:先看特征类型,再看业务场景。下面这张表可以帮你快速决策:
| 特征类型 | 推荐切片方法 | 常见陷阱 | 我的建议 |
|---|---|---|---|
| 数值型 | 等频分桶 + 业务阈值 | 等宽分桶导致样本不均 | 优先用业务阈值,其次等频 |
| 类别型 | 按类别分组 + 低频标记 | 忽略低频类别 | 低频类别合并或单独建模 |
| 时间序列 | 按时间粒度 + 事件节点 | 时间漂移被忽略 | 结合数据质量检查 |
| 文本 | 按长度/情感/主题 | OOV 词比例过高 | 先做文本预处理再切片 |
4.6 单变量切片分析知识体系
下面这张图帮你理清本章的核心逻辑。四种特征类型各自对应不同的切片策略,最终都指向同一个目标——发现模型表现不一致的区域。