专注于因果效应估计的专家
一句话点评: 不关心完整网络,只专注于回答“这个干预有多大效果?”
DoWhy 的思维方式与前几个库不同。它要求你明确定义处理变量和结果变量,然后专注于估计处理变量对结果的平均因果效应。它不擅长从数据中学习因果结构,但非常擅长在给定因果图的情况下进行稳健的效应估计。
实战演示:
我们研究“拥有博士学位”对“高薪”的因果效应。
!pip install dowhyfrom dowhy import CausalModelimport dowhy.datasetsimport datazets as dzfrom sklearn.preprocessing import LabelEncoderimport numpy as nple = LabelEncoder()df = dz.get(data='census_income')drop_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'race', 'sex']df.drop(labels=drop_cols, axis=1, inplace=True)df['education'] = df['education']=='Doctorate'df_num = df.copy()for col in df_num.columns: df_num[col] = le.fit_transform(df_num[col])treatment = “education”outcome = “salary”model = CausalModel( data=df_num, treatment=treatment, outcome=outcome, common_causes=list(df.columns[~np.isin(df.columns, [treatment, outcome])]),
graph_builder='ges', alpha=0.05, )model.view_model()
如上文代码部分所见,处理变量必须为二元变量(设为“博士学位”),所有分类变量均需编码为数值。在下文代码部分,我们将利用因果图的特性来识别因果效应。结果或许不足为奇:拥有博士学位确实能提高获得优厚薪资的概率。
identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)print(identified_estimand)"""[16-09-2025 21:06:21] [dowhy.causal_identifier.auto_identifier] [INFO] Causal effect can be identified.[16-09-2025 21:06:21] [dowhy.causal_identifier.auto_identifier] [INFO] Instrumental variables for treatment and outcome:[][16-09-2025 21:06:21] [dowhy.causal_identifier.auto_identifier] [INFO] Frontdoor variables for treatment and outcome:[][16-09-2025 21:06:21] [dowhy.causal_identifier.auto_identifier] [INFO] Number of general adjustment sets found: 1[16-09-2025 21:06:21] [dowhy.causal_identifier.auto_identifier] [INFO] Causal effect can be identified."""print(identified_estimand)"""Estimand type: EstimandType.NONPARAMETRIC_ATE### Estimand : 1Estimand name: backdoorEstimand expression: d ↪────────────(E[salary|native-country,occupation,marital-status,workclass,relat ↪d[education] ↪↪ ↪ ionship])↪ Estimand assumption 1, Unconfoundedness: If U→{education} and U→salary then P(salary|education,native-country,occupation,marital-status,workclass,relationship,U) = P(salary|education,native-country,occupation,marital-status,workclass,relationship)### Estimand : 2Estimand name: ivNo such variable(s) found!### Estimand : 3Estimand name: frontdoorNo such variable(s) found!### Estimand : 4Estimand name: general_adjustmentEstimand expression: d ↪────────────(E[salary|native-country,marital-status,occupation,workclass,relat ↪d[education] ↪↪ ↪ ionship])↪ Estimand assumption 1, Unconfoundedness: If U→{education} and U→salary then P(salary|education,native-country,marital-status,occupation,workclass,relationship,U) = P(salary|education,native-country,marital-status,occupation,workclass,relationship)"""
estimate = model.estimate_effect(identified_estimand, method_name="backdoor.propensity_score_stratification"print(estimate)"""*** Causal Estimate ***## Identified estimandEstimand type: EstimandType.NONPARAMETRIC_ATE### Estimand : 1Estimand name: backdoorEstimand expression: d ↪────────────(E[salary|native-country,occupation,marital-status,workclass,relat ↪d[education] ↪↪ ↪ ionship])↪ Estimand assumption 1, Unconfoundedness: If U→{education} and U→salary then P(salary|education,native-country,occupation,marital-status,workclass,relationship,U) = P(salary|education,native-country,occupation,marital-status,workclass,relationship)## Realized estimandb: salary~education+native-country+occupation+marital-status+workclass+relationshipTarget units: ate## EstimateMean value: 0.46973242081553496## Estimate# Mean value: 0.4697157228651772"""refute_results = model.refute_estimate(identified_estimand, estimate, method_name="random_common_cause")
总结:
多才多艺的图模型学者
一句话点评: 来自学术界的全能选手,功能丰富但文档相对学院派。
PyAgrum 是一个功能强大的概率图模型库,不仅支持贝叶斯网络,还支持马尔可夫网络等。它提供了多种学习算法和推理工具,但安装和入门可能对新手有些挑战。
实战演示:
# 安装 pyagrumpip install pyagrum# 安装 graphviz(可视化所需)pip install setgraphvizimport datazets as dzimport pandas as pdimport pyagrum as gumimport pyagrum.lib.notebook as gnbimport pyagrum.lib.explain as explainimport pyagrum.lib.bn_vs_bn as bnvsbn# 导入库以显示点图from setgraphviz import setgraphviz# 设置路径setgraphviz()# 导入数据集并剔除连续型和敏感特征df = dz.get(data='census_income')# 数据清洗drop_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'race', 'sex']df.drop(labels=drop_cols, axis=1, inplace=True)# 删除存在任何缺失值的行df2 = df.dropna().copy()# 将所有缺失值替换为占位符字符串df2 = df2.fillna(“missing”).replace(“?”, “missing”)# 确保分类列为类别型数据类型(pyAgrum要求离散变量)for col in df2.columns: df2[col] = df2[col].astype(“category”)# 从pandas数据框创建学习器learner = gum.BNLearner(df2)# 配置评分与搜索策略learner.useScoreBIC()learner.useGreedyHillClimbing()# 训练网络bn = learner.learnBN()# 学习参数bn2 = learner.learnParameters(bn.dag())# 绘制网络图gnb.showBN(bn2)
总结:
时间序列因果推断的利器
一句话点评: 专为时间序列设计,评估政策、活动等干预的短期影响。
CausalImpact 与其他库完全不同。它使用贝叶斯结构时间序列模型,来评估一次干预(如新政策上线、营销活动)对时间序列数据的影响。它的核心是比较干预发生后的实际值与“假设未发生干预”的预测值之间的差异。
实战演示:
假设我们有一段时间内的网站流量数据,并在第70天进行了一次改版。
from causalimpact import CausalImpactimport pandas as pddata = pd.DataFrame({'y': y_data, 'x1': x1_data})
impact = CausalImpact(data, pre_period=[0, 69], post_period=[70, 99])impact.run()impact.plot() impact.summary() impact.summary(output="report")
平均值列描述了干预后时段的平均时间。累计值列汇总了各个时间点的数据,当响应变量代表流量指标(如查询量、点击量、访问量、安装量、销售额或收入)时,这种视角尤为有用。本例中可见因果效应概率为100%,P值为0——这符合预期,因为数据中已包含该信息。
在图4中,我们可以看到三个子图,其描述如下:在子图1,我们既能看到原始数据,也能看到针对治疗后时期的反事实预测。子图2展示了观测数据与反事实预测之间的差异,该差异即模型所确定的点因果效应估计值。子图3通过聚合前一面板的点效应贡献,描绘了干预措施的累积效应。需特别注意的是,这些推论的有效性依赖于协变量未受干预本身影响的假设。此外,模型还假定协变量与处理时间序列在前期建立的关系,在后期始终保持一致。
总结:
写在最后
库名称 | 核心功能 | 输入数据 | 学习曲线 | 最适合场景 |
|---|
Bnlearn | 全流程贝叶斯网络 | 离散/连续/混合 | 平缓 | 通用因果发现与推理 |
Pgmpy | 概率图模型底层构建块 | 离散 | 陡峭 | 自定义模型、算法研究 |
CausalNex | 基于NOTEARS的结构学习 | 数值型离散 | 中等 | 需要先进结构学习算法的项目 |
DoWhy | 因果效应估计 | 处理+结果变量 | 中等 | A/B测试、政策效果评估 |
PyAgrum | 多种图模型 | 离散 | 中等 | 学术研究、需要多种模型类型 |
CausalImpact | 时间序列干预分析 | 时间序列 | 平缓 | 营销活动、产品改版影响评估 |
如何选择?看这里:
如果你想快速入门,从数据中自动发现因果关系并进行推理,Bnlearn 是你的不二之选。
如果你是高级用户,希望完全控制建模的每一个细节,请选择 Pgmpy。
如果你的核心任务是评估某个处理变量对结果变量的因果效应,DoWhy 提供了最严谨的框架。
如果你的数据是时间序列,并且想评估一次干预的因果影响,CausalImpact 是唯一答案。
希望这篇详尽的对比能帮助你在因果推断的旅程中,找到那盏最明亮的指路明灯!