作者:铭新
在指标分析场景中,我们经常会遇到这样的问题:
为什么这个月销售额下降了?
到底是哪个地区、渠道、商品类型或客户群体导致的?
当数据量不大、候选维度较少时,可以直接使用 Python 做归因分析。
但当场景升级到:
- 单分区约 2 亿行明细数据;
- 候选维度接近 100 个;
- 最终还要输出清晰、可解释的归因结果;
问题就不再只是选择哪种算法,而是如何控制计算规模。
我们的核心思路是:
不让 Python 直接处理全量明细,而是在数据库侧先完成维度筛选和数据聚合,最后只把少量有效数据交给 Python 精算。
一、为什么不能直接对 100 个维度做归因?
最直接的方式,是将 100 个维度全部进行组合聚合,再交给 Python 分析。
但这种方式在大数据场景下几乎不可行。
首先,维度组合数量会快速膨胀。即使每个维度只有少量维值,多个维度组合后,也可能产生巨大的结果集。
其次,一些维度虽然字段可用,但并不适合归因,例如:
- 用户 ID;
- 订单号;
- 手机号;
- 流水号;
- 设备 ID。
这类字段通常接近一行一个值,只能定位具体记录,难以形成具有业务意义的结论。
相比之下:
浙江地区线上家电销售额下降 120 万。
显然比:
用户 100023 导致销售额下降 500 元。
更容易理解,也更具行动价值。
因此,在大数据归因中,必须先解决两个问题:
- 哪些维度值得进入归因;
- 如何把数据规模压缩到 Python 可以承载的范围。
二、总体思路:SQL 做筛选,Python 做精算
整体流程可以概括为:
100 个候选维度 ↓ 拆分预设维度和待筛选维度 ↓ 过滤无效、高基维度 ↓ 计算单维波动解释能力 ↓ 通过类似树模型 Gain 的方式继续剪枝 ↓ 选出最终 15 个维度 ↓ 数据库聚合两期数据 ↓ Python 执行最终归因不同技术层的职责也很清晰:
执行层 | 主要职责 |
数据库 | 维度过滤、候选筛选、数据聚合 |
Java 服务 | 复用平台取数口径、组织任务、传递指标总值 |
Python | Shapley、TreeSHAP、贡献计算和 TopN 输出 |
核心原则是:
数据库负责降规模,Python 负责高价值计算。
三、预设维度优先保护
实际生产中,通常已经存在一些业务确认过的常用维度,例如:
- 省份;
- 城市;
- 商品类型;
- 渠道;
- 客户类型。
这些维度有明确的业务价值,不应因为某一次数据分布变化就被轻易过滤。
因此,首先将维度拆分为两类:
预设维度:业务确认需要优先保留的维度 待筛选维度:其他候选维度最终目标是选择 15 个维度。
处理规则如下:
- 预设维度少于 15 个时,全部保留,其余名额由算法补齐;
- 预设维度正好为 15 个时,直接使用;
- 预设维度超过 15 个时,只在预设维度中选出最终 15 个。
这样既保留了业务经验,也避免了维度数量失控。
四、第一轮筛选:过滤无效和高基维度
对于非预设维度,首先计算:
- 数据总行数;
- 近似去重数;
- 去重数占比;
- 空值率。
然后过滤明显不适合归因的字段。
例如:
条件 | 处理 |
只有一个维值 | 剔除 |
空值率过高 | 剔除 |
去重数接近总行数 | 剔除 |
典型被过滤的字段包括:
- 用户 ID;
- 订单号;
- 流水号;
- 设备 ID。
高基维度需要提前过滤,主要有几个原因:
- 单个维值样本太少,结果容易受偶然波动影响;
- 归因结果会被拆成大量细小切片;
- 最终结果难以阅读和解释;
- 多维组合后,聚合数据规模难以控制;
- 树模型的重要性可能偏向高基特征。
完成硬性过滤后,再按照维度基数和配置比例保留部分候选维度。
这一阶段的目标是:
约 100 个候选维度 → 30~50 个有效候选维度五、第二轮筛选:评估单维波动解释能力
仅靠基数无法判断一个维度是否真正有价值。
例如,一个维度基数很低,但它可能与指标变化几乎没有关系。
因此,需要分别从每个维度观察基期和对比期的变化。
以“省份”为例:
省份 | 基期销售额 | 对比期销售额 | 变化 |
浙江 | 200 万 | 80 万 | -120 万 |
江苏 | 150 万 | 90 万 | -60 万 |
上海 | 100 万 | 130 万 | +30 万 |
通过这些结果,可以判断一个维度:
- 能解释多少整体波动;
- 主要变化是否集中在少数维值;
- 变化方向是否与整体趋势一致。
然后为每个维度生成一个综合评分。
评分高的维度,通常具备以下特点:
- 对整体变化解释能力强;
- 头部原因比较清晰;
- 与整体变化方向一致;
- 结果更容易被业务理解。
这一阶段完成后,将候选维度进一步压缩到约 20~25 个。
六、第三轮筛选:减少维度之间的信息重复
单维评分只能判断每个维度单独是否有效,但不能判断不同维度之间是否重复。
例如:
- 省份和大区;
- 城市和城市等级;
- 商品类型和商品大类。
这些维度可能都得到较高评分,但表达的信息高度相似。
因此,可以借鉴 GBDT、LightGBM 的思路:
每一轮选择一个最能解释当前剩余波动的维度,选中后扣除它已经解释的部分,再选择下一个维度。
假设第一轮选择了“省份”。
省份解释掉一部分销售额变化后,下一轮会基于剩余未解释部分重新计算。
如果“大区”和省份高度相关,它的价值就会下降;而“商品类型”如果能够解释另一部分变化,则仍可能被选中。
通过多轮选择,可以降低最终维度之间的信息冗余。
需要强调的是:
这里并不是在 SQL 中完整实现 LightGBM,而是借鉴树模型的分裂收益和残差迭代思想,用于维度剪枝。
最终目标是从约 20~25 个候选维度中,选出最有价值的 15 个。
七、选出 15 个维度后,再进行正式聚合
维度选择完成后,数据库重新按照平台现有取数逻辑,生成基期和对比期的聚合数据。
例如:
省份 | 城市 | 商品类型 | 渠道 | 基期值 | 对比期值 | 变化 |
浙江 | 杭州 | 家电 | 线上 | 200 万 | 80 万 | -120 万 |
江苏 | 南京 | 家电 | 线上 | 150 万 | 90 万 | -60 万 |
上海 | 上海 | 数码 | 线下 | 100 万 | 130 万 | +30 万 |
这里必须继续复用平台原有取数口径,包括:
- 指标定义;
- 时间范围;
- 筛选条件;
- 下钻条件;
- 行级权限;
- 汇总规则。
维度筛选过程只决定“选择哪些维度”,不能改变正式指标口径。
八、Python 只处理可控规模的聚合数据
完成数据库侧剪枝后,Python 接收到的是最终 15 个维度的聚合数据,而不是 2 亿行明细。
Python 侧主要负责:
- Shapley 或 TreeSHAP 归因;
- 多维切片贡献计算;
- 正向和反向贡献拆分;
- TopN 排序;
- 贡献率计算;
- 结果 JSON 输出。
最终结果可以是:
导致指标下降的主要原因
排名 | 切片 | 贡献值 | 占整体下降 |
1 | 浙江 + 杭州 + 家电 + 线上 | -120 万 | 40% |
2 | 江苏 + 南京 + 家电 + 线上 | -60 万 | 20% |
抵消整体下降的因素
排名 | 切片 | 贡献值 | 抵消比例 |
1 | 上海 + 数码 + 线下 | +30 万 | 10% |
这样既保留了算法能力,也保证了结果的业务可读性。
九、非可加指标需要特别处理
对于销售额、订单金额等可加指标,可以通过聚合数据求和得到整体值。
但对于以下指标:
- 去重用户数;
- 平均值;
- 转化率;
- 留存率;
- 比例类指标;
不同切片之间通常不能简单相加。
因此,基期总值和对比期总值必须由平台现有取数逻辑提供,并传给 Python。
Python 可以负责贡献分配,但最终占比的分母仍使用平台权威总值。
这样可以避免归因结果与看板展示口径不一致。
十、从 100 个维度到 15 个维度
以销售额归因为例:
候选维度:100 个 预设维度:省份、城市、商品类型 基期销售额:1000 万 对比期销售额:700 万 整体下降:300 万处理过程如下:
100 个候选维度 → 剔除用户 ID、订单号、手机号等高基字段 → 保留约 42 个有效维度 → 单维评分后保留 25 个 → 已有 3 个预设维度 → 再从候选维度中选出 12 个 → 最终得到 15 个维度最终维度可能包括:
省份、城市、商品类型、渠道、门店类型、 品牌、供应商、客户类型、会员等级、订单类型、 支付方式、活动类型、终端类型、销售组织、业务线再由 Python 输出最终归因结果。
十一、这套方案解决的核心问题
这套方案真正解决的,并不只是算法问题,而是大数据归因的工程落地问题:
- 如何避免 Python 直接处理全量明细;
- 如何过滤无业务价值的维度;
- 如何控制维度组合规模;
- 如何减少重复维度;
- 如何保证归因口径与平台查询一致;
- 如何处理非可加指标;
- 如何让最终结果既准确又可解释。
整个方案可以总结为一句话:
SQL 负责把问题缩小,Python 负责把问题算准。
对于 2 亿行数据和 100 个候选维度,与其追求一次性完成全量计算,不如先通过数据库完成分层筛选,再将真正有价值的数据交给算法。
这也是大数据归因从“算法实验”走向“生产能力”的关键。