3. 窗口函数基础:ROW_NUMBER、RANK、DENSE_RANK、NTILE 与解释性场景
窗口函数(Window Functions)是 SQL 中用于在保持行级粒度的同时,对数据子集进行排序、分组聚合或偏移计算的强大工具。在模型解释性分析中,窗口函数常用于构建特征排序、分位数分段、异常检测标记以及对比基线生成。本章聚焦于四个核心排序窗口函数:ROW_NUMBER、RANK、DENSE_RANK 和 NTILE,并展示它们在解释性场景中的典型应用。
3.1 窗口函数核心语法
所有窗口函数均通过 OVER 子句定义窗口范围,基本语法结构如下:
<窗口函数>() OVER (
[PARTITION BY 列1, 列2, ...]
[ORDER BY 列3 [ASC|DESC], 列4 [ASC|DESC], ...]
[ROWS|RANGE 帧子句]
)
- PARTITION BY:将数据划分为独立的分区,窗口函数在每个分区内独立计算。
- ORDER BY:定义分区内的排序顺序,直接影响排序函数的排名结果。
- 帧子句:进一步限定窗口内的行范围(本章排序函数通常省略帧子句)。
3.2 四大排序函数对比
| 函数 | 排名规则 | 并列处理 | 排名是否连续 | 典型解释性场景 |
|---|---|---|---|---|
ROW_NUMBER() |
按顺序分配唯一序号 | 并列时随机分配不同序号 | 是 | 去重、Top-N 筛选、行号标记 |
RANK() |
相同值获得相同排名,后续跳过 | 保留并列,跳过后续排名 | 否(有间隙) | 特征重要性排序、竞赛排名 |
DENSE_RANK() |
相同值获得相同排名,后续不跳过 | 保留并列,不跳过后续排名 | 是 | 密度排序、分档分组 |
NTILE(n) |
将分区内行均匀分为 n 组 | 自动平衡组大小 | 分组编号 | 分位数分段、等频分箱 |
关键区别记忆: ROW_NUMBER 是“唯一编号”,RANK 是“跳跃排名”,DENSE_RANK 是“紧凑排名”,NTILE 是“等分分组”。
3.3 函数行为演示
假设有一张模型特征重要性评分表 feature_importance,包含字段 model_id、feature_name、importance_score。以下 SQL 在同一查询中展示四种函数的差异:
SELECT
model_id,
feature_name,
importance_score,
ROW_NUMBER() OVER (PARTITION BY model_id ORDER BY importance_score DESC) AS row_num,
RANK() OVER (PARTITION BY model_id ORDER BY importance_score DESC) AS rank,
DENSE_RANK() OVER (PARTITION BY model_id ORDER BY importance_score DESC) AS dense_rank,
NTILE(4) OVER (PARTITION BY model_id ORDER BY importance_score DESC) AS quartile
FROM feature_importance
WHERE model_id = 'model_v3'
ORDER BY importance_score DESC;
假设 model_v3 的部分特征重要性数据如下:
| feature_name | importance_score | ROW_NUMBER | RANK | DENSE_RANK | NTILE(4) |
|---|---|---|---|---|---|
| age | 0.85 | 1 | 1 | 1 | 1 |
| income | 0.72 | 2 | 2 | 2 | 1 |
| credit_score | 0.72 | 3 | 2 | 2 | 2 |
| loan_amount | 0.45 | 4 | 4 | 3 | 2 |
| employment_years | 0.30 | 5 | 5 | 4 | 3 |
| debt_ratio | 0.12 | 6 | 6 | 5 | 4 |
观察要点: income 和 credit_score 同为 0.72,ROW_NUMBER 给了不同序号(2 和 3),RANK 都给了 2 但跳过了 3,DENSE_RANK 都给了 2 且下一个排名为 3,NTILE(4) 将 6 行尽量均匀地分入 4 个组。
3.4 解释性场景应用
3.4.1 特征重要性 Top-N 筛选
使用 ROW_NUMBER 为每个模型选取最重要的前 K 个特征,用于模型简化或特征子集分析:
WITH ranked_features AS (
SELECT
model_id,
feature_name,
importance_score,
ROW_NUMBER() OVER (PARTITION BY model_id ORDER BY importance_score DESC) AS rn
FROM feature_importance
)
SELECT model_id, feature_name, importance_score
FROM ranked_features
WHERE rn <= 5;
3.4.2 特征重要性分档(DENSE_RANK)
使用 DENSE_RANK 将特征按重要性密度分为若干等级,便于观察重要性分布结构:
SELECT
model_id,
feature_name,
importance_score,
DENSE_RANK() OVER (PARTITION BY model_id ORDER BY importance_score DESC) AS importance_tier
FROM feature_importance
WHERE model_id = 'model_v3';
3.4.3 等频分箱与分位数分段(NTILE)
使用 NTILE 将预测值或特征值均匀分为 n 段,用于构建分位数特征或评估模型在不同分段的性能:
SELECT
prediction_id,
predicted_probability,
actual_label,
NTILE(10) OVER (ORDER BY predicted_probability) AS decile
FROM model_predictions
WHERE model_id = 'model_v3';
注意: NTILE 在行数不能被组数整除时,前若干组会多一行。例如 10 行分 3 组,分组结果为 4、3、3 行。在解释性分析中,建议使用 NTILE(10) 或 NTILE(100) 构建十分位或百分位,便于后续计算 Lift 图或累积增益。
3.5 窗口函数在解释性分析中的典型流程
3.6 常见陷阱与最佳实践
- 排序方向一致性: 确保
ORDER BY的方向与分析目标一致。特征重要性通常降序排列,异常分数通常升序排列。 - 分区键选择: 在多模型对比场景中,务必使用
PARTITION BY model_id避免跨模型排名混淆。 - NTILE 组数选择: 组数不宜过多(通常 5~20 组),否则部分组样本过少导致统计不稳定。
- 并列数据处理: 若业务要求严格唯一排名,使用
ROW_NUMBER并添加二级排序字段(如特征名称)打破平局。 - 性能注意: 窗口函数在大型数据集上可能产生较高的内存消耗,建议在分区键和排序列上建立索引。
3.7 综合案例:构建模型预测十分位评估表
以下 SQL 使用 NTILE 和 ROW_NUMBER 构建模型预测的十分位评估表,用于计算每个分段的响应率、捕获率和 Lift 值:
WITH decile_data AS (
SELECT
prediction_id,
predicted_probability,
actual_label,
NTILE(10) OVER (ORDER BY predicted_probability DESC) AS decile,
ROW_NUMBER() OVER (ORDER BY predicted_probability DESC) AS row_id
FROM model_predictions
WHERE model_id = 'model_v3'
)
SELECT
decile,
COUNT(*) AS total_records,
SUM(actual_label) AS positive_count,
ROUND(AVG(actual_label), 4) AS response_rate,
ROUND(SUM(actual_label) * 1.0 /
(SELECT SUM(actual_label) FROM decile_data), 4) AS capture_rate,
ROUND(AVG(actual_label) /
(SELECT AVG(actual_label) FROM decile_data), 4) AS lift
FROM decile_data
GROUP BY decile
ORDER BY decile;
解释性价值: 通过十分位评估表,数据科学家可以快速判断模型在高分段是否有效区分正负样本。若前两个分段的 Lift 值显著高于 1,说明模型排序能力良好。