Python 生態裏能用的因果庫有很多選哪個往往要看你對模型的理解程度,以及項目對“可解釋性”的要求。這篇文章將對比了六個目前社區中最常用的因果推斷庫:Bnlearn、Pgmpy、CausalNex、DoWhy、PyAgrum 和 CausalImpact。
貝葉斯因果模型
在因果推斷裏所有變量可以粗略分成兩種:驅動變量(driver variables)和乘客變量(passenger variables)。驅動變量會直接影響結果,而乘客變量雖然跟結果有關但並不直接影響結果。區分這兩者是整個因果分析的關鍵。比如在預測性維護或設備故障分析裏,如果能識別出“導致故障”的那幾個變量,後續的監控與優化策略就能有針對性地落地。
有時候,看似無關的變量其實藏着重要的效應。比如説假設某個工廠的發動機故障率在不同地區差異很大,你可能認為這是地理差異,其實真正的原因可能是工廠裏濕度、保養週期或人員經驗這樣的隱含驅動因子。因果推斷的價值就在這裏——它幫助區分“看上去相關”和“真正原因”的區別。
相比純預測模型,因果推斷更像是在回答“為什麼”,而不是“多少”。通過找出系統中真正起作用的變量,才能解釋模型的行為,也才能對系統做出有效干預。
數據集與整體實驗思路
為了讓對比更直觀,所有實驗都使用相同的數據:Census Income 數據集。這個經典的數據集包含 48,842 條記錄和 14 個變量,多數為離散特徵。目標也很簡單:擁有研究生(postgraduate)學歷是否能顯著提高年收入超過 50K 美元的概率?
下面的代碼用於載入和清理數據。連續變量與敏感特徵(如性別、種族等)被移除,以便專注在離散特徵的因果結構學習上。
# 安裝
pip install datazets
# 導入庫
import datazets as dz
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 導入數據集並刪除連續型與敏感特徵
df = dz.import_example(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.head()
1、Bnlearn
bnlearn
是一個封裝度很高的貝葉斯網絡庫,幾乎把因果分析的標準流程都集成在一套 API 裏。它支持離散、連續和混合類型數據,可以進行 結構學習、參數學習(CPD 估計)、推理(inference)和合成數據生成(synthetic data generation)。更重要的是,它把常用的獨立性檢驗、評分函數、拓撲排序、模型比較和可視化都做成了開箱即用的函數。
下面是使用 HillClimbSearch 和 BIC 評分方法學習結構的完整流程代碼
# 安裝
pip install bnlearn
# 加載庫
import bnlearn as bn
# 結構學習
model = bn.structure_learning.fit(df, methodtype='hillclimbsearch', scoretype='bic')
# 檢驗邊顯著性並剪枝
model = bn.independence_test(model, df, test="chi_square", alpha=0.05, prune=True)
# 參數學習(可選)
model = bn.parameter_learning.fit(model, df)
# 繪製圖形
G = bn.plot(model, interactive=False)
dotgraph = bn.plot_graphviz(model)
dotgraph.view(filename=r'c:/temp/bnlearn_plot')
模型學得的 DAG(有向無環圖)結構如下:
靜態圖展示了
bnlearn
學出的 Census Income 因果結構圖。交互式版本可以直接查看每條邊的條件概率分佈。
DAG 學成之後,可以直接執行推理。例如計算當教育水平為博士時,收入大於 50K 的後驗概率:
# 開始推理
query = bn.inference.fit(model, variables=['salary'], evidence={'education':'Doctorate'})
print(query)
結果如下:
salary <=50K : 29.1%
salary >50K : 70.9%
這個概率幾乎符合我們的理解:博士學歷的確帶來更高的收入區間。
換成高中畢業(
HS-grad
)再試一次:
query = bn.inference.fit(model, variables=['salary'], evidence={'education':'HS-grad'})
得到:
salary <=50K : 83.8%
salary >50K : 16.2%
還可以組合多個條件,例如:
# 當 education=Doctorate 且 marital-status=Never-married 時,預測 workclass
query = bn.inference.fit(model, variables=['workclass'], evidence={'education':'Doctorate', 'marital-status':'Never-married'})
從整體來看,
bnlearn
適合快速構建因果結構並做概率推理,功能完整。輸入數據可為離散、連續或混合類型。對於想在業務場景中快速驗證因果假設的人,這個庫的學習曲線非常平緩。
2、 Pgmpy
Pgmpy
是一個更偏底層的概率圖模型庫,如果説
bnlearn
是“開箱即用”那
pgmpy
更像是一套“拼裝工具箱”。他的靈活性非常高,但也意味着需要較強的貝葉斯建模功底。
這兩個庫的功能其實有重疊,因為
bnlearn
的底層實現部分依賴
pgmpy
。但在
pgmpy
中,所有步驟都要自己搭建:數據處理、建模、參數估計、推理、可視化都要自己寫。
下面是用
HillClimbSearch
和
BIC
評分方法做結構學習的例子:
# 安裝 pgmpy
pip install pgmpy
# 導入函數
from pgmpy.estimators import HillClimbSearch, BicScore, BayesianEstimator
from pgmpy.models import BayesianNetwork, NaiveBayes
from pgmpy.inference import VariableElimination
# 導入數據並刪除連續與敏感特徵
df = bn.import_example(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)
# 創建估計器與評分方法
est = HillClimbSearch(df)
scoring_method = BicScore(df)
# 建立模型並打印邊
model = est.estimate(scoring_method=scoring_method)
print(model.edges())
建好結構後需要手動擬合條件概率分佈(CPD):
vec = {
'source': ['education', 'marital-status', 'occupation', 'relationship', 'relationship', 'salary'],
'target': ['occupation', 'relationship', 'workclass', 'education', 'salary', 'education'],
'weight': [True, True, True, True, True, True]
}
vec = pd.DataFrame(vec)
# 創建貝葉斯模型
bayesianmodel = BayesianNetwork(vec)
# 擬合模型
bayesianmodel.fit(df, estimator=BayesianEstimator, prior_type='bdeu', equivalent_sample_size=1000)
# 推理
model_infer = VariableElimination(bayesianmodel)
query = model_infer.query(variables=['salary'], evidence={'education':'Doctorate'})
print(query)
輸出結果與
bnlearn
基本一致:
salary <=50K : 29.1%
salary >50K : 70.9%
不同的是,這個過程需要你自己定義 DAG、參數估計和推理方式。這個庫靈活性很高,但顯然更適合研究者或開發自定義因果框架的場景,而不是想“直接上手跑”的業務人員。
3、CausalNex
CausalNex
是一個專注於從數據中學習因果圖並量化因果效應的 Python 庫。它只支持離散分佈,這點很重要。所以在建模前必須把所有連續變量或高基數特徵離散化,否則模型無法擬合。
文檔裏也明確提到,如果特徵太多、狀態太複雜,模型效果會明顯下降。不過好處是,它提供了一些便捷函數來幫助降低類別數量和處理標籤編碼。
下面是一個完整的示例,仍然使用同樣的 Census Income 數據。第一步需要把所有類別變量轉為數值型(因為 NOTEARS 算法要求矩陣計算):
# 安裝
pip install causalnex
# 導入庫
from causalnex.structure.notears import from_pandas
from causalnex.network import BayesianNetwork
import networkx as nx
import datazets as dz
from sklearn.preprocessing import LabelEncoder
import matplotlib.pyplot as plt
le = 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_num = df.copy()
for col in df_num.columns:
df_num[col] = le.fit_transform(df_num[col])
結構學習部分用的是 NOTEARS 算法,可以自動從相關矩陣中推導 DAG。不過輸出可能太密集,需要設定閾值過濾掉弱邊:
# 結構學習
sm = from_pandas(df_num)
# 閾值剪枝
sm.remove_edges_below_threshold(0.8)
# 繪圖
plt.figure(figsize=(15,10))
edge_width = [d['weight']*0.3 for (u,v,d) in sm.edges(data=True)]
nx.draw_networkx(sm, node_size=400, arrowsize=20, alpha=0.6, edge_color='b', width=edge_width)
上圖是
CausalNex
學出的結構圖。沒有標籤的節點表示因為閾值太高被剪掉的弱邊。也可以通過調整參數
w_threshold
控制網絡稀疏度,或者用
tabu_edges
禁止某些邊生成。
建好結構後,需要學習節點的條件概率分佈:
# 第一步:創建 BayesianNetwork 實例
bn = BayesianNetwork(sm)
# 第二步:降低分類特徵基數(可選)
# 第三步:定義每個節點的狀態字典
# 第四步:擬合節點狀態
bn = bn.fit_node_states(df)
# 第五步:擬合條件概率分佈
bn = bn.fit_cpds(df, method="BayesianEstimator", bayes_prior="K2")
# 輸出某個節點的 CPD
result = bn.cpds["education"]
print(result)
CausalNex
的可解釋性和結構學習能力都不錯,但預處理要求多、類型限制嚴格,對 Python 版本也挑剔(僅兼容 3.6–3.10)。如果只想跑標準數據,它表現穩定,但要集成到更復雜的生產環境,需要額外工作。
4、DoWhy
DoWhy
是一個專注於因果推斷驗證的庫,設計理念與前面提到的貝葉斯網絡工具完全不同。它並不嘗試從數據中直接學習因果圖,而是要求用户顯式定義因果假設,包括:
- 結果變量(outcome variable)
- 處理變量(treatment variable)
- 潛在混雜變量(common causes)
也就是説
DoWhy
更像是一個驗證框架用來系統地質疑和檢驗你提出的因果假設,而不是生成因果結構。
如果沒有提供 DAG(因果圖),它會自動把所有變量連接到結果與處理變量上。但實際使用中最好結合領域知識自行定義 DAG,否則模型會過度簡化。
使用同樣的 Census Income 數據集,只是處理變量定義為“是否擁有博士學位”:
# 安裝
pip install dowhy
# 導入庫
from dowhy import CausalModel
import dowhy.datasets
import datazets as dz
from sklearn.preprocessing import LabelEncoder
import numpy as np
le = 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"
# Step 1: 創建因果圖
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()
上圖展示了
DoWhy
自動生成的 DAG,結果變量為“salary”,處理變量為“education”。可以看到,模型假設教育水平會影響薪資,並且兩者都可能受婚姻狀態、工作類型等混雜因素的干擾。
接下來是識別和估計因果效應的步驟:
# Step 2: 識別因果效應
identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)
print(identified_estimand)
一旦模型識別出可估計的因果效應,就可以計算平均處理效應(ATE):
# Step 3: 估計效應
estimate = model.estimate_effect(identified_estimand, method_name="backdoor.propensity_score_stratification")
print(estimate)
輸出的平均效應值約為 0.47,説明博士學位與高收入存在明顯的正向因果關係。最後再做穩健性檢驗:
# Step 4: 穩健性驗證
refute_results = model.refute_estimate(identified_estimand, estimate, method_name="random_common_cause")
DoWhy在學術界非常常見,優點是框架清晰、假設透明、驗證邏輯完備; 缺點是對輸入要求高(變量需二值化、分類需數值編碼),且不能從數據自動學習結構。
換句話説,它假設你已經知道潛在的因果關係,現在只想驗證這個假設是否合理。
5、PyAgrum
PyAgrum
是另一個功能相對完整的概率圖模型庫。它支持貝葉斯網絡、馬爾可夫網絡以及決策圖的構建,能做結構學習、參數學習、推理和可視化。並且語法偏工程化,比
pgmpy
直觀一些,但要求所有變量都必須是離散型。
如果數據裏有缺失值或連續變量需要進行處理,否則算法會直接報錯。下面的例子展示了從數據清洗到結構學習的完整流程:
# 安裝
pip install pyagrum
# 可選:安裝可視化依賴
pip install setgraphviz
import datazets as dz
import pandas as pd
import pyagrum as gum
import pyagrum.lib.notebook as gnb
import pyagrum.lib.explain as explain
import 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")
# 所有列轉換為分類類型
for col in df2.columns:
df2[col] = df2[col].astype("category")
# 創建學習器
learner = gum.BNLearner(df2)
learner.useScoreBIC()
learner.useGreedyHillClimbing()
# 學習結構
bn = learner.learnBN()
# 學習參數
bn2 = learner.learnParameters(bn.dag())
# 可視化結果
gnb.showBN(bn2)
上圖展示了
PyAgrum
學到的貝葉斯網絡結構。節點之間的箭頭表示潛在的因果方向。
它提供了一系列輔助模塊,可以比較不同結構(
bn_vs_bn
)、解釋單個節點(
explain
)或生成報告。
PyAgrum
的特點是:學習過程透明、支持約束學習(可指定禁止/強制邊),但代價是輸入準備相對繁瑣。 沒有自動缺失值處理、連續特徵必須離散化,而且某些函數依賴 Graphviz 進行可視化。
6、CausalImpact
CausalImpact
原本是 Google 的開源項目,用於通過貝葉斯結構時間序列模型來衡量干預效果。 它與前面幾個庫不同,不學習因果圖而是針對時間序列數據計算干預帶來的量化影響。
常見場景是評估營銷活動、價格調整或新功能上線的效果。比如,一個電商網站想知道廣告活動是否帶來了實際銷售提升。
CausalImpact
會先用干預前的數據建立基線模型,再將干預後的實際觀測值與預測值比較,計算“反事實差異”。
下面是一個簡潔的示例,構造 100 個樣本點,並在第 71 個時間點之後人為引入干預效應:
# 安裝
pip install causalimpact
# 導入庫
import numpy as np
import pandas as pd
from statsmodels.tsa.arima_process import arma_generate_sample
import matplotlib.pyplot as plt
from causalimpact import CausalImpact
# 生成樣本
x1 = arma_generate_sample(ar=[0.999], ma=[0.9], nsample=100) + 100
y = 1.2 * x1 + np.random.randn(100)
y[71:] = y[71:] + 10
data = pd.DataFrame(np.array([y, x1]).T, columns=["y","x1"])
# 初始化模型
impact = CausalImpact(data, pre_period=[0,69], post_period=[71,99])
# 執行推斷
impact.run()
# 繪圖與結果
impact.plot()
impact.summary()
圖中上半部分顯示實際值與模型預測值的對比;中間部分是兩者的差值(即時效應);下半部分是累計效應。
結果顯示,干預帶來了顯著提升,概率為 100%,P 值為 0。這種方法假設干預前後協變量與響應變量的關係穩定,且協變量本身不受干預影響。若滿足這些假設,
CausalImpact
在定量評估干預效應方面非常可靠。
總結
六個庫的思路差異很大。從整體來看,可以分成兩類:
結構學習型:
Bnlearn
、
Pgmpy
、
CausalNex
、
PyAgrum
用於發現變量間的潛在因果關係,適合探索性分析與結構建模。
因果效應型:
DoWhy
、
CausalImpact
側重在給定結構或時間序列下量化干預效果,適合政策分析或實驗驗證。
從工程角度看:
Bnlearn
的易用性最高,接口友好、兼容性強,是入門和快速驗證的好工具;
Pgmpy
靈活性最強,適合深度研究與自定義算法;
CausalNex
擁有良好的可解釋圖形界面,但依賴特定 Python 版本;
DoWhy
是學術研究常用工具,強調理論嚴謹性;
PyAgrum
穩定、透明,但數據準備較重;
CausalImpact
專注時間序列因果評估。
六個因果庫覆蓋了從結構學習到因果效應估計的完整路徑。
Bnlearn
、
Pgmpy
、
CausalNex
和
PyAgrum
負責構建網絡、識別驅動因素;
DoWhy
和
CausalImpact
則更像“量化工具”,用於評估處理或干預帶來的實際影響。
https://avoid.overfit.cn/post/dc97c8f709bc4c56bfec860dd800d75c