pyAgrum-nightly 2.3.0.9.dev202512061764412981__cp310-abi3-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyagrum/__init__.py +165 -0
- pyagrum/_pyagrum.so +0 -0
- pyagrum/bnmixture/BNMInference.py +268 -0
- pyagrum/bnmixture/BNMLearning.py +376 -0
- pyagrum/bnmixture/BNMixture.py +464 -0
- pyagrum/bnmixture/__init__.py +60 -0
- pyagrum/bnmixture/notebook.py +1058 -0
- pyagrum/causal/_CausalFormula.py +280 -0
- pyagrum/causal/_CausalModel.py +436 -0
- pyagrum/causal/__init__.py +81 -0
- pyagrum/causal/_causalImpact.py +356 -0
- pyagrum/causal/_dSeparation.py +598 -0
- pyagrum/causal/_doAST.py +761 -0
- pyagrum/causal/_doCalculus.py +361 -0
- pyagrum/causal/_doorCriteria.py +374 -0
- pyagrum/causal/_exceptions.py +95 -0
- pyagrum/causal/_types.py +61 -0
- pyagrum/causal/causalEffectEstimation/_CausalEffectEstimation.py +1175 -0
- pyagrum/causal/causalEffectEstimation/_IVEstimators.py +718 -0
- pyagrum/causal/causalEffectEstimation/_RCTEstimators.py +132 -0
- pyagrum/causal/causalEffectEstimation/__init__.py +46 -0
- pyagrum/causal/causalEffectEstimation/_backdoorEstimators.py +774 -0
- pyagrum/causal/causalEffectEstimation/_causalBNEstimator.py +324 -0
- pyagrum/causal/causalEffectEstimation/_frontdoorEstimators.py +396 -0
- pyagrum/causal/causalEffectEstimation/_learners.py +118 -0
- pyagrum/causal/causalEffectEstimation/_utils.py +466 -0
- pyagrum/causal/notebook.py +171 -0
- pyagrum/clg/CLG.py +658 -0
- pyagrum/clg/GaussianVariable.py +111 -0
- pyagrum/clg/SEM.py +312 -0
- pyagrum/clg/__init__.py +63 -0
- pyagrum/clg/canonicalForm.py +408 -0
- pyagrum/clg/constants.py +54 -0
- pyagrum/clg/forwardSampling.py +202 -0
- pyagrum/clg/learning.py +776 -0
- pyagrum/clg/notebook.py +480 -0
- pyagrum/clg/variableElimination.py +271 -0
- pyagrum/common.py +60 -0
- pyagrum/config.py +319 -0
- pyagrum/ctbn/CIM.py +513 -0
- pyagrum/ctbn/CTBN.py +573 -0
- pyagrum/ctbn/CTBNGenerator.py +216 -0
- pyagrum/ctbn/CTBNInference.py +459 -0
- pyagrum/ctbn/CTBNLearner.py +161 -0
- pyagrum/ctbn/SamplesStats.py +671 -0
- pyagrum/ctbn/StatsIndepTest.py +355 -0
- pyagrum/ctbn/__init__.py +79 -0
- pyagrum/ctbn/constants.py +54 -0
- pyagrum/ctbn/notebook.py +264 -0
- pyagrum/defaults.ini +199 -0
- pyagrum/deprecated.py +95 -0
- pyagrum/explain/_ComputationCausal.py +75 -0
- pyagrum/explain/_ComputationConditional.py +48 -0
- pyagrum/explain/_ComputationMarginal.py +48 -0
- pyagrum/explain/_CustomShapleyCache.py +110 -0
- pyagrum/explain/_Explainer.py +176 -0
- pyagrum/explain/_Explanation.py +70 -0
- pyagrum/explain/_FIFOCache.py +54 -0
- pyagrum/explain/_ShallCausalValues.py +204 -0
- pyagrum/explain/_ShallConditionalValues.py +155 -0
- pyagrum/explain/_ShallMarginalValues.py +155 -0
- pyagrum/explain/_ShallValues.py +296 -0
- pyagrum/explain/_ShapCausalValues.py +208 -0
- pyagrum/explain/_ShapConditionalValues.py +126 -0
- pyagrum/explain/_ShapMarginalValues.py +191 -0
- pyagrum/explain/_ShapleyValues.py +298 -0
- pyagrum/explain/__init__.py +81 -0
- pyagrum/explain/_explGeneralizedMarkovBlanket.py +152 -0
- pyagrum/explain/_explIndependenceListForPairs.py +146 -0
- pyagrum/explain/_explInformationGraph.py +264 -0
- pyagrum/explain/notebook/__init__.py +54 -0
- pyagrum/explain/notebook/_bar.py +142 -0
- pyagrum/explain/notebook/_beeswarm.py +174 -0
- pyagrum/explain/notebook/_showShapValues.py +97 -0
- pyagrum/explain/notebook/_waterfall.py +220 -0
- pyagrum/explain/shapley.py +225 -0
- pyagrum/lib/__init__.py +46 -0
- pyagrum/lib/_colors.py +390 -0
- pyagrum/lib/bn2graph.py +299 -0
- pyagrum/lib/bn2roc.py +1026 -0
- pyagrum/lib/bn2scores.py +217 -0
- pyagrum/lib/bn_vs_bn.py +605 -0
- pyagrum/lib/cn2graph.py +305 -0
- pyagrum/lib/discreteTypeProcessor.py +1102 -0
- pyagrum/lib/discretizer.py +58 -0
- pyagrum/lib/dynamicBN.py +390 -0
- pyagrum/lib/explain.py +57 -0
- pyagrum/lib/export.py +84 -0
- pyagrum/lib/id2graph.py +258 -0
- pyagrum/lib/image.py +387 -0
- pyagrum/lib/ipython.py +307 -0
- pyagrum/lib/mrf2graph.py +471 -0
- pyagrum/lib/notebook.py +1821 -0
- pyagrum/lib/proba_histogram.py +552 -0
- pyagrum/lib/utils.py +138 -0
- pyagrum/pyagrum.py +31495 -0
- pyagrum/skbn/_MBCalcul.py +242 -0
- pyagrum/skbn/__init__.py +49 -0
- pyagrum/skbn/_learningMethods.py +282 -0
- pyagrum/skbn/_utils.py +297 -0
- pyagrum/skbn/bnclassifier.py +1014 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSE.md +12 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSES/LGPL-3.0-or-later.txt +304 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSES/MIT.txt +18 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/METADATA +145 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/RECORD +107 -0
- pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# This file is part of the aGrUM/pyAgrum library. #
|
|
3
|
+
# #
|
|
4
|
+
# Copyright (c) 2005-2025 by #
|
|
5
|
+
# - Pierre-Henri WUILLEMIN(_at_LIP6) #
|
|
6
|
+
# - Christophe GONZALES(_at_AMU) #
|
|
7
|
+
# #
|
|
8
|
+
# The aGrUM/pyAgrum library is free software; you can redistribute it #
|
|
9
|
+
# and/or modify it under the terms of either : #
|
|
10
|
+
# #
|
|
11
|
+
# - the GNU Lesser General Public License as published by #
|
|
12
|
+
# the Free Software Foundation, either version 3 of the License, #
|
|
13
|
+
# or (at your option) any later version, #
|
|
14
|
+
# - the MIT license (MIT), #
|
|
15
|
+
# - or both in dual license, as here. #
|
|
16
|
+
# #
|
|
17
|
+
# (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) #
|
|
18
|
+
# #
|
|
19
|
+
# This aGrUM/pyAgrum library is distributed in the hope that it will be #
|
|
20
|
+
# useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
|
|
21
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
|
|
22
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
|
|
23
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
|
|
24
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
|
|
25
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
|
|
26
|
+
# OTHER DEALINGS IN THE SOFTWARE. #
|
|
27
|
+
# #
|
|
28
|
+
# See LICENCES for more details. #
|
|
29
|
+
# #
|
|
30
|
+
# SPDX-FileCopyrightText: Copyright 2005-2025 #
|
|
31
|
+
# - Pierre-Henri WUILLEMIN(_at_LIP6) #
|
|
32
|
+
# - Christophe GONZALES(_at_AMU) #
|
|
33
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later OR MIT #
|
|
34
|
+
# #
|
|
35
|
+
# Contact : info_at_agrum_dot_org #
|
|
36
|
+
# homepage : http://agrum.gitlab.io #
|
|
37
|
+
# gitlab : https://gitlab.com/agrumery/agrum #
|
|
38
|
+
# #
|
|
39
|
+
############################################################################
|
|
40
|
+
|
|
41
|
+
import numpy as np
|
|
42
|
+
import pandas as pd
|
|
43
|
+
|
|
44
|
+
import pyagrum as gum
|
|
45
|
+
import pyagrum.causal as csl
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CausalBNEstimator:
|
|
49
|
+
"""
|
|
50
|
+
A Causal Bayesian Network Estimator.
|
|
51
|
+
|
|
52
|
+
This class utilizes do-calculus identification and lazy propagation
|
|
53
|
+
inference, implemented via the pyAgrum library's causal module,
|
|
54
|
+
to determine the causal effect within Bayesian Networks.
|
|
55
|
+
|
|
56
|
+
Note: In the case of instrumental variables, the causal effect is
|
|
57
|
+
estimated using heuristic methods, as this adjustment is not
|
|
58
|
+
identifiable through do-calculus.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def _useCausalStructure(self, cm_clone: csl.CausalModel, causal_model: csl.CausalModel) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Use the causal structure given by `cm_clone` on `causal_model`.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
cm_clone: csl.CausalStructure
|
|
68
|
+
The model recieving the causal structure.
|
|
69
|
+
causal_model: csl.CausalStructure
|
|
70
|
+
The model giving the causal structure.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
for id in causal_model.latentVariablesIds():
|
|
74
|
+
childrens = causal_model.causalBN().children(id)
|
|
75
|
+
childrens = {causal_model.causalBN().variable(c).name() for c in childrens}
|
|
76
|
+
cm_clone.addLatentVariable(causal_model.causalBN().variable(id).name(), tuple(childrens))
|
|
77
|
+
for x, y in causal_model.arcs():
|
|
78
|
+
if not cm_clone.existsArc(x, y):
|
|
79
|
+
cm_clone.addCausalArc(x, y)
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self, causal_model: csl.CausalModel, treatment: str, outcome: str, instrument: str | None = None
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Initialize an Causal Model estimator.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
causal_model: csl.CausalModel
|
|
90
|
+
The causal graph.
|
|
91
|
+
treatment: str
|
|
92
|
+
The treatment variable.
|
|
93
|
+
outcome: str
|
|
94
|
+
The outcome variable.
|
|
95
|
+
instrument: str, optional
|
|
96
|
+
The instrumental variable
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
if isinstance(causal_model, csl.CausalModel):
|
|
100
|
+
self.causal_model = causal_model.clone()
|
|
101
|
+
self._useCausalStructure(self.causal_model, causal_model)
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError("`causal_model` must be instance of `pyagrum.causal.CausalModel`.")
|
|
104
|
+
|
|
105
|
+
self.treatment = treatment
|
|
106
|
+
self.outcome = outcome
|
|
107
|
+
self.instrument = instrument
|
|
108
|
+
|
|
109
|
+
def fit(self, df: pd.DataFrame, smoothing_prior: float = 1e-9) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Fit the inference model.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
df: pd.DataFrame
|
|
116
|
+
The observations.
|
|
117
|
+
smoothing_prior (Optional): float
|
|
118
|
+
The uniform prior distribution. Default is 1e-9.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
parameter_learner = gum.BNLearner(df, self.causal_model.observationalBN())
|
|
122
|
+
parameter_learner.useNMLCorrection()
|
|
123
|
+
parameter_learner.useSmoothingPrior(smoothing_prior)
|
|
124
|
+
|
|
125
|
+
bn = gum.BayesNet(self.causal_model.observationalBN())
|
|
126
|
+
|
|
127
|
+
parameter_learner.fitParameters(bn)
|
|
128
|
+
|
|
129
|
+
causal_model = csl.CausalModel(bn)
|
|
130
|
+
self._useCausalStructure(causal_model, self.causal_model)
|
|
131
|
+
self.causal_model = causal_model
|
|
132
|
+
|
|
133
|
+
return self.causal_model
|
|
134
|
+
|
|
135
|
+
def _getIntervalIndex(self, x: float, var: str) -> int:
|
|
136
|
+
"""
|
|
137
|
+
Gets the domain index of the variable.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
---------
|
|
141
|
+
x: float
|
|
142
|
+
The conditional.
|
|
143
|
+
var: str
|
|
144
|
+
The variable label string.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
int
|
|
149
|
+
The index of the conditional in the variable domain.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
splits = list()
|
|
153
|
+
accumulator = ""
|
|
154
|
+
for letter in self.causal_model.causalBN().variable(var).domain():
|
|
155
|
+
if letter in ["-", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]:
|
|
156
|
+
accumulator += letter
|
|
157
|
+
elif len(accumulator) > 0:
|
|
158
|
+
split = float(accumulator)
|
|
159
|
+
if len(splits) > 0 and splits[-1] == split:
|
|
160
|
+
splits.pop()
|
|
161
|
+
splits.append(split)
|
|
162
|
+
accumulator = ""
|
|
163
|
+
|
|
164
|
+
for i in range(len(splits)):
|
|
165
|
+
if x < splits[i]:
|
|
166
|
+
return i
|
|
167
|
+
|
|
168
|
+
return len(splits) - 1
|
|
169
|
+
|
|
170
|
+
def _predictRow(self, X: pd.Series) -> float:
|
|
171
|
+
"""
|
|
172
|
+
Predict the Individual Causal Effect (ICE) of a single row.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
X: pd.Series
|
|
177
|
+
The of covariates.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
float
|
|
182
|
+
The predicted ICE.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
keys = X.index.to_list()
|
|
186
|
+
values = list()
|
|
187
|
+
|
|
188
|
+
for covar in X.index:
|
|
189
|
+
values.append(self._getIntervalIndex(X[covar], covar))
|
|
190
|
+
|
|
191
|
+
if self.instrument is not None:
|
|
192
|
+
ie = gum.LazyPropagation(self.causal_model.observationalBN())
|
|
193
|
+
ie.setEvidence(dict(zip(keys + [self.instrument], values + [0])))
|
|
194
|
+
cptY0 = ie.posterior(self.outcome)
|
|
195
|
+
cptT0 = ie.posterior(self.treatment)
|
|
196
|
+
ie.setEvidence(dict(zip(keys + [self.instrument], values + [1])))
|
|
197
|
+
cptY1 = ie.posterior(self.outcome)
|
|
198
|
+
cptT1 = ie.posterior(self.treatment)
|
|
199
|
+
|
|
200
|
+
diffY = cptY1 - cptY0
|
|
201
|
+
diffT = cptT1 - cptT0
|
|
202
|
+
|
|
203
|
+
return diffY.expectedValue(
|
|
204
|
+
lambda d: diffY.variable(0).numerical(d[diffY.variable(0).name()])
|
|
205
|
+
) / diffT.expectedValue(lambda d: diffT.variable(0).numerical(d[diffT.variable(0).name()]))
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
_, cpt0, _ = csl.causalImpact(
|
|
209
|
+
cm=self.causal_model,
|
|
210
|
+
on=self.outcome,
|
|
211
|
+
doing=self.treatment,
|
|
212
|
+
knowing=set(X.index),
|
|
213
|
+
values=dict(zip(keys + [self.treatment], values + [0])),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
_, cpt1, _ = csl.causalImpact(
|
|
217
|
+
cm=self.causal_model,
|
|
218
|
+
on=self.outcome,
|
|
219
|
+
doing=self.treatment,
|
|
220
|
+
knowing=set(X.index),
|
|
221
|
+
values=dict(zip(keys + [self.treatment], values + [1])),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if cpt0 is None:
|
|
225
|
+
raise csl._exceptions.HedgeException("Causal effect is unidentifiable using do-calculus.")
|
|
226
|
+
|
|
227
|
+
diff = cpt1 - cpt0
|
|
228
|
+
return diff.expectedValue(lambda d: diff.variable(0).numerical(d[diff.variable(0).name()]))
|
|
229
|
+
|
|
230
|
+
def predict(
|
|
231
|
+
self,
|
|
232
|
+
w: np.matrix | np.ndarray | pd.DataFrame = None,
|
|
233
|
+
X: np.matrix | np.ndarray | pd.DataFrame = None,
|
|
234
|
+
M: np.matrix | np.ndarray | pd.DataFrame = None,
|
|
235
|
+
treatment: np.ndarray | pd.Series | None = None,
|
|
236
|
+
y: np.ndarray | pd.Series | None = None,
|
|
237
|
+
) -> np.ndarray:
|
|
238
|
+
"""
|
|
239
|
+
Predict the Individual Causal Effect (ICE),
|
|
240
|
+
also referd to as the Individual Treatment Effect (ITE).
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
w: np.matrix or np.ndarray or pd.DataFrame
|
|
245
|
+
The instrument variable.
|
|
246
|
+
X: np.matrix or np.ndarray or pd.DataFrame
|
|
247
|
+
The matrix of covariates.
|
|
248
|
+
treatment: np.ndarray or pd.Series or None, optional
|
|
249
|
+
The vector of treatment assignments.
|
|
250
|
+
y: np.ndarray or pd.Series, optional
|
|
251
|
+
The vector of outcomes.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
np.ndarray
|
|
256
|
+
An array containing the predicted ICE.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
if X is not None:
|
|
260
|
+
return X.apply(self._predictRow, axis=1).to_numpy()
|
|
261
|
+
else:
|
|
262
|
+
return M.apply(self._predictRow, axis=1).to_numpy()
|
|
263
|
+
|
|
264
|
+
def estimate_ate(
|
|
265
|
+
self,
|
|
266
|
+
w: np.matrix | np.ndarray | pd.DataFrame = None,
|
|
267
|
+
X: np.matrix | np.ndarray | pd.DataFrame = None,
|
|
268
|
+
M: np.matrix | np.ndarray | pd.DataFrame = None,
|
|
269
|
+
treatment: np.ndarray | pd.Series | None = None,
|
|
270
|
+
y: np.ndarray | pd.Series | None = None,
|
|
271
|
+
pretrain: bool = True,
|
|
272
|
+
) -> float:
|
|
273
|
+
"""
|
|
274
|
+
Predicts the Average Causal Effect (ACE),
|
|
275
|
+
also refered to as the Average Treatment Effect (ATE).
|
|
276
|
+
(The term ATE is used in the method name for compatibility purposes.)
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
w: np.matrix or np.ndarray or pd.DataFrame
|
|
281
|
+
The instrument variable.
|
|
282
|
+
X: np.matrix or np.ndarray or pd.DataFrame
|
|
283
|
+
The matrix of covariates.
|
|
284
|
+
treatment: np.ndarray or pd.Series or None, optional
|
|
285
|
+
The vector of treatment assignments.
|
|
286
|
+
y: np.ndarray or pd.Series, optional
|
|
287
|
+
The vector of outcomes.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
float
|
|
292
|
+
The value of the ACE.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
if self.instrument is not None:
|
|
296
|
+
ie = gum.LazyPropagation(self.causal_model.observationalBN())
|
|
297
|
+
ie.setEvidence({self.instrument: 0})
|
|
298
|
+
cptY0 = ie.posterior(self.outcome)
|
|
299
|
+
cptT0 = ie.posterior(self.treatment)
|
|
300
|
+
ie.setEvidence({self.instrument: 1})
|
|
301
|
+
cptY1 = ie.posterior(self.outcome)
|
|
302
|
+
cptT1 = ie.posterior(self.treatment)
|
|
303
|
+
|
|
304
|
+
diffY = cptY1 - cptY0
|
|
305
|
+
diffT = cptT1 - cptT0
|
|
306
|
+
|
|
307
|
+
return diffY.expectedValue(
|
|
308
|
+
lambda d: diffY.variable(0).numerical(d[diffY.variable(0).name()])
|
|
309
|
+
) / diffT.expectedValue(lambda d: diffT.variable(0).numerical(d[diffT.variable(0).name()]))
|
|
310
|
+
|
|
311
|
+
else:
|
|
312
|
+
_, cpt0, _ = csl.causalImpact(
|
|
313
|
+
self.causal_model, on=self.outcome, doing=self.treatment, values={self.treatment: 0}
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
_, cpt1, _ = csl.causalImpact(
|
|
317
|
+
self.causal_model, on=self.outcome, doing=self.treatment, values={self.treatment: 1}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if cpt0 is None:
|
|
321
|
+
raise csl._exceptions.HedgeException("Causal effect is unidentifiable using do-calculus.")
|
|
322
|
+
|
|
323
|
+
difference = cpt1 - cpt0
|
|
324
|
+
return difference.expectedValue(lambda d: difference.variable(0).numerical(d[difference.variable(0).name()]))
|