CR-011-特征矩阵Parquet存储形态由长表变更为宽表 » 历史记录 » 版本 1
Huarui Lin, 2026-04-17 16:36
| 1 | 1 | Huarui Lin | # CR-011-特征矩阵Parquet存储形态由长表变更为宽表 |
|---|---|---|---|
| 2 | |||
| 3 | | 字段 | 内容 | |
||
| 4 | |------|------| |
||
| 5 | | **CR ID** | CR-004 | |
||
| 6 | | **标题** | 特征矩阵落盘形态由长表(EAV)变更为宽表(38列并排) | |
||
| 7 | | **发起人** | Henry Lin (PM) | |
||
| 8 | | **日期** | 2026-04-17 | |
||
| 9 | | **状态** | ✅ Approved | |
||
| 10 | | **影响范围** | 规约 4.1 节特征目录、S3-04 产出物 Schema、`docs/data_contract.md`、下游 Step 5 数据加载逻辑 | |
||
| 11 | |||
| 12 | ### 1. 变更描述 |
||
| 13 | |||
| 14 | **原规约约束**: |
||
| 15 | > 上述 38 维特征在 `features.parquet` 中必须为**长表形态**(Schema: `fund_id`, `net_value_date`, `feature_name`, `feature_value`)。严禁 Pivot 成宽表。 |
||
| 16 | **变更为**: |
||
| 17 | > `features_YYYY.parquet` 必须为**宽表形态**。Schema 固定为:`fund_id`(String), `net_value_date`(Date), `segment_id`(UInt32), 以及 38 个特征列(如 `price_vs_ma_ratio_12w` Float64, ..., `calmar_ratio_52w` Float64)。严禁在落盘阶段使用 `unpivot` 转换为长表。 |
||
| 18 | |||
| 19 | ### 2. 变更原因(根因追溯) |
||
| 20 | |||
| 21 | 在 M3a 架构评审中发现,原“长表落盘”红线与高性能计算架构存在严重冲突: |
||
| 22 | 1. **计算层冲突**:截面 Z-Score 标准化与共线性剔除(IC 计算)的纯函数,在 Polars 中必须基于“行=样本,列=特征”的宽表矩阵才能进行高效向量化运算。若坚持长表,需在每次计算前后频繁执行 `pivot/unpivot`,带来巨额的 CPU 与内存开销。 |
||
| 23 | 2. **训练层冲突**:LightGBM 等树模型原生要求宽表输入,长表形态迫使 Step 5 必须额外维护一套高频 Pivot 读取逻辑。 |
||
| 24 | 3. **列式存储劣势**:对于 Parquet 这种列式格式,在特征维度固定(38维)且有限的情况下,宽表能完美利用“按列裁剪”优势;而长表(EAV)的 `feature_name` 字符串列会破坏列式压缩率,且按特征筛选时产生大量冗余 IO。 |
||
| 25 | 4. **维度确定性**:本系统特征已通过《特征字典基线文档》刚性冻结为 38 维,不存在“频繁增删特征导致宽表 Schema 失控”的 EAV 适用前提。 |
||
| 26 | |||
| 27 | ### 3. 影响分析 |
||
| 28 | |||
| 29 | | 受影响模块 | 影响说明 | 应对措施 | |
||
| 30 | |-----------|---------|---------| |
||
| 31 | | `feature_engineering.py` | 取消落盘前的 `unpivot()` 逻辑 | 内存中全程维持宽表,计算完毕后直接调用 PyArrow `write_parquet()` | |
||
| 32 | | `data_contract.md` | 存储形态红线描述变更 | 更新《特征矩阵接口契约单》,明确宽表 Schema 及 38 列的严格类型映射 | |
||
| 33 | | `trainer.py` (Step 5) | 简化数据加载逻辑 | 直接读取宽表,废弃原有的 GroupBy+Pivot 还原逻辑 | |
||
| 34 | | 元数据校验 | Hash 与审计逻辑适配 | `features_hash.json` 基于宽表文件计算,无需变更工具函数 | |