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.
Files changed (107) hide show
  1. pyagrum/__init__.py +165 -0
  2. pyagrum/_pyagrum.so +0 -0
  3. pyagrum/bnmixture/BNMInference.py +268 -0
  4. pyagrum/bnmixture/BNMLearning.py +376 -0
  5. pyagrum/bnmixture/BNMixture.py +464 -0
  6. pyagrum/bnmixture/__init__.py +60 -0
  7. pyagrum/bnmixture/notebook.py +1058 -0
  8. pyagrum/causal/_CausalFormula.py +280 -0
  9. pyagrum/causal/_CausalModel.py +436 -0
  10. pyagrum/causal/__init__.py +81 -0
  11. pyagrum/causal/_causalImpact.py +356 -0
  12. pyagrum/causal/_dSeparation.py +598 -0
  13. pyagrum/causal/_doAST.py +761 -0
  14. pyagrum/causal/_doCalculus.py +361 -0
  15. pyagrum/causal/_doorCriteria.py +374 -0
  16. pyagrum/causal/_exceptions.py +95 -0
  17. pyagrum/causal/_types.py +61 -0
  18. pyagrum/causal/causalEffectEstimation/_CausalEffectEstimation.py +1175 -0
  19. pyagrum/causal/causalEffectEstimation/_IVEstimators.py +718 -0
  20. pyagrum/causal/causalEffectEstimation/_RCTEstimators.py +132 -0
  21. pyagrum/causal/causalEffectEstimation/__init__.py +46 -0
  22. pyagrum/causal/causalEffectEstimation/_backdoorEstimators.py +774 -0
  23. pyagrum/causal/causalEffectEstimation/_causalBNEstimator.py +324 -0
  24. pyagrum/causal/causalEffectEstimation/_frontdoorEstimators.py +396 -0
  25. pyagrum/causal/causalEffectEstimation/_learners.py +118 -0
  26. pyagrum/causal/causalEffectEstimation/_utils.py +466 -0
  27. pyagrum/causal/notebook.py +171 -0
  28. pyagrum/clg/CLG.py +658 -0
  29. pyagrum/clg/GaussianVariable.py +111 -0
  30. pyagrum/clg/SEM.py +312 -0
  31. pyagrum/clg/__init__.py +63 -0
  32. pyagrum/clg/canonicalForm.py +408 -0
  33. pyagrum/clg/constants.py +54 -0
  34. pyagrum/clg/forwardSampling.py +202 -0
  35. pyagrum/clg/learning.py +776 -0
  36. pyagrum/clg/notebook.py +480 -0
  37. pyagrum/clg/variableElimination.py +271 -0
  38. pyagrum/common.py +60 -0
  39. pyagrum/config.py +319 -0
  40. pyagrum/ctbn/CIM.py +513 -0
  41. pyagrum/ctbn/CTBN.py +573 -0
  42. pyagrum/ctbn/CTBNGenerator.py +216 -0
  43. pyagrum/ctbn/CTBNInference.py +459 -0
  44. pyagrum/ctbn/CTBNLearner.py +161 -0
  45. pyagrum/ctbn/SamplesStats.py +671 -0
  46. pyagrum/ctbn/StatsIndepTest.py +355 -0
  47. pyagrum/ctbn/__init__.py +79 -0
  48. pyagrum/ctbn/constants.py +54 -0
  49. pyagrum/ctbn/notebook.py +264 -0
  50. pyagrum/defaults.ini +199 -0
  51. pyagrum/deprecated.py +95 -0
  52. pyagrum/explain/_ComputationCausal.py +75 -0
  53. pyagrum/explain/_ComputationConditional.py +48 -0
  54. pyagrum/explain/_ComputationMarginal.py +48 -0
  55. pyagrum/explain/_CustomShapleyCache.py +110 -0
  56. pyagrum/explain/_Explainer.py +176 -0
  57. pyagrum/explain/_Explanation.py +70 -0
  58. pyagrum/explain/_FIFOCache.py +54 -0
  59. pyagrum/explain/_ShallCausalValues.py +204 -0
  60. pyagrum/explain/_ShallConditionalValues.py +155 -0
  61. pyagrum/explain/_ShallMarginalValues.py +155 -0
  62. pyagrum/explain/_ShallValues.py +296 -0
  63. pyagrum/explain/_ShapCausalValues.py +208 -0
  64. pyagrum/explain/_ShapConditionalValues.py +126 -0
  65. pyagrum/explain/_ShapMarginalValues.py +191 -0
  66. pyagrum/explain/_ShapleyValues.py +298 -0
  67. pyagrum/explain/__init__.py +81 -0
  68. pyagrum/explain/_explGeneralizedMarkovBlanket.py +152 -0
  69. pyagrum/explain/_explIndependenceListForPairs.py +146 -0
  70. pyagrum/explain/_explInformationGraph.py +264 -0
  71. pyagrum/explain/notebook/__init__.py +54 -0
  72. pyagrum/explain/notebook/_bar.py +142 -0
  73. pyagrum/explain/notebook/_beeswarm.py +174 -0
  74. pyagrum/explain/notebook/_showShapValues.py +97 -0
  75. pyagrum/explain/notebook/_waterfall.py +220 -0
  76. pyagrum/explain/shapley.py +225 -0
  77. pyagrum/lib/__init__.py +46 -0
  78. pyagrum/lib/_colors.py +390 -0
  79. pyagrum/lib/bn2graph.py +299 -0
  80. pyagrum/lib/bn2roc.py +1026 -0
  81. pyagrum/lib/bn2scores.py +217 -0
  82. pyagrum/lib/bn_vs_bn.py +605 -0
  83. pyagrum/lib/cn2graph.py +305 -0
  84. pyagrum/lib/discreteTypeProcessor.py +1102 -0
  85. pyagrum/lib/discretizer.py +58 -0
  86. pyagrum/lib/dynamicBN.py +390 -0
  87. pyagrum/lib/explain.py +57 -0
  88. pyagrum/lib/export.py +84 -0
  89. pyagrum/lib/id2graph.py +258 -0
  90. pyagrum/lib/image.py +387 -0
  91. pyagrum/lib/ipython.py +307 -0
  92. pyagrum/lib/mrf2graph.py +471 -0
  93. pyagrum/lib/notebook.py +1821 -0
  94. pyagrum/lib/proba_histogram.py +552 -0
  95. pyagrum/lib/utils.py +138 -0
  96. pyagrum/pyagrum.py +31495 -0
  97. pyagrum/skbn/_MBCalcul.py +242 -0
  98. pyagrum/skbn/__init__.py +49 -0
  99. pyagrum/skbn/_learningMethods.py +282 -0
  100. pyagrum/skbn/_utils.py +297 -0
  101. pyagrum/skbn/bnclassifier.py +1014 -0
  102. pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSE.md +12 -0
  103. pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSES/LGPL-3.0-or-later.txt +304 -0
  104. pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/LICENSES/MIT.txt +18 -0
  105. pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/METADATA +145 -0
  106. pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/RECORD +107 -0
  107. pyagrum_nightly-2.3.0.9.dev202512061764412981.dist-info/WHEEL +4 -0
@@ -0,0 +1,718 @@
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 pandas as pd
42
+ import numpy as np
43
+
44
+ from typing import Any
45
+
46
+ from sklearn.base import clone
47
+
48
+ from ._learners import learnerFromString
49
+
50
+ from scipy.special import expit
51
+ from scipy.optimize import minimize
52
+
53
+
54
+ class Wald:
55
+ """
56
+ An implementation of the Wald estimator which computes the
57
+ Local Average Causal Effect (LACE), also know as
58
+ the Local Average Treatment Effect (LATE).
59
+ Only Supports binary instruments.
60
+ """
61
+
62
+ def __init__(self) -> None:
63
+ """
64
+ Initialize an Wald estimator.
65
+ """
66
+
67
+ def fit(
68
+ self,
69
+ X: np.matrix | np.ndarray | pd.DataFrame,
70
+ treatment: np.ndarray | pd.Series,
71
+ y: np.ndarray | pd.Series,
72
+ w: np.matrix | np.ndarray | pd.DataFrame,
73
+ ) -> None:
74
+ """
75
+ Fit the inference model.
76
+
77
+ Parameters
78
+ ----------
79
+ X: np.matrix or np.ndarray or pd.DataFrame
80
+ The covariate matrix.
81
+ w: np.matrix or np.ndarray or pd.DataFrame
82
+ The instrument variable.
83
+ treatment: np.ndarray or pd.Series
84
+ The treatment assignment vector.
85
+ y: np.ndarray or pd.Series,
86
+ The outcome vector.
87
+ """
88
+ if not X.empty:
89
+ raise ValueError(
90
+ "The Wald estimator does not support confounders, consider using `WaldIPW`, `NormalizedWaldIPW` or `TSLS`."
91
+ )
92
+ if set(np.unique(w.to_numpy())) != {0, 1}:
93
+ raise ValueError("Instrument must be binary with values 0 and 1 for LACE estimation.")
94
+
95
+ def predict(
96
+ self,
97
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
98
+ treatment: np.ndarray | pd.Series = None,
99
+ y: np.ndarray | pd.Series = None,
100
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
101
+ ) -> np.ndarray:
102
+ """
103
+ Predict the Individual Causal Effect (ICE).
104
+
105
+ Parameters
106
+ ----------
107
+ X: np.matrix or np.ndarray or pd.DataFrame
108
+ The covariate matrix.
109
+ w: np.matrix or np.ndarray or pd.DataFrame
110
+ The instrument variable.
111
+ treatment: np.ndarray or pd.Series, optional
112
+ The vector of treatment assignments.
113
+ y: np.ndarray or pd.Series, optional
114
+ The vector of outcomes.
115
+
116
+ Returns
117
+ -------
118
+ np.ndarray
119
+ An array containing the predicted ICE.
120
+ """
121
+
122
+ w = w.iloc[:, 0] if isinstance(w, pd.DataFrame) else w
123
+ return [(y[w == 1].mean() - y[w == 0].mean()) / (treatment[w == 1].mean() - treatment[w == 0].mean())] * len(w)
124
+
125
+ def estimate_ate(
126
+ self,
127
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
128
+ treatment: np.ndarray | pd.Series = None,
129
+ y: np.ndarray | pd.Series = None,
130
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
131
+ pretrain: bool = True,
132
+ ) -> float:
133
+ """
134
+ Predicts the Local Average Causal Effect (LACE),
135
+ also referred to as the Local Average Treatment Effect (LATE).
136
+ (The term "Average Treatment Effect" (ATE) is used in
137
+ the method name for compatibility purposes.)
138
+
139
+ Parameters
140
+ ----------
141
+ X: np.matrix or np.ndarray or pd.DataFrame
142
+ The covariate matrix.
143
+ w: np.matrix or np.ndarray or pd.DataFrame
144
+ The instrument variable.
145
+ treatment: np.ndarray or pd.Series, optional
146
+ The vector of treatment assignments.
147
+ y: np.ndarray or pd.Series, optional
148
+ The vector of outcomes.
149
+
150
+ Returns
151
+ -------
152
+ float
153
+ The value of the ACE.
154
+ """
155
+
156
+ w = w.iloc[:, 0] if isinstance(w, pd.DataFrame) else w
157
+ return (y[w == 1].mean() - y[w == 0].mean()) / (treatment[w == 1].mean() - treatment[w == 0].mean())
158
+
159
+
160
+ class WaldIPW:
161
+ """
162
+ A basic implementation of the Wald estimator with Inverse Propensity
163
+ Score Weighting which computes the Local Average Causal Effect (LACE).
164
+ (see https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8021190/).
165
+ Only Supports binary instruments.
166
+ """
167
+
168
+ def __init__(self, iv_probability_learner: str | Any | None = None) -> None:
169
+ """
170
+ Initialize an Wald IPW estimator for LACE.
171
+
172
+ Parameters
173
+ ----------
174
+ iv_probability_learner: str or Any, optional
175
+ Estimator for instrumental variable probability.
176
+ If not provided, defaults to LogisticRegression.
177
+ """
178
+ if iv_probability_learner is None:
179
+ self.iv_probability_learner = learnerFromString("LogisticRegression")
180
+ elif isinstance(iv_probability_learner, str):
181
+ self.iv_probability_learner = learnerFromString(iv_probability_learner)
182
+ else:
183
+ self.iv_probability_learner = clone(iv_probability_learner)
184
+
185
+ def fit(
186
+ self,
187
+ X: np.matrix | np.ndarray | pd.DataFrame,
188
+ treatment: np.ndarray | pd.Series,
189
+ y: np.ndarray | pd.Series,
190
+ w: np.matrix | np.ndarray | pd.DataFrame,
191
+ ) -> None:
192
+ """
193
+ Fit the inference model.
194
+
195
+ Parameters
196
+ ----------
197
+ X: np.matrix or np.ndarray or pd.DataFrame
198
+ The covariate matrix.
199
+ w: np.matrix or np.ndarray or pd.DataFrame
200
+ The instrument variable.
201
+ treatment: np.ndarray or pd.Series, optional
202
+ The vector of treatment assignments.
203
+ y: np.ndarray or pd.Series, optional
204
+ The vector of outcomes.
205
+ """
206
+
207
+ if set(np.unique(w.to_numpy())) != {0, 1}:
208
+ raise ValueError("Instrument must be binary with values 0 and 1 for LACE estimation.")
209
+
210
+ self.iv_probability_learner.fit(X=X, y=np.ravel(w))
211
+ # print(w, np.ravel(w))
212
+
213
+ def predict(
214
+ self,
215
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
216
+ treatment: np.ndarray | pd.Series = None,
217
+ y: np.ndarray | pd.Series = None,
218
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
219
+ ) -> np.ndarray:
220
+ """
221
+ Predict the Individual Causal Effect (ICE).
222
+
223
+ Parameters
224
+ ----------
225
+ X: np.matrix or np.ndarray or pd.DataFrame
226
+ The covariate matrix.
227
+ w: np.matrix or np.ndarray or pd.DataFrame
228
+ The instrument variable.
229
+ treatment: np.ndarray or pd.Series, optional
230
+ The vector of treatment assignments.
231
+ y: np.ndarray or pd.Series, optional
232
+ The vector of outcomes.
233
+
234
+ Returns
235
+ -------
236
+ np.ndarray
237
+ An array containing the predicted ICE.
238
+ """
239
+
240
+ w = w.iloc[:, 0] if isinstance(w, pd.DataFrame) else w
241
+
242
+ e = self.iv_probability_learner.predict_proba(X)[:, 1]
243
+
244
+ v_func = np.vectorize(lambda e, w, d: (w / e - (1 - w) / (1 - e)) * d)
245
+
246
+ return v_func(e, w, y) / v_func(e, w, treatment).mean()
247
+
248
+ def estimate_ate(
249
+ self,
250
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
251
+ treatment: np.ndarray | pd.Series = None,
252
+ y: np.ndarray | pd.Series = None,
253
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
254
+ pretrain: bool = True,
255
+ ) -> float:
256
+ """
257
+ Predicts the Local Average Causal Effect (LACE),
258
+ also referred to as the Local Average Treatment Effect (LATE).
259
+ (The term "Average Treatment Effect" (ATE) is used in
260
+ the method name for compatibility purposes.)
261
+
262
+ Parameters
263
+ ----------
264
+ X: np.matrix or np.ndarray or pd.DataFrame
265
+ The covariate matrix.
266
+ w: np.matrix or np.ndarray or pd.DataFrame
267
+ The instrument variable.
268
+ treatment: np.ndarray or pd.Series, optional
269
+ The vector of treatment assignments.
270
+ y: np.ndarray or pd.Series, optional
271
+ The vector of outcomes.
272
+
273
+ Returns
274
+ -------
275
+ float
276
+ The value of the ACE.
277
+ """
278
+
279
+ return self.predict(X, treatment, y, w).mean()
280
+
281
+
282
+ class NormalizedWaldIPW:
283
+ """
284
+ A basic implementation of the normalized Wald estimator with Inverse Propensity
285
+ Score Weighting which computes the Local Average Causal Effect (LACE).
286
+ (see https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8021190/).
287
+ Only Supports binary instruments.
288
+ """
289
+
290
+ def __init__(self, iv_probability_learner: str | Any | None = None) -> None:
291
+ """
292
+ Initialize an Wald IPW estimator for LACE.
293
+
294
+ Parameters
295
+ ----------
296
+ iv_probability_learner: str or Any, optional
297
+ Estimator for instrumental probability score.
298
+ If not provided, defaults to LogisticRegression.
299
+ """
300
+ if iv_probability_learner is None:
301
+ self.iv_probability_learner = learnerFromString("LogisticRegression")
302
+ elif isinstance(iv_probability_learner, str):
303
+ self.iv_probability_learner = learnerFromString(iv_probability_learner)
304
+ else:
305
+ self.iv_probability_learner = clone(iv_probability_learner)
306
+
307
+ def fit(
308
+ self,
309
+ X: np.matrix | np.ndarray | pd.DataFrame,
310
+ treatment: np.ndarray | pd.Series,
311
+ y: np.ndarray | pd.Series,
312
+ w: np.matrix | np.ndarray | pd.DataFrame,
313
+ ) -> None:
314
+ """
315
+ Fit the inference model.
316
+
317
+ Parameters
318
+ ----------
319
+ X: np.matrix or np.ndarray or pd.DataFrame
320
+ The covariate matrix.
321
+ w: np.matrix or np.ndarray or pd.DataFrame
322
+ The instrument variable.
323
+ treatment: np.ndarray or pd.Series, optional
324
+ The vector of treatment assignments.
325
+ y: np.ndarray or pd.Series, optional
326
+ The vector of outcomes.
327
+ """
328
+
329
+ if set(np.unique(w.to_numpy())) != {0, 1}:
330
+ raise ValueError("Instrument must be binary with values 0 and 1 for LACE estimation.")
331
+
332
+ self.iv_probability_learner.fit(X=X, y=np.ravel(w))
333
+
334
+ def predict(
335
+ self,
336
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
337
+ treatment: np.ndarray | pd.Series = None,
338
+ y: np.ndarray | pd.Series = None,
339
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
340
+ ) -> np.ndarray:
341
+ """
342
+ Predict the Individual Causal Effect (ICE).
343
+
344
+ Parameters
345
+ ----------
346
+ X: np.matrix or np.ndarray or pd.DataFrame
347
+ The covariate matrix.
348
+ w: np.matrix or np.ndarray or pd.DataFrame
349
+ The instrument variable.
350
+ treatment: np.ndarray or pd.Series, optional
351
+ The vector of treatment assignments.
352
+ y: np.ndarray or pd.Series, optional
353
+ The vector of outcomes.
354
+
355
+ Returns
356
+ -------
357
+ np.ndarray
358
+ An array containing the predicted ICE.
359
+ """
360
+
361
+ w = w.iloc[:, 0] if isinstance(w, pd.DataFrame) else w
362
+
363
+ e = self.iv_probability_learner.predict_proba(X)[:, 1]
364
+
365
+ v_func = np.vectorize(lambda e, w, d: w * d / e)
366
+
367
+ return ((v_func(e, w, y) / v_func(e, w, 1).mean()) - (v_func(1 - e, 1 - w, y) / v_func(1 - e, 1 - w, 1).mean())) / (
368
+ (v_func(e, w, treatment) / v_func(e, w, 1).mean())
369
+ - (v_func(1 - e, 1 - w, treatment) / v_func(1 - e, 1 - w, 1).mean())
370
+ ).mean()
371
+
372
+ def estimate_ate(
373
+ self,
374
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
375
+ treatment: np.ndarray | pd.Series = None,
376
+ y: np.ndarray | pd.Series = None,
377
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
378
+ pretrain: bool = True,
379
+ ) -> float:
380
+ """
381
+ Predicts the Local Average Causal Effect (LACE),
382
+ also referred to as the Local Average Treatment Effect (LATE).
383
+ (The term "Average Treatment Effect" (ATE) is used in
384
+ the method name for compatibility purposes.)
385
+
386
+ Parameters
387
+ ----------
388
+ X: np.matrix or np.ndarray or pd.DataFrame
389
+ The covariate matrix.
390
+ w: np.matrix or np.ndarray or pd.DataFrame
391
+ The instrument variable.
392
+ treatment: np.ndarray or pd.Series, optional
393
+ The vector of treatment assignments.
394
+ y: np.ndarray or pd.Series, optional
395
+ The vector of outcomes.
396
+
397
+ Returns
398
+ -------
399
+ float
400
+ The value of the ACE.
401
+ """
402
+
403
+ return self.predict(X, treatment, y, w).mean()
404
+
405
+
406
+ class TSLS:
407
+ """
408
+ A basic implementation of the Two Stage Least-Squares Estimator.
409
+ (see https://www.ncss.com/wp-content/themes/ncss/pdf/Procedures/NCSS/Two-Stage_Least_Squares.pdf)
410
+ """
411
+
412
+ def __init__(
413
+ self,
414
+ learner: str | Any | None = None,
415
+ treatment_learner: str | Any | None = None,
416
+ outcome_learner: str | Any | None = None,
417
+ ) -> None:
418
+ """
419
+ Initialize an TSLS estimator.
420
+ Only supports Linear Models, must have `.coef_` attribute.
421
+
422
+ Parameters
423
+ ----------
424
+ learner: str or Any, optional
425
+ Base estimator for all learners.
426
+ If not provided, defaults to LinearRegression.
427
+ treatment_learner: str or Any, optional
428
+ Estimator for treatment assignment.
429
+ Overrides `learner` if specified.
430
+ outcome_learner: str or Any, optional
431
+ Estimator for outcome.
432
+ Overrides `learner` if specified.
433
+ """
434
+
435
+ if learner is None:
436
+ self.treatment_learner = learnerFromString("LinearRegression")
437
+ self.outcome_learner = learnerFromString("LinearRegression")
438
+ elif isinstance(learner, str):
439
+ self.treatment_learner = learnerFromString(learner)
440
+ self.outcome_learner = learnerFromString(learner)
441
+ else:
442
+ self.treatment_learner = clone(learner)
443
+ self.outcome_learner = clone(learner)
444
+
445
+ if isinstance(treatment_learner, str):
446
+ self.treatment_learner = learnerFromString(treatment_learner)
447
+ elif treatment_learner is not None:
448
+ self.treatment_learner = clone(treatment_learner)
449
+
450
+ if isinstance(outcome_learner, str):
451
+ self.outcome_learner = learnerFromString(outcome_learner)
452
+ elif outcome_learner is not None:
453
+ self.outcome_learner = clone(outcome_learner)
454
+
455
+ def fit(
456
+ self,
457
+ X: np.matrix | np.ndarray | pd.DataFrame,
458
+ treatment: np.ndarray | pd.Series,
459
+ y: np.ndarray | pd.Series,
460
+ w: np.matrix | np.ndarray | pd.DataFrame,
461
+ ) -> None:
462
+ """
463
+ Fit the inference model.
464
+
465
+ Parameters
466
+ ----------
467
+ X: np.matrix or np.ndarray or pd.DataFrame
468
+ The covariate matrix.
469
+ w: np.matrix or np.ndarray or pd.DataFrame
470
+ The instrument variable.
471
+ treatment: np.ndarray or pd.Series, optional
472
+ The vector of treatment assignments.
473
+ y: np.ndarray or pd.Series, optional
474
+ The vector of outcomes.
475
+ """
476
+
477
+ self.treatment_learner.fit(X=pd.concat([pd.DataFrame(X), pd.DataFrame(w)], axis=1), y=treatment)
478
+
479
+ def predict(
480
+ self,
481
+ X: np.matrix | np.ndarray | pd.DataFrame,
482
+ treatment: np.ndarray | pd.Series,
483
+ y: np.ndarray | pd.Series,
484
+ w: np.matrix | np.ndarray | pd.DataFrame,
485
+ ) -> np.ndarray:
486
+ """
487
+ Predict the Individual Causal Effect (ICE).
488
+
489
+ Parameters
490
+ ----------
491
+ X: np.matrix or np.ndarray or pd.DataFrame
492
+ The covariate matrix.
493
+ w: np.matrix or np.ndarray or pd.DataFrame
494
+ The instrument variable.
495
+ treatment: np.ndarray or pd.Series, optional
496
+ The vector of treatment assignments.
497
+ y: np.ndarray or pd.Series, optional
498
+ The vector of outcomes.
499
+
500
+ Returns
501
+ -------
502
+ np.ndarray
503
+ An array containing the predicted ICE.
504
+ """
505
+
506
+ T_hat = self.treatment_learner.predict(X=pd.concat([pd.DataFrame(X), pd.DataFrame(w)], axis=1))
507
+ self.outcome_learner.fit(X=pd.concat([pd.DataFrame(X), pd.DataFrame({"T": T_hat})], axis=1), y=y)
508
+
509
+ return np.repeat(self.outcome_learner.coef_, len(X))
510
+
511
+ def estimate_ate(
512
+ self,
513
+ X: np.matrix | np.ndarray | pd.DataFrame,
514
+ treatment: np.ndarray | pd.Series,
515
+ y: np.ndarray | pd.Series = None,
516
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
517
+ pretrain: bool = True,
518
+ ) -> float:
519
+ """
520
+ Predicts the Average Causal Effect (ACE),
521
+ also referred to as the Local Average Treatment Effect (LATE).
522
+ (The term ATE is used in the method name for compatibility purposes.)
523
+
524
+ Parameters
525
+ ----------
526
+ X: np.matrix or np.ndarray or pd.DataFrame
527
+ The covariate matrix.
528
+ w: np.matrix or np.ndarray or pd.DataFrame
529
+ The instrument variable.
530
+ treatment: np.ndarray or pd.Series, optional
531
+ The vector of treatment assignments.
532
+ y: np.ndarray or pd.Series, optional
533
+ The vector of outcomes.
534
+
535
+ Returns
536
+ -------
537
+ float
538
+ The value of the ACE.
539
+ """
540
+
541
+ return self.predict(X=X, treatment=treatment, y=y, w=w).mean()
542
+
543
+
544
+ # WORK IN PROGRESS
545
+
546
+
547
+ class _ICSW:
548
+ """
549
+ A basic implementation of the Inverse Complier Score Weighting
550
+ (ICSW) estimator which computes the Average Causal Effect (ACE),
551
+ under the Latent ignorability of compliance assumption.
552
+ Based on Aronow et al. (2013)
553
+ (see https://www.jstor.org/stable/24572676?seq=1).
554
+ Only Supports binary instruments.
555
+ """
556
+
557
+ def __init__(
558
+ self,
559
+ learner: str | Any | None = None,
560
+ taker_or_complier_learner: str | Any | None = None,
561
+ conditional_taker_learner: str | Any | None = None,
562
+ ) -> None:
563
+ """
564
+ Initialize an Wald IPW estimator for LACE.
565
+
566
+ Parameters
567
+ ----------
568
+ iv_probability_learner: str or Any, optional
569
+ Estimator for instrumental variable probability..
570
+ If not provided, defaults to LogisticRegression.
571
+ """
572
+ if learner is None:
573
+ self.taker_or_complier_learner = learnerFromString("LogisticRegression")
574
+ self.conditional_taker_learner = learnerFromString("LogisticRegression")
575
+ elif isinstance(learner, str):
576
+ self.taker_or_complier_learner = learnerFromString(learner)
577
+ self.conditional_taker_learner = learnerFromString(learner)
578
+ else:
579
+ self.taker_or_complier_learner = clone(learner)
580
+ self.conditional_taker_learner = clone(learner)
581
+
582
+ if taker_or_complier_learner is None:
583
+ self.taker_or_complier_learner = learnerFromString("LogisticRegression")
584
+ elif isinstance(taker_or_complier_learner, str):
585
+ self.taker_or_complier_learner = learnerFromString(taker_or_complier_learner)
586
+ else:
587
+ self.taker_or_complier_learner = clone(taker_or_complier_learner)
588
+
589
+ if conditional_taker_learner is None:
590
+ self.conditional_taker_learner = learnerFromString("LogisticRegression")
591
+ elif isinstance(conditional_taker_learner, str):
592
+ self.conditional_taker_learner = learnerFromString(conditional_taker_learner)
593
+ else:
594
+ self.conditional_taker_learner = clone(conditional_taker_learner)
595
+
596
+ self.link_func = expit
597
+ self.theta_A = None
598
+ self.theta_AC = None
599
+
600
+ def fit(
601
+ self,
602
+ X: np.matrix | np.ndarray | pd.DataFrame,
603
+ treatment: np.ndarray | pd.Series,
604
+ y: np.ndarray | pd.Series,
605
+ w: np.matrix | np.ndarray | pd.DataFrame,
606
+ ) -> None:
607
+ """
608
+ Fit the inference model.
609
+
610
+ Parameters
611
+ ----------
612
+ X: np.matrix or np.ndarray or pd.DataFrame
613
+ The covariate matrix.
614
+ w: np.matrix or np.ndarray or pd.DataFrame
615
+ The instrument variable.
616
+ treatment: np.ndarray or pd.Series, optional
617
+ The vector of treatment assignments.
618
+ y: np.ndarray or pd.Series, optional
619
+ The vector of outcomes.
620
+ """
621
+
622
+ if set(np.unique(w.to_numpy())) != {0, 1}:
623
+ raise ValueError("Instrument must be binary with values 0 and 1 for ACE estimation.")
624
+
625
+ x = X.to_numpy() if not isinstance(X, np.ndarray) else X
626
+ t = treatment.to_numpy() if not isinstance(treatment, np.ndarray) else treatment
627
+ z = w.to_numpy() if not isinstance(w, np.ndarray) else w
628
+
629
+ def Likelihood(theta):
630
+ theta_A = theta[: X.shape[1]]
631
+ theta_AC = theta[X.shape[1] :]
632
+
633
+ def binomial(t, z, *args):
634
+ x = list(args)
635
+ p = self.link_func(x @ theta_AC) * (1 - self.link_func(x @ theta_A)) * z + self.link_func(
636
+ x @ theta_AC
637
+ ) * self.link_func(x @ theta_AC)
638
+ return -np.log(p**t * (1 - p) ** (1 - t))
639
+ ## NOT FINISHED
640
+
641
+ return
642
+
643
+ print(Likelihood(np.zeros(2 * X.shape[1])))
644
+
645
+ res = minimize(fun=Likelihood, x0=np.zeros(2 * X.shape[1]))
646
+ print(res.x)
647
+ return res.x
648
+
649
+ def predict(
650
+ self,
651
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
652
+ treatment: np.ndarray | pd.Series = None,
653
+ y: np.ndarray | pd.Series = None,
654
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
655
+ ) -> np.ndarray:
656
+ """
657
+ Predict the Individual Treatment Effect (ITE).
658
+
659
+ Parameters
660
+ ----------
661
+ X: np.matrix or np.ndarray or pd.DataFrame
662
+ The covariate matrix.
663
+ w: np.matrix or np.ndarray or pd.DataFrame
664
+ The instrument variable.
665
+ treatment: np.ndarray or pd.Series, optional
666
+ The vector of treatment assignments.
667
+ y: np.ndarray or pd.Series, optional
668
+ The vector of outcomes.
669
+
670
+ Returns
671
+ -------
672
+ np.ndarray
673
+ An array containing the predicted ITE.
674
+ """
675
+
676
+ w = w.iloc[:, 0] if isinstance(w, pd.DataFrame) else w
677
+
678
+ e = self.iv_probability_learner.predict_proba(X)[:, 1]
679
+
680
+ v_func = np.vectorize(lambda e, w, d: w * d / e)
681
+
682
+ return ((v_func(e, w, y) / v_func(e, w, 1).mean()) - (v_func(1 - e, 1 - w, y) / v_func(1 - e, 1 - w, 1).mean())) / (
683
+ (v_func(e, w, treatment) / v_func(e, w, 1).mean())
684
+ - (v_func(1 - e, 1 - w, treatment) / v_func(1 - e, 1 - w, 1).mean())
685
+ ).mean()
686
+
687
+ def estimate_ate(
688
+ self,
689
+ X: np.matrix | np.ndarray | pd.DataFrame = None,
690
+ treatment: np.ndarray | pd.Series = None,
691
+ y: np.ndarray | pd.Series = None,
692
+ w: np.matrix | np.ndarray | pd.DataFrame = None,
693
+ pretrain: bool = True,
694
+ ) -> float:
695
+ """
696
+ Predicts the Local Average Causal Effect (LACE),
697
+ also referred to as the Local Average Treatment Effect (LATE).
698
+ (The term "Average Treatment Effect" (ATE) is used in
699
+ the method name for compatibility purposes.)
700
+
701
+ Parameters
702
+ ----------
703
+ X: np.matrix or np.ndarray or pd.DataFrame
704
+ The covariate matrix.
705
+ w: np.matrix or np.ndarray or pd.DataFrame
706
+ The instrument variable.
707
+ treatment: np.ndarray or pd.Series, optional
708
+ The vector of treatment assignments.
709
+ y: np.ndarray or pd.Series, optional
710
+ The vector of outcomes.
711
+
712
+ Returns
713
+ -------
714
+ float
715
+ The value of the ACE.
716
+ """
717
+
718
+ return self.predict(X, treatment, y, w).mean()