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,81 @@
|
|
|
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
|
+
"""
|
|
42
|
+
Causality in pyAgrum mainly consists in the ability to build a causal model, i.e. a (observational) Bayesian network
|
|
43
|
+
and a set of latent variables and their relation with observation variables and in the ability to compute using
|
|
44
|
+
do-calculus the causal impact in such a model.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
__author__ = "Pierre-Henri Wuillemin, Paul Alam, Ibrahim Merad"
|
|
48
|
+
__copyright__ = "(c) 2019-2024 PARIS"
|
|
49
|
+
|
|
50
|
+
from ._causalImpact import causalImpact, counterfactualModel, counterfactual
|
|
51
|
+
from ._doorCriteria import backdoor_generator, frontdoor_generator
|
|
52
|
+
from ._doAST import ASTtree, ASTjointProba, ASTmult, ASTdiv, ASTsum, ASTposteriorProba, ASTminus, ASTplus, ASTBinaryOp
|
|
53
|
+
from ._doCalculus import doCalculusWithObservation, identifyingIntervention
|
|
54
|
+
from ._CausalFormula import CausalFormula
|
|
55
|
+
from ._CausalModel import CausalModel
|
|
56
|
+
from ._exceptions import HedgeException, UnidentifiableException
|
|
57
|
+
from .causalEffectEstimation import CausalEffectEstimation
|
|
58
|
+
import sys
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"CausalModel",
|
|
62
|
+
"CausalFormula",
|
|
63
|
+
"CausalEffectEstimation",
|
|
64
|
+
"causalImpact",
|
|
65
|
+
"counterfactualModel",
|
|
66
|
+
"counterfactual",
|
|
67
|
+
"doCalculusWithObservation",
|
|
68
|
+
"identifyingIntervention",
|
|
69
|
+
"backdoor_generator",
|
|
70
|
+
"HedgeException",
|
|
71
|
+
"UnidentifiableException",
|
|
72
|
+
"ASTtree",
|
|
73
|
+
"ASTjointProba",
|
|
74
|
+
"ASTmult",
|
|
75
|
+
"ASTdiv",
|
|
76
|
+
"ASTsum",
|
|
77
|
+
"ASTposteriorProba",
|
|
78
|
+
"ASTminus",
|
|
79
|
+
"ASTplus",
|
|
80
|
+
"ASTBinaryOp",
|
|
81
|
+
]
|
|
@@ -0,0 +1,356 @@
|
|
|
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
|
+
"""
|
|
42
|
+
This file gives an API for causal inference
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from typing import Union, Optional, Dict, Tuple, Set
|
|
46
|
+
|
|
47
|
+
import pyagrum
|
|
48
|
+
|
|
49
|
+
from pyagrum.causal._types import NameSet
|
|
50
|
+
from pyagrum.causal._dSeparation import isDSep
|
|
51
|
+
from pyagrum.causal._doAST import ASTposteriorProba
|
|
52
|
+
|
|
53
|
+
from pyagrum.causal._exceptions import HedgeException
|
|
54
|
+
|
|
55
|
+
from pyagrum.causal._CausalModel import CausalModel
|
|
56
|
+
from pyagrum.causal._CausalFormula import CausalFormula, _getLabelIdx
|
|
57
|
+
from pyagrum.causal._doCalculus import doCalculusWithObservation, doCalculus, getFrontDoorTree, getBackDoorTree
|
|
58
|
+
|
|
59
|
+
# pylint: disable=unused-import
|
|
60
|
+
import pyagrum.causal # for annotations
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def causalImpact(
|
|
64
|
+
cm: CausalModel,
|
|
65
|
+
on: Union[str, NameSet],
|
|
66
|
+
doing: Union[str, NameSet],
|
|
67
|
+
knowing: Optional[NameSet] = None,
|
|
68
|
+
values: Optional[Dict[str, int]] = None,
|
|
69
|
+
) -> Tuple["pyagrum.causal.CausalFormula", "pyagrum.Tensor", str]:
|
|
70
|
+
"""
|
|
71
|
+
Determines the causal impact of interventions.
|
|
72
|
+
|
|
73
|
+
Determines the causal impact of the interventions specified in ``doing`` on the single or list of variables ``on``
|
|
74
|
+
knowing the states of the variables in ``knowing`` (optional). These last parameters is dictionary <variable
|
|
75
|
+
name>:<value>. The causal impact is determined in the causal DAG ``cm``.
|
|
76
|
+
This function returns a triplet with a latex format formula used to compute the causal impact, a tensor
|
|
77
|
+
representing the probability distribution of ``on`` given the interventions and observations as parameters,
|
|
78
|
+
and an explanation of the method allowing the identification. If there is no impact, the joint probability of
|
|
79
|
+
``on`` is simply returned. If the impact is not identifiable the formula and the adjustment will be ``None`` but an
|
|
80
|
+
explanation is still given.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
cm: CausalModel
|
|
85
|
+
the causal model
|
|
86
|
+
on: str|NameSet
|
|
87
|
+
variable name or variable names set of interest
|
|
88
|
+
doing: str|NameSet
|
|
89
|
+
the interventions
|
|
90
|
+
knowing: str|NameSet
|
|
91
|
+
the observations
|
|
92
|
+
values: Dict[str,int] default=None
|
|
93
|
+
the values of interventions and observations
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
Tuple[CausalFormula,pyagrum.Tensor,str]
|
|
98
|
+
the CausalFormula, the computation, the explanation
|
|
99
|
+
"""
|
|
100
|
+
# Checking the args
|
|
101
|
+
son = {on} if isinstance(on, str) else on
|
|
102
|
+
sdoing = {doing} if isinstance(doing, str) else doing
|
|
103
|
+
sk = set() if knowing is None else knowing
|
|
104
|
+
|
|
105
|
+
# verifies that when len()==0, args are empty sets
|
|
106
|
+
if len(son) == 0:
|
|
107
|
+
son = set()
|
|
108
|
+
if len(sdoing) == 0:
|
|
109
|
+
sdoing = set()
|
|
110
|
+
if len(sk) == 0:
|
|
111
|
+
sk = set()
|
|
112
|
+
|
|
113
|
+
total = {cm.observationalBN().variable(cm.observationalBN().idFromName(i)).name() for i in son | sdoing | sk}
|
|
114
|
+
|
|
115
|
+
if values is not None:
|
|
116
|
+
for k in values.keys():
|
|
117
|
+
if k not in total:
|
|
118
|
+
raise ValueError(f"{k} is not in the query arguments.")
|
|
119
|
+
|
|
120
|
+
if len(son & sdoing & sk) > 0:
|
|
121
|
+
raise ValueError("The 3 parts of the query (on, doing, knowing) must not intersect.")
|
|
122
|
+
|
|
123
|
+
formula, tensor, explanation = _causalImpact(cm, son, sdoing, sk)
|
|
124
|
+
|
|
125
|
+
# no need to contextualize the tensor
|
|
126
|
+
if tensor is None or values is None:
|
|
127
|
+
potfinal = tensor
|
|
128
|
+
else:
|
|
129
|
+
sv = set(tensor.names)
|
|
130
|
+
extract_values = {k: _getLabelIdx(cm.observationalBN(), k, v) for k, v in values.items() if k in sv}
|
|
131
|
+
potfinal = tensor.extract(extract_values)
|
|
132
|
+
|
|
133
|
+
# doCalculous can change doing and knowing
|
|
134
|
+
if formula is not None:
|
|
135
|
+
formula._setDoing(sdoing)
|
|
136
|
+
formula._setKnowing(sk)
|
|
137
|
+
|
|
138
|
+
return formula, potfinal, explanation
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _causalImpact(
|
|
142
|
+
cm: CausalModel, on: Union[str, NameSet], doing: Union[str, NameSet], knowing: Union[str, NameSet]
|
|
143
|
+
) -> Tuple["pyagrum.causal.CausalFormula", "pyagrum.Tensor", str]:
|
|
144
|
+
"""
|
|
145
|
+
Determines the causal impact of interventions.
|
|
146
|
+
|
|
147
|
+
Determines the causal impact of the interventions specified in ``doing`` on the single or list of variables ``on``
|
|
148
|
+
knowing the states of the variables in ``knowing`` (optional). The causal impact is determined in the causal DAG
|
|
149
|
+
``cm``.
|
|
150
|
+
This function returns a triplet with a latex format formula used to compute the causal impact, a tensor
|
|
151
|
+
representing the probability distribution of ``on`` given the interventions and observations as parameters,
|
|
152
|
+
and an explanation of the method allowing the identification. If there is no impact, the joint probability of
|
|
153
|
+
``on`` is simply returned. If the impact is not identifiable the formula and the adjustment will be ``None`` but an
|
|
154
|
+
explanation is still given.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
cm: CausalModel
|
|
159
|
+
the causal model
|
|
160
|
+
on: str|Set[str]
|
|
161
|
+
targeted variable(s)
|
|
162
|
+
doing: str|Set[str]
|
|
163
|
+
interventions
|
|
164
|
+
knowing: str|Set[str]
|
|
165
|
+
observations
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
Tuple[CausalFormula,pyagrum.Tensor,str]
|
|
170
|
+
the latex representation, the computation, the explanation
|
|
171
|
+
"""
|
|
172
|
+
nY = list(on)
|
|
173
|
+
iY = [cm.observationalBN().idFromName(i) for i in nY]
|
|
174
|
+
|
|
175
|
+
nDo = list(doing)
|
|
176
|
+
iDo = [cm.observationalBN().idFromName(i) for i in nDo]
|
|
177
|
+
|
|
178
|
+
if knowing is None:
|
|
179
|
+
nK = []
|
|
180
|
+
else:
|
|
181
|
+
nK = list(knowing)
|
|
182
|
+
sK = {cm.observationalBN().idFromName(i) for i in nK}
|
|
183
|
+
|
|
184
|
+
# Null causal effect (different from non identifiable causal effect)
|
|
185
|
+
if isDSep(cm, set(iDo), set(iY), sK | cm.latentVariablesIds()):
|
|
186
|
+
explain = "No causal effect of X on Y, because they are d-separated "
|
|
187
|
+
explain += "(conditioning on the observed variables if any)."
|
|
188
|
+
ar = CausalFormula(cm, ASTposteriorProba(cm.causalBN(), set(nY), set(nK)), on, doing, knowing)
|
|
189
|
+
adj = ar.eval()
|
|
190
|
+
return ar, adj.reorganize([v for v in nY + nDo + nK if v in adj.names]), explain
|
|
191
|
+
|
|
192
|
+
# Front or Back door
|
|
193
|
+
if len(iDo) == 1 and len(nY) == 1 and len(nK) == 0:
|
|
194
|
+
# for bd in backdoor_generator(cm, iDo[0], iY[0], cm.latentVariablesIds()):
|
|
195
|
+
bd = cm.backDoor(iDo[0], iY[0], withNames=False)
|
|
196
|
+
if bd is not None:
|
|
197
|
+
ar = CausalFormula(cm, getBackDoorTree(cm, nDo[0], nY[0], bd), on, doing, knowing)
|
|
198
|
+
adj = ar.eval()
|
|
199
|
+
explain = "backdoor " + str([cm.causalBN().variable(i).name() for i in bd]) + " found."
|
|
200
|
+
return ar, adj.reorganize([v for v in nY + nDo + nK if v in adj.names]), explain
|
|
201
|
+
|
|
202
|
+
# for fd in frontdoor_generator(cm, iDo[0], iY[0], cm.latentVariablesIds()):
|
|
203
|
+
fd = cm.frontDoor(iDo[0], iY[0], withNames=False)
|
|
204
|
+
if fd is not None:
|
|
205
|
+
ar = CausalFormula(cm, getFrontDoorTree(cm, nDo[0], nY[0], fd), on, doing, knowing)
|
|
206
|
+
adj = ar.eval()
|
|
207
|
+
explain = "frontdoor " + str([cm.causalBN().variable(i).name() for i in fd]) + " found."
|
|
208
|
+
return ar, adj.reorganize([v for v in nY + nDo + nK if v in adj.names]), explain
|
|
209
|
+
|
|
210
|
+
# Go for do-calculus
|
|
211
|
+
try:
|
|
212
|
+
if len(nK) == 0:
|
|
213
|
+
ar = doCalculus(cm, on, set(nDo))
|
|
214
|
+
else:
|
|
215
|
+
ar = doCalculusWithObservation(cm, on, set(nDo), set(nK))
|
|
216
|
+
except HedgeException as h:
|
|
217
|
+
return None, None, h.message
|
|
218
|
+
|
|
219
|
+
adj = ar.eval()
|
|
220
|
+
lsum = nY + nDo + nK
|
|
221
|
+
lv = [v for v in lsum if v in adj.names]
|
|
222
|
+
|
|
223
|
+
# todo : check why it is possible that some variables are in names and
|
|
224
|
+
# not in lsum ... (see for instance p213, book of why and
|
|
225
|
+
# https://twitter.com/analisereal/status/1022277416205475841 : should
|
|
226
|
+
# really z be in the last formula ?)
|
|
227
|
+
ssum = set(lsum)
|
|
228
|
+
lv += [v for v in adj.names if v not in ssum]
|
|
229
|
+
|
|
230
|
+
adj = adj.reorganize(lv) # sumIn(lv).reorganize(lv)
|
|
231
|
+
explain = "Do-calculus computations"
|
|
232
|
+
return ar, adj, explain
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def counterfactualModel(
|
|
236
|
+
cm: CausalModel, profile: Union[Dict[str, int], type(None)], whatif: Union[str, Set[str]]
|
|
237
|
+
) -> CausalModel:
|
|
238
|
+
"""
|
|
239
|
+
Determines the estimation of the twin model following the three steps algorithm from "The Book Of Why" (Pearl 2018) chapter 8 page 253.
|
|
240
|
+
|
|
241
|
+
This is done according to the following algorithm:
|
|
242
|
+
-Step 1: calculate the posterior probabilities of idiosyncratic nodes (parentless nodes - whatif-latent variables) in the BN with which we created the causal model with "profile" as evidence.
|
|
243
|
+
-Step 2 : We replace in the original BN the prior probabilities of idiosyncratic nodes with tensors calculated in step 1 (it will spread to the causal model)
|
|
244
|
+
|
|
245
|
+
This function returns the twin CausalModel
|
|
246
|
+
|
|
247
|
+
Parameters
|
|
248
|
+
----------
|
|
249
|
+
cm: CausalModel
|
|
250
|
+
profile: Dict[str,int] default=None
|
|
251
|
+
evidence
|
|
252
|
+
whatif: str|Set[str]
|
|
253
|
+
idiosyncratic nodes
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
CausalModel
|
|
258
|
+
the twin CausalModel
|
|
259
|
+
"""
|
|
260
|
+
# Step 1 : calculate the posterior probabilities of idiosyncratic nodes knowing the profil
|
|
261
|
+
|
|
262
|
+
# whatif can be a string or a set of strings
|
|
263
|
+
if isinstance(whatif, str):
|
|
264
|
+
idWhatif = {whatif}
|
|
265
|
+
else:
|
|
266
|
+
idWhatif = whatif
|
|
267
|
+
idWhatif = set(map(cm.idFromName, idWhatif))
|
|
268
|
+
|
|
269
|
+
# get nodes without parents in the causal model
|
|
270
|
+
parentless = set()
|
|
271
|
+
# nodes of the causal model
|
|
272
|
+
nodes = cm.names().keys()
|
|
273
|
+
for node in nodes:
|
|
274
|
+
# if nb parents is equal to zero => parentless node
|
|
275
|
+
if len(cm.parents(node)) == 0:
|
|
276
|
+
parentless.add(node)
|
|
277
|
+
|
|
278
|
+
# idiosyncratic factors (specific to and representative of the profile) are parentless - (whatif+latent variables)
|
|
279
|
+
idiosyncratic = parentless.difference(idWhatif)
|
|
280
|
+
idiosyncratic = idiosyncratic.difference(cm.latentVariablesIds())
|
|
281
|
+
|
|
282
|
+
# copying the causal model
|
|
283
|
+
twincm = cm.clone()
|
|
284
|
+
bn = twincm.observationalBN()
|
|
285
|
+
|
|
286
|
+
# calculate the posterior probability of each idiosyncratic factor knowing the profile in the original BN
|
|
287
|
+
# posteriors will be a dict {factor : posterior probability knowing the profile}
|
|
288
|
+
posteriors = dict.fromkeys(idiosyncratic)
|
|
289
|
+
ie = pyagrum.LazyPropagation(bn)
|
|
290
|
+
ie.setEvidence(profile)
|
|
291
|
+
ie.makeInference()
|
|
292
|
+
for factor in idiosyncratic:
|
|
293
|
+
posteriors[factor] = ie.posterior(factor)
|
|
294
|
+
|
|
295
|
+
# Step 2 : We replace the prior probabilities of idiosyncratic nodes with tensors calculated in step 1 in the BN
|
|
296
|
+
# Saving the original CPTs of idiosyncratic variables
|
|
297
|
+
for factor in idiosyncratic:
|
|
298
|
+
bn.cpt(factor).fillWith(posteriors[factor])
|
|
299
|
+
|
|
300
|
+
return twincm
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def counterfactual(
|
|
304
|
+
cm: CausalModel,
|
|
305
|
+
profile: Union[Dict[str, int], type(None)],
|
|
306
|
+
on: Union[str, Set[str]],
|
|
307
|
+
whatif: Union[str, Set[str]],
|
|
308
|
+
values: Union[Dict[str, int], type(None)] = None,
|
|
309
|
+
) -> "pyagrum.Tensor":
|
|
310
|
+
"""
|
|
311
|
+
Determines the estimation of a counterfactual query following the the three steps algorithm from "The Book Of Why"
|
|
312
|
+
(Pearl 2018) chapter 8 page 253.
|
|
313
|
+
|
|
314
|
+
Determines the estimation of the counterfactual query: Given the "profile" (dictionary <variable name>:<value>),what
|
|
315
|
+
would variables in "on" (single or list of variables) be if variables in "whatif" (single or list of variables) had
|
|
316
|
+
been as specified in "values" (dictionary <variable name>:<value>)(optional).
|
|
317
|
+
|
|
318
|
+
This is done according to the following algorithm:
|
|
319
|
+
-Step 1-2: compute the twin causal model
|
|
320
|
+
-Step 3 : determine the causal impact of the interventions specified in "whatif" on the single or list of
|
|
321
|
+
variables "on" in the causal model.
|
|
322
|
+
|
|
323
|
+
This function returns the tensor calculated in step 3, representing the probability distribution of "on" given
|
|
324
|
+
the interventions "whatif", if it had been as specified in "values" (if "values" is omitted, every possible value of
|
|
325
|
+
"whatif")
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
cm: CausalModel
|
|
330
|
+
profile: Dict[str,int] default=None
|
|
331
|
+
evidence
|
|
332
|
+
on: variable name or variable names set
|
|
333
|
+
the variable(s) of interest
|
|
334
|
+
whatif: str|Set[str]
|
|
335
|
+
idiosyncratic nodes
|
|
336
|
+
values: Dict[str,int]
|
|
337
|
+
values for certain variables in whatif.
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
pyagrum.Tensor
|
|
342
|
+
the computed counterfactual impact
|
|
343
|
+
"""
|
|
344
|
+
# Step 1 and 2 : create the twin causal model
|
|
345
|
+
twincm = counterfactualModel(cm, profile, whatif)
|
|
346
|
+
|
|
347
|
+
# Step 3 : operate the intervention in the causal model based on bn
|
|
348
|
+
_, adj, _ = causalImpact(twincm, on=on, doing=whatif, values=values)
|
|
349
|
+
# cslnb.showCausalImpact(cm,on = on,whatif=whatif,values=values)
|
|
350
|
+
|
|
351
|
+
# adj is using variables from twincm. We copy it in a Tensor using variables of cm
|
|
352
|
+
res = pyagrum.Tensor()
|
|
353
|
+
for v in adj.names:
|
|
354
|
+
res.add(cm.observationalBN().variableFromName(v))
|
|
355
|
+
res.fillWith(adj)
|
|
356
|
+
return res
|