cleverly 0.1.0__py3-none-any.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 (87) hide show
  1. cleverly/__init__.py +162 -0
  2. cleverly/_typing.py +121 -0
  3. cleverly/_version.py +5 -0
  4. cleverly/assessment.py +2324 -0
  5. cleverly/data/__init__.py +8 -0
  6. cleverly/data/causal_data.py +1249 -0
  7. cleverly/data/validate.py +453 -0
  8. cleverly/data/weighting.py +627 -0
  9. cleverly/datasets/__init__.py +97 -0
  10. cleverly/datasets/longitudinal.py +894 -0
  11. cleverly/datasets/synthetic.py +1708 -0
  12. cleverly/estimators/__init__.py +43 -0
  13. cleverly/estimators/_nuisance.py +1320 -0
  14. cleverly/estimators/base.py +1233 -0
  15. cleverly/estimators/ctmle.py +1461 -0
  16. cleverly/estimators/direct_effect.py +387 -0
  17. cleverly/estimators/drtmle.py +996 -0
  18. cleverly/estimators/reduced.py +964 -0
  19. cleverly/estimators/serialize.py +99 -0
  20. cleverly/estimators/targeting.py +2610 -0
  21. cleverly/estimators/tmle.py +2809 -0
  22. cleverly/exceptions.py +108 -0
  23. cleverly/fluctuation/__init__.py +74 -0
  24. cleverly/fluctuation/_score.py +72 -0
  25. cleverly/fluctuation/iterative.py +820 -0
  26. cleverly/fluctuation/mechanism.py +641 -0
  27. cleverly/fluctuation/one_step.py +176 -0
  28. cleverly/fluctuation/reduced.py +273 -0
  29. cleverly/fluctuation/submodel.py +1362 -0
  30. cleverly/inference/__init__.py +64 -0
  31. cleverly/inference/bootstrap.py +305 -0
  32. cleverly/inference/cluster.py +267 -0
  33. cleverly/inference/delta.py +173 -0
  34. cleverly/inference/influence.py +1449 -0
  35. cleverly/inference/multiplier.py +471 -0
  36. cleverly/inference/results.py +98 -0
  37. cleverly/interventions/__init__.py +49 -0
  38. cleverly/interventions/base.py +582 -0
  39. cleverly/interventions/incremental.py +564 -0
  40. cleverly/interventions/shift.py +558 -0
  41. cleverly/interventions/support.py +238 -0
  42. cleverly/learners/__init__.py +61 -0
  43. cleverly/learners/_fitting.py +256 -0
  44. cleverly/learners/_threads.py +322 -0
  45. cleverly/learners/crossfit.py +520 -0
  46. cleverly/learners/density.py +507 -0
  47. cleverly/learners/library.py +153 -0
  48. cleverly/learners/screeners.py +170 -0
  49. cleverly/learners/super_learner.py +631 -0
  50. cleverly/longitudinal/__init__.py +69 -0
  51. cleverly/longitudinal/data.py +1378 -0
  52. cleverly/longitudinal/estimator.py +1701 -0
  53. cleverly/longitudinal/msm.py +1069 -0
  54. cleverly/longitudinal/regimen.py +346 -0
  55. cleverly/longitudinal/sequential.py +1261 -0
  56. cleverly/methods.py +866 -0
  57. cleverly/msm.py +1029 -0
  58. cleverly/provenance.py +232 -0
  59. cleverly/py.typed +0 -0
  60. cleverly/sensitivity/__init__.py +39 -0
  61. cleverly/sensitivity/_parameters.py +188 -0
  62. cleverly/sensitivity/evalue.py +321 -0
  63. cleverly/sensitivity/missingness.py +392 -0
  64. cleverly/sensitivity/omitted_variable.py +883 -0
  65. cleverly/sensitivity/positivity.py +839 -0
  66. cleverly/study.py +2071 -0
  67. cleverly/targets/__init__.py +240 -0
  68. cleverly/targets/base.py +484 -0
  69. cleverly/targets/builtin.py +637 -0
  70. cleverly/utils/__init__.py +31 -0
  71. cleverly/utils/bounds.py +272 -0
  72. cleverly/utils/frames.py +195 -0
  73. cleverly/utils/parallel.py +52 -0
  74. cleverly/utils/phases.py +300 -0
  75. cleverly/utils/records.py +101 -0
  76. cleverly/utils/text.py +49 -0
  77. cleverly/validation/__init__.py +70 -0
  78. cleverly/validation/drtmle.py +840 -0
  79. cleverly/validation/nuisance.py +555 -0
  80. cleverly/validation/refute.py +1743 -0
  81. cleverly/validation/score.py +745 -0
  82. cleverly/validation/simulation.py +650 -0
  83. cleverly/variable_importance.py +240 -0
  84. cleverly-0.1.0.dist-info/METADATA +257 -0
  85. cleverly-0.1.0.dist-info/RECORD +87 -0
  86. cleverly-0.1.0.dist-info/WHEEL +4 -0
  87. cleverly-0.1.0.dist-info/licenses/LICENSE +21 -0
cleverly/__init__.py ADDED
@@ -0,0 +1,162 @@
1
+ """cleverly: targeted maximum likelihood estimation for Python.
2
+
3
+ Quickstart
4
+ ----------
5
+ >>> from cleverly import ATE, CausalStudy, PointTreatment
6
+ >>> from cleverly.datasets import make_nonlinear_ate
7
+ >>> from sklearn.linear_model import LinearRegression, LogisticRegression
8
+ >>> frame, _ = make_nonlinear_ate(n=200, seed=0)
9
+ >>> study = CausalStudy(
10
+ ... frame,
11
+ ... design=PointTreatment(
12
+ ... outcome="Y",
13
+ ... treatment="A",
14
+ ... adjustment=["W1", "W2", "W3", "W4"],
15
+ ... ),
16
+ ... )
17
+ >>> result = study.identify(ATE()).estimate(
18
+ ... outcome_learner=LinearRegression(),
19
+ ... treatment_learner=LogisticRegression(max_iter=1000),
20
+ ... n_folds=2,
21
+ ... random_state=0,
22
+ ... )
23
+ >>> sorted(result.estimates)
24
+ ['ate']
25
+
26
+ The estimator takes pandas or polars dataframes interchangeably and returns results
27
+ in whichever backend it was given. Every result carries the fitted artifacts used by
28
+ its cache-only validation and diagnostic operations. Other assessments declare when
29
+ they retarget or refit.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ from ._version import __version__
35
+ from .assessment import (
36
+ AssessmentCapability,
37
+ AssessmentStatus,
38
+ DiagnosticReport,
39
+ Replayability,
40
+ ValidationReport,
41
+ )
42
+ from .estimators.serialize import load
43
+ from .exceptions import (
44
+ CapabilityError,
45
+ CleverlyError,
46
+ ConvergenceWarning,
47
+ DataError,
48
+ MethodConfigurationError,
49
+ NotFittedError,
50
+ PositivityWarning,
51
+ WeightingWarning,
52
+ )
53
+ from .inference import ParameterEstimate
54
+ from .learners import SuperLearner
55
+ from .methods import (
56
+ CollaborativeTMLEMethod,
57
+ CrossFitting,
58
+ DRTMLEMethod,
59
+ EstimationMethod,
60
+ Inference,
61
+ MethodAvailability,
62
+ ModelSpec,
63
+ Runtime,
64
+ Targeting,
65
+ TMLEMethod,
66
+ )
67
+ from .provenance import Provenance
68
+ from .study import (
69
+ ATC,
70
+ ATE,
71
+ ATT,
72
+ BackdoorMeanContrast,
73
+ CausalResult,
74
+ CausalStudy,
75
+ ControlledDirectEffect,
76
+ CounterfactualMean,
77
+ Estimand,
78
+ ExplicitAdjustmentProvider,
79
+ IdentificationProvider,
80
+ IdentifiedEffect,
81
+ IncrementalEffect,
82
+ IncrementalMean,
83
+ LongitudinalTreatment,
84
+ ModifiedTreatmentPolicy,
85
+ ModifiedTreatmentPolicyEffect,
86
+ MSMProjection,
87
+ NaturalCourseMean,
88
+ OddsRatio,
89
+ ParameterKey,
90
+ PointTreatment,
91
+ PopulationAttributableFraction,
92
+ PopulationAttributableRisk,
93
+ RegimeContrast,
94
+ RegimeMean,
95
+ RiskRatio,
96
+ )
97
+ from .variable_importance import (
98
+ VariableImportanceEntry,
99
+ VariableImportanceResult,
100
+ variable_importance,
101
+ )
102
+
103
+ __all__ = [
104
+ "ATC",
105
+ "ATE",
106
+ "ATT",
107
+ "AssessmentCapability",
108
+ "AssessmentStatus",
109
+ "BackdoorMeanContrast",
110
+ "CapabilityError",
111
+ "CausalResult",
112
+ "CausalStudy",
113
+ "CleverlyError",
114
+ "CollaborativeTMLEMethod",
115
+ "ControlledDirectEffect",
116
+ "ConvergenceWarning",
117
+ "CounterfactualMean",
118
+ "CrossFitting",
119
+ "DRTMLEMethod",
120
+ "DataError",
121
+ "DiagnosticReport",
122
+ "Estimand",
123
+ "EstimationMethod",
124
+ "ExplicitAdjustmentProvider",
125
+ "IdentificationProvider",
126
+ "IdentifiedEffect",
127
+ "IncrementalEffect",
128
+ "IncrementalMean",
129
+ "Inference",
130
+ "LongitudinalTreatment",
131
+ "MSMProjection",
132
+ "MethodAvailability",
133
+ "MethodConfigurationError",
134
+ "ModelSpec",
135
+ "ModifiedTreatmentPolicy",
136
+ "ModifiedTreatmentPolicyEffect",
137
+ "NaturalCourseMean",
138
+ "NotFittedError",
139
+ "OddsRatio",
140
+ "ParameterEstimate",
141
+ "ParameterKey",
142
+ "PointTreatment",
143
+ "PopulationAttributableFraction",
144
+ "PopulationAttributableRisk",
145
+ "PositivityWarning",
146
+ "Provenance",
147
+ "RegimeContrast",
148
+ "RegimeMean",
149
+ "Replayability",
150
+ "RiskRatio",
151
+ "Runtime",
152
+ "SuperLearner",
153
+ "TMLEMethod",
154
+ "Targeting",
155
+ "ValidationReport",
156
+ "VariableImportanceEntry",
157
+ "VariableImportanceResult",
158
+ "WeightingWarning",
159
+ "__version__",
160
+ "load",
161
+ "variable_importance",
162
+ ]
cleverly/_typing.py ADDED
@@ -0,0 +1,121 @@
1
+ """Shared type aliases.
2
+
3
+ Kept in a private module so the public namespace stays focused on estimators.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Literal
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+ __all__ = [
14
+ "Backend",
15
+ "BoolArray",
16
+ "CumulativeGBounds",
17
+ "EstimandName",
18
+ "Family",
19
+ "FloatArray",
20
+ "FluctuationKind",
21
+ "FoldStrata",
22
+ "GBounds",
23
+ "IntArray",
24
+ "Learner",
25
+ "ParameterAxis",
26
+ "TargetingMethod",
27
+ "TargetingScheme",
28
+ ]
29
+
30
+ FloatArray = NDArray[np.float64]
31
+ IntArray = NDArray[np.int64]
32
+ BoolArray = NDArray[np.bool_]
33
+
34
+ #: Anything implementing the scikit-learn ``fit``/``predict`` (or
35
+ #: ``predict_proba``) protocol, including :class:`cleverly.SuperLearner`,
36
+ #: :class:`sklearn.pipeline.Pipeline`, and grid-search wrappers. Typed as
37
+ #: ``Any`` deliberately: scikit-learn estimators are structurally, not
38
+ #: nominally, typed and requiring a Protocol here would reject valid learners.
39
+ Learner = Any
40
+
41
+ Backend = Literal["pandas", "polars"]
42
+ Family = Literal["gaussian", "binomial", "auto"]
43
+ FluctuationKind = Literal["logistic", "linear"]
44
+ TargetingMethod = Literal["iterative", "one_step"]
45
+ TargetingScheme = Literal["pooled", "fold"]
46
+
47
+ #: What the outer cross-fitting folds are balanced on. ``"treatment"`` is the
48
+ #: long-standing behaviour and the default; ``"treatment+outcome"`` crosses in the
49
+ #: outcome so that a rare event cannot leave a fold with none of them.
50
+ FoldStrata = Literal["treatment", "treatment+outcome"]
51
+ #: Every estimand *name* :class:`~cleverly.estimators.TMLE`'s ``estimands=`` accepts, which is
52
+ #: every key of the target registry. Which of them a *particular* fit can report depends on
53
+ #: its outcome family, its arm count and which parameter axis it declared, and that is
54
+ #: checked at runtime by :func:`~cleverly.targets.resolve_estimands` against the registry
55
+ #: itself. This alias is the static half of the same statement and cannot be derived from
56
+ #: the registry: a ``Literal`` has to be written out, and importing ``cleverly.targets`` here
57
+ #: would invert the dependency of the type aliases on the package.
58
+ #:
59
+ #: So it is a hand-maintained copy, and the way a hand-maintained copy stays honest is a gate
60
+ #: rather than care -- ``tests/unit/test_registry.py`` compares its members with ``TARGETS``
61
+ #: in both directions. It had already drifted once without one: ``ey``, ``ey_obs``, ``par``
62
+ #: and ``paf`` were reportable and absent from here, as was every non-arm axis, so annotating
63
+ #: a correct call was a type error.
64
+ #:
65
+ #: Named ``EstimandName`` rather than ``Estimand`` because :class:`cleverly.Estimand` is a
66
+ #: different thing entirely -- the :class:`~typing.Protocol` in :mod:`cleverly.study` that a
67
+ #: question like :class:`~cleverly.ATE` satisfies. The two shared a name while meaning a
68
+ #: string and an object, and the public export won, so this one could not be reached under
69
+ #: its own name from outside the package.
70
+ #:
71
+ #: It covers the *built-in* registry only. A target registered at runtime -- see
72
+ #: ``tests/unit/test_registry.py``'s ``TestRegistration`` -- is a name no ``Literal`` written
73
+ #: here can know, so passing one is a legitimate ``cast``. That is a reason to keep this
74
+ #: narrow rather than to widen it to ``str``: the annotation exists to catch a misspelt
75
+ #: built-in name, and the registry still validates every name at runtime either way.
76
+ EstimandName = Literal[
77
+ "ate",
78
+ "att",
79
+ "atc",
80
+ "ey",
81
+ "ey1",
82
+ "ey0",
83
+ "ey_obs",
84
+ "par",
85
+ "paf",
86
+ "rr",
87
+ "or",
88
+ "ey_regime",
89
+ "ate_regime",
90
+ "ey_ipsi",
91
+ "ate_ipsi",
92
+ "ey_shift",
93
+ "ate_shift",
94
+ "msm",
95
+ ]
96
+
97
+ #: What a fit's parameters are indexed *by*: a treatment arm, a declared regime, a
98
+ #: declared shift, a declared tilt of the mechanism, or a coefficient of a declared
99
+ #: working model. The five partition the target registry -- see
100
+ #: :attr:`cleverly.Target.parameter_axis` for why they are exclusive rather than
101
+ #: cumulative.
102
+ #:
103
+ #: The first four also declare what "counterfactual" means for the fit. ``"msm"`` does
104
+ #: not: its counterfactuals are still the arms, and what moves is the *summary* the fit
105
+ #: reports of them. It is an axis all the same, because a summary's coefficients are not
106
+ #: indexed by anything the other four name.
107
+ #:
108
+ #: ``"ipsi"`` is the one whose intervention is a functional of the observed-data law: its
109
+ #: ``q_delta`` is built out of the estimated mechanism, so it carries an extra influence
110
+ #: curve term and a second score equation. That is why it is not a kind of ``"regime"``.
111
+ ParameterAxis = Literal["arm", "regime", "shift", "ipsi", "msm"]
112
+
113
+ #: Propensity-score truncation: ``"auto"`` for the sample-size dependent
114
+ #: default, a single float ``lo`` meaning ``[lo, 1 - lo]``, or an explicit pair.
115
+ GBounds = Literal["auto"] | float | tuple[float, float]
116
+
117
+ #: Bounds on an estimated cumulative longitudinal treatment-and-censoring
118
+ #: probability. There is deliberately no ``"auto"`` member: cleverly has no
119
+ #: data-adaptive or depth-adaptive rule for choosing this bound. LTMLE's package
120
+ #: default is an explicit fixed pair, recorded in :mod:`cleverly.utils.bounds`.
121
+ CumulativeGBounds = float | tuple[float, float]
cleverly/_version.py ADDED
@@ -0,0 +1,5 @@
1
+ """Single source of truth for the package version."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"