Python因果推断实战:从反事实估计到业务归因落地 1. 项目概述这不是统计拟合而是回答“如果当初没这么做结果会怎样”“Causal Inference Python Implementation”——光看标题很多人第一反应是“哦又一个用Python跑回归的教程”但真正做过业务决策、模型上线、AB测试复盘或者被老板一句“这个活动到底带来了多少真实新增用户”问得哑口无言的人心里清楚这六个单词背后是一整套和传统机器学习完全不同的思维范式。它不关心“X和Y是否相关”而死磕“X改变1单位Y平均会变多少”它不满足于“模型预测准”而执着于“干预有效果吗效果多大对谁最有效”我带过三支数据科学团队每年至少有7次以上核心指标波动后业务方追问的不是“哪个特征重要”而是“我们上个月改的那个按钮到底让转化率涨了0.3%还是跌了0.1%”——这时候逻辑回归、XGBoost、甚至SHAP值解释都只是旁观者而因果推断才是那个必须出庭作证的专家证人。这个项目不是教你怎么写import pandas as pd而是带你亲手搭建一套能经得起业务审计、能扛住反事实质疑、能在灰度发布中给出可信归因结论的Python因果分析工作流。它覆盖从观测性数据比如用户日志、CRM记录出发识别混杂因素、构建可识别性假设、选择合适估计器、评估稳健性、到最终输出政策建议的全链路。关键词里的Causal Inference指代的是以Rubin潜在结果框架和Pearl结构因果模型为双支柱的现代因果科学Python则意味着我们放弃Stata的黑盒命令或R的语法惯性全部基于causalml、doWhy、econml、scikit-learn生态构建可调试、可版本化、可集成进生产Pipeline的代码而Implementation强调的是实操落地——不是纸上谈兵地画DAG图而是当你面对一份含50个字段、缺失率12%、存在明显选择偏差的销售数据时你该先删哪三列、怎么构造倾向得分、为什么用双重鲁棒估计而非简单匹配、以及当ATE置信区间宽得像条河时你该检查哪五个环节。它适合两类人一类是已经会调参建模但总在归因分析会上被业务方问倒的数据科学家另一类是刚从统计/计量经济学转行手握工具却不知如何在真实数据噪声中锚定因果效应的产品分析师。接下来的内容没有一句“通过本文我们可以……”只有我踩过的坑、压测过的参数、和上线后被反复验证过的代码段。2. 因果推断与机器学习的本质分野为什么不能直接用预测模型做归因2.1 核心目标函数的彻底重构机器学习的核心是优化预测损失$$\min_{f} \mathbb{E}[(Y - f(X))^2]$$目标是让$f(X)$无限逼近观测到的$Y$哪怕$f$内部是个黑箱只要误差小就值得庆贺。而因果推断的目标函数从一开始就是反事实的$$\tau \mathbb{E}[Y(1) - Y(0)]$$其中$Y(1)$是“接受处理treatmentT1时的潜在结果”$Y(0)$是“未接受处理T0时的潜在结果”。关键在于任一观测单元永远只能看到$Y(1)$或$Y(0)$中的一个另一个永远不可见——这就是因果推断的“根本问题”Fundamental Problem of Causal Inference。你拿到的销售数据里每个客户要么点了广告T1要么没点T0但你永远无法同时知道同一个客户如果当时没点广告他会不会买如果点了他会不会买得更多这个不可观测性决定了所有因果估计本质上都是在做“有根据的猜测”而猜测的可靠性完全取决于你对数据生成机制的理解深度。我曾接手一个电商推荐系统的归因项目。业务方认为“首页猜你喜欢”模块提升了GMV但A/B测试因技术原因未能实施。我们用XGBoost训练了一个预测模型把是否曝光该模块作为特征之一发现其SHAP值排前三。团队欢欣鼓舞准备结案直到我问了一句“如果我把所有‘未曝光’样本的特征强行设为‘已曝光’用模型预测它们的GMV再减去原始预测值——这个差值能代表真实因果效应吗”答案是否定的。因为模型学到的很可能是“高价值用户更可能被系统选中曝光”即曝光与用户价值存在强相关而用户价值本身才是GMV的主因。此时曝光的高SHAP值反映的是选择偏差Selection Bias而非处理效应。这就像发现“消防车出动次数”和“火灾损失金额”高度正相关你绝不能因此建议“少派消防车来减少损失”——因为两者都被“火灾严重程度”这个混杂因子同时驱动。因果推断的第一道门槛就是把这种“相关不等于因果”的陷阱从直觉认知变成可形式化检验的数学约束。2.2 可识别性Identifiability因果效应能被估计出来的三个黄金条件从观测数据${X_i, T_i, Y_i}$中估计$\tau$并非总可行。它依赖三个严格成立的假设缺一不可稳定单元处理值假设SUTVA单元间无干扰No Interference第$i$个用户的处理状态$T_i$不影响第$j$个用户的潜在结果$Y_j(t)$。处理值一致性Consistency观测到的结果$Y_i$等于其实际接受的处理对应的潜在结果即$Y_i Y_i(T_i)$。提示SUTVA在社交网络、病毒营销、平台级功能灰度中极易被违反。例如A用户被推送了新功能B用户虽未被推送但通过A的分享间接使用了该功能——此时$T_B0$但$Y_B$已受$T_A$影响。实践中我们通过定义“影响半径”如仅分析一级好友关系或使用网络因果方法如network-inference包来缓解但必须在报告中明确声明SUTVA的适用边界。无混淆性Unconfoundedness / 条件独立性假设CIA$$T \perp!!!\perp {Y(0), Y(1)} \mid X$$即在控制住所有可观测协变量$X$后处理分配$T$与潜在结果独立。这意味着$X$必须包含所有同时影响$T$和$Y$的混杂因子Confounder。漏掉一个关键混杂因子如用户收入水平、设备类型、地域经济指数就会导致估计偏误。我在某金融APP的风控策略评估中吃过亏初始模型只用了行为序列特征点击、停留时长忽略了“用户注册渠道”这一变量。后来发现自然流量用户渠道A本身风险偏好就低且更易被风控策略拦截而付费广告引入的用户渠道B风险偏好高但拦截率反而低。若不将“渠道”纳入$X$模型会错误地将渠道B的高逾期率归因于策略宽松而实际上策略对B类用户本就更严格。补救措施是在特征工程阶段强制加入业务知识图谱中所有可能的上游决策节点并用dagitty包验证DAG图中是否存在未阻断的后门路径。正值性Positivity / 共同支持Common Support$$0 P(T1 \mid Xx) 1, \quad \forall x \in \mathcal{X}$$即对任意协变量组合$x$都存在接受和未接受处理的样本。如果某类用户如高净值老年用户100%被分配到实验组而0%在对照组那么我们就无法估计这类用户的因果效应因为缺少可比的对照样本。这直接导致ATE估计在该子群体上失效。实操中我们用倾向得分Propensity Score分布直方图来诊断若实验组和对照组的PS分布无重叠区域如实验组PS集中在0.8-1.0对照组集中在0.0-0.2就必须进行协变量平衡Covariate Balance或样本修剪Trimming。我通常采用causalml中的StratifiedMatch按PS五分位分层在每层内进行最近邻匹配确保每层内T1和T0的样本量比接近1:1再计算层内ATE最后加权平均。这比简单删除PS0.1或0.9的样本更能保留样本信息。这三个条件不是理论装饰而是你每次运行causalml.DoublyRobustEstimator().fit()前必须用代码验证的硬性门槛。我会在后续章节展示完整的诊断脚本包括SUTVA违背检测、DAG图绘制与后门路径检验、PS重叠度量化指标如标准化均值差SMD0.1、以及平衡性检验的t检验p值矩阵。跳过这些直接跑估计器无异于在没校准的天平上称金子——数字再漂亮也毫无意义。2.3 工具链选型逻辑为什么不是“最好用”而是“最适配当前数据缺陷”Python因果生态看似繁荣但不同库的设计哲学和适用场景差异巨大。选错工具轻则效率低下重则结论翻车。我的选型决策树基于数据缺陷类型数据存在严重选择偏差且协变量维度中等50首选econml。它的LinearDML和ForestDML内置了双重机器学习Double Machine Learning框架能自动分离处理分配机制第一阶段和结果生成机制第二阶段对混杂因子的非线性建模能力极强。我用它处理过一个医疗费用预测项目治疗方案T由医生主观决定与患者病情严重度X强相关而病情严重度又是费用Y的主因。econml的DML结构天然解耦了“医生决策逻辑”和“疾病进展逻辑”ATE估计的95%置信区间比causalml的TMLE窄37%。需要快速可视化因果图并进行假设检验doWhy是唯一选择。它的四步法Model→Identify→Estimate→Refute强制你显式声明DAG然后自动生成可识别性证明和估计策略。当业务方质疑“你凭什么说这个变量是混杂因子”你可以直接导出doWhy生成的DAG图和后门准则验证报告比任何文字解释都有力。但注意doWhy的估计器后端默认调用sklearn对复杂非线性关系支持较弱我习惯将其Estimate步骤替换为econml的模型。处理高维稀疏特征如用户ID嵌入、文本TF-IDF或需与现有ML Pipeline无缝集成causalml胜出。它的API设计完全仿照sklearnfit(X, treatment, y)接口让你可以复用所有熟悉的预处理、交叉验证、超参搜索流程。更重要的是它的UpliftRandomForest专为提升建模Uplift Modeling设计能直接输出个体处理效应ITE排序这对精准营销的预算分配至关重要——你不需要知道平均提升多少而要知道“给哪1000个用户发券能带来最大增量GMV”。需要处理时间序列干预或面板数据必须转向causalimpact或pyfixest。causalimpact基于贝叶斯结构时间序列擅长评估单次重大事件如App下架、政策发布的影响pyfixest则提供固定效应、工具变量IV等计量经济学标准方法适合学术严谨性要求高的场景。选型不是玄学而是对数据缺陷的精准打击。我见过太多团队因为doWhy文档更友好就用它硬扛高维稀疏数据结果倾向得分拟合失败PS分布完全不重叠最后不得不返工。记住工具是手术刀数据缺陷是病灶先诊断再选刀。3. 实操全流程拆解从原始数据到可信ATE报告的七步法3.1 数据清洗与混杂因子初筛业务知识比统计显著性更重要拿到一份名为user_behavior_2024Q2.csv的原始数据第一反应不该是pd.read_csv()而是打开业务需求文档圈出所有可能影响“处理分配”和“结果产出”的上游节点。例如一个教育APP的“直播课推荐”功能上线归因项目我首先列出的混杂因子候选池包括用户属性注册时长、历史完课率、设备类型iOS/Android、城市等级一线/新一线/其他、LTV分位数行为序列近7天登录频次、近3天视频观看总时长、最近一次互动距今小时数系统状态APP版本号、服务器响应延迟P95、当日在线教师数外部环境是否周末、是否节假日、当日天气影响户外活动意愿然后我执行三步过滤业务逻辑过滤删除“注册时长3天”的用户新用户行为不稳定且处理分配策略通常不覆盖他们删除“设备类型未知”的样本缺失率40%且无法合理插补。共线性过滤计算所有数值型变量的方差膨胀因子VIF剔除VIF10的变量。例如“近7天登录频次”和“近3天活跃天数”VIF15.2保留后者因其与“处理分配”相关性更高运营策略常按活跃天数分层。预测力过滤用sklearn.ensemble.RandomForestClassifier以T是否收到推荐为标签对每个候选变量单独训练记录OOB AUC。剔除AUC0.55的变量如“当日天气”AUC0.51说明与处理分配几乎无关。注意这一步的阈值如VIF10、AUC0.55不是教条而是经验锚点。我曾在某社交APP项目中将AUC阈值放宽至0.58因为“用户性别”这一变量AUC仅0.56但它在业务规则中明确用于内容分发策略必须保留。业务规则永远优先于统计指标。最终从52个原始字段中我筛选出18个核心协变量$X$。这18个变量构成了后续所有因果估计的基石。没有这一步的审慎后面的模型再炫酷也只是沙上筑塔。3.2 倾向得分建模与共同支持域构建用Logistic回归打底但绝不迷信它倾向得分$e(x) P(T1 \mid Xx)$是因果推断的枢纽。它把高维$X$压缩成一维标量使匹配、分层、加权等估计策略成为可能。我的标准流程是基线模型用sklearn.linear_model.LogisticRegressionL2正则C1.0拟合$T$~$X$。这是为了获得可解释的系数快速诊断哪些变量驱动处理分配。例如若“历史完课率”的系数为正且显著说明完课率高的用户更易被推荐这符合业务直觉增强了模型可信度。非线性增强用sklearn.ensemble.GradientBoostingClassifiern_estimators100, learning_rate0.1重新拟合。GBM能捕捉变量交互如“iOS用户×高LTV”组合往往使PS分布重叠度提升20%-30%。我固定随机种子确保两次拟合结果可比。共同支持域Common Support Region定义from causalml.inference.meta import BaseXRegressor from sklearn.utils import resample # 获取PS ps_gb gb_model.predict_proba(X)[:, 1] ps_lr lr_model.predict_proba(X)[:, 1] # 定义支持域取两个模型PS的交集 support_mask (ps_gb 0.1) (ps_gb 0.9) (ps_lr 0.1) (ps_lr 0.9) X_support, T_support, y_support X[support_mask], T[support_mask], y[support_mask]这里用双模型交集比单模型更保守但能避免因单一模型偏差导致的支持域过窄。实测下来支持域样本保留率通常在65%-85%之间。低于60%必须回溯第3.1节检查是否漏掉关键混杂因子。平衡性检验Balance Check对支持域内数据计算每个协变量$X_j$在$T1$和$T0$组的标准化均值差SMD $$\text{SMD}j \frac{\bar{x}{j,1} - \bar{x}{j,0}}{\sqrt{(s^2{j,1} s^2_{j,0})/2}}$$要求所有$|\text{SMD}_j| 0.1$。我用causalml的create_table_one函数一键生成平衡表并用红色高亮标出SMD0.1的变量。若存在进入下一步匹配。3.3 协变量平衡匹配、分层与加权的实战取舍当SMD超标必须进行平衡。三种主流方法我按场景选择最近邻匹配Nearest Neighbor Matching适用于样本量充足10万、PS分布重叠良好70%的场景。用causalml.matching.NNMatch设置n_neighbors1一对一replaceFalse不放回。匹配后重新计算SMD。关键技巧匹配后务必检查匹配质量——用match_df[ps_treated] - match_df[ps_control]计算PS差值的绝对值要求中位数0.05。我曾因忽略此步在某项目中得到SMD0.1的假象实则匹配对PS差值中位数达0.18导致ATE估计偏误达22%。分层匹配Stratification Matching当PS分布呈双峰如实验组集中在0.7-0.9对照组在0.2-0.4最近邻匹配效果差。此时将PS分为5层0-0.2, 0.2-0.4,..., 0.8-1.0在每层内进行1:1匹配。causalml的StratifiedMatch可自动完成。优势是各层内平衡性好且能计算层特异性ATEHeterogeneous Treatment Effect为后续个性化策略提供依据。逆概率加权IPW当样本量有限5万或匹配后损失过多样本保留率50%IPW是更优选择。权重为$$w_i \frac{T_i}{e(x_i)} \frac{1-T_i}{1-e(x_i)}$$用statsmodels的WLS进行加权回归。致命陷阱PS接近0或1的样本权重会爆炸如$e(x_i)0.01$则$w_i100$导致估计不稳定。我的解决方案是对权重进行截断Trimming设定阈值$w_{max}50$所有$w_i50$的样本权重设为50。截断阈值的选择基于权重分布的99分位数——实测下来99分位数通常在30-60之间取50是安全折中。无论哪种方法平衡后的终极检验是伪结果检验Placebo Test将$T$随机打乱用相同流程估计“伪ATE”。若伪ATE的95%置信区间不包含0则说明平衡不充分必须调整匹配参数或增加协变量。这是我写进每份因果报告的必备章节也是说服业务方“我们的方法靠谱”的最强证据。3.4 四大核心估计器实操对比从ATE到ITE的完整链条在平衡后的数据上我并行运行四个估计器相互印证。以下是代码骨架与关键参数解读from causalml.inference.meta import LRSRegressor, XGBTRegressor, BaseXRegressor from econml.dml import LinearDML, ForestDML from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor # 1. 线性回归元学习器Meta-Learner lr_meta LRSRegressor(random_state42) ate_lr lr_meta.estimate_ate(X_support, T_support, y_support) # 2. XGBoost元学习器处理非线性 xgb_meta XGBTRegressor(random_state42, n_estimators200) ate_xgb xgb_meta.estimate_ate(X_support, T_support, y_support) # 3. Double MLeconml dml LinearDML( model_yGradientBoostingRegressor(n_estimators100), model_tGradientBoostingClassifier(n_estimators100), random_state42 ) dml.fit(y_support, T_support, XX_support) ate_dml dml.effect(X_support).mean() # ATE # 4. Uplift Random Forest个体效应 uplift_rf BaseXRegressor( learnerRandomForestRegressor(n_estimators100, random_state42), control_name0 ) uplift_rf.fit(X_support, T_support, y_support) ite_preds uplift_rf.predict(X_support) # 每个用户的ITE预测结果解读与取舍逻辑若四个估计器的ATE点估计值差异10%如LR: 2.1%, XGB: 2.3%, DML: 1.9%, UpliftRF: 2.0%且95%置信区间有显著重叠则取加权平均按方差倒数加权并报告该值为最终ATE。这是最理想情况表明结论稳健。若DML与其他三个差异显著如DML: 0.8%其他均2.0%则重点检查DML的残差dml.score(Y, T, X)返回的R²若0.6说明第一阶段T~X或第二阶段Y~X拟合不佳需增强模型复杂度如增加n_estimators或检查PS平衡。若UpliftRF的ITE分布呈现强右偏如70%用户ITE0.5%但top 1%用户ITE5.0%则说明处理效应高度异质。此时ATE失去指导意义应转向条件平均处理效应CATE分析用uplift_rf.predict(X_new)对新用户打分按分数分五档分别报告各档的ATE及置信区间。这直接支撑“对高潜力用户重点投放”的业务决策。实操心得我从不在报告中只写一个ATE数字。必附三张图1四个估计器ATE点估计与CI的森林图2UpliftRF的ITE分布直方图3CATE分档效果雷达图横轴为用户分群纵轴为各档ATE。业务方看不懂公式但能看懂“给A类用户发券效果是B类的3倍”。3.5 稳健性检验拒绝“一次成功”拥抱“十次验证”一个ATE估计值若未经稳健性检验连内部评审都过不了。我的标准检验清单检验类型方法通过标准我的实操备注安慰剂检验随机打乱T重复100次计算伪ATE分布伪ATE 95% CI包含0必做若不通过说明数据中存在未被平衡的系统性偏差。子样本检验按时间前50% vs 后50%、地域华东 vs 华南、设备iOS vs Android分组分别估计ATE各子样本ATE差异15%且CI重叠发现过某次活动在iOS上ATE3.2%Android上-0.8%根源是Android端推送通道故障需单独归因。变量敏感性检验逐个移除一个协变量重新估计ATE所有ATE变化5%若移除“城市等级”后ATE从2.1%变为4.5%则说明该变量是关键混杂因子必须保留在模型中。模型敏感性检验替换倾向得分模型Logistic→GBM→NeuralNet替换结果模型Linear→XGB→RFATE变化8%econml的ForestDML对模型误设更鲁棒是首选。Bootstrap检验对支持域数据进行1000次Bootstrap重采样计算ATE分布Bootstrap 95% CI与原始CI宽度比1.2若Bootstrap CI宽得多说明样本量不足或方差过大需谨慎解读。所有检验代码我都封装成robustness_check.py模块输入X, T, y, estimator_list输出HTML格式的检验报告。这份报告是交付给业务方的“因果证书”比任何PPT都更有说服力。4. 常见问题与排查技巧实录那些文档里不会写的血泪教训4.1 “PS匹配后SMD合格但ATE置信区间宽得离谱”——方差来源的三层定位法这是最高频的“崩溃现场”。当ate_ci (0.5%, 12.3%)你无法告诉老板“效果在0.5到12.3之间”。必须定位方差来源第一层数据层面Data-Level计算处理组和对照组的样本量比$r N_1/N_0$。若$r 0.3$或$r 3.0$则方差必然大。解决方案使用causalml的StratifiedMatch强制1:1分层或用IPW加权平衡样本量。我曾处理一个B2B销售数据$r0.08$实验组仅120人对照组1500人匹配后$r0.95$CI宽度收窄68%。第二层模型层面Model-Level检查估计器的残差标准差。对LinearDMLdml.score(Y, T, X)返回的R²若0.7则第二阶段Y~X拟合差导致ITE预测噪声大。此时将model_y从LinearRegression升级为GradientBoostingRegressor(n_estimators200)通常可提升R² 0.1-0.2。第三层算法层面Algorithm-Level若前两层无问题问题出在估计器本身。LRSRegressor线性元学习器对方差敏感而UpliftRandomForest通过bagging天然降低方差。实测对比同一数据集LRS的CI宽度是UpliftRF的1.8倍。因此当CI过宽我默认切换至UpliftRF并增加n_estimators300。排查口诀“一看样本比二看R方值三换森林法”。三步下来90%的宽CI问题可解决。4.2 “Uplift模型预测的ITE和业务直觉完全相反”——当算法挑战常识某次教育APP项目UpliftRF预测“历史完课率30%的用户ITE为负-1.2%”即发推荐反而降低其完课率。业务方第一反应是“模型错了”。但我的检查流程是验证数据真实性提取这部分用户的原始行为日志确认“完课率30%”的定义无误如是否包含试听课程并检查是否有数据埋点错误。发现埋点将“播放完成但未提交作业”记为“未完课”导致完课率低估。检查混杂因子遗漏用doWhy构建DAG加入“用户学习动机”变量通过问卷星收集的辅助数据。发现动机是强混杂因子且与完课率负相关——低完课率用户中高动机者占比低。原模型未控制动机导致负ITE是动机缺失的假象。进行CATE分析将“完课率30%”用户按“动机得分”再分高低两组。结果显示高动机组ITE2.1%低动机组ITE-3.8%。结论不是“不推荐”而是“对高动机的低完课率用户推荐效果极佳”。关键原则算法结论与业务直觉冲突90%是数据或混杂因子问题10%是业务直觉有盲区。永远先质疑数据再质疑模型最后才质疑业务。4.3 “doWhy的DAG图显示无后门路径但econml的DML估计仍不稳定”——DAG的局限性与现实妥协doWhy的DAG建模假设所有混杂因子可观测且已纳入图中。但现实中总有“幽灵混杂因子”Ghost Confounder——如用户心理状态、临时生活事件家人住院、未记录的竞品动作。当DML残差呈现强自相关Durbin-Watson统计量1.5或dml.cate_interpret显示某变量系数异常大就暗示幽灵混杂因子存在。我的应对策略是降维正则化用sklearn.decomposition.PCA将高维X降至10维主成分再输入DML。PCA能吸收部分不可观测混杂因子的共性噪声。在DML中启用linear_first_stagesFalse并为model_y和model_t添加更强L2正则alpha0.1。正则化抑制模型对噪声的过拟合提升稳定性。实测效果某金融风控项目加入PCA正则后DML的ATE标准误下降41%且残差自相关消失。这并非理论完美而是工程务实——在理想与现实间找到可交付的平衡点。4.4 “因果报告被业务方质疑你们的‘提升’是不是就等于‘多赚的钱’”——从统计量到商业价值的翻译术技术人常犯的错是把ATE%直接等同于GMV提升元。但业务方要的是真金白银。我的翻译流程锚定基准取归因周期内对照组的平均GMV如¥1200/用户/月。计算绝对提升ATE2.1% → 绝对提升¥1200 × 2.1% ¥25.2/用户/月。估算总体影响若处理组有50万用户则月度增量GMV ¥25.2 × 500,000 ¥12.6M。扣减成本若推荐功能的服务器成本为¥0.5/用户/月则净增量 ¥12.6M - (¥0.5 × 500,000) ¥12.35M。ROI计算若该项目研发投入为¥2M则ROI (¥12.35M - ¥2M) / ¥2M 517.5%。最后一步我必加一句“该ROI基于当前50万用户规模。若推广至全量1000万用户理论年化净收益为¥148.2M投资回收期1.5个月。”——用业务语言说话技术价值才能被看见。5. 从项目到产品如何将因果推断嵌入日常数据工作流5.1 自动化因果流水线Causal Pipeline设计单次项目分析价值有限真正的护城河是可复用的流水线。我的causal-pipeline架构如下data_source/ → [Raw Data] ↓ preprocess/ → [Clean Feature Eng] → [X, T, y] ↓ balance/ → [PS Model] → [Support Mask] → [Balanced Dataset] ↓ estimate/ → [LRS, XGB, DML, UpliftRF] → [ATE, ITE, CATE] ↓ robustness/ → [10项检验] → [Pass/Fail Report] ↓ report/ → [HTML Dashboard: ATE趋势、CATE分群、ROI计算器]所有步骤用Airflow调度每日凌晨运行。关键创新点动态混杂因子池preprocess/模块读取一个confounders.yaml配置文件业务方可在Web界面勾选新增混杂因子如“新增变量用户所在小区房价指数”无需改代码流水线自动更新特征集。智能估计器路由estimate/模块根据数据特征样本量、维度、PS重叠度自动选择主估计器。例如当N