第3章:单变量特征构建——从原始数据到信息提炼

各位好,欢迎来到第三章。前两章我们聊了特征工程的整体框架和自动化思路,今天要扎进一个最基础也最容易被忽视的环节——单变量特征构建。

说白了,就是只盯着一个字段,看你能从它身上榨出多少有价值的信息。我见过不少团队,一上来就搞复杂的交叉特征、高阶变换,结果模型效果还不如把单变量特征做扎实。嗯,这里要注意:单变量是地基,地基不稳,楼盖得再高也白搭。

核心观点:单变量特征构建不是简单地把原始字段丢进模型,而是通过统计变换、业务规则、时序窗口三种视角,把一个字段变成多个有区分度的特征。

单变量特征构建三大支柱 单变量特征构建 基于统计的特征 常用统计量 均值 · 方差 · 标准差 偏度 · 峰度 · 分位数 基于业务规则的特征 常见变换 比率 · 差值 · 阈值 分组归一化 · 分箱 时间序列基础特征 时序窗口操作 滞后特征 · 滑动窗口 差分 · 累积统计 💡 实践建议 先做统计特征打底 → 再结合业务规则提炼 → 最后对时序字段做窗口特征 三者可以叠加使用,但要注意特征之间的相关性

3.1 基于统计的特征——用数学语言描述分布

你想想看,一个数值字段,比如用户的消费金额,它背后藏着多少信息?平均值告诉你一般水平,方差告诉你波动大小,偏度和峰度则告诉你分布的形状。这些统计量,就是我们把原始数据翻译成模型能理解的语言。

3.1.1 集中趋势与离散程度

最基础的,均值、中位数、众数。但我个人习惯,从来不会只取一个。为什么?因为均值对异常值太敏感了。我在做电商用户画像时遇到过,某个用户的消费均值被几个大促订单拉高了好几倍,结果模型把他归为高净值用户,推荐的全是奢侈品——其实他平时只买日用品。

统计量 含义 适用场景
均值 整体平均水平 数据分布对称、无极端值
中位数 排序后中间位置的值 数据有偏、存在异常值
方差/标准差 数据波动程度 衡量稳定性、风险
变异系数 标准差/均值,无量纲 比较不同量纲字段的离散程度

我的小技巧:做风控模型时,我经常把均值和中位数的差值作为一个特征。差值越大,说明数据越偏,异常行为越明显。这个特征在反欺诈场景下特别管用。

3.1.2 分布形状:偏度与峰度

偏度描述数据分布是否对称,峰度描述分布是"尖"还是"平"。这两个特征在金融、生物统计领域用得很多。

  • 偏度 > 0:右偏,尾巴在右边,说明存在较大的极端值
  • 偏度 < 0:左偏,尾巴在左边,说明存在较小的极端值
  • 峰度 > 3:尖峰厚尾,极端值出现的概率比正态分布高
  • 峰度 < 3:平峰薄尾,数据分布比较均匀

我曾经在构建用户活跃度特征时,发现活跃天数的峰度高达 12.7。这说明什么?大部分用户要么几乎不活跃,要么天天在线,中间状态很少。这个发现直接指导我们做了用户分层策略。

import numpy as np
import pandas as pd
from scipy.stats import skew, kurtosis

def build_stat_features(series, prefix='feature'):
    """
    从单变量序列中提取统计特征
    """
    features = {}
    features[f'{prefix}_mean'] = np.mean(series)
    features[f'{prefix}_std'] = np.std(series)
    features[f'{prefix}_median'] = np.median(series)
    features[f'{prefix}_skew'] = skew(series)
    features[f'{prefix}_kurt'] = kurtosis(series)
    features[f'{prefix}_range'] = np.max(series) - np.min(series)
    features[f'{prefix}_cv'] = features[f'{prefix}_std'] / (features[f'{prefix}_mean'] + 1e-8)
    return features

# 示例:对用户消费金额提取统计特征
amount_series = df['user_consumption']
stat_feats = build_stat_features(amount_series, prefix='consumption')
print(stat_feats)

3.2 基于业务规则的特征——让领域知识说话

统计特征很通用,但真正让模型"开窍"的,往往是那些带着业务理解的特征。说白了,就是把你对业务的理解,编码成模型能看懂的数值。

3.2.1 比率特征

比率是最直观的业务特征。比如电商场景下,转化率 = 购买人数 / 访客人数。单独看访客数和购买数可能看不出什么,但一除,用户质量就出来了。

我记得在做一个信贷评分项目时,原始字段有"月收入"和"月还款额"。单独看收入,有人月入5万,有人月入5千,但还款压力完全不同。我们构造了债务收入比 = 月还款额 / 月收入,这个比率直接成了模型里权重最高的特征之一。

避坑指南:我曾经在构造比率时,没注意分母可能为0。结果线上模型跑出来一堆无穷大,把整个评分卡搞崩了。记住:所有除法都要加平滑项或做空值处理。

3.2.2 差值特征

差值反映的是"变化"和"差距"。常见的有:

  • 值差值:|A - B|,衡量差距
  • 相对差值:(A - B) / B,衡量变化幅度
  • 排名差值:rank(A) - rank(B),消除量纲影响

比如在用户增长分析中,本月新增用户数 - 上月新增用户数,正数说明增长加速,负数说明增长放缓。这个差值比原始数值更有业务含义。

3.2.3 阈值特征

阈值特征就是把连续值变成离散的"是否"判断。比如:

def build_threshold_features(series, thresholds, prefix='feature'):
    """
    根据业务阈值生成二值特征
    """
    features = {}
    for i, thresh in enumerate(thresholds):
        features[f'{prefix}_over_{thresh}'] = (series > thresh).astype(int)
        features[f'{prefix}_under_{thresh}'] = (series < thresh).astype(int)
    return features

# 示例:对用户年龄设置业务阈值
age_thresholds = [18, 30, 45, 60]
age_feats = build_threshold_features(df['age'], age_thresholds, prefix='age')

阈值怎么定?我的经验是:先看业务常识,再看数据分布。比如年龄的18岁、60岁,这是法律和退休年龄决定的,不是算出来的。如果业务上没有明确阈值,可以用分位数(比如P25、P75)作为参考。

3.3 时间序列基础特征——让历史说话

如果你的数据带时间戳,那恭喜你,你手里握着金矿。时序特征能捕捉趋势、周期性和惯性。我常说:没有时序特征的时间序列模型,就像没有记忆的人。

3.3.1 滞后特征(Lag Features)

滞后特征就是用过去的值来预测现在。比如用昨天的销量预测今天的销量。

def build_lag_features(df, col, lags=[1, 3, 7, 30]):
    """
    生成滞后特征
    """
    for lag in lags:
        df[f'{col}_lag_{lag}'] = df[col].shift(lag)
    return df

# 示例:对日销售额生成滞后1天、7天、30天的特征
sales_df = build_lag_features(sales_df, 'daily_sales', lags=[1, 7, 30])

这里有个坑:滞后特征会引入未来信息。如果你在训练时用了t-1的数据来预测t,那预测时也必须保证t-1的数据是已知的。我在做实时预测系统时,就因为滞后窗口没对齐,导致线上效果比线下差了一大截。

3.3.2 滑动窗口特征(Rolling Window Features)

滑动窗口比单点滞后更稳健。它计算过去N个时间点的统计量,能平滑噪声,捕捉趋势。

窗口统计量 含义 典型窗口大小
滑动均值 过去N期的平均水平 7天、30天
滑动标准差 过去N期的波动程度 7天、30天
滑动最大值 过去N期的峰值 3天、7天
滑动最小值 过去N期的谷值 3天、7天
滑动偏度 过去N期的分布形状 14天、30天
def build_rolling_features(df, col, windows=[7, 14, 30]):
    """
    生成滑动窗口统计特征
    """
    for w in windows:
        df[f'{col}_rolling_mean_{w}d'] = df[col].rolling(window=w).mean()
        df[f'{col}_rolling_std_{w}d'] = df[col].rolling(window=w).std()
        df[f'{col}_rolling_max_{w}d'] = df[col].rolling(window=w).max()
        df[f'{col}_rolling_min_{w}d'] = df[col].rolling(window=w).min()
    return df

# 示例:对股票收盘价生成7天和30天的滑动特征
stock_df = build_rolling_features(stock_df, 'close_price', windows=[7, 30])

经验之谈:窗口大小怎么选?我一般会试3个尺度:短期(3-7天)、中期(14-30天)、长期(60-90天)。不同业务场景敏感的时间尺度不一样。比如电商促销影响通常持续3-7天,而用户习惯变化可能需要30天才能看出来。

3.3.3 差分与累积特征

除了滞后和滑动窗口,还有两个常用的时序变换:

  • 一阶差分:当前值 - 上一个值,用来消除趋势,让序列平稳
  • 累积和:从起点到当前点的累加,用来捕捉总量变化
# 一阶差分
df['sales_diff_1'] = df['daily_sales'].diff(1)

# 累积和
df['sales_cumsum'] = df['daily_sales'].cumsum()

# 同比/环比
df['sales_yoy'] = df['daily_sales'] / df['daily_sales'].shift(365) - 1  # 同比
df['sales_mom'] = df['daily_sales'] / df['daily_sales'].shift(30) - 1   # 环比

嗯,这里要注意:差分会减少数据量(第一行变成NaN),累积和则会让数值越来越大。我在做库存预测时,累积和特征帮了大忙——它能反映"到目前为止的总需求",比单日销量稳定得多。

3.4 本章小结——单变量特征的组合策略

三种方法不是互斥的,而是可以叠加的。我常用的策略是:

  1. 先做统计特征:均值、方差、偏度、峰度,快速了解数据分布
  2. 再结合业务规则:比率、差值、阈值,把领域知识编码进去
  3. 最后处理时序字段:滞后、滑动窗口、差分,捕捉时间维度信息

举个例子,一个用户的"月消费金额"字段,经过单变量特征构建后,可以变成:

  • 统计特征:月消费均值、月消费标准差、月消费偏度
  • 业务规则:月消费/月收入(消费收入比)、月消费是否超过5000(阈值特征)
  • 时序特征:上月消费金额(滞后1月)、近3月消费滑动均值、环比增长率

一个字段变成七八个特征,而且每个都有明确的业务含义。这才是单变量特征构建的正确打开方式。

最后说一句:别小看单变量。我见过太多项目,花大量精力做复杂特征交互,结果模型提升不到1%。而把单变量特征做扎实、做干净,往往能带来5%-10%的提升。有时候,最简单的特征就是最好的特征。