3. 特征构建入门:基于统计的特征构造
我个人习惯把统计特征叫做"数据的第一印象"。它用最简单的数学语言,告诉你一批数值到底长什么样。我在做信贷风控项目时,第一版模型只用原始字段,AUC怎么也上不了0.75。后来加了申请人的收入均值、近3个月交易额方差、以及历史贷款金额的分位数,模型直接涨了5个点。说白了,统计特征就是把隐藏的分布信息,硬生生拽到台面上来。
为什么统计特征这么能打?
你想想看,原始数据只是一堆孤立的数值。均值告诉你"中心在哪",方差告诉你"散不散",分位数告诉你"分布偏不偏"。这些信息,原始字段根本没法直接表达。我建议你养成一个习惯:拿到任何数值型字段,先算一遍基础统计量。不用多,均值、方差、四分位数就够。
举个例子,用户A每次消费100元,用户B有时花10元有时花190元。原始字段"金额"看不出区别,但方差就暴露了——一个为0,一个很大。模型拿到这个信号,自然会调整权重。
五大基础统计量
嗯,这里要注意。很多人一上来就搞复杂特征,其实基础统计量已经能解决80%的问题。我总结了一份速查表,平时照着这个清单做就行。
| 统计量 | 含义 | 典型应用场景 |
|---|---|---|
| 均值 | 数据的中心位置 | 用户平均消费、系统平均响应时间 |
| 方差 / 标准差 | 数据的离散程度 | 用户行为稳定性、传感器噪声评估 |
| 中位数 | 不受极端值影响的中心 | 收入分布、房价预测 |
| 分位数(Q1 / Q3) | 数据分布的区间 | 异常值检测、风险评级 |
| 偏度 / 峰度 | 分布的形状特征 | 金融收益率分析、流量异常预警 |
你可能觉得偏度峰度有点冷门。但我在电商用户画像项目里,就是用"购买金额偏度"区分出了"偶尔大额"和"持续小额"两类用户,模型区分度一下提升了。别小看它。
滑动窗口:让统计特征活起来
静态统计量只能描述"全量"特征,但很多场景需要"最近一段时间的表现"。比如风控——用户今天的行为,跟过去7天、30天的均值比,有没有异常?
滑动窗口就是干这个的。我建议你用三种窗口:7天、14天、30天,覆盖短期和中期行为。
import pandas as pd
import numpy as np
# 模拟交易数据
df = pd.read_csv('transactions.csv')
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values(['user_id', 'date'])
# 滑动窗口统计特征
df['amt_mean_7d'] = df.groupby('user_id')['amount'].transform(
lambda x: x.rolling(7, min_periods=3).mean())
df['amt_std_7d'] = df.groupby('user_id')['amount'].transform(
lambda x: x.rolling(7, min_periods=3).std())
df['amt_max_14d'] = df.groupby('user_id')['amount'].transform(
lambda x: x.rolling(14, min_periods=5).max())
这里有个细节:min_periods控制最少需要几个值才计算。数据稀疏时设小一点,不然全是空值。我在物联网项目里吃过亏,传感器偶尔掉线,窗口全空,模型直接崩了。
分组统计:挖掘群体差异
单看一个人的统计量,信息有限。但把他放到"同类人"里一比,差异就出来了。分组统计的精髓就在这——用群体分布做参照系,刻画个体的偏离程度。
# 按用户分组聚合统计
user_stats = df.groupby('user_id')['amount'].agg([
('mean', 'mean'),
('std', 'std'),
('q25', lambda x: x.quantile(0.25)),
('q75', lambda x: x.quantile(0.75)),
('count', 'count')
]).reset_index()
# 与群体对比:计算个体相对于群体的偏离
global_mean = df['amount'].mean()
user_stats['deviation'] = user_stats['mean'] - global_mean
我记得在金融场景里,用"用户月均消费 / 全平台月均消费"这个比率特征,比直接用原始金额效果好得多。因为它天然消除了不同时期的量纲差异。
分位数与分布形状
分位数比均值更抗噪。你想啊,一个极端值就能把均值拉偏,但中位数稳如泰山。所以我做特征工程时,只要字段有长尾,一定把Q1、Q2、Q3全加上。
# 构造分位数特征
features = df.groupby('user_id')['amount'].agg(
q10 = lambda x: x.quantile(0.10),
q25 = lambda x: x.quantile(0.25),
q50 = lambda x: x.quantile(0.50),
q75 = lambda x: x.quantile(0.75),
q90 = lambda x: x.quantile(0.90),
iqr = lambda x: x.quantile(0.75) - x.quantile(0.25)
).reset_index()
四分位距(IQR)特别适合做异常值标记。我在流量预警项目里,用IQR做基线,超出1.5倍IQR的直接标红,召回率比固定阈值高了30%。
核心思路: 统计特征的本质是用"分布信息"替换"原始数值"。均值、方差、分位数各抓一个侧面的分布信号,组合起来就是一套完整的分布画像。
避坑指南
说了这么多好的,来聊聊我踩过的坑。
我曾经在一家电商公司的用户画像项目里,对所有用户直接算全局均值和方差,忽略了时间窗口。结果促销活动那几天的数据把均值拉得老高,模型误判了正常用户的购买力。后来我强制要求:统计特征必须带"时间范围",要么滑动窗口,要么固定周期。
还有几个常见问题,你碰到时留个心眼:
- 数据泄露: 用未来数据算统计量去预测过去,是时间序列里最常见的坑。一定用
shift()把窗口滞后一天。 - 稀疏数据: 用户只有一两条记录时,方差没法算。设个最小样本数,不够就用默认值。
- 特征膨胀: 均值、方差、分位数、偏度、峰度全加,一个字段衍生出十几个特征。先加基础统计量,效果好再逐步扩展。
# 防泄露的正确做法:用历史窗口,不含当前行
df['amt_mean_7d_lag'] = df.groupby('user_id')['amount'].transform(
lambda x: x.shift(1).rolling(7, min_periods=3).mean())
统计特征看似简单,但它在真实项目中的爆发力远超你想象。我见过很多团队花大量精力搞复杂特征,回过头来才发现,几个基础统计量已经把基线拉高了一大截。嗯,先把它吃透,后面再聊更高级的构建方法。