├── .gitignore
├── img
├── logo.png
├── favicon.ico
└── logo_dark.png
├── doc
├── .gitignore
├── _static
│ ├── logo.png
│ ├── dark_rapi.png
│ ├── logo_dark.png
│ ├── light_rapi.png
│ ├── dark_pythonapi.png
│ ├── dark_userguide.png
│ ├── dark_workflow.png
│ ├── light_workflow.png
│ ├── light_pythonapi.png
│ ├── light_userguide.png
│ ├── basic_iv_example_nb.png
│ ├── dag_usecase_revised.png
│ ├── dark_examplegallery.png
│ ├── dark_gettingstarted.png
│ ├── firststage_example_nb.png
│ ├── light_examplegallery.png
│ ├── light_gettingstarted.png
│ ├── robust_iv_example_nb.png
│ ├── sensitivity_example_nb.png
│ ├── switcher.json
│ └── css
│ │ └── custom.css
├── examples
│ ├── did
│ │ └── mpdta.rda
│ ├── figures
│ │ └── dag_usecase_revised.png
│ ├── data
│ │ └── orig_demand_data_example.csv
│ └── index.rst
├── _templates
│ ├── sidebar-doubleml-workflow.html
│ ├── logo.html
│ └── class.rst
├── guide
│ ├── sensitivity
│ │ ├── plm
│ │ │ ├── plm_sensitivity.inc
│ │ │ └── plr_sensitivity.rst
│ │ ├── irm
│ │ │ ├── irm_sensitivity.inc
│ │ │ ├── apo_sensitivity.rst
│ │ │ └── irm_sensitivity.rst
│ │ ├── did
│ │ │ ├── did_pa_binary_sensitivity.rst
│ │ │ ├── did_sensitivity.inc
│ │ │ ├── did_cs_binary_sensitivity.rst
│ │ │ ├── did_pa_sensitivity.rst
│ │ │ └── did_cs_sensitivity.rst
│ │ ├── benchmarking.rst
│ │ ├── theory.rst
│ │ └── implementation.rst
│ ├── models
│ │ ├── irm
│ │ │ ├── apos.rst
│ │ │ ├── apo.rst
│ │ │ ├── irm.rst
│ │ │ └── iivm.rst
│ │ ├── plm
│ │ │ ├── plr.rst
│ │ │ ├── lplr.rst
│ │ │ ├── pliv.rst
│ │ │ └── plm_models.inc
│ │ ├── did
│ │ │ ├── did_models.inc
│ │ │ ├── did_aggregation.rst
│ │ │ ├── did_implementation.rst
│ │ │ ├── did_pa.rst
│ │ │ ├── did_binary.rst
│ │ │ ├── did_cs.rst
│ │ │ └── did_setup.rst
│ │ └── ssm
│ │ │ ├── ssm.rst
│ │ │ └── ssm_models.inc
│ ├── scores
│ │ ├── ssm
│ │ │ ├── ssm_scores.inc
│ │ │ ├── mar_score.rst
│ │ │ └── nr_score.rst
│ │ ├── plm
│ │ │ ├── plm_scores.inc
│ │ │ ├── pliv_score.rst
│ │ │ ├── plr_score.rst
│ │ │ └── lplr_score.rst
│ │ ├── irm
│ │ │ ├── pq_score.rst
│ │ │ ├── cvar_score.rst
│ │ │ ├── iivm_score.rst
│ │ │ ├── irm_scores.inc
│ │ │ ├── apo_score.rst
│ │ │ ├── lpq_score.rst
│ │ │ └── irm_score.rst
│ │ └── did
│ │ │ ├── did_scores.inc
│ │ │ ├── did_pa_binary_score.rst
│ │ │ ├── did_cs_binary_score.rst
│ │ │ ├── did_pa_score.rst
│ │ │ └── did_cs_score.rst
│ ├── guide.rst
│ ├── learners
│ │ ├── r
│ │ │ ├── learners_overview.inc
│ │ │ ├── minimum_req.rst
│ │ │ ├── pipelines.rst
│ │ │ ├── tune_and_pipelines.rst
│ │ │ └── set_hyperparams.rst
│ │ └── python
│ │ │ ├── minimum_req.rst
│ │ │ ├── learners_overview.inc
│ │ │ ├── evaluate_learners.rst
│ │ │ ├── tune_hyperparams.rst
│ │ │ ├── set_hyperparams.rst
│ │ │ ├── tune_hyperparams_old.rst
│ │ │ └── external_preds.rst
│ ├── models.rst
│ ├── learners.rst
│ ├── data_backend.rst
│ ├── data
│ │ ├── rdd_data.rst
│ │ ├── ssm_data.rst
│ │ ├── panel_data.rst
│ │ └── did_data.rst
│ └── sensitivity.rst
├── shared
│ ├── dgp
│ │ ├── return_type.rst
│ │ └── return_type_iv.rst
│ ├── heterogeneity
│ │ ├── cvar_qte.rst
│ │ ├── cate.rst
│ │ ├── pq.rst
│ │ ├── cvar.rst
│ │ ├── qte.rst
│ │ ├── gate.rst
│ │ ├── cate_plr.rst
│ │ ├── gate_plr.rst
│ │ └── policytree.rst
│ └── causal_graphs
│ │ ├── plr_irm_causal_graph.rst
│ │ └── pliv_iivm_causal_graph.rst
├── api
│ ├── mixins.rst
│ ├── data_class.rst
│ ├── api.rst
│ ├── utility.rst
│ ├── datasets.rst
│ └── dml_models.rst
├── Makefile
└── make.bat
├── requirements.txt
├── .github
├── ISSUE_TEMPLATE
│ ├── docu.yml
│ ├── config.yml
│ └── literature.yml
└── workflows
│ ├── deploy_docu_stable.yml
│ ├── test_build_docu_released.yml
│ ├── deploy_docu_dev.yml
│ └── test_build_docu_dev.yml
├── README.md
├── LICENSE
└── .devcontainer
├── devcontainer.json
├── docker_guide.md
├── Dockerfile.dev
└── build_image_guide.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.idea
2 | *.vscode
3 |
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/img/logo.png
--------------------------------------------------------------------------------
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 |
3 | # Ignore generated API documentation
4 | api/generated/
--------------------------------------------------------------------------------
/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/img/favicon.ico
--------------------------------------------------------------------------------
/img/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/img/logo_dark.png
--------------------------------------------------------------------------------
/doc/_static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/logo.png
--------------------------------------------------------------------------------
/doc/_static/dark_rapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dark_rapi.png
--------------------------------------------------------------------------------
/doc/_static/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/logo_dark.png
--------------------------------------------------------------------------------
/doc/_static/light_rapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/light_rapi.png
--------------------------------------------------------------------------------
/doc/examples/did/mpdta.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/examples/did/mpdta.rda
--------------------------------------------------------------------------------
/doc/_static/dark_pythonapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dark_pythonapi.png
--------------------------------------------------------------------------------
/doc/_static/dark_userguide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dark_userguide.png
--------------------------------------------------------------------------------
/doc/_static/dark_workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dark_workflow.png
--------------------------------------------------------------------------------
/doc/_static/light_workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/light_workflow.png
--------------------------------------------------------------------------------
/doc/_static/light_pythonapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/light_pythonapi.png
--------------------------------------------------------------------------------
/doc/_static/light_userguide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/light_userguide.png
--------------------------------------------------------------------------------
/doc/_static/basic_iv_example_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/basic_iv_example_nb.png
--------------------------------------------------------------------------------
/doc/_static/dag_usecase_revised.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dag_usecase_revised.png
--------------------------------------------------------------------------------
/doc/_static/dark_examplegallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dark_examplegallery.png
--------------------------------------------------------------------------------
/doc/_static/dark_gettingstarted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/dark_gettingstarted.png
--------------------------------------------------------------------------------
/doc/_templates/sidebar-doubleml-workflow.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/doc/_static/firststage_example_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/firststage_example_nb.png
--------------------------------------------------------------------------------
/doc/_static/light_examplegallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/light_examplegallery.png
--------------------------------------------------------------------------------
/doc/_static/light_gettingstarted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/light_gettingstarted.png
--------------------------------------------------------------------------------
/doc/_static/robust_iv_example_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/robust_iv_example_nb.png
--------------------------------------------------------------------------------
/doc/_static/sensitivity_example_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/_static/sensitivity_example_nb.png
--------------------------------------------------------------------------------
/doc/examples/figures/dag_usecase_revised.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoubleML/doubleml-docs/HEAD/doc/examples/figures/dag_usecase_revised.png
--------------------------------------------------------------------------------
/doc/guide/sensitivity/plm/plm_sensitivity.inc:
--------------------------------------------------------------------------------
1 | The following partially linear models are implemented.
2 |
3 | .. _sensitivity_plr:
4 |
5 | Partially linear regression model (PLR)
6 | =======================================
7 |
8 | .. include:: /guide/sensitivity/plm/plr_sensitivity.rst
9 |
--------------------------------------------------------------------------------
/doc/shared/dgp/return_type.rst:
--------------------------------------------------------------------------------
1 | If ``'DoubleMLData'`` or ``DoubleMLData``, returns a ``DoubleMLData`` object.
2 |
3 | If ``'DataFrame'``, ``'pd.DataFrame'`` or ``pd.DataFrame``, returns a ``pd.DataFrame``.
4 |
5 | If ``'array'``, ``'np.ndarray'``, ``'np.array'`` or ``np.ndarray``, returns ``np.ndarray``'s ``(x, y, d)``.
--------------------------------------------------------------------------------
/doc/shared/dgp/return_type_iv.rst:
--------------------------------------------------------------------------------
1 | If ``'DoubleMLData'`` or ``DoubleMLData``, returns a ``DoubleMLData`` object.
2 |
3 | If ``'DataFrame'``, ``'pd.DataFrame'`` or ``pd.DataFrame``, returns a ``pd.DataFrame``.
4 |
5 | If ``'array'``, ``'np.ndarray'``, ``'np.array'`` or ``np.ndarray``, returns ``np.ndarray``'s ``(x, y, d, z)``.
--------------------------------------------------------------------------------
/doc/_static/switcher.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "dev",
4 | "version": "dev",
5 | "url": "https://docs.doubleml.org/dev/"
6 | },
7 | {
8 | "name": "stable",
9 | "version": "stable",
10 | "url": "https://docs.doubleml.org/stable/",
11 | "preferred": true
12 | }
13 | ]
--------------------------------------------------------------------------------
/doc/guide/models/irm/apos.rst:
--------------------------------------------------------------------------------
1 | If multiple treatment levels should be estimated simulatenously, another possible target parameter of interest in this model
2 | are contrasts (or average treatment effects) between treatment levels :math:`d_j` and :math:`d_k`:
3 |
4 | .. math::
5 |
6 | \theta_{0,jk} = \mathbb{E}[g_0(d_j, X) - g_0(d_k, X)].
--------------------------------------------------------------------------------
/doc/api/mixins.rst:
--------------------------------------------------------------------------------
1 | .. _api_mixins:
2 |
3 | Score Mixin Classes for DoubleML Models
4 | ---------------------------------------
5 |
6 | .. currentmodule:: doubleml
7 |
8 | .. autosummary::
9 | :toctree: generated/
10 | :template: class.rst
11 |
12 | double_ml_score_mixins.LinearScoreMixin
13 | double_ml_score_mixins.NonLinearScoreMixin
14 |
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/cvar_qte.rst:
--------------------------------------------------------------------------------
1 | For a quantile :math:`\tau \in (0,1)` the target parameter :math:`\theta_{\tau}` of interest are the
2 | **treatment effects on the conditional value at risk**,
3 |
4 | .. math::
5 |
6 | \theta_{\tau} = \theta_{\tau}(1) - \theta_{\tau}(0)
7 |
8 | where :math:`\theta_{\tau}(d)` denotes the corresponding conditional values at risk
9 | of the potential outcomes.
--------------------------------------------------------------------------------
/doc/api/data_class.rst:
--------------------------------------------------------------------------------
1 | .. _api_data_class:
2 |
3 | DoubleML Data Class
4 | ----------------------------------
5 |
6 | .. currentmodule:: doubleml.data
7 |
8 | .. autosummary::
9 | :toctree: generated/
10 | :template: class.rst
11 |
12 | DoubleMLData
13 | DoubleMLClusterData
14 | DoubleMLPanelData
15 | DoubleMLSSMData
16 | DoubleMLRDDData
17 | DoubleMLDIDData
18 |
--------------------------------------------------------------------------------
/doc/guide/scores/ssm/ssm_scores.inc:
--------------------------------------------------------------------------------
1 | The following scores for sample selection models are implemented.
2 |
3 | .. _ssm-mar-score:
4 |
5 | Missingness at Random
6 | ======================
7 |
8 | .. include:: /guide/scores/ssm/mar_score.rst
9 |
10 |
11 | .. _ssm-nr-score:
12 |
13 | Nonignorable Nonresponse
14 | =========================
15 |
16 | .. include:: /guide/scores/ssm/nr_score.rst
17 |
--------------------------------------------------------------------------------
/doc/api/api.rst:
--------------------------------------------------------------------------------
1 | .. _python_api:
2 |
3 | :parenttoc: True
4 |
5 | API Reference
6 | =============
7 |
8 | .. toctree::
9 | :maxdepth: 2
10 | :numbered:
11 |
12 | DoubleML Data Class
13 | DoubleML Models
14 | Datasets
15 | Utility Classes and Functions
16 | Score Mixin Classes for DoubleML Models
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/cate.rst:
--------------------------------------------------------------------------------
1 | **Conditional Average Treatment Effects (CATEs)** for ``DoubleMLIRM`` models consider the target parameters
2 |
3 | .. math::
4 |
5 | \theta_{0}(x) = \mathbb{E}[Y(1) - Y(0)| X=x]
6 |
7 | for a low-dimensional feature :math:`X`, where :math:`Y(d)` the potential outcome with :math:`d \in \{0, 1\}`.
8 |
9 | Point estimates and confidence intervals can be obtained via the ``gate()`` and ``confint()`` methods.
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/pq.rst:
--------------------------------------------------------------------------------
1 | For a quantile :math:`\tau \in (0,1)` the target parameters :math:`\theta_{\tau}(d)` of interest are the **potential quantiles (PQs)**,
2 |
3 | .. math::
4 |
5 | P(Y(d) \le \theta_{\tau}(d)) = \tau,
6 |
7 | and **local potential quantiles (LPQs)**,
8 |
9 | .. math::
10 |
11 | P(Y(d) \le \theta_{\tau}(d)|\text{Compliers}) = \tau.
12 |
13 | where :math:`Y(d)` denotes the potential outcome with :math:`d \in \{0, 1\}`.
14 |
15 |
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/cvar.rst:
--------------------------------------------------------------------------------
1 | For a quantile :math:`\tau \in (0,1)` the target parameters :math:`\theta_{\tau}(d)` of interest are
2 | the **conditional values at risk (CVaRs)** of the potential outcomes,
3 |
4 | .. math::
5 |
6 | \theta_{\tau}(d) = \frac{\mathbb{E}[Y(d) 1\{F_{Y(d)}(Y(d) \ge \tau)]}{1-\tau},
7 |
8 |
9 | where :math:`Y(d)` denotes the potential outcome with :math:`d \in \{0, 1\}` and
10 | :math:`F_{Y(d)}(x)` the corresponding cdf of :math:`Y(d)`.
11 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/irm/irm_sensitivity.inc:
--------------------------------------------------------------------------------
1 | The following nonparametric regression models implemented.
2 |
3 |
4 | .. _sensitivity_irm:
5 |
6 | Interactive regression model (IRM)
7 | =======================================
8 |
9 | .. include:: /guide/sensitivity/irm/irm_sensitivity.rst
10 |
11 |
12 | .. _sensitivity_apo:
13 |
14 | Average Potential Outcomes (APOs)
15 | =======================================
16 |
17 | .. include:: /guide/sensitivity/irm/apo_sensitivity.rst
18 |
19 |
--------------------------------------------------------------------------------
/doc/guide/models/plm/plr.rst:
--------------------------------------------------------------------------------
1 | **Partially linear regression (PLR)** models take the form
2 |
3 | .. math::
4 |
5 | Y = D \theta_0 + g_0(X) + \zeta, & &\mathbb{E}(\zeta | D,X) = 0,
6 |
7 | D = m_0(X) + V, & &\mathbb{E}(V | X) = 0,
8 |
9 | where :math:`Y` is the outcome variable and :math:`D` is the policy variable of interest.
10 | The high-dimensional vector :math:`X = (X_1, \ldots, X_p)` consists of other confounding covariates,
11 | and :math:`\zeta` and :math:`V` are stochastic errors.
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/qte.rst:
--------------------------------------------------------------------------------
1 | For a quantile :math:`\tau \in (0,1)` the target parameter :math:`\theta_{\tau}` of interest are the **quantile treatment effect (QTE)**,
2 |
3 | .. math::
4 |
5 | \theta_{\tau} = \theta_{\tau}(1) - \theta_{\tau}(0)
6 |
7 | where :math:`\theta_{\tau}(d)` denotes the corresponding potential quantile.
8 |
9 | Analogously, the **local quantile treatment effect (LQTE)** can be defined as the difference of
10 | the corresponding local potential quantiles.
11 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | DoubleML[rdd]
2 | scikit-learn==1.6.1
3 |
4 | # test
5 | pytest
6 | rpy2
7 |
8 | # doc
9 | sphinx==8.1.3
10 | sphinx-copybutton
11 | nbsphinx==0.9.6
12 | ipykernel
13 | sphinx-gallery==0.18.0
14 | sphinx-panels
15 | sphinx-design
16 | jupyter-sphinx
17 | pydata-sphinx-theme==0.15.4
18 | pickleshare
19 | matplotlib
20 | plotly==5.24.1
21 | seaborn
22 | xgboost
23 | lightgbm
24 | flaml
25 |
26 | # notebooks
27 | ipykernel
28 | pyreadr
29 | # tabpfn # only relevant for TabPFN example notebooks
--------------------------------------------------------------------------------
/doc/shared/causal_graphs/plr_irm_causal_graph.rst:
--------------------------------------------------------------------------------
1 | .. graphviz::
2 | :align: center
3 | :caption: Causal diagram
4 |
5 | digraph {
6 | nodesep=1;
7 | ranksep=1;
8 | rankdir=LR;
9 | { node [shape=circle, style=filled]
10 | Y [fillcolor="#56B4E9"]
11 | D [fillcolor="#F0E442"]
12 | V [fillcolor="#F0E442"]
13 | X [fillcolor="#D55E00"]
14 | }
15 | Y -> D -> V [dir="back"];
16 | X -> D;
17 | Y -> X [dir="back"];
18 | }
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/gate.rst:
--------------------------------------------------------------------------------
1 | **Group Average Treatment Effects (GATEs)** for ``DoubleMLIRM`` models consider the target parameters
2 |
3 | .. math::
4 |
5 | \theta_{0,k} = \mathbb{E}[Y(1) - Y(0)| G_k],\quad k=1,\dots, K.
6 |
7 | where :math:`G_k` denotes a group indicator and :math:`Y(d)` the potential outcome with :math:`d \in \{0, 1\}`.
8 |
9 | Point estimates and confidence intervals can be obtained via the ``gate()`` and ``confint()`` methods.
10 | Remark that for straightforward interpretation, the groups have to be mutually exclusive.
--------------------------------------------------------------------------------
/doc/guide/models/plm/lplr.rst:
--------------------------------------------------------------------------------
1 | **Logistic partially linear regression (LPLR)** models take the form
2 |
3 | .. math::
4 |
5 | \mathbb{E} [Y | D, X] = \mathbb{P} (Y=1 | D, X) = \text{expit} \{\beta_0 D + r_0 (X) \}
6 |
7 | where :math:`Y` is the binary outcome variable and :math:`D` is the policy variable of interest.
8 | The high-dimensional vector :math:`X = (X_1, \ldots, X_p)` consists of confounding covariates and
9 | :math:`\text{expit}` is the logistic link function
10 |
11 | .. math::
12 | \text{expit} ( X ) = \frac{1}{1 + e^{-x}}
13 |
14 |
--------------------------------------------------------------------------------
/doc/_static/css/custom.css:
--------------------------------------------------------------------------------
1 | /* PYDATA THEME */
2 | html[data-theme="dark"] img:not(.only-dark):not(.dark-light) {
3 | filter: brightness(1) contrast(1) !important;
4 | }
5 |
6 | html[data-theme="dark"] .bd-content img:not(.only-dark):not(.dark-light) {
7 | background: none !important;
8 | border-radius: 0;
9 | }
10 |
11 | html[data-theme="light"] {
12 | --pst-color-primary: #0063BC;
13 | }
14 |
15 | html[data-theme="dark"] {
16 | --pst-color-primary: #0092ff;
17 | }
18 |
19 | /* BOOTSTRAP */
20 | .card {
21 | background: none !important;
22 | }
23 |
--------------------------------------------------------------------------------
/doc/guide/models/plm/pliv.rst:
--------------------------------------------------------------------------------
1 | **Partially linear IV regression (PLIV)** models take the form
2 |
3 | .. math::
4 |
5 | Y - D \theta_0 = g_0(X) + \zeta, & &\mathbb{E}(\zeta | Z, X) = 0,
6 |
7 | Z = m_0(X) + V, & &\mathbb{E}(V | X) = 0.
8 |
9 | where :math:`Y` is the outcome variable, :math:`D` is the policy variable of interest and :math:`Z`
10 | denotes one or multiple instrumental variables. The high-dimensional vector
11 | :math:`X = (X_1, \ldots, X_p)` consists of other confounding covariates, and :math:`\zeta` and
12 | :math:`V` are stochastic errors.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/docu.yml:
--------------------------------------------------------------------------------
1 | name: Documentation and User Guide
2 | description: Issues related to the documentation and user guide on docs.doubleml.org
3 | labels: ["documentation"]
4 |
5 | body:
6 | - type: textarea
7 | attributes:
8 | label: Description of the issue or change proposal
9 | description: |
10 | Please provide a clear and concise discription of the issue and if relevant give links to the affected pages
11 | on docs.doubleml.org
12 | validations:
13 | required: true
14 | - type: textarea
15 | attributes:
16 | label: Comments, context or references
17 |
--------------------------------------------------------------------------------
/doc/guide/models/irm/apo.rst:
--------------------------------------------------------------------------------
1 | For general discrete-values treatments :math:`D \in \lbrace d_0, \dots, d_l \rbrace` the model can be generalized to
2 |
3 | .. math::
4 |
5 | Y = g_0(D, X) + U, & &\mathbb{E}(U | X, D) = 0,
6 |
7 | A_j = m_{0,j}(X) + V, & &\mathbb{E}(V | X) = 0,
8 |
9 | where :math:`A_j := 1\lbrace D = d_j\rbrace` is an indicator variable for treatment level :math:`d_j` and :math:`m_{0,j}(X)` denotes
10 | the corresponding propensity score.
11 |
12 | Possible target parameters of interest in this model are the average potential outcomes (APOs)
13 |
14 | .. math::
15 |
16 | \theta_{0,j} = \mathbb{E}[g_0(d_j, X)].
--------------------------------------------------------------------------------
/doc/guide/scores/plm/plm_scores.inc:
--------------------------------------------------------------------------------
1 | The following scores for partially linear models are implemented.
2 |
3 | .. _plr-score:
4 |
5 | Partially linear regression model (PLR)
6 | =======================================
7 |
8 | .. include:: /guide/scores/plm/plr_score.rst
9 |
10 | .. _lplr-score:
11 |
12 | Logistic partial linear regression (LPLR)
13 | ===========================================
14 |
15 | .. include:: /guide/scores/plm/lplr_score.rst
16 |
17 | .. _pliv-score:
18 |
19 | Partially linear IV regression model (PLIV)
20 | ===========================================
21 |
22 | .. include:: /guide/scores/plm/pliv_score.rst
23 |
--------------------------------------------------------------------------------
/doc/guide/guide.rst:
--------------------------------------------------------------------------------
1 | .. _guide:
2 |
3 | :parenttoc: True
4 |
5 | User Guide
6 | ==========
7 |
8 | .. toctree::
9 | :maxdepth: 2
10 | :numbered:
11 |
12 | The basics of double/debiased machine learning
13 | Data Backend
14 | Models
15 | Heterogeneous Treatment Effects
16 | Score functions
17 | Double machine learning algorithms
18 | Learners, hyperparameters and hyperparameter tuning
19 | Variance estimation and confidence intervals
20 | Sample-splitting, cross-fitting and repeated cross-fitting
21 | Sensitivity Analysis
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/cate_plr.rst:
--------------------------------------------------------------------------------
1 | **Conditional Average Treatment Effects (CATEs)** for ``DoubleMLPLR`` models consider a slightly adjusted version of the ``DoubleMLPLR`` model.
2 | Instead of considering a constant treatment effect :math:`\theta_0` for all observations, the adjusted model allows for a different effect based on groups.
3 |
4 | .. math::
5 |
6 | Y = D \theta_0(X) + g_0(X) + \zeta, & &\mathbb{E}(\zeta | D,X) = 0,
7 |
8 | D = m_0(X) + V, & &\mathbb{E}(V | X) = 0,
9 |
10 | where :math:`\theta_0(X)` denotes the heterogeneous treatment effect.
11 |
12 | Point estimates and confidence intervals can be obtained via the ``gate()`` and ``confint()`` methods.
--------------------------------------------------------------------------------
/doc/guide/models/irm/irm.rst:
--------------------------------------------------------------------------------
1 | **Interactive regression (IRM)** models take the form
2 |
3 | .. math::
4 |
5 | Y = g_0(D, X) + U, & &\mathbb{E}(U | X, D) = 0,
6 |
7 | D = m_0(X) + V, & &\mathbb{E}(V | X) = 0,
8 |
9 | where the treatment variable is binary, :math:`D \in \lbrace 0,1 \rbrace`.
10 | We consider estimation of the average treatment effects when treatment effects are fully heterogeneous.
11 |
12 | Target parameters of interest in this model are the average treatment effect (ATE),
13 |
14 | .. math::
15 |
16 | \theta_0 = \mathbb{E}[g_0(1, X) - g_0(0,X)]
17 |
18 | and the average treatment effect of the treated (ATTE),
19 |
20 | .. math::
21 |
22 | \theta_0 = \mathbb{E}[g_0(1, X) - g_0(0,X) | D=1].
23 |
--------------------------------------------------------------------------------
/doc/api/utility.rst:
--------------------------------------------------------------------------------
1 | .. _api_utility:
2 |
3 | Utility Classes and Functions
4 | -----------------------------
5 |
6 | Utility Classes
7 | ~~~~~~~~~~~~~~~
8 |
9 | .. currentmodule:: doubleml
10 |
11 | .. autosummary::
12 | :toctree: generated/
13 | :template: class.rst
14 |
15 | utils.DMLDummyRegressor
16 | utils.DMLDummyClassifier
17 | utils.DMLOptunaResult
18 | utils.DoubleMLBLP
19 | utils.DoubleMLPolicyTree
20 | utils.GlobalRegressor
21 | utils.GlobalClassifier
22 | utils.PSProcessorConfig
23 | utils.PSProcessor
24 |
25 | Utility Functions
26 | ~~~~~~~~~~~~~~~~~
27 |
28 | .. currentmodule:: doubleml
29 |
30 | .. autosummary::
31 | :toctree: generated/
32 |
33 | utils.gain_statistics
--------------------------------------------------------------------------------
/doc/shared/causal_graphs/pliv_iivm_causal_graph.rst:
--------------------------------------------------------------------------------
1 | .. graphviz::
2 | :align: center
3 | :caption: Causal diagram
4 |
5 | digraph {
6 | nodesep=1;
7 | ranksep=1;
8 | rankdir=LR;
9 | { node [shape=circle, style=filled]
10 | Y [fillcolor="#56B4E9"]
11 | D [fillcolor="#56B4E9"]
12 | Z [fillcolor="#F0E442"]
13 | V [fillcolor="#F0E442"]
14 | X [fillcolor="#D55E00"]
15 | }
16 |
17 | Z -> V [dir="back"];
18 | D -> X [dir="back"];
19 | Y -> D [dir="both"];
20 | X -> Y;
21 | Z -> X [dir="back"];
22 | Z -> D;
23 |
24 | { rank=same; Y D }
25 | { rank=same; Z X }
26 | { rank=same; V }
27 | }
--------------------------------------------------------------------------------
/doc/_templates/logo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: API Documentation Improvement [Python Package DoubleML]
4 | url: https://github.com/DoubleML/doubleml-for-py/issues/new?assignees=&labels=documentation&template=api_docu.yml&title=%5BAPI+Documentation%5D%3A+
5 | about: Suggest an improvement for the API documentation [Python Package DoubleML]
6 | - name: API Documentation Improvement [R Package DoubleML]
7 | url: https://github.com/DoubleML/doubleml-for-r/issues/new?assignees=&labels=documentation&template=api_docu.yml&title=%5BAPI+Documentation%5D%3A+
8 | about: Suggest an improvement for the API documentation [R Package DoubleML]
9 | - name: Blank Issue
10 | url: https://github.com/DoubleML/doubleml-docs/issues/new
11 | about: Open a blank issue
12 |
--------------------------------------------------------------------------------
/doc/guide/scores/irm/pq_score.rst:
--------------------------------------------------------------------------------
1 | For ``DoubleMLPQ`` the only valid option is ``score='PQ'``. For ``treatment=d`` with :math:`d\in\{0,1\}` and
2 | a quantile :math:`\tau\in (0,1)` this implements the nonlinear score function:
3 |
4 | .. math::
5 |
6 | \psi(W; \theta, \eta) := g_{d}(X, \tilde{\theta}) + \frac{1\{D=d\}}{m(X)}(1\{Y\le \theta\} - g_d(X, \tilde{\theta})) - \tau
7 |
8 |
9 | where :math:`\eta=(g_d,m)` with true values
10 |
11 | .. math::
12 |
13 | g_{d,0}(X, \theta_0) &= \mathbb{E}[1\{Y\le \theta_0\}|X, D=d]
14 |
15 | m_0(X) &= P(D=d|X).
16 |
17 | Remark that :math:`g_{d,0}(X,\theta_0)` depends on the target parameter :math:`\theta_0`, such that
18 | the score is estimated with a preliminary estimate :math:`\tilde{\theta}`. For further details, see `Kallus et al. (2019) `_.
19 |
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/gate_plr.rst:
--------------------------------------------------------------------------------
1 | **Group Average Treatment Effects (GATEs)** for ``DoubleMLPLR`` models consider a slightly adjusted version of the ``DoubleMLPLR`` model.
2 | Instead of considering a constant treatment effect :math:`\theta_0` for all observations, the adjusted model allows for a different effect based on groups.
3 |
4 | .. math::
5 |
6 | Y = D \theta_0(G_k) + g_0(X) + \zeta, & &\mathbb{E}(\zeta | D,X) = 0,
7 |
8 | D = m_0(X) + V, & &\mathbb{E}(V | X) = 0,
9 |
10 | where :math:`G_k` for :math:`k=1,\dots, K` denotes a group indicator where the groups can depend on the counfounding features :math:`X`.
11 |
12 | Point estimates and confidence intervals can be obtained via the ``gate()`` and ``confint()`` methods.
13 | Remark that for straightforward interpretation, the groups have to be mutually exclusive.
14 |
--------------------------------------------------------------------------------
/doc/guide/scores/ssm/mar_score.rst:
--------------------------------------------------------------------------------
1 | For ``DoubleMLSSM`` the ``score='missing-at-random'`` implements the score function:
2 |
3 | .. math::
4 |
5 | \psi(W; \theta, \eta) := \tilde{\psi}_1(W; \eta) - \tilde{\psi}_0(W; \eta) - \theta
6 |
7 | where
8 |
9 | .. math::
10 |
11 | \tilde{\psi}_1(W; \eta) &= \frac{D \cdot S \cdot [Y - g(1,1,X)]}{m(X) \cdot \pi(1, X)} + g(1,1,X)
12 |
13 | \tilde{\psi}_0(W; \eta) &= \frac{(1-D) \cdot S \cdot [Y - g(0,1,X)]}{(1-m(X)) \cdot \pi(0, X)} + g(0,1,X)
14 |
15 | for :math:`d\in\{0,1\}` and :math:`\eta=(g, m, \pi)` with true values
16 |
17 | .. math::
18 |
19 | g_0(d,s,X) &= \mathbb{E}[Y|D=d, S=s, X]
20 |
21 | m_0(X) &= P(D=1|X)
22 |
23 | \pi_0(d, X) &= P(S=1|D=d, X).
24 |
25 |
26 | For further details, see `Bia, Huber and Lafférs (2023) `_.
27 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 | NBSPHINX_EXECUTE = auto
11 |
12 | # Add NBSPHINX_EXECUTE to SPHINXOPTS
13 | SPHINXOPTS += -D nbsphinx_execute=$(NBSPHINX_EXECUTE)
14 |
15 |
16 | # Put it first so that "make" without argument is like "make help".
17 | help:
18 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
19 |
20 | .PHONY: help Makefile
21 |
22 | # Catch-all target: route all unknown targets to Sphinx using the new
23 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
24 | %: Makefile
25 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
26 |
--------------------------------------------------------------------------------
/doc/examples/data/orig_demand_data_example.csv:
--------------------------------------------------------------------------------
1 | ,Date,StockCode,Country,Description,Quantity,revenue,UnitPrice
2 | 0,2010-12-01,10002,France,INFLATABLE POLITICAL GLOBE ,48,40.8,0.85
3 | 1,2010-12-01,10002,United Kingdom,INFLATABLE POLITICAL GLOBE ,12,10.2,0.85
4 | 2,2010-12-01,10125,United Kingdom,MINI FUNKY DESIGN TAPES,2,1.7,0.85
5 | 3,2010-12-01,10133,United Kingdom,COLOURING PENCILS BROWN TUBE,5,4.25,0.85
6 | 4,2010-12-01,10135,United Kingdom,COLOURING PENCILS BROWN TUBE,1,2.51,2.51
7 | 5,2010-12-01,11001,United Kingdom,ASSTD DESIGN RACING CAR PEN,3,10.08,3.36
8 | 6,2010-12-01,15044B,United Kingdom,BLUE PAPER PARASOL ,1,2.95,2.95
9 | 7,2010-12-01,15056BL,United Kingdom,EDWARDIAN PARASOL BLACK,20,113.0,5.65
10 | 8,2010-12-01,15056N,United Kingdom,EDWARDIAN PARASOL NATURAL,50,236.3,4.726
11 | 9,2010-12-01,15056P,United Kingdom,EDWARDIAN PARASOL PINK,48,220.79999999999998,4.6
12 |
--------------------------------------------------------------------------------
/doc/guide/scores/irm/cvar_score.rst:
--------------------------------------------------------------------------------
1 | For ``DoubleMLCVAR`` the only valid option is ``score='CVaR'``. For ``treatment=d`` with :math:`d\in\{0,1\}` and
2 | a quantile :math:`\tau\in (0,1)` this implements the score function:
3 |
4 | .. math::
5 |
6 | \psi(W; \theta, \eta) := g_{d}(X, \gamma) + \frac{1\{D=d\}}{m(X)}(\max(\gamma, (1 - \tau)^{-1}(Y - \tau \gamma)) - g_d(X, \gamma)) - \theta
7 |
8 | where :math:`\eta=(g_d,m,\gamma)` with true values
9 |
10 | .. math::
11 |
12 | g_{d,0}(X, \gamma_0) &= \mathbb{E}[\max(\gamma_0, (1 - \tau)^{-1}(Y - \tau \gamma_0))|X, D=d]
13 |
14 | m_0(X) &= P(D=d|X)
15 |
16 | and :math:`\gamma_0` being the potential quantile of :math:`Y(d)`. As for potential quantiles, the estimate :math:`g_d` is constructed via
17 | a preliminary estimate of :math:`\gamma_0`. For further details, see `Kallus et al. (2019) `_.
18 |
--------------------------------------------------------------------------------
/doc/guide/scores/irm/iivm_score.rst:
--------------------------------------------------------------------------------
1 | For the IIVM model implemented in ``DoubleMLIIVM``
2 | we employ for ``score='LATE'`` the score function:
3 |
4 | ``score='LATE'`` implements the score function:
5 |
6 | .. math::
7 |
8 | \psi(W; \theta, \eta) :=\; &g(1,X) - g(0,X)
9 | + \frac{Z (Y - g(1,X))}{m(X)} - \frac{(1 - Z)(Y - g(0,X))}{1 - m(X)}
10 |
11 | &- \bigg(r(1,X) - r(0,X) + \frac{Z (D - r(1,X))}{m(X)} - \frac{(1 - Z)(D - r(0,X))}{1 - m(X)} \bigg) \theta
12 |
13 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
14 |
15 | with :math:`\eta=(g, m, r)` and where the components of the linear score are
16 |
17 | .. math::
18 |
19 | \psi_a(W; \eta) &= - \bigg(r(1,X) - r(0,X) + \frac{Z (D - r(1,X))}{m(X)} - \frac{(1 - Z)(D - r(0,X))}{1 - m(X)} \bigg),
20 |
21 | \psi_b(W; \eta) &= g(1,X) - g(0,X) + \frac{Z (Y - g(1,X))}{m(X)} - \frac{(1 - Z)(Y - g(0,X))}{1 - m(X)}.
22 |
--------------------------------------------------------------------------------
/doc/_templates/class.rst:
--------------------------------------------------------------------------------
1 | {{ fullname | escape | underline}}
2 |
3 | .. currentmodule:: {{ module }}
4 |
5 | .. autoclass:: {{ objname }}
6 |
7 | {% block methods %}
8 | {% if methods %}
9 | .. rubric:: Methods
10 |
11 | .. autosummary::
12 | {% for item in methods %}
13 | {%- if not item.startswith('_') or item in ['__call__'] %} ~{{ name }}.{{ item }}
14 | {% endif %}
15 | {%- endfor %}
16 | {% endif %}
17 | {% endblock %}
18 |
19 | {% block attributes %}
20 | {% if attributes %}
21 | .. rubric:: Attributes
22 |
23 | .. autosummary::
24 | {% for item in attributes %}
25 | ~{{ name }}.{{ item }}
26 | {%- endfor %}
27 | {% endif %}
28 | {% endblock %}
29 |
30 |
31 | {% if methods %}
32 | {% for item in methods %}
33 | {%- if not item.startswith('_') or item in ['__call__'] %}
34 | .. automethod:: {{ name }}.{{ item }}
35 | {% endif %}
36 | {%- endfor %}
37 | {% endif %}
--------------------------------------------------------------------------------
/doc/guide/learners/r/learners_overview.inc:
--------------------------------------------------------------------------------
1 | .. _r_learner_req:
2 |
3 | Minimum requirements for learners
4 | #################################
5 |
6 | .. include:: /guide/learners/r/minimum_req.rst
7 |
8 |
9 | .. _r_set_params:
10 |
11 | Specifying learners and set hyperparameters
12 | ###########################################
13 |
14 | .. include:: /guide/learners/r/set_hyperparams.rst
15 |
16 |
17 | .. _r_pipelines:
18 |
19 | Using pipelines to construct learners
20 | #####################################
21 |
22 | .. include:: /guide/learners/r/pipelines.rst
23 |
24 |
25 | .. _r_tune_params:
26 |
27 | Hyperparameter tuning
28 | #####################
29 |
30 | .. include:: /guide/learners/r/tune_hyperparams.rst
31 |
32 |
33 | .. _r_tune_and_pipelines:
34 |
35 | Hyperparameter tuning with pipelines
36 | ####################################
37 |
38 | .. include:: /guide/learners/r/tune_and_pipelines.rst
39 |
40 |
--------------------------------------------------------------------------------
/doc/shared/heterogeneity/policytree.rst:
--------------------------------------------------------------------------------
1 | **Policy Learning** considers to find an optimal decision policy. We consider deterministic binary policies, which are defined as mapping
2 |
3 | .. math::
4 |
5 | \pi: X\mapsto \{0,1\}.
6 |
7 | Using the score component :math:`\psi_b(W_i,\hat{\eta})` of the :ref:`IRM ` score,
8 | we can find the optimal treatment policy by solving the weighted classification problem
9 |
10 | .. math::
11 |
12 | \hat{\pi} = \mathop{\arg \max}\limits_{\pi\in\Pi} \frac{1}{n}\sum_{i=1}^n(2\pi(X_i)-1)\hat{\psi_b(W_i,\hat{\eta})},
13 |
14 | where :math:`\Pi` denotes a policy class, which we define as depth-:math:`m` classification trees.
15 | Thus, we estimate splits in the features :math:`X` that reflect the heterogeneity of the treatment effect
16 | and consequently maximize the sum of the estimated individual treatment effects of all individuals by assigning different treatments.
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Documentation and User Guide for DoubleML - Double Machine Learning in Python & R
2 |
3 | [](https://badge.fury.io/py/DoubleML)
4 | [](https://cran.r-project.org/package=DoubleML)
5 |
6 | - This repo contains the source code for the documentation and user guide for the Python and R packages **DoubleML**.
7 | - The documentation is available at [https://docs.doubleml.org/](https://docs.doubleml.org/).
8 | - The source code for the Python package **DoubleML** is available here: [https://github.com/DoubleML/doubleml-for-py](https://github.com/DoubleML/doubleml-for-py).
9 | - The source code for the R package **DoubleML** is available here: [https://github.com/DoubleML/doubleml-for-r](https://github.com/DoubleML/doubleml-for-r).
10 |
11 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/doc/guide/scores/did/did_scores.inc:
--------------------------------------------------------------------------------
1 | The following scores for difference-in-differences models are implemented.
2 |
3 |
4 | .. _did-pa-score:
5 |
6 | Panel Data
7 | ==========
8 |
9 | .. include:: /guide/scores/did/did_pa_score.rst
10 |
11 |
12 | .. _did-cs-score:
13 |
14 | Repeated Cross-Sectional Data
15 | =============================
16 |
17 | .. include:: /guide/scores/did/did_cs_score.rst
18 |
19 |
20 | Two treatment periods
21 | =====================
22 |
23 | .. warning::
24 | This documentation refers to the deprecated implementation for two time periods.
25 | This functionality will be removed in a future version. The generalized version are :ref:`did-pa-score` and :ref:`did-cs-score`.
26 |
27 |
28 | Panel Data
29 | ~~~~~~~~~~~
30 |
31 | .. include:: /guide/scores/did/did_pa_binary_score.rst
32 |
33 |
34 | Repeated Cross-Sectional Data
35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 |
37 |
38 | .. include:: /guide/scores/did/did_cs_binary_score.rst
--------------------------------------------------------------------------------
/doc/guide/models.rst:
--------------------------------------------------------------------------------
1 | .. _models:
2 |
3 | Models
4 | ----------
5 |
6 | The :ref:`DoubleML `-package includes the following models.
7 |
8 | .. _plm-models:
9 |
10 | Partially linear models (PLM)
11 | +++++++++++++++++++++++++++++
12 |
13 | .. include:: models/plm/plm_models.inc
14 |
15 |
16 | .. _irm-models:
17 |
18 | Interactive regression models (IRM)
19 | ++++++++++++++++++++++++++++++++++++
20 |
21 | .. include:: models/irm/irm_models.inc
22 |
23 |
24 | .. _did-models:
25 |
26 | Difference-in-Differences Models (DID)
27 | ++++++++++++++++++++++++++++++++++++++
28 |
29 | .. include:: models/did/did_models.inc
30 |
31 |
32 | .. _ssm-models:
33 |
34 | Sample Selection Models (SSM)
35 | ++++++++++++++++++++++++++++++++++++++
36 |
37 | .. include:: models/ssm/ssm_models.inc
38 |
39 |
40 | .. _rdd-models:
41 |
42 | Regression Discontinuity Designs (RDD)
43 | ++++++++++++++++++++++++++++++++++++++
44 |
45 | .. include:: models/rdd/rdd_models.inc
46 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/did/did_pa_binary_sensitivity.rst:
--------------------------------------------------------------------------------
1 | In the :ref:`did-pa-model` with ``score='observational'`` and ``in_sample_normalization=True`` the score function implies the following representations
2 |
3 | .. math::
4 |
5 | m(W,g) &= \big(g(1,X) - g(0,X))\frac{D}{\mathbb{E}[D]}
6 |
7 | \alpha(W) &= \frac{D}{\mathbb{E}[D]} - \frac{\frac{m(X)(1-D)}{1-m(X)}}{\mathbb{E}\left[\frac{m(X)(1-D)}{1-m(X)}\right]}.
8 |
9 | If instead ``in_sample_normalization=False``, the Riesz representer changes to
10 |
11 | .. math::
12 |
13 | \alpha(W) = \frac{D}{\mathbb{E}[D]} - \frac{m(X)(1-D)}{\mathbb{E}[D](1-m(X))}.
14 |
15 | For ``score='experimental'`` implies the score function implies the following representations
16 |
17 | .. math::
18 |
19 | m(W,g) &= g(1,X) - g(0,X)
20 |
21 | \alpha(W) &= \frac{D}{\mathbb{E}[D]} - \frac{1-D}{1-\mathbb{E}[D]}.
22 |
23 | The ``nuisance_elements`` are then computed with plug-in versions according to the general :ref:`sensitivity_implementation`.
--------------------------------------------------------------------------------
/doc/guide/scores/ssm/nr_score.rst:
--------------------------------------------------------------------------------
1 | For ``DoubleMLSSM`` the ``score='nonignorable'`` implements the score function:
2 |
3 | .. math::
4 |
5 | \psi(W; \theta, \eta) := \tilde{\psi}_1(W; \eta) - \tilde{\psi}_0(W; \eta) - \theta
6 |
7 | where
8 |
9 | .. math::
10 |
11 | \tilde{\psi}_1(W; \eta) &= \frac{D \cdot S \cdot [Y - g(1,1,X,\Pi)]}{m(X, \Pi) \cdot \pi(1,X,Z)} + g(1,1,X,\Pi)
12 |
13 | \tilde{\psi}_0(W; \eta) &= \frac{(1-D) \cdot S \cdot [Y - g(0,1,X,\Pi)]}{(1-m(X,\Pi)) \cdot \pi(0,X,Z)} + g(0,1,X,\Pi)
14 |
15 | for :math:`d\in\{0,1\}` and :math:`\eta=(g, m, \pi, \Pi)` with true values
16 |
17 | .. math::
18 |
19 | \pi_0(d, X, Z) &= P(S=1|D=d, X, Z)
20 |
21 | \Pi_0 &:= \pi_0(D, Z, X) = P(S=1|D,X,Z)
22 |
23 | g_0(d,s,X) &= \mathbb{E}[Y|D=d, S=s, X, \Pi_0]
24 |
25 | m_0(X, \Pi_0) &= P(D=1|X, \Pi_0).
26 |
27 | The estimate of :math:`\Pi_0` is constructed via a preliminary estimate of :math:`\pi_0(D,X,Z)` via nested cross-fitting.
28 |
29 | For further details, see `Bia, Huber and Lafférs (2023) `_.
30 |
--------------------------------------------------------------------------------
/doc/guide/scores/irm/irm_scores.inc:
--------------------------------------------------------------------------------
1 | The following scores for nonparametric regression models are implemented.
2 |
3 | .. _irm-score:
4 |
5 | Binary Interactive Regression Model (IRM)
6 | ==========================================
7 |
8 | .. include:: /guide/scores/irm/irm_score.rst
9 |
10 |
11 | .. _apo-score:
12 |
13 | Average Potential Outcomes (APOs)
14 | =================================
15 |
16 | .. include:: /guide/scores/irm/apo_score.rst
17 |
18 |
19 | .. _iivm-score:
20 |
21 | Interactive IV model (IIVM)
22 | ===========================
23 |
24 | .. include:: /guide/scores/irm/iivm_score.rst
25 |
26 |
27 | .. _pq-score:
28 |
29 | Potential quantiles (PQs)
30 | =========================
31 |
32 | .. include:: /guide/scores/irm/pq_score.rst
33 |
34 |
35 | .. _lpq-score:
36 |
37 | Local potential quantiles (LPQs)
38 | ================================
39 |
40 | .. include:: /guide/scores/irm/lpq_score.rst
41 |
42 |
43 | .. _cvar-score:
44 |
45 | Conditional value at risk (CVaR)
46 | ================================
47 |
48 | .. include:: /guide/scores/irm/cvar_score.rst
--------------------------------------------------------------------------------
/doc/api/datasets.rst:
--------------------------------------------------------------------------------
1 | .. _api_datasets:
2 |
3 | Datasets
4 | ---------
5 |
6 | Dataset Loaders
7 | ~~~~~~~~~~~~~~~
8 |
9 | .. currentmodule:: doubleml.datasets
10 |
11 | .. autosummary::
12 | :toctree: generated/
13 |
14 | fetch_401K
15 | fetch_bonus
16 |
17 | Dataset Generators
18 | ~~~~~~~~~~~~~~~~~~
19 |
20 | .. currentmodule:: doubleml
21 |
22 | .. autosummary::
23 | :toctree: generated/
24 |
25 | irm.datasets.make_irm_data
26 | irm.datasets.make_iivm_data
27 | irm.datasets.make_heterogeneous_data
28 | irm.datasets.make_irm_data_discrete_treatments
29 | irm.datasets.make_confounded_irm_data
30 | irm.datasets.make_ssm_data
31 |
32 | plm.datasets.make_plr_CCDDHNR2018
33 | plm.datasets.make_plr_turrell2018
34 | plm.datasets.make_lplr_LZZ2020
35 | plm.datasets.make_pliv_CHS2015
36 | plm.datasets.make_pliv_multiway_cluster_CKMS2021
37 | plm.datasets.make_confounded_plr_data
38 |
39 | did.datasets.make_did_SZ2020
40 | did.datasets.make_did_CS2021
41 | did.datasets.make_did_cs_CS2021
42 |
43 | rdd.datasets.make_simple_rdd_data
44 |
--------------------------------------------------------------------------------
/doc/guide/models/did/did_models.inc:
--------------------------------------------------------------------------------
1 | .. include:: /guide/models/did/did_setup.rst
2 |
3 | .. _did-implementation-model:
4 |
5 | Parameters & Implementation
6 | ***************************
7 |
8 | .. include:: /guide/models/did/did_implementation.rst
9 |
10 |
11 | .. _did-pa-model:
12 |
13 | Panel data
14 | ******************
15 |
16 | .. include:: /guide/models/did/did_pa.rst
17 |
18 |
19 | .. _did-cs-model:
20 |
21 | Repeated cross-sections
22 | *******************************
23 |
24 | .. include:: /guide/models/did/did_cs.rst
25 |
26 |
27 | .. _did-aggregation:
28 |
29 | Effect Aggregation
30 | ******************
31 |
32 | .. include:: /guide/models/did/did_aggregation.rst
33 |
34 |
35 | .. _did-binary-model:
36 |
37 | Two treatment periods
38 | *********************
39 |
40 | .. warning::
41 | This documentation refers to the deprecated implementation for two time periods.
42 | This functionality will be removed in a future version.
43 |
44 | .. note::
45 | We recommend using the implementation :ref:`did-pa-model` and :ref:`did-cs-model`.
46 |
47 | .. include:: /guide/models/did/did_binary.rst
48 |
--------------------------------------------------------------------------------
/doc/guide/learners.rst:
--------------------------------------------------------------------------------
1 | .. _learners:
2 |
3 | Learners, hyperparameters and hyperparameter tuning
4 | -----------------------------------------------------------
5 |
6 | The estimation of a double/debiased machine learning model involves the estimation of several nuisance function with
7 | machine learning estimators.
8 | Such learners are implemented in various Python and R packages.
9 | The implementation of :ref:`DoubleML ` is based on the meta-packages
10 | `scikit-learn `_ for Python and `mlr3 `_ for R.
11 | The interfaces to specify the learners, set hyperparameters and tune hyperparameters are described in the following
12 | separately for :ref:`Python ` and :ref:`R `.
13 |
14 | .. _learners_python:
15 |
16 | Python: Learners and hyperparameters
17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18 |
19 | .. include:: learners/python/learners_overview.inc
20 |
21 |
22 | .. _learners_r:
23 |
24 | R: Learners and hyperparameters
25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26 |
27 | .. include:: learners/r/learners_overview.inc
28 |
29 |
30 |
--------------------------------------------------------------------------------
/doc/guide/models/irm/iivm.rst:
--------------------------------------------------------------------------------
1 | **Interactive IV regression (IIVM)** models take the form
2 |
3 | .. math::
4 |
5 | Y = \ell_0(D, X) + \zeta, & &\mathbb{E}(\zeta | Z, X) = 0,
6 |
7 | Z = m_0(X) + V, & &\mathbb{E}(V | X) = 0,
8 |
9 | where the treatment variable is binary, :math:`D \in \lbrace 0,1 \rbrace`
10 | and the instrument is binary, :math:`Z \in \lbrace 0,1 \rbrace`.
11 | Consider the functions :math:`g_0`, :math:`r_0` and :math:`m_0`, where :math:`g_0` maps the support of :math:`(Z,X)` to
12 | :math:`\mathbb{R}` and :math:`r_0` and :math:`m_0` respectively map the support of :math:`(Z,X)` and :math:`X` to
13 | :math:`(\varepsilon, 1-\varepsilon)` for some :math:`\varepsilon \in (0, 1/2)`, such that
14 |
15 | .. math::
16 |
17 | Y = g_0(Z, X) + \nu, & &\mathbb{E}(\nu | Z, X) = 0,
18 |
19 | D = r_0(Z, X) + U, & &\mathbb{E}(U | Z, X) = 0,
20 |
21 | Z = m_0(X) + V, & &\mathbb{E}(V | X) = 0.
22 |
23 | The target parameter of interest in this model is the local average treatment effect (LATE),
24 |
25 | .. math::
26 |
27 | \theta_0 = \frac{\mathbb{E}[g_0(1, X)] - \mathbb{E}[g_0(0,X)]}{\mathbb{E}[r_0(1, X)] - \mathbb{E}[r_0(0,X)]}.
--------------------------------------------------------------------------------
/doc/guide/scores/plm/pliv_score.rst:
--------------------------------------------------------------------------------
1 | For the PLIV model implemented in ``DoubleMLPLIV`` one can choose between
2 | ``score='IV-type'`` and ``score='partialling out'``.
3 |
4 | ``score='partialling out'`` implements the score function:
5 |
6 | .. math::
7 |
8 | \psi(W; \theta, \eta) &:= [Y - \ell(X) - \theta (D - r(X))] [Z - m(X)]
9 |
10 | &= - (D - r(X)) (Z - m(X)) \theta + (Y - \ell(X)) (Z - m(X))
11 |
12 | &= \psi_a(W; \eta) \theta + \psi_b(W; \eta)
13 |
14 | with :math:`\eta=(\ell, m, r)` and where the components of the linear score are
15 |
16 | .. math::
17 |
18 | \psi_a(W; \eta) &= - (D - r(X)) (Z - m(X)),
19 |
20 | \psi_b(W; \eta) &= (Y - \ell(X)) (Z - m(X)).
21 |
22 | ``score='IV-type'`` implements the score function:
23 |
24 | .. math::
25 |
26 | \psi(W; \theta, \eta) &:= [Y - D \theta - g(X)] [Z - m(X)]
27 |
28 | &= - D (Z - m(X)) \theta + (Y - g(X)) (Z - m(X))
29 |
30 | &= \psi_a(W; \eta) \theta + \psi_b(W; \eta)
31 |
32 | with :math:`\eta=(g,m)` and where the components of the linear score are
33 |
34 | .. math::
35 |
36 | \psi_a(W; \eta) &= - D (Z - m(X)),
37 |
38 | \psi_b(W; \eta) &= (Y - g(X)) (Z - m(X)).
--------------------------------------------------------------------------------
/doc/guide/learners/python/minimum_req.rst:
--------------------------------------------------------------------------------
1 | The minimum requirement for a learner to be used for nuisance models in the :ref:`DoubleML `
2 | package is
3 |
4 | * The implementation of a ``fit()`` and ``predict()`` method.
5 | Some models, like :py:class:`doubleml.DoubleMLIRM` and :py:class:`doubleml.DoubleMLIIVM` require classifiers.
6 | * In case of classifiers, the learner needs to come with a ``predict_proba()`` instead of, or in addition to, a
7 | ``predict()`` method, see for example :py:meth:`sklearn.ensemble.RandomForestClassifier.predict_proba`.
8 | * In order to be able to use the ``set_ml_nuisance_params()`` method of :ref:`DoubleML ` classes the
9 | learner additionally needs to come with a ``set_params()`` method,
10 | see for example :py:meth:`sklearn.ensemble.RandomForestRegressor.set_params`.
11 | * We further rely on the function :py:func:`sklearn.base.clone` which adds the requirement of a ``get_params()``
12 | method for a learner in order to be used for nuisance models of :ref:`DoubleML ` model classes.
13 |
14 | Most learners from `scikit-learn `_ satisfy all these minimum requirements.
15 |
--------------------------------------------------------------------------------
/doc/guide/data_backend.rst:
--------------------------------------------------------------------------------
1 | .. _data_backend:
2 |
3 | Data Backend
4 | ------------
5 |
6 | :ref:`DoubleML ` provides a unified data interface via the :mod:`doubleml.data` module.
7 | It supports both :py:class:`pandas.DataFrame` objects and :py:class:`numpy.ndarray` arrays and now allows
8 | clustered data to be handled directly via :class:`~doubleml.data.DoubleMLData`.
9 |
10 | .. _dml_data:
11 |
12 | DoubleMLData
13 | ~~~~~~~~~~~~
14 |
15 | .. include:: data/base_data.rst
16 |
17 |
18 | .. _dml_data_types:
19 |
20 | Special Data Types
21 | ~~~~~~~~~~~~~~~~~~
22 |
23 | The :ref:`DoubleMLData ` class is extended by the following classes to support special data
24 | types or allow for additional parameters.
25 |
26 | .. _dml_did_data:
27 |
28 | DoubleMLDIDData
29 | ^^^^^^^^^^^^^^^
30 |
31 | .. include:: data/did_data.rst
32 |
33 |
34 | .. _dml_panel_data:
35 |
36 | DoubleMLPanelData
37 | ^^^^^^^^^^^^^^^^^
38 |
39 | .. include:: data/panel_data.rst
40 |
41 |
42 | .. _dml_rdd_data:
43 |
44 | DoubleMLRDDData
45 | ^^^^^^^^^^^^^^^
46 |
47 | .. include:: data/rdd_data.rst
48 |
49 |
50 | .. _dml_ssm_data:
51 |
52 | DoubleMLSSMData
53 | ^^^^^^^^^^^^^^^
54 |
55 | .. include:: data/ssm_data.rst
56 |
57 |
--------------------------------------------------------------------------------
/doc/guide/scores/irm/apo_score.rst:
--------------------------------------------------------------------------------
1 | For the average potential outcomes (APO) models implemented in ``DoubleMLAPO`` and ``DoubleMLAPOS``
2 | the ``score='APO'`` is implemented. Furthermore, weights :math:`\omega(Y,D,X)` and
3 |
4 | .. math::
5 |
6 | \bar{\omega}(X) = \mathbb{E}[\omega(Y,D,X)|X]
7 |
8 | can be specified. For a given treatment level :math:`d` the general score function takes the form
9 |
10 | .. math::
11 |
12 | \psi(W; \theta, \eta) :=\; &\omega(Y,D,X) \cdot g(d,X) + \bar{\omega}(X)\cdot \frac{1\lbrace D = d\rbrace }{m(X)}(Y - g(d,X)) - \theta
13 |
14 | =& \psi_a(W; \eta) \theta + \psi_b(W; \eta)
15 |
16 | with :math:`\eta=(g,m)`, where the true nuisance elements are
17 |
18 | .. math::
19 |
20 | g_0(D, X) &= \mathbb{E}[Y | D, X],
21 |
22 | m_{0,d}(X) &= \mathbb{E}[1\lbrace D = d\rbrace | X] = P(D=d|X).
23 |
24 | The components of the linear score are
25 |
26 | .. math::
27 |
28 | \psi_a(W; \eta) =& - 1,
29 |
30 | \psi_b(W; \eta) =\; &\omega(Y,D,X) \cdot g(d,X) + \bar{\omega}(X)\cdot \frac{1\lbrace D = d\rbrace }{m(X)}(Y - g(d,X)).
31 |
32 |
33 | If no weights are specified, the weights are set to
34 |
35 | .. math::
36 |
37 | \omega(Y,D,X) &= 1
38 |
39 | \bar{\omega}(X) &= 1.
40 |
--------------------------------------------------------------------------------
/doc/guide/learners/python/learners_overview.inc:
--------------------------------------------------------------------------------
1 | .. _py_learner_req:
2 |
3 | Minimum requirements for learners
4 | #################################
5 |
6 | .. include:: /guide/learners/python/minimum_req.rst
7 |
8 |
9 | .. _py_set_params:
10 |
11 | Specifying learners and set hyperparameters
12 | ###########################################
13 |
14 | .. include:: /guide/learners/python/set_hyperparams.rst
15 |
16 | .. _py_tune_params:
17 |
18 | Hyperparameter tuning
19 | #####################
20 |
21 | .. include:: /guide/learners/python/tune_hyperparams.rst
22 |
23 |
24 | Hyperparameter tuning (Grid Search)
25 | ###################################
26 |
27 | .. warning::
28 | **Deprecated:** The ``tune()`` method is deprecated and be removed in a future version.
29 | Please use ``tune_ml_models()`` for hyperparameter tuning, see :ref:`Hyperparameter tuning `.
30 |
31 | .. include:: /guide/learners/python/tune_hyperparams_old.rst
32 |
33 | .. _py_eval_learners:
34 |
35 | Evaluate learners
36 | #################
37 |
38 | .. include:: /guide/learners/python/evaluate_learners.rst
39 |
40 |
41 | .. _py_ext_pred:
42 |
43 | Advanced: External Predictions
44 | ##############################
45 |
46 |
47 | .. include:: /guide/learners/python/external_preds.rst
--------------------------------------------------------------------------------
/doc/guide/models/ssm/ssm.rst:
--------------------------------------------------------------------------------
1 | **Sample Selection Models (SSM)** implemented in the package focus on the the binary treatment case when outcomes are only observed for a subpopulation
2 | due to sample selection or outcome attrition.
3 |
4 | The implementation and notation is based on `Bia, Huber and Lafférs (2023) `_.
5 | Let :math:`D_i` be the binary treatment indicator and :math:`Y_{i}(d)` the potential outcome under treatment value :math:`d`. Further, define
6 | :math:`Y_{i}:=Y_{i}(D)` to be the realized outcome and :math:`S_{i}` as a binary selection indicator. The outcome :math:`Y_{i}` is only observed if :math:`S_{i}=1`.
7 | Finally, let :math:`X_i` be a vector of observed covariates, measures prior to treatment assignment.
8 |
9 | Target parameter of interest is the average treatment effect (ATE)
10 |
11 | .. math::
12 |
13 | \theta_0 = \mathbb{E}[Y_{i}(1)- Y_{i}(0)].
14 |
15 | The corresponding identifying assumption is
16 |
17 | - **Cond. Independence of Treatment:** :math:`Y_i(d) \perp D_i|X_i\quad a.s.` for :math:`d=0,1`
18 |
19 | where further assmputions are made in the context of the respective sample selection model.
20 |
21 | .. note::
22 | A more detailed example can be found in the :ref:`Example Gallery `.
--------------------------------------------------------------------------------
/doc/guide/data/rdd_data.rst:
--------------------------------------------------------------------------------
1 | The ``DoubleMLRDDData`` class specialises :ref:`DoubleMLData ` for
2 | regression discontinuity designs. In addition to the standard causal roles it
3 | tracks a mandatory running variable.
4 |
5 | Key arguments
6 | """""""""""""
7 |
8 | * ``score_col``: column with the running/score variable.
9 | * ``cluster_cols``: optional cluster identifiers inherited from the base data
10 | class.
11 | * ``from_arrays``: expects an additional ``score`` array alongside ``x``, ``y``
12 | and ``d``.
13 |
14 | ``DoubleMLRDDData`` ensures that the running variable is kept separate from the
15 | other feature sets and exposes the ``score`` property for convenient access.
16 |
17 | Example usage
18 | """""""""""""
19 |
20 | .. tab-set::
21 |
22 | .. tab-item:: Python
23 | :sync: py
24 |
25 | .. ipython:: python
26 |
27 | import doubleml as dml
28 | from doubleml.rdd.datasets import make_simple_rdd_data
29 |
30 | dict_rdd = make_simple_rdd_data(n_obs=500, return_type="DataFrame")
31 | dml_data = dml.DoubleMLRDDData.from_arrays(
32 | x=dict_rdd["X"],
33 | y=dict_rdd["Y"],
34 | d=dict_rdd["D"],
35 | score=dict_rdd["score"]
36 | )
37 |
38 | print(dml_data)
39 |
40 |
--------------------------------------------------------------------------------
/doc/api/dml_models.rst:
--------------------------------------------------------------------------------
1 | .. _api_dml_models:
2 |
3 | DoubleML Models
4 | ------------------------------
5 |
6 |
7 | .. _api_plm_models:
8 |
9 | doubleml.plm
10 | ~~~~~~~~~~~~~~~
11 |
12 | .. currentmodule:: doubleml.plm
13 |
14 | .. autosummary::
15 | :toctree: generated/
16 | :template: class.rst
17 |
18 | DoubleMLPLR
19 | DoubleMLLPLR
20 | DoubleMLPLIV
21 |
22 |
23 | .. _api_irm_models:
24 |
25 | doubleml.irm
26 | ~~~~~~~~~~~~~~~
27 |
28 | .. currentmodule:: doubleml.irm
29 |
30 | .. autosummary::
31 | :toctree: generated/
32 | :template: class.rst
33 |
34 | DoubleMLIRM
35 | DoubleMLAPO
36 | DoubleMLAPOS
37 | DoubleMLIIVM
38 | DoubleMLPQ
39 | DoubleMLLPQ
40 | DoubleMLCVAR
41 | DoubleMLQTE
42 | DoubleMLSSM
43 |
44 |
45 | .. _api_did_models:
46 |
47 | doubleml.did
48 | ~~~~~~~~~~~~~~~
49 |
50 | .. currentmodule:: doubleml.did
51 |
52 | .. autosummary::
53 | :toctree: generated/
54 | :template: class.rst
55 |
56 | DoubleMLDIDMulti
57 | DoubleMLDIDAggregation
58 | DoubleMLDIDBinary
59 | DoubleMLDID
60 | DoubleMLDIDCS
61 |
62 |
63 | .. _api_rdd_models:
64 |
65 | doubleml.rdd
66 | ~~~~~~~~~~~~~
67 |
68 | .. currentmodule:: doubleml.rdd
69 |
70 | .. autosummary::
71 | :toctree: generated/
72 | :template: class.rst
73 |
74 | RDFlex
--------------------------------------------------------------------------------
/doc/guide/data/ssm_data.rst:
--------------------------------------------------------------------------------
1 | The ``DoubleMLSSMData`` class covers the sample selection model backend.
2 | It extends :ref:`DoubleMLData ` with a dedicated selection indicator and inherits support for clustered data.
3 |
4 | Key arguments
5 | """""""""""""
6 |
7 | * ``s_col``: column containing the selection indicator.
8 | * ``cluster_cols``: optional cluster identifiers.
9 | * ``from_arrays``: expects an additional ``s`` array together with ``x``, ``y`` and ``d``.
10 |
11 | The object exposes the ``s`` property and keeps the selection indicator
12 | separate from covariates and treatment variables.
13 |
14 | Example usage
15 | """""""""""""
16 |
17 | .. tab-set::
18 |
19 | .. tab-item:: Python
20 | :sync: py
21 |
22 | .. ipython:: python
23 |
24 | import doubleml as dml
25 | from doubleml.irm.datasets import make_ssm_data
26 |
27 | df = make_ssm_data(n_obs=500, return_type="DataFrame")
28 | dml_data = dml.DoubleMLSSMData(
29 | df,
30 | y_col="y",
31 | d_cols="d",
32 | s_col="s"
33 | )
34 |
35 | x, y, d, _, s = make_ssm_data(n_obs=200, return_type="array")
36 | dml_data_arrays = dml.DoubleMLSSMData.from_arrays(x, y, d, s=s)
37 | print(dml_data)
38 |
39 |
--------------------------------------------------------------------------------
/doc/guide/scores/irm/lpq_score.rst:
--------------------------------------------------------------------------------
1 | For ``DoubleMLLPQ`` the only valid option is ``score='LPQ'``. For ``treatment=d`` with :math:`d\in\{0,1\}`, instrument :math:`Z` and
2 | a quantile :math:`\tau\in (0,1)` this implements the nonlinear score function:
3 |
4 | .. math::
5 |
6 | \psi(W; \theta, \eta) :=& \Big(g_{d, Z=1}(X, \tilde{\theta}) - g_{d, Z=0}(X, \tilde{\theta}) + \frac{Z}{m(X)}(1\{D=d\} \cdot 1\{Y\le \theta\} - g_{d, Z=1}(X, \tilde{\theta}))
7 |
8 | &\quad - \frac{1-Z}{1-m(X)}(1\{D=d\} \cdot 1\{Y\le \theta\} - g_{d, Z=0}(X, \tilde{\theta}))\Big) \cdot \frac{2d -1}{\gamma} - \tau
9 |
10 |
11 | where :math:`\eta=(g_{d,Z=1}, g_{d,Z=0}, m, \gamma)` with true values
12 |
13 | .. math::
14 |
15 | g_{d,Z=z,0}(X, \theta_0) &= \mathbb{E}[1\{D=d\} \cdot 1\{Y\le \theta_0\}|X, Z=z],\quad z\in\{0,1\}
16 |
17 | m_{Z=z,0}(X) &= P(D=d|X, Z=z),\quad z\in\{0,1\}
18 |
19 | m_0(X) &= P(Z=1|X)
20 |
21 | \gamma_0 &= \mathbb{E}[P(D=d|X, Z=1) - P(D=d|X, Z=0)].
22 |
23 | Further, the compliance probability :math:`\gamma_0` is estimated with the two additional nuisance components
24 |
25 | .. math::
26 |
27 | m_{Z=z,0}(X) = P(D=d|X, Z=z),\quad z\in\{0,1\}.
28 |
29 | Remark that :math:`g_{d,Z=z,0}(X, \theta_0)` depends on the target parameter :math:`\theta_0`, such that
30 | the score is estimated with a preliminary estimate :math:`\tilde{\theta}`. For further details, see `Kallus et al. (2019) `_.
31 |
--------------------------------------------------------------------------------
/doc/guide/data/panel_data.rst:
--------------------------------------------------------------------------------
1 | The ``DoubleMLPanelData`` class serves as data-backend for :ref:`DiD models ` and can be initialized from a dataframe.
2 | The class is a subclass of :ref:`DoubleMLData ` and inherits all methods and attributes.
3 | Furthermore, it provides additional methods and attributes to handle panel data.
4 |
5 | Key arguments
6 | """""""""""""
7 |
8 | * ``id_col``: column to with unique identifiers for each unit
9 | * ``t_col``: column to specify the time periods of the observation
10 | * ``datetime_unit``: unit of the time periods (e.g. 'Y', 'M', 'D', 'h', 'm', 's')
11 |
12 | .. note::
13 | The ``t_col`` can contain ``float``, ``int`` or ``datetime`` values.
14 |
15 | Example usage
16 | """""""""""""
17 |
18 | .. tab-set::
19 |
20 | .. tab-item:: Python
21 | :sync: py
22 |
23 | .. ipython:: python
24 |
25 | import numpy as np
26 | import doubleml as dml
27 | from doubleml.did.datasets import make_did_CS2021
28 |
29 | np.random.seed(42)
30 | df = make_did_CS2021(n_obs=500)
31 | dml_data = dml.data.DoubleMLPanelData(
32 | df,
33 | y_col="y",
34 | d_cols="d",
35 | id_col="id",
36 | t_col="t",
37 | x_cols=["Z1", "Z2", "Z3", "Z4"],
38 | datetime_unit="M"
39 | )
40 |
41 | print(dml_data)
42 |
--------------------------------------------------------------------------------
/doc/guide/scores/plm/plr_score.rst:
--------------------------------------------------------------------------------
1 | For the PLR model implemented in ``DoubleMLPLR`` one can choose between
2 | ``score='partialling out'`` and ``score='IV-type'``.
3 |
4 | ``score='partialling out'`` implements the score function:
5 |
6 | .. math::
7 |
8 | \psi(W; \theta, \eta) &:= [Y - \ell(X) - \theta (D - m(X))] [D - m(X)]
9 |
10 | &= - (D - m(X)) (D - m(X)) \theta + (Y - \ell(X)) (D - m(X))
11 |
12 | &= \psi_a(W; \eta) \theta + \psi_b(W; \eta)
13 |
14 | with :math:`\eta=(\ell,m)`, where
15 |
16 | .. math::
17 |
18 | \ell_0(X) &:= \mathbb{E}[Y \mid X] = \theta_0\mathbb{E}[D \mid X] + g(X),
19 |
20 | m_0(X) &:= \mathbb{E}[D \mid X].
21 |
22 | The components of the linear score are
23 |
24 | .. math::
25 |
26 | \psi_a(W; \eta) &= - (D - m(X)) (D - m(X)),
27 |
28 | \psi_b(W; \eta) &= (Y - \ell(X)) (D - m(X)).
29 |
30 | ``score='IV-type'`` implements the score function:
31 |
32 | .. math::
33 |
34 | \psi(W; \theta, \eta) &:= [Y - D \theta - g(X)] [D - m(X)]
35 |
36 | &= - D (D - m(X)) \theta + (Y - g(X)) (D - m(X))
37 |
38 | &= \psi_a(W; \eta) \theta + \psi_b(W; \eta)
39 |
40 | with :math:`\eta=(g,m)`, where
41 |
42 | .. math::
43 |
44 | g_0(X) &:= \mathbb{E}[Y - D \theta_0\mid X],
45 |
46 | m_0(X) &:= \mathbb{E}[D \mid X].
47 |
48 | The components of the linear score are
49 |
50 | .. math::
51 |
52 | \psi_a(W; \eta) &= - D (D - m(X)),
53 |
54 | \psi_b(W; \eta) &= (Y - g(X)) (D - m(X)).
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/literature.yml:
--------------------------------------------------------------------------------
1 | name: Double Machine Learning Literature Overview
2 | description: Issues related to the double machine learning literature overview
3 | title: "[ADD/EDIT] Reference in literature overview"
4 | labels: ["literature"]
5 | assignees:
6 | - FrederikBornemann
7 |
8 | body:
9 | - type: checkboxes
10 | attributes:
11 | label: Type of the issue
12 | options:
13 | - label: "add reference"
14 | required: false
15 | - label: "edit reference"
16 | required: false
17 | - type: textarea
18 | attributes:
19 | label: Proposed change or addition
20 | description: |
21 | Propose a change or addition for the [double machine learning literature overview](https://docs.doubleml.org/stable/literature/literature.html).
22 | In case of a change request, please also copy the existing entry below here.
23 | validations:
24 | required: true
25 | - type: checkboxes
26 | attributes:
27 | label: Checklist
28 | description: |
29 | Your reference should contain the [Core Components of an APA Reference](https://www.mendeley.com/guides/apa-citation-guide/).
30 | Don't worry about formatting!
31 | options:
32 | - label: "Author(s)"
33 | required: false
34 | - label: "Date"
35 | required: false
36 | - label: "Title"
37 | required: false
38 | - label: "Publisher"
39 | required: false
40 | - label: "URL"
41 | required: false
42 |
--------------------------------------------------------------------------------
/doc/guide/scores/plm/lplr_score.rst:
--------------------------------------------------------------------------------
1 | For the LPLR model implemented in ``DoubleMLLPLR`` one can choose between
2 | ``score='nuisance_space'`` and ``score='instrument'``.
3 |
4 | ``score='nuisance_space'`` implements the score function:
5 |
6 | .. math::
7 |
8 | \psi(W, \beta, \eta) := \psi(X) \{Y e^{\beta D} -(1-Y)e^{r_0(X)} \} \{ D - m_0(X)\}
9 |
10 | with nuisance elements :math:`\eta = { r(\cdot), m(\cdot), \psi(\cdot) }`, where
11 |
12 | .. math::
13 |
14 | r_0(X) = t_0(X) - \breve \beta a_0(X),
15 |
16 | m_0(X) = \mathbb{E} [D | X, Y=0],
17 |
18 | \psi(X) = \text{expit} (-r_0(X)).
19 |
20 | For the estimation of :math:`r_0(X)`, we further need to obtain a preliminary estimate :math:`\breve \beta` and
21 | :math:`M (D, X) = \mathbb{P} [Y=1 | D, X]` as described in `Liu et al. (2021) `_
22 | and the following estimates:
23 |
24 | .. math::
25 |
26 | t_0(X) = \mathbb{E} [\text{logit}(M (D, X)) | X],
27 |
28 | a_0(X) = \mathbb{E} [D | X].
29 |
30 |
31 |
32 | ``score='instrument'`` implements the score function:
33 |
34 | .. math::
35 |
36 | \psi(W; \beta, \eta) := \mathbb E [ \{Y - \text{expit} (\beta_0 D + r_0(X )) \} Z_0 ]
37 |
38 |
39 | with :math:`Z_0=D-m(X)` and :math:`\eta = { r(\cdot), m(\cdot), \psi(\cdot) }`, where
40 |
41 | .. math::
42 |
43 | r_0(X) = t_0(X) - \breve \beta a_0(X),
44 |
45 | m_0(X) = \mathbb{E} [D | X].
46 |
47 | and :math:`r_0(X)` is computed as for ``score='nuisance_space'``.
--------------------------------------------------------------------------------
/doc/guide/sensitivity/did/did_sensitivity.inc:
--------------------------------------------------------------------------------
1 | The following difference-in-differences models implemented.
2 |
3 | .. note::
4 | Remark that :ref:`sensitivity_benchmark` is only relevant for ``score='observational'``, since no effect of :math:`X` on treatment assignment is assumed.
5 | Generally, we recommend ``score='observational'``, if unobserved confounding seems plausible.
6 |
7 |
8 | .. _sensitivity-did-pa:
9 |
10 | Difference-in-Differences for Panel Data
11 | ========================================
12 |
13 | .. include:: /guide/sensitivity/did/did_pa_sensitivity.rst
14 |
15 |
16 | .. _sensitivity-did-cs:
17 |
18 | Difference-in-Differences for repeated cross-sections
19 | =====================================================
20 |
21 | .. include:: /guide/sensitivity/did/did_cs_sensitivity.rst
22 |
23 |
24 | .. _sensitivity-did-binary:
25 |
26 | Two treatment periods
27 | ======================
28 |
29 |
30 | .. warning::
31 | This documentation refers to the deprecated implementation for two time periods.
32 | This functionality will be removed in a future version. The generalized version are :ref:`sensitivity-did-pa` and :ref:`sensitivity-did-cs`.
33 |
34 |
35 | .. _sensitivity-did-pa-binary:
36 |
37 | Panel Data
38 | """"""""""
39 |
40 | .. include:: /guide/sensitivity/did/did_pa_binary_sensitivity.rst
41 |
42 |
43 | .. _sensitivity-did-cs-binary:
44 |
45 | Repeated Cross-Sectional Data
46 | """""""""""""""""""""""""""""
47 |
48 | .. include:: /guide/sensitivity/did/did_cs_binary_sensitivity.rst
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019-2023 Philipp Bach, Victor Chernozhukov, Sven Klaassen, Malte S. Kurz, Martin Spindler
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/doc/guide/scores/irm/irm_score.rst:
--------------------------------------------------------------------------------
1 | For the IRM model implemented in ``DoubleMLIRM`` one can choose between
2 | ``score='ATE'`` and ``score='ATTE'``. Furthermore, weights :math:`\omega(Y,D,X)` and
3 |
4 | .. math::
5 |
6 | \bar{\omega}(X) = \mathbb{E}[\omega(Y,D,X)|X]
7 |
8 | can be specified. The general score function takes the form
9 |
10 | .. math::
11 |
12 | \psi(W; \theta, \eta) :=\; &\omega(Y,D,X) \cdot (g(1,X) - g(0,X))
13 |
14 | & + \bar{\omega}(X)\cdot \bigg(\frac{D (Y - g(1,X))}{m(X)} - \frac{(1 - D)(Y - g(0,X))}{1 - m(X)}\bigg) - \theta
15 |
16 | =& \psi_a(W; \eta) \theta + \psi_b(W; \eta)
17 |
18 | with :math:`\eta=(g,m)` and where the components of the linear score are
19 |
20 | .. math::
21 |
22 | \psi_a(W; \eta) =& - 1,
23 |
24 | \psi_b(W; \eta) =\; &\omega(Y,D,X) \cdot (g(1,X) - g(0,X))
25 |
26 | & + \bar{\omega}(X)\cdot \bigg(\frac{D (Y - g(1,X))}{m(X)} - \frac{(1 - D)(Y - g(0,X))}{1 - m(X)}\bigg).
27 |
28 | If no weights are specified, ``score='ATE'`` sets the weights
29 |
30 | .. math::
31 |
32 | \omega(Y,D,X) &= 1
33 |
34 | \bar{\omega}(X) &= 1
35 |
36 | whereas ``score='ATTE'`` changes weights to:
37 |
38 | .. math::
39 |
40 | \omega(Y,D,X) &= \frac{D}{\mathbb{E}_n[D]}
41 |
42 | \bar{\omega}(Y,D,X) &= \frac{m(X)}{\mathbb{E}_n[D]}.
43 |
44 | This score is identical to the original presentation in Section 5.1. of Chernozhukov et al. (2018)
45 |
46 | .. math::
47 |
48 | \psi_a(W; \eta) &= -\frac{D}{\mathbb{E}_n[D]}
49 |
50 | \psi_b(W; \eta) &= \frac{D(Y-g(0,X))}{\mathbb{E}_n[D]} - \frac{m(X)(1-D)(Y-g(0,X))}{\mathbb{E}_n[D](1-m(X))}.
51 |
52 | For more details on other weight specifications, see :ref:`weighted_cates`.
53 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity.rst:
--------------------------------------------------------------------------------
1 | .. _sensitivity:
2 |
3 | Sensitivity analysis
4 | ------------------------
5 |
6 | The :ref:`DoubleML ` package implements sensitivity analysis with respect to omitted variable bias
7 | based on `Chernozhukov et al. (2022) `_.
8 |
9 | .. _sensitivity_general:
10 |
11 | General algorithm
12 | +++++++++++++++++
13 |
14 | The section :ref:`sensitivity_theory` contains a general summary and the relevant definitions, whereas :ref:`sensitivity_implementation` considers
15 | the general part of the implementation.
16 |
17 | .. _sensitivity_theory:
18 |
19 | Theory
20 | ~~~~~~
21 |
22 | .. include:: ./sensitivity/theory.rst
23 |
24 | .. _sensitivity_implementation:
25 |
26 | Implementation
27 | ~~~~~~~~~~~~~~
28 |
29 | .. include:: ./sensitivity/implementation.rst
30 |
31 | .. _sensitivity_benchmark:
32 |
33 | Benchmarking
34 | ~~~~~~~~~~~~
35 |
36 | .. include:: ./sensitivity/benchmarking.rst
37 |
38 | .. _sensitivity_models:
39 |
40 | Model-specific implementations
41 | +++++++++++++++++++++++++++++++++++
42 |
43 | This section contains the implementation details for each specific model and model specific interpretations.
44 |
45 | .. _plm-sensitivity:
46 |
47 | Partially linear models (PLM)
48 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49 |
50 | .. include:: sensitivity/plm/plm_sensitivity.inc
51 |
52 |
53 | .. _irm-sensitivity:
54 |
55 | Interactive regression models (IRM)
56 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 |
58 | .. include:: sensitivity/irm/irm_sensitivity.inc
59 |
60 |
61 | .. _did-sensitivity:
62 |
63 | Difference-in-Differences Models
64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65 |
66 | .. include:: sensitivity/did/did_sensitivity.inc
67 |
--------------------------------------------------------------------------------
/doc/guide/data/did_data.rst:
--------------------------------------------------------------------------------
1 | The ``DoubleMLDIDData`` class tailors :ref:`DoubleMLData ` to difference-in-differences
2 | applications. It handles both panel settings and repeated cross-sections by tracking an optional time indicator.
3 |
4 | Key arguments
5 | """""""""""""
6 |
7 | * ``t_col``: column containing the time variable for repeated cross-sections. It
8 | must be unique from ``y_col``, ``d_cols``, ``x_cols``, ``z_cols`` and
9 | ``cluster_cols``.
10 | * ``cluster_cols``: optional cluster identifiers inherited from
11 | :class:`doubleml.DoubleMLData`.
12 | * ``force_all_d_finite``: controls how missing or infinite treatment values are
13 | handled. For standard DiD applications it defaults to ``True``.
14 |
15 | ``DoubleMLDIDData`` exposes additional helpers such as the ``t`` property and
16 | an extended ``from_arrays`` constructor that accepts the ``t`` array (and
17 | ``cluster_vars``) alongside the standard covariates.
18 |
19 | Example usage
20 | """""""""""""
21 |
22 | .. tab-set::
23 |
24 | .. tab-item:: Python
25 | :sync: py
26 |
27 | .. ipython:: python
28 | :okwarning:
29 |
30 | import doubleml as dml
31 | from doubleml.did.datasets import make_did_SZ2020
32 |
33 | df = make_did_SZ2020(n_obs=500, return_type="DataFrame")
34 | print(df.head())
35 | dml_data = dml.DoubleMLDIDData(
36 | df,
37 | y_col="y",
38 | d_cols="d",
39 | )
40 |
41 | # from arrays
42 | x, y, d, t = make_did_SZ2020(n_obs=200, return_type="array")
43 | dml_data_arrays = dml.DoubleMLDIDData.from_arrays(x, y, d)
44 | print(dml_data)
45 |
46 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/did/did_cs_binary_sensitivity.rst:
--------------------------------------------------------------------------------
1 | In the :ref:`did-cs-model` with ``score='observational'`` and ``in_sample_normalization=True`` the score function implies the following representations
2 |
3 | .. math::
4 |
5 | m(W,g) &= \Big(\big(g(1,1,X) - g(1,0,X)\big) - \big(g(0,1,X) - g(0,0,X)\big)\Big) \frac{D}{\mathbb{E}[D]}
6 |
7 | \alpha(W) &= \frac{DT}{\mathbb{E}[DT]} - \frac{D(1-T)}{\mathbb{E}[D(1-T)]}
8 |
9 | &\quad - \frac{m(X)(1-D)T}{1-m(X)}\mathbb{E}\left[\frac{m(X)(1-D)T}{1-m(X)}\right]^{-1}
10 |
11 | &\quad + \frac{m(X)(1-D)(1-T)}{1-m(X)}\mathbb{E}\left[\frac{m(X)(1-D)(1-T)}{1-m(X)}\right]^{-1}.
12 |
13 | If instead ``in_sample_normalization=False``, the Riesz representer (after simplifications) changes to
14 |
15 | .. math::
16 |
17 | \alpha(W) = \left(\frac{T}{\mathbb{E}[D]\mathbb{E}[T]} + \frac{1-T}{\mathbb{E}[D](1-\mathbb{E}[T])}\right)\left(D - (1-D)\frac{m(X)}{1-m(X)}\right).
18 |
19 | For ``score='experimental'`` and ``in_sample_normalization=True`` implies the score function implies the following representations
20 |
21 | .. math::
22 |
23 | m(W,g) &= \big(g(1,1,X) - g(1,0,X)\big) - \big(g(0,1,X) - g(0,0,X)\big)
24 |
25 | \alpha(W) &= \frac{DT}{\mathbb{E}[DT]} - \frac{D(1-T)}{\mathbb{E}[D(1-T)]} - \frac{(1-D)T}{\mathbb{E}[(1-D)T]} + \frac{(1-D)(1-T)}{\mathbb{E}[(1-D)(1-T)]}.
26 |
27 | And again, if instead ``in_sample_normalization=False``, the Riesz representer (after simplifications) changes to
28 |
29 | .. math::
30 |
31 | \alpha(W) = \frac{DT}{\mathbb{E}[D]\mathbb{E}[T]} - \frac{D(1-T)}{\mathbb{E}[D](1-\mathbb{E}[T])} - \frac{(1-D)T}{(1-\mathbb{E}[D])\mathbb{E}[T]} + \frac{(1-D)(1-T)}{(1-\mathbb{E}[D])(1-\mathbb{E}[T])}.
32 |
33 |
34 | The ``nuisance_elements`` are then computed with plug-in versions according to the general :ref:`sensitivity_implementation`.
--------------------------------------------------------------------------------
/doc/guide/sensitivity/did/did_pa_sensitivity.rst:
--------------------------------------------------------------------------------
1 | For a detailed description of the scores and nuisance elements, see :ref:`did-pa-score`.
2 |
3 | In the :ref:`did-pa-model` with ``score='observational'`` and ``in_sample_normalization=True`` the score function implies the following representations
4 |
5 | .. math::
6 |
7 | m(W,g) &= \big(g(1,X) - g(0,X)\big)\cdot \frac{G^{\mathrm{g}}}{\mathbb{E}[G^{\mathrm{g}}]}\cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
8 |
9 | \alpha(W) &= \left(\frac{G^{\mathrm{g}}}{\mathbb{E}[G^{\mathrm{g}}]} - \frac{\frac{m(X)(1-G^{\mathrm{g}})}{1-m(X)}}{\mathbb{E}\left[\frac{m(X)(1-G^{\mathrm{g}})}{1-m(X)}\right]}\right) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
10 |
11 | If instead ``in_sample_normalization=False``, the Riesz representer changes to
12 |
13 | .. math::
14 |
15 | \alpha(W) = \left(\frac{G^{\mathrm{g}}}{\mathbb{E}[G^{\mathrm{g}}]} - \frac{m(X)(1-G^{\mathrm{g}})}{\mathbb{E}[G^{\mathrm{g}}](1-m(X))}\right) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
16 |
17 | For ``score='experimental'`` implies the score function implies the following representations
18 |
19 | .. math::
20 |
21 | m(W,g) &= \big(g(1,X) - g(0,X)\big)\cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
22 |
23 | \alpha(W) &= \left(\frac{G^{\mathrm{g}}}{\mathbb{E}[G^{\mathrm{g}}]} - \frac{1-G^{\mathrm{g}}}{1-\mathbb{E}[G^{\mathrm{g}}]}\right) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
24 |
25 | The ``nuisance_elements`` are then computed with plug-in versions according to the general :ref:`sensitivity_implementation`, but the scores :math:`\psi_{\sigma^2}` and :math:`\psi_{\nu^2}` are scaled according to the sample size of the subset, i.e. with scaling factor :math:`c=\frac{n_{\text{ids}}}{n_{\text{subset}}}`.
26 |
27 | .. note::
28 | Remark that the elements are only non-zero for units in the corresponding treatment group :math:`\mathrm{g}` and control group :math:`C^{(\cdot)}`, as :math:`1-G^{\mathrm{g}}=C^{(\cdot)}` if :math:`\max(G^{\mathrm{g}}, C^{(\cdot)})=1`.
29 |
--------------------------------------------------------------------------------
/doc/guide/learners/r/minimum_req.rst:
--------------------------------------------------------------------------------
1 | The minimum requirement for a learner to be used for nuisance models in the :ref:`DoubleML ` package is
2 |
3 | * The implementation as a learner for regression or classification in the `mlr3 `_ package
4 | or its extension packages `mlr3learners `_ and
5 | `mlr3extralearners `_ . A guide on how to add a learner is provided in the
6 | `chapter on extending learners in the mlr3 book `_ .
7 | * The `mlr3 `_ package makes sure that the learners satisfy some core functionalities.
8 | To specify a specific learner in :ref:`DoubleML ` users can pass objects of the class
9 | `Learner `_. A fast way to construct these objects is to use the
10 | `mlr3 `_ function `lrn() `_.
11 | An introduction to learners in `mlr3 `_ is provided in the `chapter on learners of the mlr3 book `_.
12 | * It is also possible to pass learners that have been constructed from a pipeline with the `mlr3pipelines `_
13 | package.
14 | * The models `DoubleML::DoubleMLIRM `_ and
15 | `DoubleML::DoubleMLIIVM `_ require classifiers.
16 | Users can also specify classifiers in the `DoubleML::DoubleMLPLR `_
17 | in cases with binary treatment variables.
18 | * Hyperparameters of learners can either be set at instantiation in `mlr3 `_ or after
19 | instantiation using the ``set_ml_nuisance_params()`` method.
20 |
21 |
22 | An interactive list of provided learners in the `mlr3 `_ and extension packages can be found on the
23 | `website of the mlr3extralearners package `_.
24 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DoubleML Documentation Development",
3 | "image": "svenklaassen/doubleml-docs:latest",
4 | // "dockerFile": "Dockerfile.dev",
5 | // "context": "..",
6 | "workspaceFolder": "/workspace", // Folder inside the container for your project
7 | // Customizations for VS Code
8 | "customizations": {
9 | "vscode": {
10 | "extensions": [
11 | "ms-python.python", // Python extension for VS Code
12 | "ms-azuretools.vscode-docker", // Docker integration for VS Code
13 | "njpwerner.autodocstring", // Optional: Auto-generate docstrings
14 | "ms-python.black-formatter", // Optional: Black formatter
15 | "streetsidesoftware.code-spell-checker", // Optional: Spell checker
16 | "github.copilot", // Add GitHub Copilot extension
17 | "GitHub.github-vscode-theme", // GitHub theme
18 | "github.vscode-github-actions", // GitHub Actions extension
19 | "ms-toolsai.jupyter", // Jupyter extension
20 | "charliermarsh.ruff" // Ruff extension
21 | ],
22 | "settings": {
23 | "python.defaultInterpreterPath": "/home/ubuntu/.venv/bin/python",
24 | "editor.formatOnSave": true, // Auto-format code when saving
25 | "editor.codeActionsOnSave": {
26 | "source.organizeImports": true // Auto-organize imports on save
27 | },
28 | "python.linting.enabled": true, // Enable linting
29 | "python.linting.flake8Enabled": false, // Disable Flake8 for linting
30 | "python.linting.ruffEnabled": true, // Enable Ruff for linting
31 | "python.formatting.provider": "none",
32 | "[python]": {
33 | "editor.defaultFormatter": "ms-python.black-formatter",
34 | "editor.formatOnSave": true
35 | },
36 | "python.testing.pytestEnabled": true, // Enable Pytest for testing
37 | "python.testing.pytestArgs": [],
38 | "python.testing.unittestEnabled": false,
39 | "files.exclude": {
40 | "**/__pycache__": true, // Hide __pycache__ directories
41 | "**/*.pyc": true, // Hide .pyc files
42 | "**/.DS_Store": true // Hide .DS_Store files (macOS)
43 | }
44 | }
45 | }
46 | },
47 | "mounts": [
48 | "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached" // Mount your local workspace into the container
49 | ],
50 | "remoteUser": "ubuntu",
51 | "postCreateCommand": "id && ls -la /workspace && echo 'Container is ready!'"
52 | }
--------------------------------------------------------------------------------
/doc/guide/learners/python/evaluate_learners.rst:
--------------------------------------------------------------------------------
1 | To compare different learners it is possible to evaluate the out-of-sample performance of each learner. The ``summary``
2 | already displays either the root-mean-squared error (for regressions) or log-loss (for classifications) for each learner
3 | and each corresponding repetition of cross-fitting (``n_rep`` argument).
4 |
5 | To illustrate the parameter tuning, we work with the following example.
6 |
7 | .. tab-set::
8 |
9 | .. tab-item:: Python
10 | :sync: py
11 |
12 | .. ipython:: python
13 |
14 | import doubleml as dml
15 | from doubleml.plm.datasets import make_plr_CCDDHNR2018
16 | from sklearn.ensemble import RandomForestRegressor
17 |
18 | np.random.seed(1234)
19 | ml_l = RandomForestRegressor()
20 | ml_m = RandomForestRegressor()
21 | data = make_plr_CCDDHNR2018(alpha=0.5, return_type='DataFrame')
22 | obj_dml_data = dml.DoubleMLData(data, 'y', 'd')
23 | dml_plr_obj = dml.DoubleMLPLR(obj_dml_data, ml_l, ml_m)
24 | dml_plr_obj.fit()
25 | print(dml_plr_obj)
26 |
27 | The loss of each learner are also stored in the ``nuisance_loss`` attribute.
28 | Further, the ``evaluate_learners()`` method allows to evalute customized evaluation metrics as e.g. the mean absolute error.
29 | The default option is still the root-mean-squared error for evaluation.
30 |
31 | .. tab-set::
32 |
33 | .. tab-item:: Python
34 | :sync: py
35 |
36 | .. ipython:: python
37 |
38 | print(dml_plr_obj.nuisance_loss)
39 | print(dml_plr_obj.evaluate_learners())
40 |
41 | To evaluate a customized metric one has to define a ``callable``. For some models (e.g. the IRM model) it is important that
42 | the metric can handle ``nan`` values as not all target values are known.
43 |
44 | .. tab-set::
45 |
46 | .. tab-item:: Python
47 | :sync: py
48 |
49 | .. ipython:: python
50 |
51 | from sklearn.metrics import mean_absolute_error
52 |
53 | def mae(y_true, y_pred):
54 | subset = np.logical_not(np.isnan(y_true))
55 | return mean_absolute_error(y_true[subset], y_pred[subset])
56 |
57 | dml_plr_obj.evaluate_learners(learners=['ml_l'], metric=mae)
58 |
59 | A more detailed notebook on the choice of learners is available in the :ref:`example gallery `.
--------------------------------------------------------------------------------
/doc/guide/learners/python/tune_hyperparams.rst:
--------------------------------------------------------------------------------
1 | Parameter tuning of learners for the nuisance functions of :ref:`DoubleML ` models can be done via
2 | the ``tune_ml_models()`` method.
3 | To illustrate the parameter tuning, we generate data from a sparse partially linear regression model.
4 |
5 | .. tab-set::
6 |
7 | .. tab-item:: Python
8 | :sync: py
9 |
10 | .. ipython:: python
11 |
12 | import doubleml as dml
13 | import numpy as np
14 |
15 | np.random.seed(3141)
16 | n_obs = 200
17 | n_vars = 200
18 | theta = 3
19 | X = np.random.normal(size=(n_obs, n_vars))
20 | d = np.dot(X[:, :3], np.array([5, 5, 5])) + np.random.standard_normal(size=(n_obs,))
21 | y = theta * d + np.dot(X[:, :3], np.array([5, 5, 5])) + np.random.standard_normal(size=(n_obs,))
22 | dml_data = dml.DoubleMLData.from_arrays(X, y, d)
23 |
24 | The hyperparameter-tuning is performed using `Optuna `_ as backend. Here, we illustrate
25 | the tuning via defining a search space for the nuisance function learners over ``100`` trials. The most important input
26 | argument is the hyperparameter space via a dictionary of functions. This search space will internally transformed into a
27 | suitable ``objective(trial)`` function for `Optuna `_.
28 |
29 | .. tab-set::
30 |
31 | .. tab-item:: Python
32 | :sync: py
33 |
34 | .. ipython:: python
35 | :okwarning:
36 |
37 | import doubleml as dml
38 | from sklearn.linear_model import Lasso
39 | import optuna
40 |
41 | ml_l = Lasso()
42 | ml_m = Lasso()
43 | dml_plr_obj = dml.DoubleMLPLR(dml_data, ml_l, ml_m)
44 |
45 | def ml_l_params(trial):
46 | return {'alpha': trial.suggest_float('alpha', 0.05, 1.0)}
47 |
48 | def ml_m_params(trial):
49 | return {'alpha': trial.suggest_float('alpha', 0.05, 1.0)}
50 |
51 | param_space = {'ml_l': ml_l_params, 'ml_m': ml_m_params}
52 | optuna_settings = {'n_trials': 100, 'verbosity': optuna.logging.WARNING}
53 |
54 | dml_plr_obj.tune_ml_models(ml_param_space=param_space, optuna_settings=optuna_settings)
55 |
56 | print(dml_plr_obj.params)
57 | print(dml_plr_obj.fit().summary)
58 |
59 | A more detailed description of hyperparameter-tuning possibilities can be found in the :ref:`Example Gallery `.
--------------------------------------------------------------------------------
/doc/guide/sensitivity/irm/apo_sensitivity.rst:
--------------------------------------------------------------------------------
1 | In the :ref:`irm-model` the (weighted) average potential outcome for the treatment level :math:`d` can be written as
2 |
3 | .. math::
4 |
5 | \theta_0 = \mathbb{E}[g_0(d,X)\omega(Y,D,X)]
6 |
7 | where :math:`\omega(Y,D,X)` are weights (e.g. set to :math:`1` for the APO).
8 | This implies the following representations
9 |
10 | .. math::
11 |
12 | m(W,g) &= g(d,X)\omega(Y,D,X)
13 |
14 | \alpha(W) &= \frac{1\lbrace D = d\rbrace }{m(X)}\cdot\mathbb{E}[\omega(Y,D,X)|X].
15 |
16 | .. note::
17 |
18 | In the :ref:`irm-model` the form and interpretation of ``cf_y`` only depends on the conditional expectation :math:`\mathbb{E}[Y|D,X]`.
19 |
20 | - ``cf_y`` has the interpretation as the *nonparametric partial* :math:`R^2` *of* :math:`A` *with* :math:`Y` *given* :math:`(D,X)`
21 |
22 | .. math::
23 |
24 | \frac{\textrm{Var}(\mathbb{E}[Y|D,X,A]) - \textrm{Var}(\mathbb{E}[Y|D,X])}{\textrm{Var}(Y)-\textrm{Var}(\mathbb{E}[Y|D,X])}
25 |
26 | - ``cf_d`` takes the following form
27 |
28 | .. math::
29 |
30 | \frac{\mathbb{E}\left[\frac{1}{P(D=d|X,A)}\right] - \mathbb{E}\left[\frac{1}{P(D=d|X)}\right]}{\mathbb{E}\left[\frac{1}{P(D=d|X,A)}\right]}
31 |
32 | where the numerator measures the *average change in inverse propensity weights for* :math:`D=d` *conditional on* :math:`A` *in addition to* :math:`X`.
33 | The denominator is the *average inverse propensity weights for* :math:`D=d` *conditional on* :math:`A` *and* :math:`X`. Consequently ``cf_d`` measures the *relative change in inverse propensity weights*.
34 | Including weights changes only the definition of ``cf_d`` to
35 |
36 | .. math::
37 |
38 | \frac{\mathbb{E}\left[\frac{1}{P(D=d|X,A)}\mathbb{E}[\omega(Y,D,X)|X,A]^2\right] - \mathbb{E}\left[\frac{1}{P(D=d|X)}\mathbb{E}[\omega(Y,D,X)|X]^2\right]}{\mathbb{E}\left[\frac{1}{P(D=d|X,A)}\mathbb{E}[\omega(Y,D,X)|X,A]^2\right]}
39 |
40 | which has a interpretation as the *relative weighted change in inverse propensity weights*.
41 |
42 | The ``nuisance_elements`` are then computed with plug-in versions according to the general :ref:`sensitivity_implementation`.
43 | The default weights are set to one
44 |
45 | .. math::
46 |
47 | \omega(Y,D,X) = 1,
48 |
49 | whereas
50 |
51 | .. math::
52 |
53 | \bar{\omega}(X) := \mathbb{E}[\omega(Y,D,X)|X],
54 |
55 | have to be supplied for weights which depend on :math:`Y` or :math:`D`.
56 |
--------------------------------------------------------------------------------
/doc/examples/index.rst:
--------------------------------------------------------------------------------
1 |
2 | :parenttoc: True
3 |
4 | .. _examplegallery:
5 |
6 | Examples
7 | ==========
8 |
9 | Python: Case studies
10 | ---------------------
11 |
12 | These are case studies with the Python package :ref:`DoubleML `.
13 |
14 | General Examples
15 | ++++++++++++++++
16 |
17 | .. nbgallery::
18 | :name: case-studies-py
19 |
20 | py_double_ml_basics.ipynb
21 | py_double_ml_pension.ipynb
22 | py_double_ml_sensitivity.ipynb
23 | py_double_ml_apo.ipynb
24 | py_double_ml_irm_vs_apo.ipynb
25 | py_double_ml_lplr.ipynb
26 | py_double_ml_ssm.ipynb
27 | learners/py_optuna.ipynb
28 | learners/py_learner.ipynb
29 | py_double_ml_firststage.ipynb
30 | py_double_ml_multiway_cluster.ipynb
31 | py_double_ml_sensitivity_booking.ipynb
32 | learners/py_tabpfn.ipynb
33 | py_double_ml_basic_iv.ipynb
34 | py_double_ml_robust_iv.ipynb
35 | py_double_ml_plm_irm_hetfx.ipynb
36 | py_double_ml_meets_flaml.ipynb
37 | py_double_ml_rdflex.ipynb
38 |
39 |
40 | Effect Heterogeneity
41 | ++++++++++++++++++++
42 |
43 | .. nbgallery::
44 | :name: case-studies-py-heterogeneity
45 |
46 | py_double_ml_gate.ipynb
47 | py_double_ml_gate_plr.ipynb
48 | py_double_ml_cate.ipynb
49 | py_double_ml_cate_plr.ipynb
50 | py_double_ml_gate_sensitivity.ipynb
51 | py_double_ml_policy_tree.ipynb
52 | py_double_ml_pension_qte.ipynb
53 | py_double_ml_pq.ipynb
54 | py_double_ml_cvar.ipynb
55 |
56 |
57 | .. _did_examplegallery:
58 |
59 | Difference-in-Differences
60 | +++++++++++++++++++++++++
61 |
62 | .. nbgallery::
63 | :name: case-studies-py-did
64 |
65 | did/py_panel_simple.ipynb
66 | did/py_panel.ipynb
67 | did/py_panel_data_example.ipynb
68 | did/py_rep_cs.ipynb
69 |
70 |
71 | R: Case studies
72 | ---------------
73 |
74 | These are case studies with the R package :ref:`DoubleML `.
75 |
76 | .. nbgallery::
77 | :name: case-studies-r
78 |
79 | R_double_ml_basics.ipynb
80 | R_double_ml_pension.ipynb
81 | R_double_ml_did.ipynb
82 | R_double_ml_multiway_cluster.ipynb
83 | R_double_ml_ssm.ipynb
84 | R_double_ml_basic_iv.ipynb
85 |
86 | Sandbox/Archive
87 | ---------------
88 |
89 | These are examples which are work-in-progress and/or not yet fully documented.
90 |
91 | .. nbgallery::
92 | :name: sandbox_gallery
93 | :maxdepth: 1
94 |
95 | R_double_ml_pipeline.ipynb
96 | double_ml_bonus_data.ipynb
97 | did/py_did.ipynb
98 | did/py_did_pretest.ipynb
99 |
--------------------------------------------------------------------------------
/doc/guide/learners/r/pipelines.rst:
--------------------------------------------------------------------------------
1 | Users can also specify learners that have been constructed from a pipeline using the `mlr3pipelines `_
2 | package. In general, pipelines can be used to perform data preprocessing, feature selection, combine learners and even
3 | to perform hyperparameter tuning. In the following, we provide two examples on how to construct a single learner and how
4 | to stack different learners via a pipeline. For a more detailed introduction to `mlr3pipelines `_,
5 | we refer to the `Pipelines Chapter in the mlr3book `_. Moreover, a
6 | notebook on how to use `mlr3pipelines `_ in combination with :ref:`DoubleML `
7 | is available in the example gallery.
8 |
9 | .. tab-set::
10 |
11 | .. tab-item:: R
12 | :sync: r
13 |
14 | .. jupyter-execute::
15 |
16 | library(DoubleML)
17 | library(mlr3)
18 | library(mlr3learners)
19 | library(mlr3pipelines)
20 | library(data.table)
21 |
22 | set.seed(3141)
23 | # Define random forest learner in a pipeline
24 | single_learner_pipeline = po("learner", lrn("regr.ranger", num.trees = 10))
25 |
26 | # Use pipeline to create a new instance of a learner
27 | ml_g = as_learner(single_learner_pipeline)
28 | ml_m = as_learner(single_learner_pipeline)
29 |
30 | data = make_plr_CCDDHNR2018(alpha=0.5, return_type='data.table')
31 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_cols="d")
32 |
33 | n_rep = 2
34 | n_folds = 3
35 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_g, ml_m, n_rep=n_rep, n_folds=n_folds)
36 | dml_plr_obj$learner
37 | dml_plr_obj$fit()
38 | dml_plr_obj$summary()
39 |
40 | set.seed(3141)
41 | # Define ensemble learner in a pipeline
42 | ensemble_learner_pipeline = gunion(list(
43 | po("learner", lrn("regr.cv_glmnet", s = "lambda.min")),
44 | po("learner", lrn("regr.ranger")),
45 | po("learner", lrn("regr.rpart", cp = 0.01)))) %>>%
46 | po("regravg", 3)
47 |
48 | # Use pipeline to create a new instance of a learner
49 | ml_g = as_learner(ensemble_learner_pipeline)
50 | ml_m = as_learner(ensemble_learner_pipeline)
51 |
52 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_cols="d")
53 |
54 | n_rep = 2
55 | n_folds = 3
56 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_g, ml_m, n_rep=n_rep, n_folds=n_folds)
57 | dml_plr_obj$learner
58 | dml_plr_obj$fit()
59 | dml_plr_obj$summary()
60 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/did/did_cs_sensitivity.rst:
--------------------------------------------------------------------------------
1 | For a detailed description of the scores and nuisance elements, see :ref:`did-cs-score`.
2 |
3 | In the :ref:`did-cs-model` with ``score='observational'`` and ``in_sample_normalization=True`` the score function implies the following representations
4 |
5 | .. math::
6 |
7 | m(W,g) &= \Big(\big(g(1,1,X) - g(1,0,X)\big) - \big(g(0,1,X) - g(0,0,X)\big)\Big) \frac{G^{\mathrm{g}}}{\mathbb{E}[G^{\mathrm{g}}]} \cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
8 |
9 | \alpha(W) &= \Bigg(\frac{G^{\mathrm{g}}T}{\mathbb{E}[G^{\mathrm{g}}T]} - \frac{G^{\mathrm{g}}(1-T)}{\mathbb{E}[G^{\mathrm{g}}(1-T)]}
10 |
11 | &\quad - \frac{m(X)(1-G^{\mathrm{g}})T}{1-m(X)}\mathbb{E}\left[\frac{m(X)(1-G^{\mathrm{g}})T}{1-m(X)}\right]^{-1}
12 |
13 | &\quad + \frac{m(X)(1-G^{\mathrm{g}})(1-T)}{1-m(X)}\mathbb{E}\left[\frac{m(X)(1-G^{\mathrm{g}})(1-T)}{1-m(X)}\right]^{-1} \Bigg) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
14 |
15 | If instead ``in_sample_normalization=False``, the Riesz representer changes to
16 |
17 | .. math::
18 |
19 | \alpha(W) = \left(\frac{T}{\mathbb{E}[G^{\mathrm{g}}]\mathbb{E}[T]} + \frac{1-T}{\mathbb{E}[G^{\mathrm{g}}](1-\mathbb{E}[T])}\right)\left(G^{\mathrm{g}} - (1-G^{\mathrm{g}})\frac{m(X)}{1-m(X)}\right) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
20 |
21 | For ``score='experimental'`` the score function implies the following representations
22 |
23 | .. math::
24 |
25 | m(W,g) &= \big(g(1,1,X) - g(1,0,X)\big) - \big(g(0,1,X) - g(0,0,X)\big) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
26 |
27 | \alpha(W) &= \left(\frac{G^{\mathrm{g}}T}{\mathbb{E}[G^{\mathrm{g}}T]} - \frac{G^{\mathrm{g}}(1-T)}{\mathbb{E}[G^{\mathrm{g}}(1-T)]} - \frac{(1-G^{\mathrm{g}})T}{\mathbb{E}[(1-G^{\mathrm{g}})T]} + \frac{(1-G^{\mathrm{g}})(1-T)}{\mathbb{E}[(1-G^{\mathrm{g}})(1-T)]}\right) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
28 |
29 | And again, if instead ``in_sample_normalization=False``, the Riesz representer changes to
30 |
31 | .. math::
32 |
33 | \alpha(W) = \left(\frac{G^{\mathrm{g}}T}{\mathbb{E}[G^{\mathrm{g}}]\mathbb{E}[T]} - \frac{G^{\mathrm{g}}(1-T)}{\mathbb{E}[G^{\mathrm{g}}](1-\mathbb{E}[T])} - \frac{(1-G^{\mathrm{g}})T}{(1-\mathbb{E}[G^{\mathrm{g}}])\mathbb{E}[T]} + \frac{(1-G^{\mathrm{g}})(1-T)}{(1-\mathbb{E}[G^{\mathrm{g}}])(1-\mathbb{E}[T])}\right) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}).
34 |
35 | The ``nuisance_elements`` are then computed with plug-in versions according to the general :ref:`sensitivity_implementation`, but the scores :math:`\psi_{\sigma^2}` and :math:`\psi_{\nu^2}` are scaled according to the sample size of the subset, i.e. with scaling factor :math:`c=\frac{n_{\text{obs}}}{n_{\text{subset}}}`.
36 |
37 | .. note::
38 | Remark that the elements are only non-zero for units in the corresponding treatment group :math:`\mathrm{g}` and control group :math:`C^{(\cdot)}`, as :math:`1-G^{\mathrm{g}}=C^{(\cdot)}` if :math:`\max(G^{\mathrm{g}}, C^{(\cdot)})=1`.
--------------------------------------------------------------------------------
/doc/guide/learners/python/set_hyperparams.rst:
--------------------------------------------------------------------------------
1 | The learners are set during initialization of the :ref:`DoubleML ` model classes
2 | :py:class:`doubleml.DoubleMLPLR`, :py:class:`doubleml.DoubleMLPLIV`,
3 | :py:class:`doubleml.DoubleMLIRM` and :py:class:`doubleml.DoubleMLIIVM`.
4 | Lets simulate some data and consider the partially linear regression model.
5 | We need to specify learners for the nuisance functions :math:`g_0(X) = E[Y|X]` and :math:`m_0(X) = E[D|X]`,
6 | for example :py:class:`sklearn.ensemble.RandomForestRegressor`.
7 |
8 | .. tab-set::
9 |
10 | .. tab-item:: Python
11 | :sync: py
12 |
13 | .. ipython:: python
14 |
15 | import doubleml as dml
16 | from doubleml.plm.datasets import make_plr_CCDDHNR2018
17 | from sklearn.ensemble import RandomForestRegressor
18 |
19 | np.random.seed(1234)
20 | ml_l = RandomForestRegressor()
21 | ml_m = RandomForestRegressor()
22 | data = make_plr_CCDDHNR2018(alpha=0.5, return_type='DataFrame')
23 | obj_dml_data = dml.DoubleMLData(data, 'y', 'd')
24 | dml_plr_obj = dml.DoubleMLPLR(obj_dml_data, ml_l, ml_m)
25 | dml_plr_obj.fit().summary
26 |
27 | Without further specification of the hyperparameters, default values are used. To set hyperparameters:
28 |
29 | * We can also use pre-parametrized learners, like ``RandomForestRegressor(n_estimators=10)``.
30 | * Alternatively, hyperparameters can also be set after initialization via the method
31 | ``set_ml_nuisance_params(learner, treat_var, params)``
32 |
33 |
34 | .. tab-set::
35 |
36 | .. tab-item:: Python
37 | :sync: py
38 |
39 | .. ipython:: python
40 |
41 | np.random.seed(1234)
42 | dml_plr_obj = dml.DoubleMLPLR(obj_dml_data,
43 | RandomForestRegressor(n_estimators=10),
44 | RandomForestRegressor())
45 | print(dml_plr_obj.fit().summary)
46 |
47 | np.random.seed(1234)
48 | dml_plr_obj = dml.DoubleMLPLR(obj_dml_data,
49 | RandomForestRegressor(),
50 | RandomForestRegressor())
51 | dml_plr_obj.set_ml_nuisance_params('ml_l', 'd', {'n_estimators': 10});
52 | print(dml_plr_obj.fit().summary)
53 |
54 | Setting treatment-variable-specific or fold-specific hyperparameters:
55 |
56 | * In the multiple-treatment case, the method ``set_ml_nuisance_params(learner, treat_var, params)`` can be used to set
57 | different hyperparameters for different treatment variables.
58 | * The method ``set_ml_nuisance_params(learner, treat_var, params)`` accepts dicts and lists for ``params``.
59 | A dict should be provided if for each fold the same hyperparameters should be used.
60 | Fold-specific parameters are supported. To do so, provide a nested list as ``params``, where the outer list is of
61 | length ``n_rep`` and the inner list of length ``n_folds``.
--------------------------------------------------------------------------------
/.github/workflows/deploy_docu_stable.yml:
--------------------------------------------------------------------------------
1 | # Workflow based on https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml
2 |
3 | name: Deploy Docu (stable)
4 |
5 | on:
6 | workflow_dispatch:
7 |
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-22.04
13 |
14 | steps:
15 | - name: Check out the repo containing the docu source
16 | uses: actions/checkout@v4
17 |
18 | - name: Install graphviz
19 | run: sudo apt-get install graphviz
20 |
21 | - name: Install python
22 | uses: actions/setup-python@v5
23 | with:
24 | python-version: '3.12'
25 | - name: Install dependencies and the python package
26 | run: |
27 | python -m pip install --upgrade pip
28 | pip install -r requirements.txt
29 |
30 | - name: Add R repository
31 | run: |
32 | sudo apt install dirmngr gnupg apt-transport-https ca-certificates software-properties-common
33 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
34 | sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/'
35 | - name: Install R
36 | run: |
37 | sudo apt-get update
38 | sudo apt-get install r-base
39 | sudo apt-get install r-base-dev
40 | sudo apt-get install -y zlib1g-dev libicu-dev pandoc make libcurl4-openssl-dev libssl-dev
41 |
42 | - name: Get user library folder
43 | run: |
44 | mkdir ${GITHUB_WORKSPACE}/tmp_r_libs_user
45 | echo R_LIBS_USER=${GITHUB_WORKSPACE}/tmp_r_libs_user >> $GITHUB_ENV
46 |
47 | - name: Query R version
48 | run: |
49 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
50 | shell: Rscript {0}
51 |
52 | - name: Cache R packages
53 | uses: actions/cache@v4
54 | with:
55 | path: ${{ env.R_LIBS_USER }}
56 | key: doubleml-user-guide-stable-${{ hashFiles('.github/R-version') }}
57 |
58 | - name: Install R kernel for Jupyter and the R package DoubleML (dev)
59 | run: |
60 | install.packages('remotes')
61 | remotes::install_cran('DoubleML', dependencies = TRUE)
62 | install.packages(c('ggplot2', 'IRkernel', 'xgboost', 'hdm', 'reshape2', 'gridExtra', "igraph", "mlr3filters", "mlr3measures", "did"))
63 | IRkernel::installspec()
64 | shell: Rscript {0}
65 |
66 | - name: Build docu with sphinx
67 | run: |
68 | make -C doc html
69 |
70 | - name: Deploy to stable
71 | uses: JamesIves/github-pages-deploy-action@v4
72 | with:
73 | repository-name: DoubleML/doubleml.github.io
74 | branch: main
75 | folder: doc/_build/html
76 | target-folder: stable
77 | git-config-name: DoubleML Deploy Bot
78 | git-config-email: DoubleML@users.noreply.github.com
79 | clean: true
80 | ssh-key: ${{ secrets.DEPLOY_KEY }}
81 |
--------------------------------------------------------------------------------
/.devcontainer/docker_guide.md:
--------------------------------------------------------------------------------
1 | # Build Documentation with Development Container
2 |
3 | This guide shows how to use WSL2 (Windows Subsystem for Linux), Docker Desktop, Visual Studio Code (VS Code), and how to work with Development Containers in VS Code on a Windows machine.
4 |
5 | Requirements:
6 | - [VS Code](https://code.visualstudio.com/)
7 | - [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install)
8 | - [Docker Desktop](https://docs.docker.com/desktop/setup/install/windows-install/)
9 |
10 | ## Step 1: Verify installations & Setup
11 |
12 | You can verify the installations in a terminal:
13 |
14 | ```bash
15 | code --version
16 | wsl --version
17 | docker --version
18 | ```
19 |
20 | ### Configure Docker to Use WSL2
21 |
22 | See [Docker Desktop Documentation](https://docs.docker.com/desktop/features/wsl/#turn-on-docker-desktop-wsl-2).
23 | 1. Open Docker Desktop.
24 | 2. Go to **Settings > General** and make sure **Use the WSL 2 based engine** is checked.
25 | 3. Under **Settings > Resources > WSL Integration**, ensure that your desired Linux distribution(s) are selected for integration with Docker.
26 |
27 | ### Install Extensions
28 |
29 | 1. Open Visual Studio Code.
30 | 2. Press `Ctrl+Shift+X` to open the Extensions view.
31 | 3. Search and install (includes WSL and Dev Containers Extensions):
32 | - [Remote Development Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)
33 |
34 | Helpful VS Code Documentations:
35 | - [Developing in WSL](https://code.visualstudio.com/docs/remote/wsl)
36 | - [Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)
37 |
38 |
39 | ## Step 2: Open the Development Container (Using Pre-built Image)
40 |
41 | For faster setup, we'll use a pre-built Docker image:
42 |
43 | 1. Open the `doubleml-docs` repository in VS Code:
44 |
45 | ```bash
46 | code /path/to/doubleml-docs
47 | ```
48 |
49 | 2. Open the Command Palette (`Ctrl+Shift+P`).
50 | 3. Type `Dev Containers: Reopen in Container`.
51 |
52 | VS Code will pull the `svenklaassen/doubleml-docs:latest` image (if needed) based on `devcontainer.json` and open the project in the container.
53 | This approach is much faster than building the container from scratch. VS Code automatically downloads the image from Docker Hub if it's not already on your system.
54 |
55 |
56 | ## Step 3: Build the documentation
57 |
58 | 1. Open a terminal in VS Code (`Terminal > New Terminal`)
59 |
60 | 2. Build the documentation:
61 |
62 | ```bash
63 | cd doc
64 | make html
65 | ```
66 |
67 | To build without notebook examples:
68 | ```bash
69 | make html NBSPHINX_EXECUTE=never
70 | ```
71 |
72 | 3. View the built documentation by opening the output files:
73 |
74 | ```bash
75 | # On Windows
76 | explorer.exe _build/html
77 |
78 | # On Linux
79 | xdg-open _build/html
80 |
81 | # On macOS
82 | open _build/html
83 | ```
84 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/plm/plr_sensitivity.rst:
--------------------------------------------------------------------------------
1 | In the :ref:`plr-model` the confounding strength ``cf_d`` can be further be simplified to match the explanation of ``cf_y``.
2 | Given the that the Riesz representer takes the following form
3 |
4 | .. math::
5 |
6 | \alpha(W) = \frac{D-\mathbb{E}[D|X]}{\mathbb{E}[(D-\mathbb{E}[D|X]^2)]}
7 |
8 | \tilde{\alpha}(\tilde{W}) = \frac{D-\mathbb{E}[D|X,A]}{\mathbb{E}[(D-\mathbb{E}[D|X,A]^2)]}
9 |
10 | one can show that
11 |
12 | .. math::
13 |
14 | C_D^2 :=\frac{\frac{\mathbb{E}\big[\big(\mathbb{E}[D|X,A] - \mathbb{E}[D|X]\big)^2\big]}{\mathbb{E}\big[\big(D - \mathbb{E}[D|X]\big)^2\big]}}{1-\frac{\mathbb{E}\big[\big(\mathbb{E}[D|X,A] - \mathbb{E}[D|X]\big)^2\big]}{\mathbb{E}\big[\big(D - \mathbb{E}[D|X]\big)^2\big]}}.
15 |
16 | Therefore,
17 |
18 | - ``cf_y``:math:`:=\frac{\mathbb{E}[(\tilde{g}(\tilde{W}) - g(W))^2]}{\mathbb{E}[(Y - g(W))^2]}` measures the proportion of residual variance in the outcome :math:`Y` explained by the latent confounders :math:`A`
19 |
20 | - ``cf_d``:math:`:=\frac{\mathbb{E}\big[\big(\mathbb{E}[D|X,A] - \mathbb{E}[D|X]\big)^2\big]}{\mathbb{E}\big[\big(D - \mathbb{E}[D|X]\big)^2\big]}` measures the proportion of residual variance in the treatment :math:`D` explained by the latent confounders :math:`A`
21 |
22 | .. note::
23 | In the :ref:`plr-model`, both ``cf_y`` and ``cf_d`` can be interpreted as *nonparametric partial* :math:`R^2`
24 |
25 | - ``cf_y`` has the interpretation as the *nonparametric partial* :math:`R^2` *of* :math:`A` *with* :math:`Y` *given* :math:`(D,X)`
26 |
27 | .. math::
28 |
29 | \frac{\textrm{Var}(\mathbb{E}[Y|D,X,A]) - \textrm{Var}(\mathbb{E}[Y|D,X])}{\textrm{Var}(Y)-\textrm{Var}(\mathbb{E}[Y|D,X])}
30 |
31 | - ``cf_d`` has the interpretation as the *nonparametric partial* :math:`R^2` *of* :math:`A` *with* :math:`D` *given* :math:`X`
32 |
33 | .. math::
34 |
35 | \frac{\textrm{Var}(\mathbb{E}[D|X,A]) - \textrm{Var}(\mathbb{E}[D|X])}{\textrm{Var}(D)-\textrm{Var}(\mathbb{E}[D|X])}
36 |
37 | Using the partially linear regression model with ``score='partialling out'`` the ``nuisance_elements`` are implemented in the following form
38 |
39 | .. math::
40 |
41 | \hat{\sigma}^2 &:= \mathbb{E}_n\Big[\big(Y-\hat{l}(X) - \hat{\theta}(D-\hat{m}(X))\big)^2\Big]
42 |
43 | \hat{\nu}^2 &:= \mathbb{E}_n[\hat{\alpha}(W)^2] = \frac{1}{\mathbb{E}_n\big[(D - \hat{m}(X))^2\big]}
44 |
45 | with scores
46 |
47 | .. math::
48 |
49 | \psi_{\sigma^2}(W, \hat{\sigma}^2, g) &:= \big(Y-\hat{l}(X) - \hat{\theta}(D-\hat{m}(X))\big)^2 - \hat{\sigma}^2
50 |
51 | \psi_{\nu^2}(W, \hat{\nu}^2, \alpha) &:= \hat{\nu}^2 - \big(D-\hat{m}(X)\big)^2\big(\hat{\nu}^2)^2.
52 |
53 | If ``score='IV-type'`` the senstivity elements are instead set to
54 |
55 | .. math::
56 |
57 | \hat{\sigma}^2 &:= \mathbb{E}_n\Big[\big(Y - \hat{\theta}D - \hat{g}(X)\big)^2\Big]
58 |
59 | \psi_{\sigma^2}(W, \hat{\sigma}^2, g) &:= \big(Y - \hat{\theta}D - \hat{g}(X)\big)^2 - \hat{\sigma}^2.
--------------------------------------------------------------------------------
/doc/guide/scores/did/did_pa_binary_score.rst:
--------------------------------------------------------------------------------
1 | For the difference-in-differences model implemented in ``DoubleMLDID`` one can choose between
2 | ``score='observational'`` and ``score='experimental'``.
3 |
4 | ``score='observational'`` implements the score function (dropping the unit index :math:`i`):
5 |
6 | .. math::
7 |
8 | \psi(W,\theta, \eta)
9 | :&= -\frac{D}{\mathbb{E}_n[D]}\theta + \left(\frac{D}{\mathbb{E}_n[D]} - \frac{\frac{m(X) (1-D)}{1-m(X)}}{\mathbb{E}_n\left[\frac{m(X) (1-D)}{1-m(X)}\right]}\right) \left(Y_1 - Y_0 - g(0,X)\right)
10 |
11 | &= \psi_a(W; \eta) \theta + \psi_b(W; \eta)
12 |
13 | where the components of the linear score are
14 |
15 | .. math::
16 |
17 | \psi_a(W; \eta) &= - \frac{D}{\mathbb{E}_n[D]},
18 |
19 | \psi_b(W; \eta) &= \left(\frac{D}{\mathbb{E}_n[D]} - \frac{\frac{m(X) (1-D)}{1-m(X)}}{\mathbb{E}_n\left[\frac{m(X) (1-D)}{1-m(X)}\right]}\right) \left(Y_1 - Y_0 - g(0,X)\right)
20 |
21 | and the nuisance elements :math:`\eta=(g, m)` are defined as
22 |
23 | .. math::
24 |
25 | g_{0}(0, X) &= \mathbb{E}[Y_1 - Y_0|D=0, X]
26 |
27 | m_0(X) &= P(D=1|X).
28 |
29 | If ``in_sample_normalization='False'``, the score is set to
30 |
31 | .. math::
32 |
33 | \psi(W,\theta,\eta) &= - \frac{D}{p}\theta + \frac{D - m(X)}{p(1-m(X))}\left(Y_1 - Y_0 -g(0,X)\right)
34 |
35 | &= \psi_a(W; \eta) \theta + \psi_b(W; \eta)
36 |
37 | with :math:`\eta=(g, m, p)`, where :math:`p_0 = \mathbb{E}[D]` is estimated on the cross-fitting folds.
38 | Remark that this will result in the same score, but just uses slightly different normalization.
39 |
40 | ``score='experimental'`` assumes that the treatment probability is independent of the covariates :math:`X` and
41 | implements the score function:
42 |
43 | .. math::
44 |
45 | \psi(W,\theta, \eta)
46 | :=\; &-\theta + \left(\frac{D}{\mathbb{E}_n[D]} - \frac{1-D}{\mathbb{E}_n[1-D]}\right)\left(Y_1 - Y_0 -g(0,X)\right)
47 |
48 | &+ \left(1 - \frac{D}{\mathbb{E}_n[D]}\right) \left(g(1,X) - g(0,X)\right)
49 |
50 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
51 |
52 | where the components of the linear score are
53 |
54 | .. math::
55 |
56 | \psi_a(W; \eta) \;= &- 1,
57 |
58 | \psi_b(W; \eta) \;= &\left(\frac{D}{\mathbb{E}_n[D]} - \frac{1-D}{\mathbb{E}_n[1-D]}\right)\left(Y_1 - Y_0 -g(0,X)\right)
59 |
60 | &+ \left(1 - \frac{D}{\mathbb{E}_n[D]}\right) \left(g(1,X) - g(0,X)\right)
61 |
62 | and the nuisance elements :math:`\eta=(g)` are defined as
63 |
64 | .. math::
65 |
66 | g_{0}(0, X) &= \mathbb{E}[Y_1 - Y_0|D=0, X]
67 |
68 | g_{0}(1, X) &= \mathbb{E}[Y_1 - Y_0|D=1, X]
69 |
70 | Analogously, if ``in_sample_normalization='False'``, the score is set to
71 |
72 | .. math::
73 |
74 | \psi(W,\theta, \eta)
75 | :=\; &-\theta + \frac{D - p}{p(1-p)}\left(Y_1 - Y_0 -g(0,X)\right)
76 |
77 | &+ \left(1 - \frac{D}{p}\right) \left(g(1,X) - g(0,X)\right)
78 |
79 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
80 |
81 | with :math:`\eta=(g, p)`, where :math:`p_0 = \mathbb{E}[D]` is estimated on the cross-fitting folds.
82 | Remark that this will result in the same score, but just uses slightly different normalization.
83 |
--------------------------------------------------------------------------------
/doc/guide/learners/python/tune_hyperparams_old.rst:
--------------------------------------------------------------------------------
1 | Parameter tuning of learners for the nuisance functions of :ref:`DoubleML ` models can be done via
2 | the ``tune()`` method.
3 | To illustrate the parameter tuning, we generate data from a sparse partially linear regression model.
4 |
5 | .. tab-set::
6 |
7 | .. tab-item:: Python
8 | :sync: py
9 |
10 | .. ipython:: python
11 |
12 | import doubleml as dml
13 | import numpy as np
14 |
15 | np.random.seed(3141)
16 | n_obs = 200
17 | n_vars = 200
18 | theta = 3
19 | X = np.random.normal(size=(n_obs, n_vars))
20 | d = np.dot(X[:, :3], np.array([5, 5, 5])) + np.random.standard_normal(size=(n_obs,))
21 | y = theta * d + np.dot(X[:, :3], np.array([5, 5, 5])) + np.random.standard_normal(size=(n_obs,))
22 | dml_data = dml.DoubleMLData.from_arrays(X, y, d)
23 |
24 | The hyperparameter-tuning is performed using either an exhaustive search over specified parameter values
25 | implemented in :class:`sklearn.model_selection.GridSearchCV` or via a randomized search implemented in
26 | :class:`sklearn.model_selection.RandomizedSearchCV`.
27 |
28 | .. tab-set::
29 |
30 | .. tab-item:: Python
31 | :sync: py
32 |
33 | .. ipython:: python
34 | :okwarning:
35 |
36 | import doubleml as dml
37 | from sklearn.linear_model import Lasso
38 |
39 | ml_l = Lasso()
40 | ml_m = Lasso()
41 | dml_plr_obj = dml.DoubleMLPLR(dml_data, ml_l, ml_m)
42 | par_grids = {'ml_l': {'alpha': np.arange(0.05, 1., 0.1)},
43 | 'ml_m': {'alpha': np.arange(0.05, 1., 0.1)}}
44 | dml_plr_obj.tune(par_grids, search_mode='grid_search');
45 | print(dml_plr_obj.params)
46 | print(dml_plr_obj.fit().summary)
47 |
48 | np.random.seed(1234)
49 | par_grids = {'ml_l': {'alpha': np.arange(0.05, 1., 0.01)},
50 | 'ml_m': {'alpha': np.arange(0.05, 1., 0.01)}}
51 | dml_plr_obj.tune(par_grids, search_mode='randomized_search', n_iter_randomized_search=20);
52 | print(dml_plr_obj.params)
53 | print(dml_plr_obj.fit().summary)
54 |
55 | Hyperparameter tuning can also be done with more sophisticated methods, like for example an iterative fitting along
56 | a regularization path implemented in :py:class:`sklearn.linear_model.LassoCV`.
57 | In this case the tuning should be done externally and the parameters can then be set via the
58 | ``set_ml_nuisance_params()`` method.
59 |
60 | .. tab-set::
61 |
62 | .. tab-item:: Python
63 | :sync: py
64 |
65 | .. ipython:: python
66 |
67 | import doubleml as dml
68 | from sklearn.linear_model import LassoCV
69 |
70 | np.random.seed(1234)
71 | ml_l_tune = LassoCV().fit(dml_data.x, dml_data.y)
72 | ml_m_tune = LassoCV().fit(dml_data.x, dml_data.d)
73 |
74 | ml_l = Lasso()
75 | ml_m = Lasso()
76 | dml_plr_obj = dml.DoubleMLPLR(dml_data, ml_l, ml_m)
77 | dml_plr_obj.set_ml_nuisance_params('ml_l', 'd', {'alpha': ml_l_tune.alpha_});
78 | dml_plr_obj.set_ml_nuisance_params('ml_m', 'd', {'alpha': ml_m_tune.alpha_});
79 | print(dml_plr_obj.params)
80 | print(dml_plr_obj.fit().summary)
81 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04
2 |
3 | # Set non-interactive mode to avoid prompts
4 | ENV DEBIAN_FRONTEND=noninteractive
5 |
6 | # Update package list and install dependencies
7 | RUN apt-get update && \
8 | apt-get install -y \
9 | software-properties-common \
10 | graphviz \
11 | wget \
12 | dirmngr \
13 | gnupg \
14 | apt-transport-https \
15 | ca-certificates \
16 | git \
17 | cmake \
18 | locales && \
19 | locale-gen en_US.UTF-8 && \
20 | update-locale LANG=en_US.UTF-8 && \
21 | apt-get clean && \
22 | rm -rf /var/lib/apt/lists/*
23 |
24 | # Set environment variables for locale
25 | ENV LANG=en_US.UTF-8
26 | ENV LANGUAGE=en_US:en
27 | ENV LC_ALL=en_US.UTF-8
28 |
29 | # Install Python 3.12
30 | RUN add-apt-repository ppa:deadsnakes/ppa && \
31 | apt-get update && \
32 | apt-get install -y python3.12 python3.12-venv python3.12-dev python3-pip python3-full && \
33 | update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
34 | update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1 && \
35 | apt-get clean && \
36 | rm -rf /var/lib/apt/lists/*
37 |
38 | # Add R repository and install R
39 | RUN wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc && \
40 | add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu noble-cran40/' && \
41 | apt-get update && \
42 | apt-get install -y r-base r-base-dev zlib1g-dev libicu-dev pandoc make libcurl4-openssl-dev libssl-dev && \
43 | apt-get clean && \
44 | rm -rf /var/lib/apt/lists/*
45 |
46 | # Reuse existing 'ubuntu' user (UID 1000)
47 | ARG USERNAME=ubuntu
48 |
49 | RUN mkdir -p /workspace && \
50 | chown -R $USERNAME:$USERNAME /workspace
51 |
52 | # Create a directory for R user libraries
53 | RUN mkdir -p /usr/local/lib/R/site-library && \
54 | chown -R $USERNAME:$USERNAME /usr/local/lib/R/site-library
55 | ENV R_LIBS_USER=/usr/local/lib/R/site-library
56 |
57 | # Switch to non-root user for remaining operations
58 | USER $USERNAME
59 |
60 | # Install Python packages in the virtual environment
61 | COPY --chown=$USERNAME:$USERNAME requirements.txt /tmp/requirements.txt
62 | RUN python -m venv /home/$USERNAME/.venv && \
63 | /home/$USERNAME/.venv/bin/python -m pip install --upgrade pip && \
64 | /home/$USERNAME/.venv/bin/pip install --no-cache-dir -r /tmp/requirements.txt && \
65 | /home/$USERNAME/.venv/bin/pip install --no-cache-dir "DoubleML[rdd] @ git+https://github.com/DoubleML/doubleml-for-py.git@main"
66 |
67 | # Set the virtual environment as the default Python environment
68 | ENV PATH="/home/$USERNAME/.venv/bin:$PATH"
69 |
70 | # Install R packages and Jupyter kernel
71 | RUN Rscript -e "install.packages('remotes')" && \
72 | Rscript -e "remotes::install_github('DoubleML/doubleml-for-r', dependencies = TRUE)" && \
73 | Rscript -e "install.packages(c('ggplot2', 'IRkernel', 'xgboost', 'hdm', 'reshape2', 'gridExtra', 'igraph', 'mlr3filters', 'mlr3measures', 'did', dependencies=TRUE))" && \
74 | Rscript -e "IRkernel::installspec()"
75 |
76 | # Set the working directory
77 | WORKDIR /workspace
78 |
--------------------------------------------------------------------------------
/.github/workflows/test_build_docu_released.yml:
--------------------------------------------------------------------------------
1 | # Workflow based on https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml
2 |
3 | name: Test Docu Build (with released pkgs)
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | pull_request:
10 | branches:
11 | - main
12 | - dev
13 | workflow_dispatch:
14 | inputs:
15 | nbsphinx-execute:
16 | description: 'Execute notebooks with nbsphinx'
17 | required: false
18 | default: 'auto'
19 | schedule:
20 | - cron: "0 9 * * 1,3,5"
21 |
22 |
23 | jobs:
24 | build:
25 |
26 | runs-on: ubuntu-22.04
27 |
28 | steps:
29 | - name: Check out the repo containing the docu source
30 | uses: actions/checkout@v4
31 |
32 | - name: Install graphviz
33 | run: sudo apt-get install graphviz
34 |
35 | - name: Install python
36 | uses: actions/setup-python@v5
37 | with:
38 | python-version: '3.12'
39 | - name: Install dependencies and the python package
40 | run: |
41 | python -m pip install --upgrade pip
42 | pip install -r requirements.txt
43 |
44 | - name: Add R repository
45 | run: |
46 | sudo apt install dirmngr gnupg apt-transport-https ca-certificates software-properties-common
47 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
48 | sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/'
49 | - name: Install R
50 | run: |
51 | sudo apt-get update
52 | sudo apt-get install r-base
53 | sudo apt-get install r-base-dev
54 | sudo apt-get install -y zlib1g-dev libicu-dev pandoc make libcurl4-openssl-dev libssl-dev
55 |
56 | - name: Get user library folder
57 | run: |
58 | mkdir ${GITHUB_WORKSPACE}/tmp_r_libs_user
59 | echo R_LIBS_USER=${GITHUB_WORKSPACE}/tmp_r_libs_user >> $GITHUB_ENV
60 |
61 | - name: Query R version
62 | run: |
63 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
64 | shell: Rscript {0}
65 |
66 | - name: Cache R packages
67 | uses: actions/cache@v4
68 | with:
69 | path: ${{ env.R_LIBS_USER }}
70 | key: doubleml-test-build-stable-${{ hashFiles('.github/R-version') }}
71 |
72 | - name: Install R kernel for Jupyter and the R package DoubleML (dev)
73 | run: |
74 | install.packages('remotes')
75 | remotes::install_cran('DoubleML', dependencies = TRUE)
76 | install.packages(c('ggplot2', 'IRkernel', 'xgboost', 'hdm', 'reshape2', 'gridExtra', "igraph", "mlr3filters", "mlr3measures", "did"))
77 | IRkernel::installspec()
78 | shell: Rscript {0}
79 |
80 | - name: Build docu with sphinx
81 | run: |
82 | make -C doc html NBSPHINX_EXECUTE=${{ github.event.inputs.nbsphinx-execute || 'auto' }}
83 |
84 | - name: Check for broken links / URLs
85 |
86 | run: |
87 | make -C doc linkcheck
88 |
89 | - name: Upload html artifacts
90 | uses: actions/upload-artifact@v4
91 | with:
92 | name: build_html
93 | path: doc/_build/html/
94 |
--------------------------------------------------------------------------------
/doc/guide/learners/r/tune_and_pipelines.rst:
--------------------------------------------------------------------------------
1 | As an alternative to the previously presented tuning approach, it is possible to base the parameter tuning on a pipeline
2 | as provided by the `mlr3pipelines `_ package. The basic idea of this approach is to
3 | define a learner via a pipeline and then perform the tuning via the ``tune()``. We will shortly repeat the lasso example
4 | from above. In general, the pipeline-based approach can be used to find optimal values not only for the parameters of
5 | one or multiple learners, but also for other parameters, which are, for example, involved in the data preprocessing. We
6 | refer to more details provided in the `Pipelines Chapter in the mlr3book `_.
7 |
8 | .. tab-set::
9 |
10 | .. tab-item:: R
11 | :sync: r
12 |
13 | .. jupyter-execute::
14 |
15 | library(DoubleML)
16 | library(mlr3)
17 | library(mlr3tuning)
18 | library(mlr3pipelines)
19 | lgr::get_logger("mlr3")$set_threshold("warn")
20 | lgr::get_logger("bbotk")$set_threshold("warn")
21 |
22 | set.seed(3141)
23 | n_obs = 200
24 | n_vars = 200
25 | theta = 3
26 | X = matrix(stats::rnorm(n_obs * n_vars), nrow = n_obs, ncol = n_vars)
27 | d = X[, 1:3, drop = FALSE] %*% c(5, 5, 5) + stats::rnorm(n_obs)
28 | y = theta * d + X[, 1:3, drop = FALSE] %*% c(5, 5, 5) + stats::rnorm(n_obs)
29 | dml_data = double_ml_data_from_matrix(X = X, y = y, d = d)
30 |
31 | # Define learner in a pipeline
32 | set.seed(1234)
33 | lasso_pipe = po("learner",
34 | learner = lrn("regr.glmnet"))
35 | ml_g = as_learner(lasso_pipe)
36 | ml_m = as_learner(lasso_pipe)
37 |
38 | # Instantiate a DoubleML object
39 | dml_plr_obj = DoubleMLPLR$new(dml_data, ml_g, ml_m)
40 |
41 | # Parameter grid for lambda
42 | par_grids = ps(regr.glmnet.lambda = p_dbl(lower = 0.05, upper = 0.1))
43 |
44 | tune_settings = list(terminator = trm("evals", n_evals = 100),
45 | algorithm = tnr("grid_search", resolution = 10),
46 | rsmp_tune = rsmp("cv", folds = 5),
47 | measure = list("ml_g" = msr("regr.mse"),
48 | "ml_m" = msr("regr.mse")))
49 | dml_plr_obj$tune(param_set = list("ml_g" = par_grids,
50 | "ml_m" = par_grids),
51 | tune_settings=tune_settings,
52 | tune_on_fold=TRUE)
53 | dml_plr_obj$fit()
54 | dml_plr_obj$summary()
55 |
56 | References
57 | ++++++++++
58 |
59 | * Lang, M., Binder, M., Richter, J., Schratz, P., Pfisterer, F., Coors, S., Au, Q., Casalicchio, G., Kotthoff, L., Bischl, B. (2019), mlr3: A modern object-oriented machine learing framework in R. Journal of Open Source Software, `doi:10.21105/joss.01903 `_.
60 |
61 | * Becker, M., Binder, M., Bischl, B., Lang, M., Pfisterer, F., Reich, N.G., Richter, J., Schratz, P., Sonabend, R. (2020), mlr3 book, available at `https://mlr3book.mlr-org.com `_.
62 |
--------------------------------------------------------------------------------
/.github/workflows/deploy_docu_dev.yml:
--------------------------------------------------------------------------------
1 | # Workflow based on https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml
2 |
3 | name: Deploy Docu (dev)
4 |
5 | on:
6 | workflow_dispatch:
7 |
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-22.04
13 | env:
14 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
15 |
16 | steps:
17 | - name: Check out the repo containing the docu source
18 | uses: actions/checkout@v4
19 |
20 | - name: Check out the repo containing the python pkg DoubleML (dev)
21 | uses: actions/checkout@v4
22 | with:
23 | repository: DoubleML/doubleml-for-py
24 | path: doubleml-for-py
25 |
26 | - name: Install graphviz
27 | run: sudo apt-get install graphviz
28 |
29 | - name: Install python
30 | uses: actions/setup-python@v5
31 | with:
32 | python-version: '3.12'
33 | - name: Install dependencies and the python package
34 | run: |
35 | python -m pip install --upgrade pip
36 | pip install -r requirements.txt
37 | pip uninstall -y DoubleML
38 | cd doubleml-for-py
39 | pip install -e .[rdd]
40 |
41 | - name: Add R repository
42 | run: |
43 | sudo apt install dirmngr gnupg apt-transport-https ca-certificates software-properties-common
44 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
45 | sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/'
46 | - name: Install R
47 | run: |
48 | sudo apt-get update
49 | sudo apt-get install r-base
50 | sudo apt-get install r-base-dev
51 | sudo apt-get install -y zlib1g-dev libicu-dev pandoc make libcurl4-openssl-dev libssl-dev
52 |
53 | - name: Get user library folder
54 | run: |
55 | mkdir ${GITHUB_WORKSPACE}/tmp_r_libs_user
56 | echo R_LIBS_USER=${GITHUB_WORKSPACE}/tmp_r_libs_user >> $GITHUB_ENV
57 |
58 | - name: Query R version
59 | run: |
60 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
61 | shell: Rscript {0}
62 |
63 | - name: Cache R packages
64 | uses: actions/cache@v4
65 | with:
66 | path: ${{ env.R_LIBS_USER }}
67 | key: doubleml-user-guide-dev-${{ hashFiles('.github/R-version') }}
68 |
69 | - name: Install R kernel for Jupyter and the R package DoubleML (dev)
70 | run: |
71 | install.packages('remotes')
72 | remotes::install_github('DoubleML/doubleml-for-r', dependencies = TRUE)
73 | install.packages(c('ggplot2', 'IRkernel', 'xgboost', 'hdm', 'reshape2', 'gridExtra', "igraph", "mlr3filters", "mlr3measures", "did"))
74 | IRkernel::installspec()
75 | shell: Rscript {0}
76 |
77 | - name: Build docu with sphinx
78 | run: |
79 | make -C doc html
80 |
81 | - name: Deploy to dev
82 | uses: JamesIves/github-pages-deploy-action@v4
83 | with:
84 | repository-name: DoubleML/doubleml.github.io
85 | branch: main
86 | folder: doc/_build/html
87 | target-folder: dev
88 | git-config-name: DoubleML Deploy Bot
89 | git-config-email: DoubleML@users.noreply.github.com
90 | clean: true
91 | ssh-key: ${{ secrets.DEPLOY_KEY }}
92 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/irm/irm_sensitivity.rst:
--------------------------------------------------------------------------------
1 | In the :ref:`irm-model` the target parameter can be written as
2 |
3 | .. math::
4 |
5 | \theta_0 = \mathbb{E}[(g_0(1,X) - g_0(0,X))\omega(Y,D,X)]
6 |
7 | where :math:`\omega(Y,D,X)` are weights (e.g. set to :math:`1` for the ATE).
8 | This implies the following representations
9 |
10 | .. math::
11 |
12 | m(W,g) &= \big(g(1,X) - g(0,X)\big)\omega(Y,D,X)
13 |
14 | \alpha(W) &= \bigg(\frac{D}{m(X)} - \frac{1-D}{1-m(X)}\bigg) \mathbb{E}[\omega(Y,D,X)|X].
15 |
16 |
17 | .. note::
18 |
19 | In the :ref:`irm-model` with for the ATE (weights equal to :math:`1`), the form and interpretation of ``cf_y`` is the same as in the :ref:`plr-model`.
20 |
21 | - ``cf_y`` has the interpretation as the *nonparametric partial* :math:`R^2` *of* :math:`A` *with* :math:`Y` *given* :math:`(D,X)`
22 |
23 | .. math::
24 |
25 | \frac{\textrm{Var}(\mathbb{E}[Y|D,X,A]) - \textrm{Var}(\mathbb{E}[Y|D,X])}{\textrm{Var}(Y)-\textrm{Var}(\mathbb{E}[Y|D,X])}
26 |
27 | - ``cf_d`` takes the following form
28 |
29 | .. math::
30 |
31 | \small{\frac{\mathbb{E}\Big[\big(P(D=1|X,A)(1-P(D=1|X,A))\big)^{-1}\Big] - \mathbb{E}\Big[\big(P(D=1|X)(1-P(D=1|X))\big)^{-1}\Big]}{\mathbb{E}\Big[\big(P(D=1|X,A)(1-P(D=1|X,A))\big)^{-1}\Big]}}
32 |
33 | where the numerator measures the *gain in average conditional precision to predict* :math:`D` *by using* :math:`A` *in addition to* :math:`X`.
34 | The denominator is the *average conditional precision to predict* :math:`D` *by using* :math:`A` *and* :math:`X`. Consequently ``cf_d`` measures the *relative gain in average conditional precision*.
35 |
36 | Remark that :math:`P(D=1|X,A)(1-P(D=1|X,A))` denotes the variance of the conditional distribution of :math:`D` given :math:`(X,A)`, such that the inverse measures the precision of
37 | predicting :math:`D` conditional on :math:`(X,A)`.
38 |
39 | Since :math:`C_D^2=\frac{cf_d}{1 - cf_d}`, this corresponds to
40 |
41 | .. math::
42 |
43 | C_D^2= \small{\frac{\mathbb{E}\Big[\big(P(D=1|X,A)(1-P(D=1|X,A))\big)^{-1}\Big] - \mathbb{E}\Big[\big(P(D=1|X)(1-P(D=1|X))\big)^{-1}\Big]}{\mathbb{E}\Big[\big(P(D=1|X)(1-P(D=1|X))\big)^{-1}\Big]}}
44 |
45 | which has the same numerator but is instead relative to the *average conditional precision to predict* :math:`D` *by using only* :math:`X`.
46 |
47 | Including weights changes only the definition of ``cf_d`` to
48 |
49 | .. math::
50 |
51 | \frac{\mathbb{E}\left[\frac{\mathbb{E}[\omega(Y,D,X)|X,A]^2}{P(D=1|X,A)(1-P(D=1|X,A))}\right] - \mathbb{E}\left[\frac{\mathbb{E}[\omega(Y,D,X)|X]^2}{P(D=1|X)(1-P(D=1|X))}\right]}{\mathbb{E}\left[\frac{\mathbb{E}[\omega(Y,D,X)|X,A]^2}{P(D=1|X,A)(1-P(D=1|X,A))}\right]}
52 |
53 | which has a interpretation as the *relative weighted gain in average conditional precision*.
54 |
55 | The ``nuisance_elements`` are then computed with plug-in versions according to the general :ref:`sensitivity_implementation`.
56 | For ``score='ATE'``, the weights are set to one
57 |
58 | .. math::
59 |
60 | \omega(Y,D,X) = 1,
61 |
62 | wheras for ``score='ATTE'``
63 |
64 | .. math::
65 |
66 | \omega(Y,D,X) = \frac{D}{\mathbb{E}[D]},
67 |
68 | such that
69 |
70 | .. math::
71 |
72 | \mathbb{E}[\omega(Y,D,X)|X] = \frac{m(X)}{\mathbb{E}[D]}.
73 |
--------------------------------------------------------------------------------
/doc/guide/models/did/did_aggregation.rst:
--------------------------------------------------------------------------------
1 | The following section considers the aggregation of different :math:`ATT(\mathrm{g},t)` to summary measures based on `Callaway and Sant'Anna (2021) `_.
2 | All implemented aggregation schemes take the form of a weighted average of the :math:`ATT(\mathrm{g},t)` estimates
3 |
4 | .. math::
5 | \theta = \sum_{\mathrm{g}\in \mathcal{G}} \sum_{t=2}^{\mathcal{T}} \omega(\mathrm{g},t) \cdot ATT(\mathrm{g},t)
6 |
7 | where :math:`\omega(\mathrm{g},t)` is a weight function based on the treatment group :math:`\mathrm{g}` and time period :math:`t`.
8 | The aggragation schemes are implmented via the ``aggregate()`` method of the ``DoubleMLDIDMulti`` class.
9 |
10 |
11 | .. tab-set::
12 |
13 | .. tab-item:: Python
14 | :sync: py
15 |
16 | .. ipython:: python
17 | :okwarning:
18 |
19 | import numpy as np
20 | import doubleml as dml
21 | from doubleml.did.datasets import make_did_CS2021
22 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
23 |
24 | np.random.seed(42)
25 | df = make_did_CS2021(n_obs=500)
26 | dml_data = dml.data.DoubleMLPanelData(
27 | df,
28 | y_col="y",
29 | d_cols="d",
30 | id_col="id",
31 | t_col="t",
32 | x_cols=["Z1", "Z2", "Z3", "Z4"],
33 | datetime_unit="M"
34 | )
35 | dml_did_obj = dml.did.DoubleMLDIDMulti(
36 | obj_dml_data=dml_data,
37 | ml_g=RandomForestRegressor(min_samples_split=10),
38 | ml_m=RandomForestClassifier(min_samples_split=10),
39 | gt_combinations="standard",
40 | control_group="never_treated",
41 | )
42 | dml_did_obj.fit()
43 |
44 | agg_did_obj = dml_did_obj.aggregate(aggregation="group")
45 | agg_did_obj.aggregated_frameworks.bootstrap()
46 | print(agg_did_obj)
47 |
48 | The method ``aggregate()`` requires the ``aggregation`` argument to be set to one of the following values:
49 |
50 | * ``'group'``: aggregates :math:`ATT(\mathrm{g},t)` estimates by the treatment group :math:`\mathrm{g}`.
51 | * ``'time'``: aggregates :math:`ATT(\mathrm{g},t)` estimates by the time period :math:`t` (based on group size).
52 | * ``'eventstudy'``: aggregates :math:`ATT(\mathrm{g},t)` estimates based on time difference to first treatment assignment like an event study (based on group size).
53 | * ``dictionary``: a dictionary with values containing the aggregation weights (as ``numpy.ma.MaskedArray``).
54 |
55 | .. warning::
56 | Remark that ``'time'`` and ``'eventstudy'`` aggregation use internal group reweighting according to the total group size (e.g. the group decomposition should be relatively stable over time, as assumed in Assumption 2).
57 | It can be helpful to check the aggregation weights as in the :ref:`example gallery `.
58 |
59 | .. note::
60 | A more detailed example on effect aggregation is available in the :ref:`example gallery `.
61 | For a detailed discussion on different aggregation schemes, we refer to of `Callaway and Sant'Anna (2021) `_.
62 |
--------------------------------------------------------------------------------
/doc/guide/models/did/did_implementation.rst:
--------------------------------------------------------------------------------
1 | To estimate the target parameter :math:`ATT(\mathrm{g},t_\text{eval})`, the implementation (both for panel data or repeated cross sections) is based on the following parameters:
2 |
3 | * :math:`\mathrm{g}` is the first post-treatment period of interest, i.e. the treatment group.
4 | * :math:`t_\text{pre}` is the pre-treatment period, i.e. the time period from which the conditional parallel trends are assumed.
5 | * :math:`t_\text{eval}` is the time period of interest or evaluation period, i.e. the time period where the treatment effect is evaluated.
6 | * :math:`\delta` is number of anticipation periods, i.e. the number of time periods for which units are assumed to anticipate the treatment.
7 |
8 |
9 | Under the assumptions above the target parameter :math:`ATT(\mathrm{g},t_\text{eval})` can be estimated by choosing a suitable combination
10 | of :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval}, \delta)` if :math:`t_\text{eval} - t_\text{pre} \ge 1 + \delta`, i.e. the parallel trends are assumed to hold at least one period more than the anticipation period.
11 |
12 | .. note::
13 | The choice :math:`t_\text{pre}= \min(\mathrm{g},t_\text{eval}) -\delta-1` corresponds to the definition of :math:`ATT_{dr}(\mathrm{g},t_\text{eval};\delta)` from `Callaway and Sant'Anna (2021) `_.
14 |
15 | As an example, if the target parameter is the effect on the group receiving treatment in :math:`2006` but evaluated in :math:`2007` with an anticipation period of :math:`\delta=1`, then the pre-treatment period is :math:`2004`.
16 | The parallel trend assumption is slightly stronger with anticipation as the trends have to parallel for a longer periods, i.e. :math:`ATT_{dr}(2006,2007;1)=ATT(2006,2004;2006)`.
17 |
18 | In the following, we will omit the subscript :math:`\delta` in the notation of the nuisance functions and the control group (implicitly assuming :math:`\delta=0`).
19 |
20 | For a given tuple :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` the target parameter :math:`ATT(\mathrm{g},t)` is estimated by solving the empirical version of the the following linear moment condition:
21 |
22 | .. math::
23 | ATT(\mathrm{g}, t_\text{pre}, t_\text{eval}):= -\frac{\mathbb{E}[\psi_b(W,\eta_0)]}{\mathbb{E}[\psi_a(W,\eta_0)]}
24 |
25 | with nuisance elements :math:`\eta_0` which depend on the parameter combination :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` and score function :math:`\psi(W,\theta, \eta)` (for details, see :ref:`Panel Data Details ` or :ref:`Repeated Cross-Section Details `).
26 | Under the identifying assumptions above
27 |
28 | .. math::
29 | ATT(\mathrm{g}, t_\text{pre}, t_\text{eval}) = ATT(\mathrm{g},t).
30 |
31 | ``DoubleMLDIDMulti`` implements the estimation of :math:`ATT(\mathrm{g}, t_\text{pre}, t_\text{eval})` for multiple time periods and requires :ref:`DoubleMLPanelData ` as input.
32 |
33 | Setting ``gt_combinations='standard'`` will estimate the target parameter for all (possible) combinations of :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` with :math:`\mathrm{g}\in\{2,\dots,\mathcal{T}\}` and :math:`(t_\text{pre}, t_\text{eval})` with :math:`t_\text{eval}\in\{2,\dots,\mathcal{T}\}` and
34 | :math:`t_\text{pre}= \min(\mathrm{g},t_\text{eval}) -\delta-1`.
35 | This corresponds to the setting where all trends are set as short as possible, but still respecting the anticipation period.
36 |
--------------------------------------------------------------------------------
/doc/guide/learners/python/external_preds.rst:
--------------------------------------------------------------------------------
1 | Since there might be cases where the user wants to use a learner that is not supported by :ref:`DoubleML `
2 | or do some extensive hyperparameter tuning, it is possible to use external predictions for the nuisance functions.
3 | Remark that this requires the user to take care of the cross-fitting procedure and learner evaluation.
4 |
5 | To illustrate the use of external predictions, we work with the following example.
6 |
7 | .. tab-set::
8 |
9 | .. tab-item:: Python
10 | :sync: py
11 |
12 | .. ipython:: python
13 |
14 | import numpy as np
15 | import doubleml as dml
16 | from doubleml.irm.datasets import make_irm_data
17 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
18 |
19 | np.random.seed(3333)
20 | data = make_irm_data(theta=0.5, n_obs=500, dim_x=10, return_type='DataFrame')
21 | obj_dml_data = dml.DoubleMLData(data, 'y', 'd')
22 |
23 | # DoubleML with interal predictions
24 | ml_g = RandomForestRegressor(n_estimators=100, max_features=10, max_depth=5, min_samples_leaf=2)
25 | ml_m = RandomForestClassifier(n_estimators=100, max_features=10, max_depth=5, min_samples_leaf=2)
26 | dml_irm_obj = dml.DoubleMLIRM(obj_dml_data, ml_g, ml_m)
27 | dml_irm_obj.fit()
28 | print(dml_irm_obj.summary)
29 |
30 | The :py:class:`doubleml.DoubleMLIRM` model class saves nuisance predictions in the ``predictions`` attribute as a nested dictionary.
31 | To rely on external predictions, the user has to provide a nested dictionary, where the outer level keys correspond to the treatment
32 | variable names and the inner level keys correspond to the nuisance learner names. Further the values have to be ``numpy`` arrays of shape
33 | ``(n_obs, n_rep)``. Here we generate an external predictions dictionary from the internal ``predictions`` attribute.
34 |
35 | .. tab-set::
36 |
37 | .. tab-item:: Python
38 | :sync: py
39 |
40 | .. ipython:: python
41 |
42 | pred_dict = {"d": {
43 | "ml_g0": dml_irm_obj.predictions["ml_g0"][:, :, 0],
44 | "ml_g1": dml_irm_obj.predictions["ml_g1"][:, :, 0],
45 | "ml_m": dml_irm_obj.predictions["ml_m"][:, :, 0]
46 | }
47 | }
48 |
49 | The external predictions can be passed to the ``fit()`` method of the :py:class:`doubleml.DoubleML` class via the ``external_predictions`` argument.
50 |
51 | .. tab-set::
52 |
53 | .. tab-item:: Python
54 | :sync: py
55 |
56 | .. ipython:: python
57 |
58 | ml_g = dml.utils.DMLDummyRegressor()
59 | ml_m = dml.utils.DMLDummyClassifier()
60 | dml_irm_obj_ext = dml.DoubleMLIRM(obj_dml_data, ml_g, ml_m)
61 | dml_irm_obj_ext.fit(external_predictions=pred_dict)
62 | print(dml_irm_obj_ext.summary)
63 |
64 | Both model have identical estimates. Remark that :py:class:`doubleml.DoubleML` class usually require learners for initialization.
65 | With external predictions these learners are not used. The ``DMLDummyRegressor`` and ``DMLDummyClassifier`` are dummy learners which
66 | are used to initialize the :py:class:`doubleml.DoubleML` class. Both dummy learners raise errors if specific methods are called to safeguard against
67 | undesired behavior. Further, the :py:class:`doubleml.DoubleMLData` class requires features (e.g. via the ``x_cols`` argument) which are not used.
68 | This can be handled by adding a dummy column to the data.
--------------------------------------------------------------------------------
/doc/guide/models/did/did_pa.rst:
--------------------------------------------------------------------------------
1 | For the estimation of the target parameters :math:`ATT(\mathrm{g},t)` the following nuisance functions are required:
2 |
3 | .. math::
4 | \begin{align}
5 | g_{0, \mathrm{g}, t_\text{pre}, t_\text{eval}, \delta}(X_i) &:= \mathbb{E}[Y_{i,t_\text{eval}} - Y_{i,t_\text{pre}}|X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)} = 1], \\
6 | m_{0, \mathrm{g}, t_\text{eval} + \delta}(X_i) &:= P(G_i^{\mathrm{g}}=1|X_i, G_i^{\mathrm{g}} + C_{i,t_\text{eval} + \delta}^{(\cdot)}=1).
7 | \end{align}
8 |
9 | where :math:`g_{0, \mathrm{g}, t_\text{pre}, t_\text{eval},\delta}(\cdot)` denotes the population outcome change regression function and :math:`m_{0, \mathrm{g}, t_\text{eval} + \delta}(\cdot)` the generalized propensity score.
10 |
11 | .. note::
12 | Remark that the nuisance functions depend on the control group used for the estimation of the target parameter.
13 | By slight abuse of notation we use the same notation for both control groups :math:`C_{i,t}^{(\text{nev})}` and :math:`C_{i,t}^{(\text{nyt})}`. More specifically, the
14 | control group only depends on :math:`\delta` for *not yet treated* units.
15 |
16 | For a given tuple :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` the target parameter :math:`ATT(\mathrm{g},t)` is estimated by solving the empirical version of the the following linear moment condition:
17 |
18 | .. math::
19 | ATT(\mathrm{g}, t_\text{pre}, t_\text{eval}):= -\frac{\mathbb{E}[\psi_b(W,\eta_0)]}{\mathbb{E}[\psi_a(W,\eta_0)]}
20 |
21 | with nuisance elements :math:`\eta_0=(g_{0, \mathrm{g}, t_\text{pre}, t_\text{eval}}, m_{0, \mathrm{g}, t_\text{eval}})` and score function :math:`\psi(W,\theta, \eta)` being defined in the :ref:`DiD Score Section`.
22 |
23 | Estimation is conducted via its ``fit()`` method:
24 |
25 | .. tab-set::
26 |
27 | .. tab-item:: Python
28 | :sync: py
29 |
30 | .. ipython:: python
31 | :okwarning:
32 |
33 | import numpy as np
34 | import doubleml as dml
35 | from doubleml.did.datasets import make_did_CS2021
36 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
37 |
38 | np.random.seed(42)
39 | df = make_did_CS2021(n_obs=500)
40 | dml_data = dml.data.DoubleMLPanelData(
41 | df,
42 | y_col="y",
43 | d_cols="d",
44 | id_col="id",
45 | t_col="t",
46 | x_cols=["Z1", "Z2", "Z3", "Z4"],
47 | datetime_unit="M"
48 | )
49 | dml_did_obj = dml.did.DoubleMLDIDMulti(
50 | obj_dml_data=dml_data,
51 | ml_g=RandomForestRegressor(min_samples_split=10),
52 | ml_m=RandomForestClassifier(min_samples_split=10),
53 | gt_combinations="standard",
54 | control_group="never_treated",
55 | )
56 | print(dml_did_obj.fit())
57 |
58 | .. note::
59 | Remark that the output contains two different outcome regressions :math:`g(0,X)` and :math:`g(1,X)`. As in the :ref:`IRM model `
60 | the outcome regression :math:`g(0,X)` refers to the control group, whereas :math:`g(1,X)` refers to the outcome change regression for the treatment group, i.e.
61 |
62 | .. math::
63 | \begin{align}
64 | g(0,X) &\approx g_{0, \mathrm{g}, t_\text{pre}, t_\text{eval}, \delta}(X_i) = \mathbb{E}[Y_{i,t_\text{eval}} - Y_{i,t_\text{pre}}|X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)} = 1],\\
65 | g(1,X) &\approx \mathbb{E}[Y_{i,t_\text{eval}} - Y_{i,t_\text{pre}}|X_i, G_i^{\mathrm{g}} = 1].
66 | \end{align}
67 |
68 | Further, :math:`g(1,X)` is only required for :ref:`Sensitivity Analysis ` and is not used for the estimation of the target parameter.
69 |
70 | .. note::
71 | A more detailed example is available in the :ref:`Example Gallery `.
72 |
--------------------------------------------------------------------------------
/.devcontainer/build_image_guide.md:
--------------------------------------------------------------------------------
1 | # Building and Publishing the Docker Image
2 |
3 | This guide shows how to build the DoubleML documentation development container locally and publish it to Docker Hub.
4 |
5 | ## Prerequisites
6 |
7 | - [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed and running
8 | - Access to the `svenklaassen` [Docker Hub](https://www.docker.com/products/docker-hub/) account
9 | - [doubleml-docs](https://github.com/DoubleML/doubleml-docs) repository cloned to your local machine
10 |
11 | ## Step 1: Login to Docker Hub
12 |
13 | Open a terminal and login to Docker Hub:
14 |
15 | ```bash
16 | docker login
17 | ```
18 |
19 | Enter the Docker Hub username (`svenklaassen`) and password (or token) when prompted.
20 |
21 | ## Step 2: Build the Docker Image
22 |
23 | Navigate to your project root directory and build the image (using the `latest`-tag):
24 |
25 | ```bash
26 | docker build -t svenklaassen/doubleml-docs:latest -f .devcontainer/Dockerfile.dev .
27 | ```
28 |
29 | To force a complete rebuild without using cache:
30 |
31 | ```bash
32 | docker build --no-cache -t svenklaassen/doubleml-docs:latest -f .devcontainer/Dockerfile.dev .
33 | ```
34 |
35 | ## Step 3 (Optional): Verify the image
36 |
37 | ### Open the repository in VS Code
38 |
39 | 1. Ensure your `.devcontainer/devcontainer.json` is configured to use your local image:
40 |
41 | ```json
42 | "image": "svenklaassen/doubleml-docs:latest"
43 | ```
44 | Note: The `.devcontainer/devcontainer.json` file is configured to use the pre-built image. If you want to build the container from scratch, uncomment the `dockerFile` and `context` lines and comment out the `image` line.
45 |
46 | 2. Open the `doubleml-docs` repository in VS Code:
47 |
48 | ```bash
49 | code /path/to/doubleml-docs
50 | ```
51 |
52 | 3. Open the Command Palette (`Ctrl+Shift+P`) and select `Dev Containers: Reopen in Container`.
53 | VS Code will use your locally built image.
54 |
55 | ### Build the documentation
56 |
57 | Once inside the container, verify that you can successfully build the documentation:
58 |
59 | 1. Open a terminal in VS Code (`Terminal > New Terminal`)
60 |
61 | 2. Build the documentation:
62 |
63 | ```bash
64 | cd doc
65 | make html
66 | ```
67 |
68 | 3. Check the output for any errors or warnings
69 |
70 | 4. View the built documentation by opening the output files:
71 |
72 | ```bash
73 | # On Windows
74 | explorer.exe _build/html
75 |
76 | # On Linux
77 | xdg-open _build/html
78 |
79 | # On macOS
80 | open _build/html
81 | ```
82 |
83 | If the documentation builds successfully and looks correct, your Docker image is working properly and ready to be pushed to Docker Hub.
84 |
85 | ## Step 4: Push to Docker Hub
86 |
87 | Push your built image to Docker Hub:
88 |
89 | ```bash
90 | docker push svenklaassen/doubleml-docs:latest
91 | ```
92 |
93 | ## Step 5: Using the Published Image
94 |
95 | After publishing, there are two ways to use the image:
96 |
97 | ### Option 1: Manual Container Management
98 | Pull and run the container manually:
99 |
100 | ```bash
101 | docker pull svenklaassen/doubleml-docs:latest
102 | # Then run commands to create a container from this image
103 | ```
104 |
105 | ### Option 2: VS Code Integration (Recommended)
106 | Simply reference the image in your `devcontainer.json` file:
107 |
108 | ```json
109 | "image": "svenklaassen/doubleml-docs:latest"
110 | ```
111 |
112 | VS Code will automatically pull the image when opening the project in a container - no separate `docker pull` command needed.
113 |
114 | ## Troubleshooting
115 |
116 | ### Clear Docker Cache
117 |
118 | If you're experiencing issues with cached layers:
119 |
120 | ```bash
121 | # Remove build cache
122 | docker builder prune
123 |
124 | # For a more thorough cleanup
125 | docker system prune -a
126 | ```
127 |
128 | ### Check Image Size
129 |
130 | To verify the image size before pushing:
131 |
132 | ```bash
133 | docker images svenklaassen/doubleml-docs
134 | ```
--------------------------------------------------------------------------------
/doc/guide/scores/did/did_cs_binary_score.rst:
--------------------------------------------------------------------------------
1 | For the difference-in-differences model implemented in ``DoubleMLDIDCS`` one can choose between
2 | ``score='observational'`` and ``score='experimental'``.
3 |
4 | ``score='observational'`` implements the score function (dropping the unit index :math:`i`):
5 |
6 | .. math::
7 |
8 | \psi(W,\theta,\eta) :=\; & - \frac{D}{\mathbb{E}_n[D]}\theta + \frac{D}{\mathbb{E}_n[D]}\Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
9 |
10 | & + \frac{DT}{\mathbb{E}_n[DT]} (Y - g(1,1,X))
11 |
12 | & - \frac{D(1-T)}{\mathbb{E}_n[D(1-T)]}(Y - g(1,0,X))
13 |
14 | & - \frac{m(X) (1-D)T}{1-m(X)} \mathbb{E}_n\left[\frac{m(X) (1-D)T}{1-m(X)}\right]^{-1} (Y-g(0,1,X))
15 |
16 | & + \frac{m(X) (1-D)(1-T)}{1-m(X)} \mathbb{E}_n\left[\frac{m(X) (1-D)(1-T)}{1-m(X)}\right]^{-1} (Y-g(0,0,X))
17 |
18 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
19 |
20 | where the components of the linear score are
21 |
22 | .. math::
23 |
24 | \psi_a(W; \eta) =\; &- \frac{D}{\mathbb{E}_n[D]},
25 |
26 | \psi_b(W; \eta) =\; &\frac{D}{\mathbb{E}_n[D]}\Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
27 |
28 | & + \frac{DT}{\mathbb{E}_n[DT]} (Y - g(1,1,X))
29 |
30 | & - \frac{D(1-T)}{\mathbb{E}_n[D(1-T)]}(Y - g(1,0,X))
31 |
32 | & - \frac{m(X) (1-D)T}{1-m(X)} \mathbb{E}_n\left[\frac{m(X) (1-D)T}{1-m(X)}\right]^{-1} (Y-g(0,1,X))
33 |
34 | & + \frac{m(X) (1-D)(1-T)}{1-m(X)} \mathbb{E}_n\left[\frac{m(X) (1-D)(1-T)}{1-m(X)}\right]^{-1} (Y-g(0,0,X))
35 |
36 | and the nuisance elements :math:`\eta=(g)` are defined as
37 |
38 | .. math::
39 |
40 | g_{0}(d, t, X) = \mathbb{E}[Y|D=d, T=t, X].
41 |
42 | If ``in_sample_normalization='False'``, the score is set to
43 |
44 | .. math::
45 |
46 | \psi(W,\theta,\eta) :=\; & - \frac{D}{p}\theta + \frac{D}{p}\Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
47 |
48 | & + \frac{DT}{p\lambda} (Y - g(1,1,X))
49 |
50 | & - \frac{D(1-T)}{p(1-\lambda)}(Y - g(1,0,X))
51 |
52 | & - \frac{m(X) (1-D)T}{p(1-m(X))\lambda} (Y-g(0,1,X))
53 |
54 | & + \frac{m(X) (1-D)(1-T)}{p(1-m(X))(1-\lambda)} (Y-g(0,0,X))
55 |
56 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
57 |
58 | with :math:`\eta=(g, p, \lambda)`, where :math:`p_0 = \mathbb{E}[D]` and :math:`\lambda_0 = \mathbb{E}[T]` are estimated on the whole sample.
59 | Remark that this will result in a similar score, but just uses slightly different normalization.
60 |
61 | ``score='experimental'`` assumes that the treatment probability is independent of the covariates :math:`X` and
62 | implements the score function:
63 |
64 | .. math::
65 |
66 | \psi(W,\theta,\eta) :=\; & - \theta + \Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
67 |
68 | & + \frac{DT}{\mathbb{E}_n[DT]} (Y - g(1,1,X))
69 |
70 | & - \frac{D(1-T)}{\mathbb{E}_n[D(1-T)]}(Y - g(1,0,X))
71 |
72 | & - \frac{(1-D)T}{\mathbb{E}_n[(1-D)T]} (Y-g(0,1,X))
73 |
74 | & + \frac{(1-D)(1-T)}{\mathbb{E}_n[(1-D)(1-T)]} (Y-g(0,0,X))
75 |
76 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
77 |
78 | where the components of the linear score are
79 |
80 | .. math::
81 |
82 | \psi_a(W; \eta) \;= &- 1,
83 |
84 | \psi_b(W; \eta) \;= &\Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
85 |
86 | & + \frac{DT}{\mathbb{E}_n[DT]} (Y - g(1,1,X))
87 |
88 | & - \frac{D(1-T)}{\mathbb{E}_n[D(1-T)]}(Y - g(1,0,X))
89 |
90 | & - \frac{(1-D)T}{\mathbb{E}_n[(1-D)T]} (Y-g(0,1,X))
91 |
92 | & + \frac{(1-D)(1-T)}{\mathbb{E}_n[(1-D)(1-T)]} (Y-g(0,0,X))
93 |
94 | and the nuisance elements :math:`\eta=(g, m)` are defined as
95 |
96 | .. math::
97 |
98 | g_{0}(d, t, X) &= \mathbb{E}[Y|D=d, T=t, X]
99 |
100 | m_0(X) &= P(D=1|X).
101 |
102 | Analogously, if ``in_sample_normalization='False'``, the score is set to
103 |
104 | .. math::
105 |
106 | \psi(W,\theta,\eta) :=\; & - \theta + \Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
107 |
108 | & + \frac{DT}{p\lambda} (Y - g(1,1,X))
109 |
110 | & - \frac{D(1-T)}{p(1-\lambda)}(Y - g(1,0,X))
111 |
112 | & - \frac{(1-D)T}{(1-p)\lambda} (Y-g(0,1,X))
113 |
114 | & + \frac{(1-D)(1-T)}{(1-p)(1-\lambda)} (Y-g(0,0,X))
115 |
116 | =\; &\psi_a(W; \eta) \theta + \psi_b(W; \eta)
117 |
118 | with :math:`\eta=(g, m, p, \lambda)`, where :math:`p_0 = \mathbb{E}[D]` and :math:`\lambda_0 = \mathbb{E}[T]` are estimated on the whole sample.
119 | Remark that this will result in a similar score, but just uses slightly different normalization.
120 |
--------------------------------------------------------------------------------
/doc/guide/models/did/did_binary.rst:
--------------------------------------------------------------------------------
1 | **Difference-in-Differences Models (DID)** implemented in the package focus on the the binary treatment case with
2 | with two treatment periods.
3 |
4 | Adopting the notation from `Sant'Anna and Zhao (2020) `_,
5 | let :math:`Y_{it}` be the outcome of interest for unit :math:`i` at time :math:`t`. Further, let :math:`D_{it}=1` indicate
6 | if unit :math:`i` is treated before time :math:`t` (otherwise :math:`D_{it}=0`). Since all units start as untreated (:math:`D_{i0}=0`), define
7 | :math:`D_{i}=D_{i1}.` Relying on the potential outcome notation, denote :math:`Y_{it}(0)` as the outcome of unit :math:`i` at time :math:`t` if the unit did not receive
8 | treatment up until time :math:`t` and analogously for :math:`Y_{it}(1)` with treatment. Consequently, the observed outcome
9 | for unit is :math:`i` at time :math:`t` is :math:`Y_{it}=D_{it} Y_{it}(1) + (1-D_{it}) Y_{it}(0)`. Further, let
10 | :math:`X_i` be a vector of pre-treatment covariates.
11 |
12 | Target parameter of interest is the average treatment effect on the treated (ATTE)
13 |
14 | .. math::
15 |
16 | \theta_0 = \mathbb{E}[Y_{i1}(1)- Y_{i1}(0)|D_i=1].
17 |
18 | The corresponding identifying assumptions are
19 |
20 | - **(Cond.) Parallel Trends:** :math:`\mathbb{E}[Y_{i1}(0) - Y_{i0}(0)|X_i, D_i=1] = \mathbb{E}[Y_{i1}(0) - Y_{i0}(0)|X_i, D_i=0]\quad a.s.`
21 | - **Overlap:** :math:`\exists\epsilon > 0`: :math:`P(D_i=1) > \epsilon` and :math:`P(D_i=1|X_i) \le 1-\epsilon\quad a.s.`
22 |
23 | .. note::
24 | For a more detailed introduction and recent developments of the difference-in-differences literature see e.g. `Roth et al. (2022) `_.
25 |
26 |
27 | Panel Data
28 | ~~~~~~~~~~~
29 |
30 | If panel data are available, the observations are assumed to be iid. of form :math:`(Y_{i0}, Y_{i1}, D_i, X_i)`.
31 | Remark that the difference :math:`\Delta Y_i= Y_{i1}-Y_{i0}` has to be defined as the outcome ``y`` in the ``DoubleMLData`` object.
32 |
33 | ``DoubleMLIDID`` implements difference-in-differences models for panel data.
34 | Estimation is conducted via its ``fit()`` method:
35 |
36 | .. tab-set::
37 |
38 | .. tab-item:: Python
39 | :sync: py
40 |
41 | .. ipython:: python
42 | :okwarning:
43 |
44 | import numpy as np
45 | import doubleml as dml
46 | from doubleml.did.datasets import make_did_SZ2020
47 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
48 |
49 | ml_g = RandomForestRegressor(n_estimators=100, max_depth=5, min_samples_leaf=5)
50 | ml_m = RandomForestClassifier(n_estimators=100, max_depth=5, min_samples_leaf=5)
51 | np.random.seed(42)
52 | data = make_did_SZ2020(n_obs=500, return_type='DataFrame')
53 | # y is already defined as the difference of observed outcomes
54 | obj_dml_data = dml.DoubleMLDIDData(data, 'y', 'd')
55 | dml_did_obj = dml.DoubleMLDID(obj_dml_data, ml_g, ml_m)
56 | print(dml_did_obj.fit())
57 |
58 |
59 | Repeated cross-sections
60 | ~~~~~~~~~~~~~~~~~~~~~~~~~
61 |
62 | For repeated cross-sections, the observations are assumed to be iid. of form :math:`(Y_{i}, D_i, X_i, T_i)`,
63 | where :math:`T_i` is a dummy variable if unit :math:`i` is observed pre- or post-treatment period, such
64 | that the observed outcome can be defined as
65 |
66 | .. math::
67 |
68 | Y_i = T_i Y_{i1} + (1-T_i) Y_{i0}.
69 |
70 | Further, treatment and covariates are assumed to be stationary, such that the joint distribution of :math:`(D,X)` is invariant to :math:`T`.
71 |
72 | ``DoubleMLIDIDCS`` implements difference-in-differences models for repeated cross-sections.
73 | Estimation is conducted via its ``fit()`` method:
74 |
75 | .. tab-set::
76 |
77 | .. tab-item:: Python
78 | :sync: py
79 |
80 | .. ipython:: python
81 | :okwarning:
82 |
83 | import numpy as np
84 | import doubleml as dml
85 | from doubleml.did.datasets import make_did_SZ2020
86 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
87 |
88 | ml_g = RandomForestRegressor(n_estimators=100, max_depth=5, min_samples_leaf=5)
89 | ml_m = RandomForestClassifier(n_estimators=100, max_depth=5, min_samples_leaf=5)
90 | np.random.seed(42)
91 | data = make_did_SZ2020(n_obs=500, cross_sectional_data=True, return_type='DataFrame')
92 | obj_dml_data = dml.DoubleMLDIDData(data, 'y', 'd', t_col='t')
93 | dml_did_obj = dml.DoubleMLDIDCS(obj_dml_data, ml_g, ml_m)
94 | print(dml_did_obj.fit())
95 |
--------------------------------------------------------------------------------
/doc/guide/models/did/did_cs.rst:
--------------------------------------------------------------------------------
1 | For the estimation of the target parameters :math:`ATT(\mathrm{g},t)` the following nuisance functions are required:
2 |
3 | .. math::
4 | \begin{align}
5 | g^{\text{treat}}_{0,\mathrm{g}, t, \text{eval} + \delta}(X_i) &:= \mathbb{E}[Y_{i,t} |X_i, G_i^{\mathrm{g}}=1, T_i=t], \\
6 | g^{\text{control}}_{0,\mathrm{g}, t, \text{eval} + \delta}(X_i) &:= \mathbb{E}[Y_{i,t} |X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)}=1, T_i=t], \\
7 | m_{0, \mathrm{g}, t_\text{eval} + \delta}(X_i) &:= P(G_i^{\mathrm{g}}=1|X_i, G_i^{\mathrm{g}} + C_{i,t_\text{eval} + \delta}^{(\cdot)}=1).
8 | \end{align}
9 |
10 | for :math:`t\in\{t_\text{pre}, t_\text{eval}\}`.
11 | Here, :math:`g^{(\cdot)}_{\mathrm{g}, t, \text{eval} + \delta}(\cdot)` denotes the population outcome regression function (for either treatment or control group at time period :math:`t`) and :math:`m_{0, \mathrm{g}, t_\text{eval} + \delta}(\cdot)` the generalized propensity score.
12 |
13 | .. note::
14 | Remark that the nuisance functions depend on the control group used for the estimation of the target parameter.
15 | By slight abuse of notation we use the same notation for both control groups :math:`C_{i,t}^{(\text{nev})}` and :math:`C_{i,t}^{(\text{nyt})}`. More specifically, the
16 | control group only depends on :math:`\delta` for *not yet treated* units.
17 |
18 | For a given tuple :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` the target parameter :math:`ATT(\mathrm{g},t)` is estimated by solving the empirical version of the the following linear moment condition:
19 |
20 | .. math::
21 | ATT(\mathrm{g}, t_\text{pre}, t_\text{eval}):= -\frac{\mathbb{E}[\psi_b(W,\eta_0)]}{\mathbb{E}[\psi_a(W,\eta_0)]}
22 |
23 | with nuisance elements :math:`\eta_0=(g^{0,\text{treat}}_{\mathrm{g}, t_\text{pre}, t_\text{eval} + \delta}, g^{0,\text{control}}_{\mathrm{g}, t_\text{pre}, t_\text{eval} + \delta}, g^{0,\text{treat}}_{\mathrm{g}, t_\text{eval}, t_\text{eval} + \delta}, g^{0,\text{control}}_{\mathrm{g}, t_\text{eval}, t_\text{eval} + \delta}, m_{0, \mathrm{g}, t_\text{eval}})` and score function :math:`\psi(W,\theta, \eta)` defined in the :ref:`DiD Score Section`.
24 |
25 | Setting ``panel=False`` will estimate the target parameter for repeated cross sections. Estimation is conducted via its ``fit()`` method:
26 |
27 | .. tab-set::
28 |
29 | .. tab-item:: Python
30 | :sync: py
31 |
32 | .. ipython:: python
33 | :okwarning:
34 |
35 | import numpy as np
36 | import doubleml as dml
37 | from doubleml.did.datasets import make_did_cs_CS2021
38 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
39 |
40 | np.random.seed(42)
41 | df = make_did_cs_CS2021(n_obs=500)
42 | dml_data = dml.data.DoubleMLPanelData(
43 | df,
44 | y_col="y",
45 | d_cols="d",
46 | id_col="id",
47 | t_col="t",
48 | x_cols=["Z1", "Z2", "Z3", "Z4"],
49 | datetime_unit="M"
50 | )
51 | dml_did_obj = dml.did.DoubleMLDIDMulti(
52 | obj_dml_data=dml_data,
53 | ml_g=RandomForestRegressor(min_samples_split=10),
54 | ml_m=RandomForestClassifier(min_samples_split=10),
55 | gt_combinations="standard",
56 | control_group="never_treated",
57 | panel=False,
58 | )
59 | print(dml_did_obj.fit())
60 |
61 | .. note::
62 | Remark that the output contains four different outcome regressions :math:`g(d,t, X)` for :math:`d,t\in\{0,1\}` . As in the :ref:`IRM model `
63 | the outcome regression with :math:`d=0` refers to the control group, whereas :math:`t=0` refers to the pre-treatment period, i.e.
64 |
65 | .. math::
66 | \begin{align}
67 | g(0,0,X) &\approx g^{\text{control}}_{0,\mathrm{g}, t_\text{pre}, \text{eval} + \delta}(X_i) = \mathbb{E}[Y_{i,t_\text{pre}} |X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)}=1, T_i=t_\text{pre}],\\
68 | g(0,1,X) &\approx g^{\text{control}}_{0,\mathrm{g}, t_\text{eval}, \text{eval} + \delta}(X_i) = \mathbb{E}[Y_{i,t_\text{eval}} |X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)}=1, T_i=t_\text{eval}],\\
69 | g(1,0,X) &\approx g^{\text{treat}}_{0,\mathrm{g}, t_\text{pre}, \text{eval} + \delta}(X_i) = \mathbb{E}[Y_{i,t_\text{pre}} |X_i, G_i^{\mathrm{g}}=1,, T_i=t_\text{pre}],\\
70 | g(1,1,X) &\approx g^{\text{treat}}_{0,\mathrm{g}, t_\text{eval}, \text{eval} + \delta}(X_i) = \mathbb{E}[Y_{i,t_\text{eval}} |X_i, G_i^{\mathrm{g}}=1,, T_i=t_\text{eval}].
71 | \end{align}
72 |
73 | .. note::
74 | A more detailed example is available in the :ref:`Example Gallery `.
--------------------------------------------------------------------------------
/doc/guide/sensitivity/benchmarking.rst:
--------------------------------------------------------------------------------
1 | The input parameters for the sensitivity analysis are quite hard to interpret (depending on the model). Consequently it is challenging to come up with reasonable bounds
2 | for the confounding strength ``cf_y`` and ``cf_d`` (and ``rho``). To get a grasp on the magnitude of the bounds a popular approach is to rely on observed confounders
3 | to obtain an informed guess on the strength of possibly unobserved confounders.
4 |
5 | The underlying principle is relatively simple. If we have an observed confounder :math:`X_1`, we are able to emulate omitted confounding by purposely omitting
6 | :math:`X_1` and refitting the whole model. This enables us to compare the "long" and "short" form with and without omitted confounding.
7 | Considering the ``sensitivity_params`` of both models one can estimate the corresponding strength of confounding ``cf_y`` and ``cf_d`` (and ``rho``).
8 |
9 | .. note::
10 |
11 | - The benchmarking can also be done with a set of benchmarking variables (e.g. :math:`X_1, X_2, X_3`), which tries to emulate the effect of multiple unobserved confounders.
12 | - The approach is quite computationally demanding, as the short model that omits the benchmark variables has to be fitted.
13 |
14 | The ``sensitivity_benchmark()`` method implements this approach.
15 | The method just requires a set of valid covariates, the ``benchmarking_set``, to compute the benchmark. The benchmark variables have to be a subset of the covariates used in the main analysis.
16 |
17 | .. tab-set::
18 |
19 | .. tab-item:: Python
20 | :sync: py
21 |
22 | .. ipython:: python
23 |
24 | dml_plr_obj.sensitivity_benchmark(benchmarking_set=["X1"])
25 |
26 | The method returns a :py:class:`pandas.DataFrame`, containing the benchmarked values for ``cf_y``, ``cf_d``, ``rho`` and the change in the estimates
27 | ``delta_theta``.
28 |
29 | .. note::
30 |
31 | - The benchmarking results should be used to get an idea of the magnitude/validity of proposed confounding strength of the omitted confounders. Whether these values are close to the real confounding, depends entirely on the
32 | setting and choice of the benchmarking variables. A good benchmarking set has a strong justification which refers to the omitted confounders.
33 | - If the benchmarking variables are only weak confounders, the estimates of ``rho`` can be slightly unstable (due to small denominators).
34 |
35 | The implementation is based on `Chernozhukov et al. (2022) `_ Appendix D and corresponds to a generalization of
36 | the benchmarking process in the `Sensemakr package `_ for regression models to the use with double machine learning.
37 | For an introduction to Sensemakr see `Cinelli and Hazlett (2020) `_ and the `Sensemakr introduction `_.
38 |
39 | The benchmarked estimates are the following:
40 |
41 | Let the subscript :math:`short`, denote the "short" form of the model, where the benchmarking variables are omitted.
42 |
43 | - :math:`\hat{\sigma}^2_{short}` denotes the variance of the outcome regression in the "short" form.
44 | - :math:`\hat{\nu}^2_{short}` denotes the second moment of the Riesz representer in the "short" form.
45 |
46 | Both parameters are contained in the ``sensitivity_params`` of the "short" form.
47 | This enables the following estimation of the nonparametric :math:`R^2`'s of the outcome regression
48 |
49 | - :math:`\hat{R}^2:= 1 - \frac{\hat{\sigma}^2}{\textrm{Var}(Y)}`
50 | - :math:`\hat{R}^2_{short}:= 1 - \frac{\hat{\sigma}^2_{short}}{\textrm{Var}(Y)}`
51 |
52 | and the correlation ratio of the estimated Riesz representations
53 |
54 | .. math::
55 |
56 | \hat{R}^2_{\alpha}:= \frac{\hat{\nu}^2_{short}}{\hat{\nu}^2}.
57 |
58 | The benchmarked estimates are then defined as
59 |
60 | - ``cf_y``:math:`:=\frac{\hat{R}^2 - \hat{R}^2_{short}}{1 - \hat{R}^2}` measures the proportion of residual variance in the outcome :math:`Y` explained by adding the purposely omitted ``benchmarking_set``
61 |
62 | - ``cf_d``:math:`:=\frac{1 - \hat{R}^2_{\alpha}}{\hat{R}^2_{\alpha}}` measures the proportional gain in variation that the ``benchmarking_set`` creates in the Riesz representer
63 |
64 | Further, the degree of adversity :math:`\rho` can be estimated via
65 |
66 | .. math::
67 |
68 | \hat{\rho} := \frac{\hat{\theta}_{short} - \hat{\theta}}{ \sqrt{(\hat{\sigma}^2_{short} - \hat{\sigma}^2)(\hat{\nu}^2 - \hat{\nu}^2_{short})}}.
69 |
70 |
71 | For a more detailed description, see `Chernozhukov et al. (2022) `_ Appendix D.
72 |
73 | .. note::
74 | - As benchmarking requires the estimation of a seperate model, the use with external predictions is generally not possible, without supplying further predictions.
--------------------------------------------------------------------------------
/.github/workflows/test_build_docu_dev.yml:
--------------------------------------------------------------------------------
1 | # Workflow based on https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml
2 |
3 | name: Test Docu Build (with dev pkgs)
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | pull_request:
10 | branches:
11 | - main
12 | - dev
13 | workflow_dispatch:
14 | inputs:
15 | doubleml-py-branch:
16 | description: 'Branch in https://github.com/DoubleML/doubleml-for-py'
17 | required: true
18 | default: 'main'
19 | doubleml-r-branch:
20 | description: 'Branch in https://github.com/DoubleML/doubleml-for-r'
21 | required: true
22 | default: 'main'
23 | nbsphinx-execute:
24 | description: 'Execute notebooks with nbsphinx'
25 | required: false
26 | default: 'auto'
27 | schedule:
28 | - cron: "0 9 * * 1,3,5"
29 |
30 |
31 | jobs:
32 | build:
33 |
34 | runs-on: ubuntu-22.04
35 | env:
36 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
37 |
38 | steps:
39 | - name: Check out the repo containing the docu source
40 | uses: actions/checkout@v4
41 |
42 | - name: Check out the repo containing the python pkg DoubleML (dev)
43 | if: ${{ github.event_name != 'workflow_dispatch' }}
44 | uses: actions/checkout@v4
45 | with:
46 | repository: DoubleML/doubleml-for-py
47 | path: doubleml-for-py
48 |
49 | - name: Check out the repo containing the python pkg DoubleML (dev)
50 | if: ${{ github.event_name == 'workflow_dispatch' }}
51 | uses: actions/checkout@v4
52 | with:
53 | repository: DoubleML/doubleml-for-py
54 | path: doubleml-for-py
55 | ref: ${{ github.event.inputs.doubleml-py-branch }}
56 |
57 | - name: Install graphviz
58 | run: sudo apt-get install graphviz
59 |
60 | - name: Install python
61 | uses: actions/setup-python@v5
62 | with:
63 | python-version: '3.12'
64 | - name: Install dependencies and the python package
65 | run: |
66 | python -m pip install --upgrade pip
67 | pip install -r requirements.txt
68 | pip uninstall -y DoubleML
69 | cd doubleml-for-py
70 | pip install -e .[rdd]
71 |
72 | - name: Add R repository
73 | run: |
74 | sudo apt install dirmngr gnupg apt-transport-https ca-certificates software-properties-common
75 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
76 | sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/'
77 | - name: Install R
78 | run: |
79 | sudo apt-get update
80 | sudo apt-get install r-base
81 | sudo apt-get install r-base-dev
82 | sudo apt-get install -y zlib1g-dev libicu-dev pandoc make libcurl4-openssl-dev libssl-dev
83 |
84 | - name: Get user library folder
85 | run: |
86 | mkdir ${GITHUB_WORKSPACE}/tmp_r_libs_user
87 | echo R_LIBS_USER=${GITHUB_WORKSPACE}/tmp_r_libs_user >> $GITHUB_ENV
88 |
89 | - name: Query R version
90 | run: |
91 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
92 | shell: Rscript {0}
93 |
94 | - name: Cache R packages
95 | uses: actions/cache@v4
96 | with:
97 | path: ${{ env.R_LIBS_USER }}
98 | key: doubleml-test-build-dev-${{ hashFiles('.github/R-version') }}
99 |
100 | - name: Install R kernel for Jupyter and the R package DoubleML (dev)
101 | if: ${{ github.event_name != 'workflow_dispatch' }}
102 | run: |
103 | install.packages('remotes')
104 | remotes::install_github('DoubleML/doubleml-for-r', dependencies = TRUE)
105 | install.packages(c('ggplot2', 'IRkernel', 'xgboost', 'hdm', 'reshape2', 'gridExtra', "igraph", "mlr3filters", "mlr3measures", "did"))
106 | IRkernel::installspec()
107 | shell: Rscript {0}
108 |
109 | - name: Install R kernel for Jupyter and the R package DoubleML (dev)
110 | if: ${{ github.event_name == 'workflow_dispatch' }}
111 | run: |
112 | install.packages('remotes')
113 | remotes::install_github('DoubleML/doubleml-for-r@${{ github.event.inputs.doubleml-r-branch }}', dependencies = TRUE)
114 | install.packages(c('ggplot2', 'IRkernel', 'xgboost', 'hdm', 'reshape2', 'gridExtra', "igraph", "mlr3filters", "mlr3measures", "did"))
115 | IRkernel::installspec()
116 | shell: Rscript {0}
117 |
118 | - name: Build docu with sphinx
119 | run: |
120 | make -C doc html NBSPHINX_EXECUTE=${{ github.event.inputs.nbsphinx-execute || 'auto' }}
121 |
122 | - name: Check for broken links / URLs
123 | run: |
124 | make -C doc linkcheck
125 |
126 | - name: Upload html artifacts
127 | uses: actions/upload-artifact@v4
128 | with:
129 | name: build_html
130 | path: doc/_build/html/
131 |
--------------------------------------------------------------------------------
/doc/guide/scores/did/did_pa_score.rst:
--------------------------------------------------------------------------------
1 | As in the description of the :ref:`DiD model `, the required nuisance elements are
2 |
3 | .. math::
4 | \begin{align}
5 | g_{0, \mathrm{g}, t_\text{pre}, t_\text{eval}, \delta}(X_i) &:= \mathbb{E}[Y_{i,t_\text{eval}} - Y_{i,t_\text{pre}}|X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)} = 1], \\
6 | m_{0, \mathrm{g}, t_\text{eval} + \delta}(X_i) &:= P(G_i^{\mathrm{g}}=1|X_i, G_i^{\mathrm{g}} + C_{i,t_\text{eval} + \delta}^{(\cdot)}=1).
7 | \end{align}
8 |
9 | for a certain choice of :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` and :math:`\delta` and control group :math:`C_{i,t_\text{eval} + \delta}^{(\cdot)}`.
10 |
11 | For notational purposes, we will omit the subscripts :math:`\mathrm{g}, t_\text{pre}, t_\text{eval}, \delta` in the following and use the notation
12 |
13 | * :math:`g_0(0, X_i)\equiv g_{0, \mathrm{g}, t_\text{pre}, t_\text{eval}, \delta}(X_i)` (population outcome change regression function of the control group)
14 | * :math:`m_0(X_i)\equiv m_{0, \mathrm{g}, t_\text{eval} + \delta}(X_i)` (generalized propensity score)
15 |
16 | All scores in the multi-period setting have the form
17 |
18 | .. math::
19 |
20 | \psi(W_i,\theta, \eta) :=
21 | \begin{cases}
22 | \tilde{\psi}(W_i,\theta, \eta) & \text{for } G_i^{\mathrm{g}} \vee C_{i,t_\text{eval} + \delta}^{(\cdot)}=1 \\
23 | 0 & \text{otherwise}
24 | \end{cases}
25 |
26 | i.e. the score is only non-zero for units in the corresponding treatment group :math:`\mathrm{g}` and control group :math:`C_{i,t_\text{eval} + \delta}^{(\cdot)}`.
27 |
28 | For the difference-in-differences model implemented in ``DoubleMLDIDMulti`` one can choose between
29 | ``score='observational'`` and ``score='experimental'``.
30 |
31 | ``score='observational'`` implements the score function (dropping the unit index :math:`i`):
32 |
33 | .. math::
34 |
35 | \tilde{\psi}(W,\theta, \eta)
36 | :&= -\frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]}\theta + \left(\frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]} - \frac{\frac{m(X) (1-G^{\mathrm{g}})}{1-m(X)}}{\mathbb{E}_n\left[\frac{m(X) (1-G^{\mathrm{g}})}{1-m(X)}\right]}\right) \left(Y_{t_\text{eval}} - Y_{t_\text{pre}} - g(0,X)\right)
37 |
38 | &= \tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
39 |
40 | where the components of the final linear score :math:`\psi` are
41 |
42 | .. math::
43 | \psi_a(W; \eta) &= \tilde{\psi}_a(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}),
44 |
45 | \psi_b(W; \eta) &= \tilde{\psi}_b(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
46 |
47 | and the nuisance elements :math:`\eta=(g, m)`.
48 |
49 | .. note::
50 | Remark that :math:`1-G^{\mathrm{g}}=C^{(\cdot)}` if :math:`G^{\mathrm{g}} \vee C_{t_\text{eval} + \delta}^{(\cdot)}=1`.
51 |
52 | If ``in_sample_normalization='False'``, the score is set to
53 |
54 | .. math::
55 |
56 | \tilde{\psi}(W,\theta,\eta) &= - \frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]}\theta + \frac{G^{\mathrm{g}} - m(X)}{\mathbb{E}_n[G^{\mathrm{g}}](1-m(X))}\left(Y_{t_\text{eval}} - Y_{t_\text{pre}} - g(0,X)\right)
57 |
58 | &= \tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
59 |
60 | with :math:`\eta=(g, m)`.
61 | Remark that this will result in the same score, but just uses slightly different normalization.
62 |
63 | ``score='experimental'`` assumes that the treatment probability is independent of the covariates :math:`X` and does not rely on the propensity score. Instead define
64 | the population outcome regression for treated and control group as
65 |
66 | * :math:`g_0(0, X_i)\equiv \mathbb{E}[Y_{i,t_\text{eval}} - Y_{i,t_\text{pre}}|X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)} = 1]` (control group)
67 | * :math:`g_0(1, X_i)\equiv \mathbb{E}[Y_{i,t_\text{eval}} - Y_{i,t_\text{pre}}|X_i, G_i^{\mathrm{g}} = 1]` (treated group)
68 |
69 | ``score='experimental'`` implements the score function:
70 |
71 | .. math::
72 |
73 | \tilde{\psi}(W,\theta, \eta)
74 | :=\; &-\theta + \left(\frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]} - \frac{1-G^{\mathrm{g}}}{\mathbb{E}_n[1-G^{\mathrm{g}}]}\right)\left(Y_{t_\text{eval}} - Y_{t_\text{pre}} - g(0,X)\right)
75 |
76 | &+ \left(1 - \frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]}\right) \left(g(1,X) - g(0,X)\right)
77 |
78 | =\; &\tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
79 |
80 | where the components of the final linear score :math:`\psi` are
81 |
82 | .. math::
83 | \psi_a(W; \eta) &= \tilde{\psi}_a(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}),
84 |
85 | \psi_b(W; \eta) &= \tilde{\psi}_b(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
86 |
87 | and the nuisance elements :math:`\eta=(g)`.
88 |
89 | Analogously, if ``in_sample_normalization='False'``, the score is set to
90 |
91 | .. math::
92 |
93 | \tilde{\psi}(W,\theta, \eta)
94 | :=\; &-\theta + \frac{G^{\mathrm{g}} - \mathbb{E}_n[G^{\mathrm{g}}]}{\mathbb{E}_n[G^{\mathrm{g}}](1-\mathbb{E}_n[G^{\mathrm{g}}])}\left(Y_{t_\text{eval}} - Y_{t_\text{pre}} - g(0,X)\right)
95 |
96 | &+ \left(1 - \frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]}\right) \left(g(1,X) - g(0,X)\right)
97 |
98 | =\; &\tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
99 |
100 | with :math:`\eta=(g)`.
101 | Remark that this will result in the same score, but just uses slightly different normalization.
102 |
--------------------------------------------------------------------------------
/doc/guide/models/did/did_setup.rst:
--------------------------------------------------------------------------------
1 | **Difference-in-Differences Models (DID)** implemented in the package focus on the the binary treatment case with staggered adoption.
2 |
3 | .. note::
4 | The notation and identifying assumptions are based on `Callaway and Sant'Anna (2021) `_, but adjusted to better fit into the general package documentation conventions, sometimes slightly abusing notation.
5 | The underlying score functions are based on `Sant'Anna and Zhao (2020) `_, `Zimmert (2018) `_ and `Chang (2020) `_.
6 | For a more detailed introduction and recent developments of the difference-in-differences literature see e.g. `Roth et al. (2022) `_.
7 |
8 | We consider :math:`n` observed units at time periods :math:`t=1,\dots, \mathcal{T}`.
9 | The treatment status for unit :math:`i` at time period :math:`t` is denoted by the binary variable :math:`D_{i,t}=1`. The package considers the staggered adoption setting,
10 | where a unit stays treated after it has been treated once (*Irreversibility of Treatment*).
11 |
12 | Let :math:`G^{\mathrm{g}}_i` be an indicator variable that takes value one if unit :math:`i` is treated at time period :math:`t=\mathrm{g}`, :math:`G^{\mathrm{g}}_i=1\{G_i=\mathrm{g}\}` with :math:`G_i` refering to the first post-treatment period.
13 | I units are never exposed to the treatment, define :math:`G_i=\infty`.
14 |
15 | The target parameters are defined in terms of differences in potential outcomes. The observed and potential outcome for each unit :math:`i` at time period :math:`t` are assumed to be of the form
16 |
17 | .. math::
18 | Y_{i,t} = Y_{i,t}(0) + \sum_{\mathrm{g}=2}^{\mathcal{T}} (Y_{i,t}(\mathrm{g}) - Y_{i,t}(0)) \cdot G^{\mathrm{g}}_i,
19 |
20 | such that we observe one consistent potential outcome for each unit at each time period.
21 |
22 | The corresponding target parameters are the average causal effects of the treatment
23 |
24 | .. math::
25 | ATT(\mathrm{g},t):= \mathbb{E}[Y_{i,t}(\mathrm{g}) - Y_{i,t}(0)|G^{\mathrm{g}}_i=1].
26 |
27 | This target parameter quantifies the average change in potential outcomes for units that are treated the first time in period :math:`\mathrm{g}` with the difference in outcome being evaluated for time period :math:`t`.
28 | The corresponding control groups, defined by an indicator :math:`C`, can be typically set as either the *never treated* or *not yet treated* units.
29 | Let
30 |
31 | .. math::
32 | \begin{align}
33 | C_{i,t}^{(\text{nev})} \equiv C_{i}^{(\text{nev})} &:= 1\{G_i=\infty\} \quad \text{(never treated)}, \\
34 | C_{i,t}^{(\text{nyt})} &:= 1\{G_i > t\} \quad \text{(not yet treated)}.
35 | \end{align}
36 |
37 | The corresponding identifying assumptions are:
38 |
39 | 1. **Irreversibility of Treatment:**
40 | :math:`D_{i,1} = 0 \quad a.s.`
41 | For all :math:`t=2,\dots,\mathcal{T}`, :math:`D_{i,t-1} = 1` implies :math:`D_{i,t} = 1 \quad a.s.`
42 |
43 | 2. **Data:**
44 | The observed data are generated according to the following mechanisms:
45 |
46 | a. **Panel Data (Random Sampling):**
47 | The sample :math:`(Y_{i,1},\dots, Y_{i,\mathcal{T}}, X_i, D_{i,1}, \dots, D_{i,\mathcal{T}})_{i=1}^n` is independent and identically distributed.
48 |
49 | b. **Repeated Cross Sections:**
50 | The sample consists of :math:`(Y_{i,t},G^{2}_i,\dots,G^{\mathcal{T}}_i, C_i,T_i, X_i)_{i=1}^n`, where :math:`T_i\in \{1,\dots,\mathcal{T}\}` denotes the time period of unit :math:`i` being observed.
51 | Conditional on :math:`T=t`, the data are independent and identically distributed from the distribution of :math:`(Y_{t},G^{2},\dots,G^{\mathcal{T}}, C, X)`, with :math:`(G^{2},\dots,G^{\mathcal{T}}, C, X)` being invariant to :math:`T`.
52 |
53 | 3. **Limited Treatment Anticipation:**
54 | There is a known :math:`\delta\ge 0` such that
55 | :math:`\mathbb{E}[Y_{i,t}(\mathrm{g})|X_i, G_i^{\mathrm{g}}=1] = \mathbb{E}[Y_{i,t}(0)|X_i, G_i^{\mathrm{g}}=1]\quad a.s.` for all :math:`\mathrm{g}\in\mathcal{G}, t\in\{1,\dots,\mathcal{T}\}` such that :math:`t< \mathrm{g}-\delta`.
56 |
57 | 4. **Conditional Parallel Trends:**
58 | Let :math:`\delta` be defined as in Assumption 3.\\
59 | For each :math:`\mathrm{g}\in\mathcal{G}` and :math:`t\in\{2,\dots,\mathcal{T}\}` such that :math:`t\ge \mathrm{g}-\delta`:
60 |
61 | a. **Never Treated:**
62 | :math:`\mathbb{E}[Y_{i,t}(0) - Y_{i,t-1}(0)|X_i, G_i^{\mathrm{g}}=1] = \mathbb{E}[Y_{i,t}(0) - Y_{i,t-1}(0)|X_i,C_{i}^{(\text{nev})}=1] \quad a.s.`
63 |
64 | b. **Not Yet Treated:**
65 | :math:`\mathbb{E}[Y_{i,t}(0) - Y_{i,t-1}(0)|X_i, G_i^{\mathrm{g}}=1] = \mathbb{E}[Y_{i,t}(0) - Y_{i,t-1}(0)|X_i,C_{i,t+\delta}^{(\text{nyt})}=1] \quad a.s.`
66 |
67 | 5. **Overlap:**
68 | For each time period :math:`t=2,\dots,\mathcal{T}` and :math:`\mathrm{g}\in\mathcal{G}` there exists a :math:`\epsilon > 0` such that
69 | :math:`P(G_i^{\mathrm{g}}=1) > \epsilon` and :math:`P(G_i^{\mathrm{g}}=1|X_i, G_i^{\mathrm{g}} + C_{i,t}^{(\text{nyt})}=1) < 1-\epsilon\quad a.s.`
70 |
71 | .. note::
72 | For a detailed discussion of the assumptions see `Callaway and Sant'Anna (2021) `_.
73 |
74 | Under the assumptions above (either Assumption a. or b.), the target parameter :math:`ATT(\mathrm{g},t)` is identified see Theorem 1. `Callaway and Sant'Anna (2021) `_.
75 |
--------------------------------------------------------------------------------
/doc/guide/sensitivity/theory.rst:
--------------------------------------------------------------------------------
1 | Assume that we can write the model in the following representation
2 |
3 | .. math::
4 |
5 | \theta_0 = \mathbb{E}[m(W,g_0)],
6 |
7 | where usually :math:`g_0(W) = \mathbb{E}[Y|X, D]` (currently, the sensitivity analysis is only available for linear models).
8 | As long as :math:`\mathbb{E}[m(W,f)]` is a continuous linear functional of :math:`f`, there exists a unique square
9 | integrable random variable :math:`\alpha_0(W)`, called Riesz representer
10 | (see `Riesz-Fréchet representation theorem `_), such that
11 |
12 | .. math::
13 |
14 | \theta_0 = \mathbb{E}[g_0(W)\alpha_0(W)].
15 |
16 | The target parameter :math:`\theta_0` has the following representation
17 |
18 | .. math::
19 |
20 | \theta_0 = \mathbb{E}[m(W,g_0) + (Y-g_0(W))\alpha_0(W)],
21 |
22 | which corresponds to a Neyman orthogonal score function (orthogonal with respect to nuisance elements :math:`(g, \alpha)`).
23 | To bound the omitted variable bias, the following further elements are needed.
24 | The variance of the outcome regression
25 |
26 | .. math::
27 |
28 | \sigma_0^2 := \mathbb{E}[(Y-g_0(W))^2]
29 |
30 | and the second moment of the Riesz representer
31 |
32 | .. math::
33 |
34 | \nu_0^2 := \mathbb{E}[\alpha_0(W)^2] =2\mathbb{E}[m(W,\alpha_0)] - \mathbb{E}[\alpha_0(W)^2].
35 |
36 | Both representations are Neyman orthogonal with respect to :math:`g` and :math:`\alpha`, respectively.
37 | Further, define the corresponding score functions
38 |
39 | .. math::
40 |
41 | \psi_{\sigma^2}(W, \sigma^2, g) &:= (Y-g_0(W))^2 - \sigma^2\\
42 | \psi_{\nu^2}(W, \nu^2, \alpha) &:= 2m(W,\alpha) - \alpha(W)^2 - \nu^2.
43 |
44 | Recall that the parameter :math:`\theta_0` is identified via the moment condition
45 |
46 | .. math::
47 |
48 | \theta_0 = \mathbb{E}[m(W,g_0)].
49 |
50 | If :math:`W=(Y, D, X)` does not include all confounding variables, the "true" target parameter :math:`\tilde{\theta}_0`
51 | would only be identified via the extendend (or "long") form
52 |
53 | .. math::
54 |
55 | \tilde{\theta}_0 = \mathbb{E}[m(\tilde{W},\tilde{g}_0)],
56 |
57 | where :math:`\tilde{W}=(Y, D, X, A)` includes the unobserved counfounders :math:`A`.
58 | In Theorem 2 of their paper `Chernozhukov et al. (2022) `_ are able to bound the omitted variable bias
59 |
60 | .. math::
61 |
62 | |\tilde{\theta}_0 -\theta_0|^2 = \rho^2 B^2,
63 |
64 | where
65 |
66 | .. math::
67 |
68 | B^2 := \mathbb{E}\Big[\big(g(W) - \tilde{g}(\tilde{W})\big)^2\Big]\mathbb{E}\Big[\big(\alpha(W) - \tilde{\alpha}(\tilde{W})\big)^2\Big],
69 |
70 | denotes the product of additional variations in the outcome regression and Riesz representer generated by omitted confounders and
71 |
72 | .. math::
73 |
74 | \rho^2 := \textrm{Cor}^2\Big(g(W) - \tilde{g}(\tilde{W}),\alpha(W) - \tilde{\alpha}(\tilde{W})\Big),
75 |
76 | denotes the correlations between the deviations generated by omitted confounders. The choice :math:`\rho=1` is conservative and
77 | accounts for adversarial confounding. Further, the bound can be expressed as
78 |
79 | .. math::
80 |
81 | B^2 := \sigma_0^2 \nu_0^2 C_Y^2 C_D^2,
82 |
83 | where
84 |
85 | .. math::
86 |
87 | C_Y^2 &:= \frac{\mathbb{E}[(\tilde{g}(\tilde{W}) - g(W))^2]}{\mathbb{E}[(Y - g(W))^2]}
88 |
89 | C_D^2 &:=\frac{1 - \frac{\mathbb{E}\big[\alpha(W)^2\big]}{\mathbb{E}\big[\tilde{\alpha}(\tilde{W})^2\big]}}{\frac{\mathbb{E}\big[\alpha(W)^2\big]}{\mathbb{E}\big[\tilde{\alpha}(\tilde{W})^2\big]}}.
90 |
91 | As :math:`\sigma_0^2` and :math:`\nu_0^2` do not depend on the unobserved confounders :math:`A` they are identified. Further, the other parts have the following interpretations
92 |
93 | - ``cf_y``:math:`:=\frac{\mathbb{E}[(\tilde{g}(\tilde{W}) - g(W))^2]}{\mathbb{E}[(Y - g(W))^2]}` measures the proportion of residual variance in the outcome :math:`Y` explained by the latent confounders :math:`A`
94 |
95 | - ``cf_d``:math:`:=1 - \frac{\mathbb{E}\big[\alpha(W)^2\big]}{\mathbb{E}\big[\tilde{\alpha}(\tilde{W})^2\big]}` measures the proportion of residual variance in the Riesz representer :math:`\tilde{\alpha}(\tilde{W})` generated by the latent confounders :math:`A`
96 |
97 | .. note::
98 | - ``cf_y`` has the interpretation as the *nonparametric partial* :math:`R^2` *of* :math:`A` *with* :math:`Y` *given* :math:`(D,X)`
99 |
100 | .. math::
101 |
102 | \frac{\textrm{Var}(\mathbb{E}[Y|D,X,A]) - \textrm{Var}(\mathbb{E}[Y|D,X])}{\textrm{Var}(Y)-\textrm{Var}(\mathbb{E}[Y|D,X])}
103 |
104 | - For model-specific interpretations of ``cf_d`` or :math:`C_D^2`, see the corresponding chapters (e.g. :ref:`sensitivity_plr`).
105 |
106 | Consequently, for given values ``cf_y`` and ``cf_d``, we can create lower and upper bounds for target parameter :math:`\tilde{\theta}_0` of the form
107 |
108 | .. math::
109 |
110 | \theta_{\pm}:=\theta_0 \pm |\rho| \sigma_0 \nu_0 C_Y C_D
111 |
112 | Let :math:`\psi(W,\theta,\eta)` the (correctly scaled) score function for the target parameter :math:`\theta_0`. Then
113 |
114 | .. math::
115 |
116 | \psi_{\pm}(W,\theta,\eta_\pm):= \psi(W,\theta,\eta) \pm \frac{|\rho| C_Y C_D}{2 \sigma \nu} \Big(\sigma^2 \psi_{\nu^2}(W, \nu^2, \alpha) + \nu^2 \psi_{\sigma^2}(W, \sigma^2, g)\Big)
117 |
118 | determines a orthongonal score function for :math:`\theta_{\pm}`, with nuisance elements :math:`\eta_\pm:=(g, \alpha, \sigma, \nu)`.
119 | The score can be used to calculate the standard deviations of :math:`\theta_{\pm}` via
120 |
121 | .. math::
122 |
123 | \sigma^2_{\pm}= \mathbb{E}[\psi_{\pm}(W,\theta,\eta_\pm)^2]
124 |
125 | For more detail and interpretations see `Chernozhukov et al. (2022) `_.
--------------------------------------------------------------------------------
/doc/guide/sensitivity/implementation.rst:
--------------------------------------------------------------------------------
1 | The :ref:`plr-model` will be used as an example
2 |
3 | .. tab-set::
4 |
5 | .. tab-item:: Python
6 | :sync: py
7 |
8 | .. ipython:: python
9 |
10 | import numpy as np
11 | import doubleml as dml
12 | from doubleml.plm.datasets import make_plr_CCDDHNR2018
13 | from sklearn.ensemble import RandomForestRegressor
14 | from sklearn.base import clone
15 |
16 | learner = RandomForestRegressor(n_estimators=100, max_features=20, max_depth=5, min_samples_leaf=2)
17 | ml_l = clone(learner)
18 | ml_m = clone(learner)
19 | np.random.seed(1111)
20 | data = make_plr_CCDDHNR2018(alpha=0.5, n_obs=500, dim_x=20, return_type='DataFrame')
21 | obj_dml_data = dml.DoubleMLData(data, 'y', 'd')
22 | dml_plr_obj = dml.DoubleMLPLR(obj_dml_data, ml_l, ml_m)
23 |
24 | If the sensitivity analysis is implemented (see :ref:`sensitivity_models`), the corresponding sensitivity elements are estimated
25 | automatically by calling the ``fit()`` method. In most cases these elements are based on the following plug-in estimators
26 |
27 | .. math::
28 |
29 | \hat{\sigma}^2 &:= \mathbb{E}_n[(Y-\hat{g}(W))^2]
30 |
31 | \hat{\nu}^2 &:= \mathbb{E}_n[2m(W,\hat{\alpha}) - \hat{\alpha}(W)^2]
32 |
33 | where :math:`\hat{g}(W)` and :math:`\hat{\alpha}(W)` denote the cross-fitted predictions of the outcome regression and the Riesz
34 | representer (both are model specific, see :ref:`sensitivity_models`). Further, the corresponding scores are defined as
35 |
36 | .. math::
37 |
38 | \psi_{\sigma^2}(W, \hat{\sigma}^2, g) &:= (Y-\hat{g}(W))^2 - \hat{\sigma}^2\\
39 | \psi_{\nu^2}(W, \hat{\nu}^2, \alpha) &:= 2m(W,\hat{\alpha}) - \hat{\alpha}(W)^2 - \hat{\nu}^2.
40 |
41 | After the ``fit()`` call, the sensitivity elements are stored in a dictionary and can be accessed via the ``sensitivity_elements`` property.
42 |
43 | .. tab-set::
44 |
45 | .. tab-item:: Python
46 | :sync: py
47 |
48 | .. ipython:: python
49 |
50 | dml_plr_obj.fit()
51 | dml_plr_obj.sensitivity_elements.keys()
52 |
53 | Each value is a :math:`3`-dimensional array, with the variances being of form ``(1, n_rep, n_coefs)`` and the scores of form ``(n_obs, n_rep, n_coefs)``.
54 | The ``sensitivity_analysis()`` method then computes the upper and lower bounds for the estimate, based on the sensitivity parameters
55 | ``cf_y``, ``cf_d`` and ``rho`` (default is ``rho=1.0`` to account for adversarial confounding). Additionally, one-sided confidence bounds are computed
56 | based on a supplied significance level (default ``level=0.95``).
57 | The results are summarized as a formatted string in the ``sensitivity_summary``
58 |
59 | .. tab-set::
60 |
61 | .. tab-item:: Python
62 | :sync: py
63 |
64 | .. ipython:: python
65 |
66 | dml_plr_obj.sensitivity_analysis(cf_y=0.03, cf_d=0.03, rho=1.0, level=0.95)
67 | print(dml_plr_obj.sensitivity_summary)
68 |
69 | or can be directly accessed via the ``sensitivity_params`` property.
70 |
71 | .. tab-set::
72 |
73 | .. tab-item:: Python
74 | :sync: py
75 |
76 | .. ipython:: python
77 |
78 | dml_plr_obj.sensitivity_params
79 |
80 | The bounds are saved as a nested dictionary, where the keys ``'theta'``
81 | denote the bounds on the parameter :math:`\hat{\theta}_{\pm}`, ``'se'`` denotes the corresponding standard error and ``'ci'`` denotes the lower and upper
82 | confidence bounds for :math:`\hat{\theta}_{\pm}`. Each of the keys refers to a dictionary with keys ``'lower'`` and ``'upper'``
83 | which refer to the lower or upper bound, e.g. ``sensitivity_params['theta']['lower']`` refers to the lower bound :math:`\hat{\theta}_{-}` of the estimated cofficient .
84 |
85 | Further, the sensitivity analysis has an input parameter ``theta`` (with default ``theta=0.0``), which refers to the null hypothesis used for each coefficient.
86 | This null hypothesis is used to calculate the robustness values as displayed in the ``sensitivity_params``.
87 |
88 | The robustness value $RV$ is defined as the required confounding strength (``cf_y=rv`` and ``cf_d=rv``), such that the lower or upper bound of the causal parameter includes the null hypothesis.
89 | If the estimated parameter :math:`\hat{\theta}` is larger than the null hypothesis the lower bound is used and vice versa.
90 | The robustness value $RVa$ defined analogous, but additionally incorporates statistical uncertainty (as it is based on the confidence intervals of the bounds).
91 |
92 | To obtain a more complete overview over the sensitivity one can call the ``sensitivity_plot()`` method. The methods creates a contour plot, which calculates estimate of the upper or lower bound for :math:`\theta`
93 | (based on the null hypothesis) for each combination of ``cf_y`` and ``cf_d`` in a grid of values.
94 |
95 | .. figure:: /_static/sensitivity_example_nb.png
96 | :alt: Contour plot
97 | :figclass: captioned-image
98 |
99 | Contour plot example (see :ref:`examplegallery`)
100 |
101 | By adjusting the parameter ``value='ci'`` in the ``sensitivity_plot()`` method the bounds are displayed for the corresponding confidence level.
102 |
103 | .. note::
104 |
105 | - The ``sensitivity_plot()`` requires to call ``sensitivity_analysis`` first, since the choice of the bound (upper or lower) is based on
106 | the corresponding null hypothesis. Further, the parameters ``rho`` and ``level`` are used. Both are contained in the ``sensitivity_params`` property.
107 | - The ``sensitivity_plot()`` is created for the first treatment variable. This can be changed via the ``idx_treatment`` parameter.
108 | - The robustness values are given via the intersection countour of the null hypothesis and the identity.
--------------------------------------------------------------------------------
/doc/guide/learners/r/set_hyperparams.rst:
--------------------------------------------------------------------------------
1 | The learners are set during initialization of the :ref:`DoubleML ` model classes
2 | `DoubleML::DoubleMLPLR `_,
3 | `DoubleML::DoubleMLPLIV `_ ,
4 | `DoubleML::DoubleMLIRM `_
5 | and `DoubleML::DoubleMLIIVM `_.
6 | Lets simulate some data and consider the partially linear regression model.
7 | We need to specify learners for the nuisance functions :math:`g_0(X) = E[Y|X]` and :math:`m_0(X) = E[D|X]`,
8 | for example `LearnerRegrRanger `_
9 | (``lrn("regr.ranger")``) for regression with random forests based on the `ranger `_
10 | package for R.
11 |
12 | .. tab-set::
13 |
14 | .. tab-item:: R
15 | :sync: r
16 |
17 | .. jupyter-execute::
18 |
19 | library(DoubleML)
20 | library(mlr3)
21 | library(mlr3learners)
22 | library(data.table)
23 | lgr::get_logger("mlr3")$set_threshold("warn")
24 |
25 | # set up a mlr3 learner
26 | learner = lrn("regr.ranger")
27 | ml_l = learner$clone()
28 | ml_m = learner$clone()
29 | set.seed(3141)
30 | data = make_plr_CCDDHNR2018(alpha=0.5, return_type='data.table')
31 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_cols="d")
32 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_l, ml_m)
33 | dml_plr_obj$fit()
34 | dml_plr_obj$summary()
35 |
36 | Without further specification of the hyperparameters, default values are used. To set hyperparameters:
37 |
38 | * We can also use pre-parametrized learners ``lrn("regr.ranger", num.trees=10)``.
39 | * Alternatively, hyperparameters can be set after initialization via the method
40 | ``set_ml_nuisance_params(learner, treat_var, params, set_fold_specific)``.
41 |
42 | .. tab-set::
43 |
44 | .. tab-item:: R
45 | :sync: r
46 |
47 | .. jupyter-execute::
48 |
49 | set.seed(3141)
50 | ml_l = lrn("regr.ranger", num.trees=10)
51 | ml_m = lrn("regr.ranger")
52 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_cols="d")
53 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_l, ml_m)
54 | dml_plr_obj$fit()
55 | dml_plr_obj$summary()
56 |
57 | set.seed(3141)
58 | ml_l = lrn("regr.ranger")
59 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_l , ml_m)
60 | dml_plr_obj$set_ml_nuisance_params("ml_l", "d", list("num.trees"=10))
61 | dml_plr_obj$fit()
62 | dml_plr_obj$summary()
63 |
64 | Setting treatment-variable-specific or fold-specific hyperparameters:
65 |
66 | * In the multiple-treatment case, the method ``set_ml_nuisance_params(learner, treat_var, params, set_fold_specific)``
67 | can be used to set different hyperparameters for different treatment variables.
68 | * The method ``set_ml_nuisance_params(learner, treat_var, params, set_fold_specific)`` accepts lists for ``params``.
69 | The structure of the list depends on whether the same parameters should be provided for all folds or separate values
70 | are passed for specific folds.
71 | * Global parameter passing: The values in ``params`` are used for estimation on all folds.
72 | The named list in the argument ``params`` should have entries with names corresponding to
73 | the parameters of the learners. It is required that option ``set_fold_specific`` is set to ``FALSE`` (default).
74 | * Fold-specific parameter passing: ``params`` is a nested list. The outer list needs to be of length ``n_rep`` and the inner
75 | list of length ``n_folds``. The innermost list must have named entries that correspond to the parameters of the learner.
76 | It is required that option ``set_fold_specific`` is set to ``TRUE``. Moreover, fold-specific
77 | parameter passing is only supported, if all parameters are set fold-specific.
78 | * External setting of parameters will override previously set parameters. To assert the choice of parameters, access the
79 | fields ``$learner`` and ``$params``.
80 |
81 | .. tab-set::
82 |
83 | .. tab-item:: R
84 | :sync: r
85 |
86 | .. jupyter-execute::
87 |
88 | set.seed(3141)
89 | ml_l = lrn("regr.ranger")
90 | ml_m = lrn("regr.ranger")
91 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_cols="d")
92 |
93 | n_rep = 2
94 | n_folds = 3
95 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_l, ml_m, n_rep=n_rep, n_folds=n_folds)
96 |
97 | # Set globally
98 | params = list("num.trees"=10)
99 | dml_plr_obj$set_ml_nuisance_params("ml_l", "d", params=params)
100 | dml_plr_obj$set_ml_nuisance_params("ml_m", "d", params=params)
101 | dml_plr_obj$learner
102 | dml_plr_obj$params
103 | dml_plr_obj$fit()
104 | dml_plr_obj$summary()
105 |
106 |
107 | The following example illustrates how to set parameters for each fold.
108 |
109 | .. tab-set::
110 |
111 | .. tab-item:: R
112 | :sync: r
113 |
114 | .. jupyter-execute::
115 |
116 | learner = lrn("regr.ranger")
117 | ml_l = learner$clone()
118 | ml_m = learner$clone()
119 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_l, ml_m, n_rep=n_rep, n_folds=n_folds)
120 |
121 | # Set values for each fold
122 | params_exact = rep(list(rep(list(params), n_folds)), n_rep)
123 | dml_plr_obj$set_ml_nuisance_params("ml_l", "d", params=params_exact,
124 | set_fold_specific=TRUE)
125 | dml_plr_obj$set_ml_nuisance_params("ml_m", "d", params=params_exact,
126 | set_fold_specific=TRUE)
127 | dml_plr_obj$learner
128 | dml_plr_obj$params
129 | dml_plr_obj$fit()
130 | dml_plr_obj$summary()
131 |
--------------------------------------------------------------------------------
/doc/guide/models/plm/plm_models.inc:
--------------------------------------------------------------------------------
1 | The partially linear models (PLM) take the form
2 |
3 | .. math::
4 |
5 | Y = D \theta_0 + g_0(X) + \zeta,
6 |
7 | where treatment effects are additive with some sort of linear form.
8 |
9 | .. _plr-model:
10 |
11 | Partially linear regression model (PLR)
12 | ***************************************
13 |
14 | .. include:: /guide/models/plm/plr.rst
15 |
16 | .. include:: /shared/causal_graphs/plr_irm_causal_graph.rst
17 |
18 | ``DoubleMLPLR`` implements PLR models. Estimation is conducted via its ``fit()`` method.
19 |
20 | .. note::
21 | Remark that the standard approach with ``score='partialling out'`` does not rely on a direct estimate of :math:`g_0(X)`,
22 | but :math:`\ell_0(X) := \mathbb{E}[Y \mid X] = \theta_0 \mathbb{E}[D \mid X] + g(X)`.
23 |
24 | .. tab-set::
25 |
26 | .. tab-item:: Python
27 | :sync: py
28 |
29 | .. ipython:: python
30 |
31 | import numpy as np
32 | import doubleml as dml
33 | from doubleml.plm.datasets import make_plr_CCDDHNR2018
34 | from sklearn.ensemble import RandomForestRegressor
35 | from sklearn.base import clone
36 |
37 | learner = RandomForestRegressor(n_estimators=100, max_features=20, max_depth=5, min_samples_leaf=2)
38 | ml_l = clone(learner)
39 | ml_m = clone(learner)
40 | np.random.seed(1111)
41 | data = make_plr_CCDDHNR2018(alpha=0.5, n_obs=500, dim_x=20, return_type='DataFrame')
42 | obj_dml_data = dml.DoubleMLData(data, 'y', 'd')
43 | dml_plr_obj = dml.DoubleMLPLR(obj_dml_data, ml_l, ml_m)
44 | print(dml_plr_obj.fit())
45 |
46 | .. tab-item:: R
47 | :sync: r
48 |
49 | .. jupyter-execute::
50 |
51 | library(DoubleML)
52 | library(mlr3)
53 | library(mlr3learners)
54 | library(data.table)
55 | lgr::get_logger("mlr3")$set_threshold("warn")
56 |
57 | learner = lrn("regr.ranger", num.trees = 100, mtry = 20, min.node.size = 2, max.depth = 5)
58 | ml_l = learner$clone()
59 | ml_m = learner$clone()
60 | set.seed(1111)
61 | data = make_plr_CCDDHNR2018(alpha=0.5, n_obs=500, dim_x=20, return_type='data.table')
62 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_cols="d")
63 | dml_plr_obj = DoubleMLPLR$new(obj_dml_data, ml_l, ml_m)
64 | dml_plr_obj$fit()
65 | print(dml_plr_obj)
66 |
67 | .. _lplr-model:
68 |
69 | Logistic partially linear regression model (LPLR)
70 | *************************************************
71 |
72 | .. include:: /guide/models/plm/lplr.rst
73 |
74 | .. include:: /shared/causal_graphs/plr_irm_causal_graph.rst
75 |
76 | ``DoubleMLLPLR`` implements LPLR models. Estimation is conducted via its ``fit()`` method.
77 |
78 | .. note::
79 | Remark that the treatment effects are not additive in this model. The partial linear term enters the model through a logistic link function.
80 |
81 | .. tab-set::
82 |
83 | .. tab-item:: Python
84 | :sync: py
85 |
86 | .. ipython:: python
87 |
88 | import numpy as np
89 | import doubleml as dml
90 | from doubleml.plm.datasets import make_lplr_LZZ2020
91 | from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
92 | from sklearn.base import clone
93 | np.random.seed(3141)
94 | ml_t = RandomForestRegressor(n_estimators=100, max_features=15, max_depth=15, min_samples_leaf=5)
95 | ml_m = RandomForestRegressor(n_estimators=100, max_features=15, max_depth=15, min_samples_leaf=5)
96 | ml_M = RandomForestClassifier(n_estimators=100, max_features=15, max_depth=15, min_samples_leaf=5)
97 | obj_dml_data = make_lplr_LZZ2020(alpha=0.5, n_obs=1000, dim_x=15)
98 | dml_lplr_obj = dml.DoubleMLLPLR(obj_dml_data, ml_M, ml_t, ml_m)
99 | dml_lplr_obj.fit().summary
100 |
101 |
102 | .. _pliv-model:
103 |
104 | Partially linear IV regression model (PLIV)
105 | *******************************************
106 |
107 | .. include:: /guide/models/plm/pliv.rst
108 |
109 | .. include:: /shared/causal_graphs/pliv_iivm_causal_graph.rst
110 |
111 | ``DoubleMLPLIV`` implements PLIV models.
112 | Estimation is conducted via its ``fit()`` method:
113 |
114 | .. tab-set::
115 |
116 | .. tab-item:: Python
117 | :sync: py
118 |
119 | .. ipython:: python
120 | :okwarning:
121 |
122 | import numpy as np
123 | import doubleml as dml
124 | from doubleml.plm.datasets import make_pliv_CHS2015
125 | from sklearn.ensemble import RandomForestRegressor
126 | from sklearn.base import clone
127 |
128 | learner = RandomForestRegressor(n_estimators=100, max_features=5, max_depth=5, min_samples_leaf=5)
129 | ml_l = clone(learner)
130 | ml_m = clone(learner)
131 | ml_r = clone(learner)
132 | np.random.seed(2222)
133 | data = make_pliv_CHS2015(alpha=0.5, n_obs=500, dim_x=5, dim_z=1, return_type='DataFrame')
134 | obj_dml_data = dml.DoubleMLData(data, 'y', 'd', z_cols='Z1')
135 | dml_pliv_obj = dml.DoubleMLPLIV(obj_dml_data, ml_l, ml_m, ml_r)
136 | print(dml_pliv_obj.fit())
137 |
138 | .. tab-item:: R
139 | :sync: r
140 |
141 | .. jupyter-execute::
142 |
143 | library(DoubleML)
144 | library(mlr3)
145 | library(mlr3learners)
146 | library(data.table)
147 |
148 | learner = lrn("regr.ranger", num.trees = 100, mtry = 20, min.node.size = 2, max.depth = 5)
149 | ml_l = learner$clone()
150 | ml_m = learner$clone()
151 | ml_r = learner$clone()
152 | set.seed(2222)
153 | data = make_pliv_CHS2015(alpha=0.5, n_obs=500, dim_x=20, dim_z=1, return_type="data.table")
154 | obj_dml_data = DoubleMLData$new(data, y_col="y", d_col = "d", z_cols= "Z1")
155 | dml_pliv_obj = DoubleMLPLIV$new(obj_dml_data, ml_l, ml_m, ml_r)
156 | dml_pliv_obj$fit()
157 | print(dml_pliv_obj)
158 |
--------------------------------------------------------------------------------
/doc/guide/scores/did/did_cs_score.rst:
--------------------------------------------------------------------------------
1 | As in the description of the :ref:`DiD model `, the required nuisance elements are
2 |
3 | .. math::
4 | \begin{align}
5 | g^{\text{treat}}_{0,\mathrm{g}, t, \text{eval} + \delta}(X_i) &:= \mathbb{E}[Y_{i,t} |X_i, G_i^{\mathrm{g}}=1, T_i=t], \\
6 | g^{\text{control}}_{0,\mathrm{g}, t, \text{eval} + \delta}(X_i) &:= \mathbb{E}[Y_{i,t} |X_i, C_{i,t_\text{eval} + \delta}^{(\cdot)}=1, T_i=t], \\
7 | m_{0, \mathrm{g}, t_\text{eval} + \delta}(X_i) &:= P(G_i^{\mathrm{g}}=1|X_i, G_i^{\mathrm{g}} + C_{i,t_\text{eval} + \delta}^{(\cdot)}=1).
8 | \end{align}
9 |
10 | for :math:`t\in\{t_\text{pre}, t_\text{eval}\}` and a certain choice of :math:`(\mathrm{g}, t_\text{pre}, t_\text{eval})` and :math:`\delta` and control group :math:`C_{i,t_\text{eval} + \delta}^{(\cdot)}`.
11 |
12 | For notational purposes, we will omit the subscripts :math:`\mathrm{g}, t_\text{pre}, t_\text{eval}, \delta` in the following and use the notation
13 |
14 | * :math:`g_0(1, 0, X_i) \equiv g^{\text{treat}}_{0,\mathrm{g}, t_\text{pre}, \text{eval} + \delta}(X_i)` (pop. outcome regr. function for treatment group in :math:`t_\text{pre}`)
15 | * :math:`g_0(1, 1, X_i) \equiv g^{\text{treat}}_{0,\mathrm{g}, t_\text{eval}, \text{eval} + \delta}(X_i)` (pop. outcome regr. function for treatment group in :math:`t_\text{eval}`)
16 | * :math:`g_0(0, 0, X_i) \equiv g^{\text{control}}_{0,\mathrm{g}, t_\text{pre}, \text{eval} + \delta}(X_i)` (pop. outcome regr. function for control group in :math:`t_\text{pre}`)
17 | * :math:`g_0(0, 1, X_i) \equiv g^{\text{control}}_{0,\mathrm{g}, t_\text{eval}, \text{eval} + \delta}(X_i)` (pop. outcome regr. function for control group in :math:`t_\text{eval}`)
18 | * :math:`m_0(X_i)\equiv m_{0, \mathrm{g}, t_\text{eval} + \delta}(X_i)` (generalized propensity score).
19 |
20 | All scores in the multi-period setting have the form
21 |
22 | .. math::
23 |
24 | \psi(W_i,\theta, \eta) :=
25 | \begin{cases}
26 | \tilde{\psi}(W_i,\theta, \eta) & \text{for } G_i^{\mathrm{g}} \vee C_{i,t_\text{eval} + \delta}^{(\cdot)}=1 \\
27 | 0 & \text{otherwise}
28 | \end{cases}
29 |
30 | i.e. the score is only non-zero for units in the corresponding treatment group :math:`\mathrm{g}` and control group :math:`C_{i,t_\text{eval} + \delta}^{(\cdot)}`.
31 |
32 | For the difference-in-differences model implemented in ``DoubleMLDIDMulti`` one can choose between
33 | ``score='observational'`` and ``score='experimental'``.
34 |
35 | ``score='observational'`` implements the score function (dropping the unit index :math:`i`):
36 |
37 | .. math::
38 |
39 | \tilde{\psi}(W,\theta,\eta) :=\; & - \frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]}\theta + \frac{G^{\mathrm{g}}}{\mathbb{E}_n[G^{\mathrm{g}}]}\Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
40 |
41 | & + \frac{G^{\mathrm{g}}T}{\mathbb{E}_n[G^{\mathrm{g}}T]} (Y - g(1,1,X))
42 |
43 | & - \frac{G^{\mathrm{g}}(1-T)}{\mathbb{E}_n[G^{\mathrm{g}}(1-T)]}(Y - g(1,0,X))
44 |
45 | & - \frac{m(X) (1-G^{\mathrm{g}})T}{1-m(X)} \mathbb{E}_n\left[\frac{m(X) (1-G^{\mathrm{g}})T}{1-m(X)}\right]^{-1} (Y-g(0,1,X))
46 |
47 | & + \frac{m(X) (1-G^{\mathrm{g}})(1-T)}{1-m(X)} \mathbb{E}_n\left[\frac{m(X) (1-G^{\mathrm{g}})(1-T)}{1-m(X)}\right]^{-1} (Y-g(0,0,X))
48 |
49 | =\; &\tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
50 |
51 | where the components of the final linear score :math:`\psi` are
52 |
53 | .. math::
54 | \psi_a(W; \eta) &= \tilde{\psi}_a(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}),
55 |
56 | \psi_b(W; \eta) &= \tilde{\psi}_b(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
57 |
58 | and the nuisance elements :math:`\eta=(g, m)`.
59 |
60 | .. note::
61 | Remark that :math:`1-G^{\mathrm{g}}=C^{(\cdot)}` if :math:`G^{\mathrm{g}} \vee C_{t_\text{eval} + \delta}^{(\cdot)}=1`.
62 |
63 | If ``in_sample_normalization='False'``, the score is set to
64 |
65 | .. math::
66 |
67 | \tilde{\psi}(W,\theta,\eta) :=\; & - \frac{G^{\mathrm{g}}}{p}\theta + \frac{G^{\mathrm{g}}}{p}\Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
68 |
69 | & + \frac{G^{\mathrm{g}}T}{p\lambda} (Y - g(1,1,X))
70 |
71 | & - \frac{G^{\mathrm{g}}(1-T)}{p(1-\lambda)}(Y - g(1,0,X))
72 |
73 | & - \frac{m(X) (1-G^{\mathrm{g}})T}{p(1-m(X))\lambda} (Y-g(0,1,X))
74 |
75 | & + \frac{m(X) (1-G^{\mathrm{g}})(1-T)}{p(1-m(X))(1-\lambda)} (Y-g(0,0,X))
76 |
77 | =\; &\tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
78 |
79 | with :math:`\eta=(g, m, p, \lambda)`, where :math:`p_0 = \mathbb{E}[G^{\mathrm{g}}]` and :math:`\lambda_0 = \mathbb{E}[T]` are estimated on the subsample.
80 | Remark that this will result a similar score, but just uses slightly different normalization.
81 |
82 | ``score='experimental'`` assumes that the treatment probability is independent of the covariates :math:`X` and
83 | implements the score function:
84 |
85 | .. math::
86 |
87 | \tilde{\psi}(W,\theta,\eta) :=\; & - \theta + \Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
88 |
89 | & + \frac{G^{\mathrm{g}}T}{\mathbb{E}_n[G^{\mathrm{g}}T]} (Y - g(1,1,X))
90 |
91 | & - \frac{G^{\mathrm{g}}(1-T)}{\mathbb{E}_n[G^{\mathrm{g}}(1-T)]}(Y - g(1,0,X))
92 |
93 | & - \frac{(1-G^{\mathrm{g}})T}{\mathbb{E}_n[(1-G^{\mathrm{g}})T]} (Y-g(0,1,X))
94 |
95 | & + \frac{(1-G^{\mathrm{g}})(1-T)}{\mathbb{E}_n[(1-G^{\mathrm{g}})(1-T)]} (Y-g(0,0,X))
96 |
97 | =\; &\tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
98 |
99 | where the components of the final linear score :math:`\psi` are
100 |
101 | .. math::
102 | \psi_a(W; \eta) &= \tilde{\psi}_a(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)}),
103 |
104 | \psi_b(W; \eta) &= \tilde{\psi}_b(W; \eta) \cdot \max(G^{\mathrm{g}}, C^{(\cdot)})
105 |
106 | and the nuisance elements :math:`\eta=(g, m)`.
107 |
108 | Analogously, if ``in_sample_normalization='False'``, the score is set to
109 |
110 | .. math::
111 |
112 | \tilde{\psi}(W,\theta,\eta) :=\; & - \theta + \Big(g(1,1,X) - g(1,0,X) - (g(0,1,X) - g(0,0,X))\Big)
113 |
114 | & + \frac{G^{\mathrm{g}}T}{p\lambda} (Y - g(1,1,X))
115 |
116 | & - \frac{G^{\mathrm{g}}(1-T)}{p(1-\lambda)}(Y - g(1,0,X))
117 |
118 | & - \frac{(1-G^{\mathrm{g}})T}{(1-p)\lambda} (Y-g(0,1,X))
119 |
120 | & + \frac{(1-G^{\mathrm{g}})(1-T)}{(1-p)(1-\lambda)} (Y-g(0,0,X))
121 |
122 | =\; &\tilde{\psi}_a(W; \eta) \theta + \tilde{\psi}_b(W; \eta)
123 |
124 | with :math:`\eta=(g, m, p, \lambda)`, where :math:`p_0 = \mathbb{E}[G^{\mathrm{g}}]` and :math:`\lambda_0 = \mathbb{E}[T]` are estimated on the subsample.
125 | Remark that this will result in a similar score, but just uses slightly different normalization.
--------------------------------------------------------------------------------
/doc/guide/models/ssm/ssm_models.inc:
--------------------------------------------------------------------------------
1 | .. include:: /guide/models/ssm/ssm.rst
2 |
3 | .. _ssm-mar-model:
4 |
5 | Missingness at Random
6 | *********************
7 |
8 | Consider the following two additional assumptions for the sample selection model:
9 |
10 | - **Cond. Independence of Selection:** :math:`Y_i(d) \perp S_i|D_i=d, X_i\quad a.s.` for :math:`d=0,1`
11 | - **Common Support:** :math:`P(D_i=1|X_i)>0` and :math:`P(S_i=1|D_i=d, X_i)>0` for :math:`d=0,1`
12 |
13 | such that outcomes are missing at random (for the score see :ref:`Scores `).
14 |
15 | ``DoubleMLSSM`` implements sample selection models. The score ``score='missing-at-random'`` refers to the correponding score
16 | relying on the assumptions above. The ``DoubleMLData`` object has to be defined with the additional argument ``s_col`` for the selection indicator.
17 | Estimation is conducted via its ``fit()`` method:
18 |
19 | .. tab-set::
20 |
21 | .. tab-item:: Python
22 | :sync: py
23 |
24 | .. ipython:: python
25 | :okwarning:
26 |
27 | import numpy as np
28 | from sklearn.linear_model import LassoCV, LogisticRegressionCV
29 | from doubleml.irm.datasets import make_ssm_data
30 | import doubleml as dml
31 |
32 | np.random.seed(42)
33 | n_obs = 2000
34 | df = make_ssm_data(n_obs=n_obs, mar=True, return_type='DataFrame')
35 | dml_data = dml.DoubleMLSSMData(df, 'y', 'd', s_col='s')
36 |
37 | ml_g = LassoCV()
38 | ml_m = LogisticRegressionCV(penalty='l1', solver='liblinear')
39 | ml_pi = LogisticRegressionCV(penalty='l1', solver='liblinear')
40 |
41 | dml_ssm = dml.DoubleMLSSM(dml_data, ml_g, ml_m, ml_pi, score='missing-at-random')
42 | dml_ssm.fit()
43 | print(dml_ssm)
44 |
45 | .. tab-item:: R
46 | :sync: r
47 |
48 | .. jupyter-execute::
49 |
50 | library(DoubleML)
51 | library(mlr3)
52 | library(data.table)
53 |
54 | set.seed(3141)
55 | n_obs = 2000
56 | df = make_ssm_data(n_obs=n_obs, mar=TRUE, return_type="data.table")
57 | dml_data = DoubleMLData$new(df, y_col="y", d_cols="d", s_col="s")
58 |
59 | ml_g = lrn("regr.cv_glmnet", nfolds = 5, s = "lambda.min")
60 | ml_m = lrn("classif.cv_glmnet", nfolds = 5, s = "lambda.min")
61 | ml_pi = lrn("classif.cv_glmnet", nfolds = 5, s = "lambda.min")
62 |
63 | dml_ssm = DoubleMLSSM$new(dml_data, ml_g, ml_m, ml_pi, score="missing-at-random")
64 | dml_ssm$fit()
65 | print(dml_ssm)
66 |
67 |
68 | .. _ssm-nr-model:
69 |
70 | Nonignorable Nonresponse
71 | ************************
72 |
73 | When sample selection or outcome attriction is realated to unobservables, identification generally requires an instrument for the selection indicator :math:`S_i`.
74 | Consider the following additional assumptions for the instrumental variable:
75 |
76 | - **Cond. Correlation:** :math:`\exists Z: \mathbb{E}[Z\cdot S|D,X] \neq 0`
77 | - **Cond. Independence:** :math:`Y_i(d,z)=Y_i(d)` and :math:`Y_i \perp Z_i|D_i=d, X_i\quad a.s.` for :math:`d=0,1`
78 |
79 | This requires the instrumental variable :math:`Z_i`, which must not affect :math:`Y_i` or be associated
80 | with unobservables affecting :math:`Y_i` conditional on :math:`D_i` and :math:`X_i`. Further, the selection is determined via
81 | a (unknown) threshold model:
82 |
83 | - **Threshold:** :math:`S_i = 1\{V_i \le \xi(D,X,Z)\}` where :math:`\xi` is a general function and :math:`V_i` is a scalar with strictly monotonic cumulative distribution function conditional on :math:`X_i`.
84 | - **Cond. Independence:** :math:`V_i \perp (Z_i, D_i)|X_i`.
85 |
86 | Let :math:`\Pi_i := P(S_i=1|D_i, X_i, Z_i)` denote the selection probability.
87 | Additionally, the following assumptions are required:
88 |
89 | - **Common Support for Treatment:** :math:`P(D_i=1|X_i, \Pi)>0`
90 | - **Cond. Effect Homogeneity:** :math:`\mathbb{E}[Y_i(1)-Y_i(0)|S_i=1, X_i=x, V_i=v] = \mathbb{E}[Y_i(1)-Y_i(0)|X_i=x, V_i=v]`
91 | - **Common Support for Selection:** :math:`P(S_i=1|D_i=d, X_i=x, Z_i=z)>0\quad a.s.` for :math:`d=0,1`
92 |
93 | For further details, see `Bia, Huber and Lafférs (2023) `_.
94 |
95 | .. figure:: /guide/figures/py_ssm.svg
96 | :width: 400
97 | :alt: DAG
98 | :align: center
99 |
100 | Causal paths under nonignorable nonresponse
101 |
102 |
103 | ``DoubleMLSSM`` implements sample selection models. The score ``score='nonignorable'`` refers to the correponding score
104 | relying on the assumptions above. The ``DoubleMLData`` object has to be defined with the additional argument ``s_col`` for the selection indicator
105 | and ``z_cols`` for the instrument.
106 | Estimation is conducted via its ``fit()`` method:
107 |
108 | .. tab-set::
109 |
110 | .. tab-item:: Python
111 | :sync: py
112 |
113 | .. ipython:: python
114 | :okwarning:
115 |
116 | import numpy as np
117 | from sklearn.linear_model import LassoCV, LogisticRegressionCV
118 | from doubleml.irm.datasets import make_ssm_data
119 | import doubleml as dml
120 |
121 | np.random.seed(42)
122 | n_obs = 2000
123 | df = make_ssm_data(n_obs=n_obs, mar=False, return_type='DataFrame')
124 | dml_data = dml.DoubleMLSSMData(df, 'y', 'd', z_cols='z', s_col='s')
125 |
126 | ml_g = LassoCV()
127 | ml_m = LogisticRegressionCV(penalty='l1', solver='liblinear')
128 | ml_pi = LogisticRegressionCV(penalty='l1', solver='liblinear')
129 |
130 | dml_ssm = dml.DoubleMLSSM(dml_data, ml_g, ml_m, ml_pi, score='nonignorable')
131 | dml_ssm.fit()
132 | print(dml_ssm)
133 |
134 | .. tab-item:: R
135 | :sync: r
136 |
137 | .. jupyter-execute::
138 |
139 | library(DoubleML)
140 | library(mlr3)
141 | library(data.table)
142 |
143 | set.seed(3141)
144 | n_obs = 2000
145 | df = make_ssm_data(n_obs=n_obs, mar=FALSE, return_type="data.table")
146 | dml_data = DoubleMLData$new(df, y_col="y", d_cols="d", z_cols = "z", s_col="s")
147 |
148 | ml_g = lrn("regr.cv_glmnet", nfolds = 5, s = "lambda.min")
149 | ml_m = lrn("classif.cv_glmnet", nfolds = 5, s = "lambda.min")
150 | ml_pi = lrn("classif.cv_glmnet", nfolds = 5, s = "lambda.min")
151 |
152 | dml_ssm = DoubleMLSSM$new(dml_data, ml_g, ml_m, ml_pi, score="nonignorable")
153 | dml_ssm$fit()
154 | print(dml_ssm)
--------------------------------------------------------------------------------