From 762e0e4caf62ab001103e987f157c9d0f2c982cf Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Wed, 31 Mar 2021 12:50:50 -0400 Subject: [PATCH 01/27] Implement DynamicDML * DynamicDML with all the OrthoLearner functionality * API tests similar to the DML ones --- econml/_cate_estimator.py | 2 +- econml/dml/__init__.py | 7 +- econml/dml/dynamic_dml.py | 660 +++++++++++++++++++++++++++++++ econml/tests/test_dynamic_dml.py | 258 ++++++++++++ 4 files changed, 925 insertions(+), 2 deletions(-) create mode 100644 econml/dml/dynamic_dml.py create mode 100644 econml/tests/test_dynamic_dml.py diff --git a/econml/_cate_estimator.py b/econml/_cate_estimator.py index 787dc1045..f9333b1bd 100644 --- a/econml/_cate_estimator.py +++ b/econml/_cate_estimator.py @@ -563,7 +563,7 @@ def effect(self, X=None, *, T0, T1): """ Calculate the heterogeneous treatment effect :math:`\\tau(X, T0, T1)`. - The effect is calculatred between the two treatment points + The effect is calculated between the two treatment points conditional on a vector of features on a set of m test samples :math:`\\{T0_i, T1_i, X_i\\}`. Since this class assumes a linear effect, only the difference between T0ᵢ and T1ᵢ matters for this computation. diff --git a/econml/dml/__init__.py b/econml/dml/__init__.py index a88d3b693..2c3785d34 100644 --- a/econml/dml/__init__.py +++ b/econml/dml/__init__.py @@ -33,10 +33,14 @@ Orthogonal Statistical Learning. ACM Conference on Learning Theory. ``_ +.. [dynamicdml] Greg Lewis and Vasilis Syrgkanis. + Double/Debiased Machine Learning for Dynamic Treatment Effects. + ``_, 2021. """ from .dml import (DML, LinearDML, SparseLinearDML, KernelDML, NonParamDML, ForestDML) +from .dynamic_dml import DynamicDML from .causal_forest import CausalForestDML __all__ = ["DML", @@ -45,4 +49,5 @@ "KernelDML", "NonParamDML", "ForestDML", - "CausalForestDML", ] + "CausalForestDML", + "DynamicDML"] diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py new file mode 100644 index 000000000..b5cd6d917 --- /dev/null +++ b/econml/dml/dynamic_dml.py @@ -0,0 +1,660 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import abc +import numpy as np +from warnings import warn +from sklearn.base import clone +from sklearn.model_selection import GroupKFold +from scipy.stats import norm +from sklearn.linear_model import (ElasticNetCV, LassoCV, LogisticRegressionCV) +from ..sklearn_extensions.linear_model import (StatsModelsLinearRegression, WeightedLassoCVWrapper) +from ..sklearn_extensions.model_selection import WeightedStratifiedKFold +from .dml import _FirstStageWrapper, _FinalWrapper +from .._cate_estimator import TreatmentExpansionMixin, LinearModelFinalCateEstimatorMixin +from .._ortho_learner import _OrthoLearner +from ..utilities import (_deprecate_positional, add_intercept, + broadcast_unit_treatments, check_high_dimensional, + cross_product, deprecated, fit_with_groups, + hstack, inverse_onehot, ndim, reshape, + reshape_treatmentwise_effects, shape, transpose, + get_feature_names_or_default) + + +class _DynamicModelNuisance: + """ + Nuisance model fits the model_y and model_t at fit time and at predict time + calculates the residual Y and residual T based on the fitted models and returns + the residuals as two nuisance parameters. + """ + + def __init__(self, model_y, model_t, n_periods): + self._model_y = model_y + self._model_t = model_t + self.n_periods = n_periods + + def fit(self, Y, T, X=None, W=None, sample_weight=None, groups=None): + """Fit a series of nuisance models for each period or period pairs""" + assert Y.shape[0] % self.n_periods == 0, \ + "Length of training data should be an integer multiple of time periods." + inds_train = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] + self._model_y_trained = {} + self._model_t_trained = {} + for kappa in np.arange(self.n_periods): + self._model_y_trained[kappa] = clone(self._model_y, safe=False).fit( + self._filter_or_None(X, inds_train + kappa), + self._filter_or_None( + W, inds_train + kappa), + Y[inds_train + self.n_periods - 1]) + self._model_t_trained[kappa] = {} + for tau in np.arange(kappa, self.n_periods): + self._model_t_trained[kappa][tau] = clone(self._model_t, safe=False).fit( + self._filter_or_None(X, inds_train + kappa), + self._filter_or_None(W, inds_train + kappa), + T[inds_train + tau]) + return self + + def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): + """Calculate nuisances for each period or period pairs. + + Returns + ------- + Y_res : (n, d_y) matrix or vector of length n + Y residuals for each period in panel format. + This shape is required for _OrthoLearner's crossfitting. + T_res : (n, d_t, n_periods) matrix + T residuals for pairs of periods (kappa, tau), where the data is in panel format for kappa + and in index form for tau. For example, the residuals for (kappa, tau) can be retrieved via + T_res[np.arange(n) % n_periods == kappa, ..., tau]. For tau < kappa, the entries of this + matrix are np.nan. + This shape is required for _OrthoLearner's crossfitting. + """ + assert Y.shape[0] % self.n_periods == 0, \ + "Length of training data should be an integer multiple of time periods." + inds_predict = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] + Y_res = np.full(Y.shape, np.nan) + T_res = np.full(T.shape + (self.n_periods, ), np.nan) + shape_formatter = self._get_shape_formatter(X, W) + for kappa in np.arange(self.n_periods): + Y_slice = Y[inds_predict + self.n_periods - 1] + Y_pred = self._model_y_trained[kappa].predict( + self._filter_or_None(X, inds_predict + kappa), + self._filter_or_None(W, inds_predict + kappa)) + Y_res[np.arange(Y.shape[0]) % self.n_periods == kappa] = Y_slice\ + - shape_formatter(Y_slice, Y_pred).reshape(Y_slice.shape) + for tau in np.arange(kappa, self.n_periods): + T_slice = T[inds_predict + tau] + T_pred = self._model_t_trained[kappa][tau].predict( + self._filter_or_None(X, inds_predict + kappa), + self._filter_or_None(W, inds_predict + kappa)) + T_res[np.arange(Y.shape[0]) % self.n_periods == kappa, ..., tau] = T_slice\ + - shape_formatter(T_slice, T_pred).reshape(T_slice.shape) + return Y_res, T_res + + def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): + # TODO: implement scores + # TODO: fix correctness? + assert Y.shape[0] % self.n_periods == 0, \ + "Length of training data should be an integer multiple of time periods." + inds_score = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] + if hasattr(self._model_y, 'score'): + Y_score = np.full((self.n_periods, ), np.nan) + for kappa in np.arange(self.n_periods): + Y_score[kappa] = self._model_y_trained[kappa].score( + self._filter_or_None(X, inds_score + kappa), + self._filter_or_None(W, inds_score + kappa), + Y[inds_score + self.n_periods - 1]) + else: + Y_score = None + if hasattr(self._model_t, 'score'): + T_score = np.full((self.n_periods, self.n_periods), np.nan) + for kappa in np.arange(self.n_periods): + for tau in np.arange(kappa, self.n_periods): + T_score[kappa][tau] = self._model_t_trained[kappa][tau].score( + self._filter_or_None(X, inds_score + kappa), + self._filter_or_None(W, inds_score + kappa), + T[inds_score + tau]) + else: + T_score = None + return Y_score, T_score + + def _get_shape_formatter(self, X, W): + if (X is None) and (W is None): + return lambda x, x_pred: np.tile(x_pred.reshape(1, -1), (x.shape[0], 1)) + return lambda x, x_pred: x_pred + + def _filter_or_None(self, X, filter_idx): + return None if X is None else X[filter_idx] + + +class _DynamicModelFinal: + """ + Final model at fit time, fits a residual on residual regression with a heterogeneous coefficient + that depends on X, i.e. + + .. math :: + Y - E[Y | X, W] = \\theta(X) \\cdot (T - E[T | X, W]) + \\epsilon + + and at predict time returns :math:`\\theta(X)`. The score method returns the MSE of this final + residual on residual regression. + Assumes model final is parametric with no intercept. + """ + # TODO: update docs + + def __init__(self, model_final, n_periods): + self._model_final = model_final + self.n_periods = n_periods + self._model_final_trained = {k: clone(self._model_final, safe=False) for k in np.arange(n_periods)} + + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + # TODO: handle sample weight, sample var + Y_res, T_res = nuisances + self._d_y = Y.shape[1:] + for kappa in np.arange(self.n_periods): + period = self.n_periods - 1 - kappa + period_filter = self.period_filter_gen(period, Y.shape[0]) + Y_adj = Y_res[period_filter].copy() + if kappa > 0: + Y_adj -= np.sum( + [self._model_final_trained[tau].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - tau, Y.shape[0])] if X is not None else None, + T_res[period_filter, ..., self.n_periods - 1 - tau] + ) for tau in np.arange(kappa)], axis=0) + self._model_final_trained[kappa].fit( + X[period_filter] if X is not None else None, T[period_filter], + T_res[period_filter, ..., period], Y_adj) + + return self + + def predict(self, X=None): + """ + Return shape: m x dy x (p*dt) + """ + d_t_tuple = self._model_final_trained[0]._d_t + d_t = d_t_tuple[0] if d_t_tuple else 1 + x_dy_shape = (X.shape[0] if X is not None else 1, ) + \ + self._model_final_trained[0]._d_y + preds = np.zeros( + x_dy_shape + + (self.n_periods * d_t, ) + ) + for kappa in range(self.n_periods): + preds[..., kappa * d_t: (kappa + 1) * d_t] = \ + self._model_final_trained[kappa].predict(X).reshape( + x_dy_shape + (d_t, ) + ) + return preds + + def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + # TODO: implement score + return None + + def period_filter_gen(self, p, n): + return (np.arange(n) % self.n_periods == p) + + +class _LinearDynamicModelFinal(_DynamicModelFinal): + """Wrapper for the DynamicModelFinal with StatsModelsLinearRegression final model. + + The final model is a linear model with (d_t*n_periods) coefficients. + This model is defined after the coefficients and covariance are calculated. + """ + + def __init__(self, model_final, n_periods): + super().__init__(model_final, n_periods) + self.model_final_ = StatsModelsLinearRegression(fit_intercept=False) + + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + super().fit(Y, T, X=X, W=W, Z=Z, nuisances=nuisances, sample_weight=sample_weight, sample_var=sample_var) + # Compose final model + cov = self._get_cov(nuisances, X) + coef = self._get_coef_() + self.model_final_._n_out = self._d_y[0] if self._d_y else 0 + self.model_final_._param_var = cov / (Y.shape[0] / self.n_periods) + self.model_final_._param = coef.T if self.model_final_._n_out else coef + + def _get_coef_(self): + period_coefs = np.array([self._model_final_trained[kappa]._model.coef_ for kappa in range(self.n_periods)]) + if self._d_y: + return np.array([ + np.array([period_coefs[k, i, :] for k in range(self.n_periods)]).flatten() + for i in range(self._d_y[0]) + ]) + return period_coefs.flatten() + + def _get_cov(self, nuisances, X): + if self._d_y: + return np.array( + [self._fit_single_output_cov((nuisances[0][:, i], nuisances[1]), X, i) for i in range(self._d_y[0])] + ) + return self._fit_single_output_cov(nuisances, X, -1) + + def _fit_single_output_cov(self, nuisances, X, y_index): + """ Calculates the covariance (n_periods*n_treatments) + x (n_periods*n_treatments) matrix for a single outcome. + """ + Y_res, T_res = nuisances + XT_res = np.array([ + [ + self._model_final_trained[0]._combine( + X[self.period_filter_gen(tau, Y_res.shape[0])] if X is not None else None, + T_res[self.period_filter_gen(kappa, Y_res.shape[0]), ..., tau], + fitting=False + ) + for tau in range(self.n_periods) + ] + for kappa in range(self.n_periods) + ]) + d_xt = XT_res.shape[-1] + M = np.zeros((self.n_periods * d_xt, + self.n_periods * d_xt)) + Sigma = np.zeros((self.n_periods * d_xt, + self.n_periods * d_xt)) + for kappa in np.arange(self.n_periods): + # Calculating the (kappa, kappa) block entry (of size n_treatments x n_treatments) of matrix Sigma + period = self.n_periods - 1 - kappa + period_filter = self.period_filter_gen(period, Y_res.shape[0]) + Y_diff = np.sum([ + self._model_final_trained[tau].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - tau, + Y_res.shape[0])] if X is not None else None, + T_res[period_filter, ..., self.n_periods - 1 - tau]) + for tau in np.arange(kappa + 1) + ], axis=0) + res_epsilon = (Y_res[period_filter] - + (Y_diff[:, y_index] if y_index >= 0 else Y_diff) + ).reshape(-1, 1, 1) + cur_resT = XT_res[period][period] + cov_cur_resT = cur_resT.reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt) + sigma_kappa = np.mean((res_epsilon**2) * cov_cur_resT, axis=0) + Sigma[kappa * d_xt:(kappa + 1) * d_xt, + kappa * d_xt:(kappa + 1) * d_xt] = sigma_kappa + for tau in np.arange(kappa + 1): + # Calculating the (kappa, tau) block entry (of size n_treatments x n_treatments) of matrix M + m_kappa_tau = np.mean( + XT_res[period][self.n_periods - 1 - tau].reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt), + axis=0) + M[kappa * d_xt:(kappa + 1) * d_xt, + tau * d_xt:(tau + 1) * d_xt] = m_kappa_tau + return np.linalg.inv(M) @ Sigma @ np.linalg.inv(M).T + + +class _DynamicFinalWrapper(_FinalWrapper): + + def predict_with_res(self, X, T_res): + fts = self._combine(X, T_res, fitting=False) + prediction = self._model.predict(fts) + if self._intercept is not None: + prediction -= self._intercept + return reshape(prediction, (prediction.shape[0],) + self._d_y) + + +class DynamicDML(LinearModelFinalCateEstimatorMixin, _OrthoLearner): + """CATE estimator for dynamic treatment effect estimation. + + This estimator is an extension of the Double ML approach for treatments assigned sequentially + over time periods. + + The estimator is a special case of an :class:`_OrthoLearner` estimator, so it follows the two + stage process, where a set of nuisance functions are estimated in the first stage in a crossfitting + manner and a final stage estimates the CATE model. See the documentation of + :class:`._OrthoLearner` for a description of this two stage process. + + Parameters + ---------- + model_y: estimator or 'auto', optional (default is 'auto') + The estimator for fitting the response to the features. Must implement + `fit` and `predict` methods. + If 'auto' :class:`.WeightedLassoCV`/:class:`.WeightedMultiTaskLassoCV` will be chosen. + + model_t: estimator or 'auto', optional (default is 'auto') + The estimator for fitting the treatment to the features. + If estimator, it must implement `fit` and `predict` methods; + If 'auto', :class:`~sklearn.linear_model.LogisticRegressionCV` will be applied for discrete treatment, + and :class:`.WeightedLassoCV`/:class:`.WeightedMultiTaskLassoCV` + will be applied for continuous treatment. + + featurizer : :term:`transformer`, optional, default None + Must support fit_transform and transform. Used to create composite features in the final CATE regression. + It is ignored if X is None. The final CATE will be trained on the outcome of featurizer.fit_transform(X). + If featurizer=None, then CATE is trained on X. + + fit_cate_intercept : bool, optional, default True + Whether the linear CATE model should have a constant term. + + linear_first_stages: bool + Whether the first stage models are linear (in which case we will expand the features passed to + `model_y` accordingly) + + discrete_treatment: bool, optional (default is ``False``) + Whether the treatment values should be treated as categorical, rather than continuous, quantities + + categories: 'auto' or list, default 'auto' + The categories to use when encoding discrete treatments (or 'auto' to use the unique sorted values). + The first category will be treated as the control treatment. + + cv: int, cross-validation generator or an iterable, optional (Default=2) + Determines the cross-validation splitting strategy. + Possible inputs for cv are: + + - None, to use the default 3-fold cross-validation, + - integer, to specify the number of folds. + - :term:`CV splitter` + - An iterable yielding (train, test) splits as arrays of indices. + Iterables should make sure a group belongs to a single split. + + For integer/None inputs, :class:`~sklearn.model_selection.GroupKFold` is used + + Unless an iterable is used, we call `split(X, T, groups)` to generate the splits. + + mc_iters: int, optional (default=None) + The number of times to rerun the first stage models to reduce the variance of the nuisances. + + mc_agg: {'mean', 'median'}, optional (default='mean') + How to aggregate the nuisance value for each sample across the `mc_iters` monte carlo iterations of + cross-fitting. + + random_state: int, :class:`~numpy.random.mtrand.RandomState` instance or None, optional (default=None) + If int, random_state is the seed used by the random number generator; + If :class:`~numpy.random.mtrand.RandomState` instance, random_state is the random number generator; + If None, the random number generator is the :class:`~numpy.random.mtrand.RandomState` instance used + by :mod:`np.random`. + + Examples + -------- + A simple example with default models: + + .. testcode:: + :hide: + + import numpy as np + np.set_printoptions(suppress=True) + + .. testcode:: + + from econml.dml import DynamicDML + + np.random.seed(123) + + n_panels = 100 # number of panels + n_periods = 3 # number of time periods per panel + n = n_panels * n_periods + groups = np.repeat(a=np.arange(n_panels), repeats=n_periods, axis=0) + X = np.random.normal(size=(n, 1)) + T = np.random.normal(size=(n, 2)) + y = np.random.normal(size=(n, )) + est = DynamicDML() + est.fit(y, T, X=X, W=None, groups=groups, inference="auto") + + >>> est.const_marginal_effect(X[:2]) + array([[-0.012..., 0.031..., 0.069..., 0.111..., -0.349..., + -0.076...], + [-0.411..., -0.088..., 0.021..., -0.171..., -0.126... , + 0.397...]]) + >>> est.effect(X[:2], T0=0, T1=1) + array([-0.225..., -0.378...]) + >>> est.effect(X[:2], T0=np.zeros((2, n_periods*T.shape[1])), T1=np.ones((2, n_periods*T.shape[1]))) + array([-0.225..., -0.378...]) + >>> est.coef_ + array([[-0.191...], + [-0.057...], + [-0.023...], + [-0.136...], + [ 0.107...], + [ 0.227...]]) + >>> est.coef__interval() + (array([[-0.333...], + [-0.171...], + [-0.158...], + [-0.352...], + [-0.045...], + [ 0.049...]]), + array([[-0.050...], + [ 0.056...], + [ 0.112...], + [ 0.079...], + [ 0.260...], + [ 0.405...]])) + """ + + def __init__(self, *, + model_y='auto', model_t='auto', + featurizer=None, + fit_cate_intercept=True, + linear_first_stages=False, + discrete_treatment=False, + categories='auto', + cv=2, + mc_iters=None, + mc_agg='mean', + random_state=None): + self.fit_cate_intercept = fit_cate_intercept + self.linear_first_stages = linear_first_stages + self.featurizer = clone(featurizer, safe=False) + self.model_y = clone(model_y, safe=False) + self.model_t = clone(model_t, safe=False) + super().__init__(discrete_treatment=discrete_treatment, + discrete_instrument=False, + categories=categories, + cv=GroupKFold(cv) if isinstance(cv, int) else cv, + mc_iters=mc_iters, + mc_agg=mc_agg, + random_state=random_state) + + def _gen_featurizer(self): + return clone(self.featurizer, safe=False) + + def _gen_model_y(self): + if self.model_y == 'auto': + model_y = WeightedLassoCVWrapper(random_state=self.random_state) + else: + model_y = clone(self.model_y, safe=False) + return _FirstStageWrapper(model_y, True, self._gen_featurizer(), + self.linear_first_stages, self.discrete_treatment) + + def _gen_model_t(self): + if self.model_t == 'auto': + if self.discrete_treatment: + model_t = LogisticRegressionCV(cv=WeightedStratifiedKFold(random_state=self.random_state), + random_state=self.random_state) + else: + model_t = WeightedLassoCVWrapper(random_state=self.random_state) + else: + model_t = clone(self.model_t, safe=False) + return _FirstStageWrapper(model_t, False, self._gen_featurizer(), + self.linear_first_stages, self.discrete_treatment) + + def _gen_model_final(self): + return StatsModelsLinearRegression(fit_intercept=False) + + def _gen_ortho_learner_model_nuisance(self, n_periods): + return _DynamicModelNuisance( + model_t=self._gen_model_t(), + model_y=self._gen_model_y(), + n_periods=n_periods) + + def _gen_ortho_learner_model_final(self, n_periods): + wrapped_final_model = _DynamicFinalWrapper( + StatsModelsLinearRegression(fit_intercept=False), + fit_cate_intercept=self.fit_cate_intercept, + featurizer=self.featurizer, + use_weight_trick=False) + return _LinearDynamicModelFinal(wrapped_final_model, n_periods=n_periods) + + def _prefit(self, Y, T, *args, groups=None, only_final=False, **kwargs): + u_periods = np.unique(np.bincount(groups.astype(int))) + if len(u_periods) > 1: + raise AttributeError( + "Imbalanced panel. Method currently expects only panels with equal number of periods. Pad your data") + self._n_periods = u_periods[0] + # generate an instance of the final model + self._ortho_learner_model_final = self._gen_ortho_learner_model_final(self._n_periods) + if not only_final: + # generate an instance of the nuisance model + self._ortho_learner_model_nuisance = self._gen_ortho_learner_model_nuisance(self._n_periods) + TreatmentExpansionMixin._prefit(self, Y, T, *args, **kwargs) + + def _postfit(self, Y, T, *args, **kwargs): + super()._postfit(Y, T, *args, **kwargs) + # Set _d_t to effective number of treatments + self._d_t = (self._n_periods * self._d_t[0], ) if self._d_t else (self._n_periods, ) + + def _strata(self, Y, T, X=None, W=None, Z=None, + sample_weight=None, sample_var=None, groups=None, + cache_values=False, only_final=False, check_input=True): + # Required for bootstrap inference + return groups + + @_deprecate_positional("X, and should be passed by keyword only. In a future release " + "we will disallow passing X and W by position.", ['X', 'W']) + def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, groups, + cache_values=False, inference=None): + """ + Estimate the counterfactual model from data, i.e. estimates function :math:`\\theta(\\cdot)`. + + The input data has to be in panel format, i.e. a sequence of groups, each with the same size corresponding + to the number of time periods the treatments were assigned over. + + Parameters + ---------- + Y: (n, d_y) matrix or vector of length n + Outcomes for each sample (required: n = n_groups * n_periods) + T: (n, d_t) matrix or vector of length n + Treatments for each sample (required: n = n_groups * n_periods) + X: optional(n, d_x) matrix or None (Default=None) + Features for each sample (Required: n = n_groups * n_periods) + W: optional(n, d_w) matrix or None (Default=None) + Controls for each sample (Required: n = n_groups * n_periods) + sample_weight: optional(n,) vector or None (Default=None) + Weights for each samples + sample_var: optional(n,) vector or None (Default=None) + Sample variance for each sample + groups: (n,) vector, required + All rows corresponding to the same group will be kept together during splitting. + If groups is not None, the `cv` argument passed to this class's initializer + must support a 'groups' argument to its split method. + cache_values: bool, default False + Whether to cache inputs and first stage results, which will allow refitting a different final model + inference: string,:class:`.Inference` instance, or None + Method for performing inference. This estimator supports 'bootstrap' + (or an instance of :class:`.BootstrapInference`) and 'auto' + (or an instance of :class:`.LinearModelFinalInference`). + + Returns + ------- + self: DynamicDML instance + """ + if sample_weight is not None or sample_var is not None: + warn("This CATE estimator does not yet support sample weights and sample variance. " + "These inputs will be ignored during fitting.", + UserWarning) + # TODO: support sample_weight, sample_var? + return super().fit(Y, T, X=X, W=W, + sample_weight=None, sample_var=None, groups=groups, + cache_values=cache_values, + inference=inference) + + def cate_treatment_names(self, treatment_names=None): + """ + Get treatment names for each time period. + + If the treatment is discrete, it will return expanded treatment names. + + Parameters + ---------- + treatment_names: list of strings of length T.shape[1] or None + The names of the treatments. If None and the T passed to fit was a dataframe, + it defaults to the column names from the dataframe. + + Returns + ------- + out_treatment_names: list of strings + Returns (possibly expanded) treatment names. + """ + slice_treatment_names = super().cate_treatment_names(treatment_names) + treatment_names_out = [] + for k in range(self._n_periods): + treatment_names_out += [f"$({t})_{k}$" for t in slice_treatment_names] + return treatment_names_out + + def cate_feature_names(self, feature_names=None): + """ + Get the output feature names. + + Parameters + ---------- + feature_names: list of strings of length X.shape[1] or None + The names of the input features. If None and X is a dataframe, it defaults to the column names + from the dataframe. + + Returns + ------- + out_feature_names: list of strings or None + The names of the output features :math:`\\phi(X)`, i.e. the features with respect to which the + final constant marginal CATE model is linear. It is the names of the features that are associated + with each entry of the :meth:`coef_` parameter. Not available when the featurizer is not None and + does not have a method: `get_feature_names(feature_names)`. Otherwise None is returned. + """ + if self._d_x is None: + # Handles the corner case when X=None but featurizer might be not None + return None + if feature_names is None: + feature_names = self._input_names["feature_names"] + if self.original_featurizer is None: + return feature_names + return get_feature_names_or_default(self.original_featurizer, feature_names) + + def _expand_treatments(self, X, *Ts): + # Expand treatments for each time period + outTs = [] + base_expand_treatments = super()._expand_treatments + for T in Ts: + if ndim(T) == 0: + one_T = base_expand_treatments(X, T)[1] + one_T = one_T.reshape(-1, 1) if ndim(one_T) == 1 else one_T + T = np.tile(one_T, (1, self._n_periods, )) + else: + assert (T.shape[1] == self._n_periods if self.transformer else T.shape[1] == self._d_t[0]), \ + f"Expected a list of time period * d_t, instead got a treatment array of shape {T.shape}." + if self.transformer: + T = np.hstack([ + base_expand_treatments( + X, T[:, [kappa]])[1] for kappa in range(self._n_periods) + ]) + outTs.append(T) + return (X,) + tuple(outTs) + + @property + def bias_part_of_coef(self): + return self.ortho_learner_model_final_._model_final._fit_cate_intercept + + @property + def fit_cate_intercept_(self): + return self.ortho_learner_model_final_._model_final._fit_cate_intercept + + @property + def original_featurizer(self): + # NOTE: important to use the rlearner_model_final_ attribute instead of the + # attribute so that the trained featurizer will be passed through + return self.ortho_learner_model_final_._model_final_trained[0]._original_featurizer + + @property + def featurizer_(self): + # NOTE This is used by the inference methods and has to be the overall featurizer. intended + # for internal use by the library + return self.ortho_learner_model_final_._model_final_trained[0]._featurizer + + @property + def model_final_(self): + # NOTE This is used by the inference methods and is more for internal use to the library + # We need to use the rlearner's copy to retain the information from fitting + return self.ortho_learner_model_final_.model_final_ + + @property + def model_final(self): + return self._gen_model_final() + + @model_final.setter + def model_final(self, model): + if model is not None: + raise ValueError("Parameter `model_final` cannot be altered for this estimator!") diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py new file mode 100644 index 000000000..bb5aa8eac --- /dev/null +++ b/econml/tests/test_dynamic_dml.py @@ -0,0 +1,258 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest +import pytest +import pickle +import numpy as np +from contextlib import ExitStack +from sklearn.preprocessing import OneHotEncoder, FunctionTransformer, PolynomialFeatures +from sklearn.linear_model import (LinearRegression, LassoCV, Lasso, MultiTaskLasso, + MultiTaskLassoCV, LogisticRegression) +from econml.dml import DynamicDML +from econml.inference import BootstrapInference, EmpiricalInferenceResults, NormalInferenceResults +from econml.utilities import shape, hstack, vstack, reshape, cross_product +import econml.tests.utilities # bugfix for assertWarns + + +class TestDynamicDML(unittest.TestCase): + + def test_cate_api(self): + """Test that we correctly implement the CATE API.""" + n_panels = 100 # number of panels + n_periods = 3 # number of time periods per panel + n = n_panels * n_periods + groups = np.repeat(a=np.arange(n_panels), repeats=n_periods, axis=0) + + def make_random(n, is_discrete, d): + if d is None: + return None + sz = (n, d) if d >= 0 else (n,) + if is_discrete: + return np.random.choice(['a', 'b', 'c'], size=sz) + else: + return np.random.normal(size=sz) + + for d_t in [2, 1, -1]: + for is_discrete in [True, False] if d_t <= 1 else [False]: + # for is_discrete in [False]: + for d_y in [3, 1, -1]: + for d_x in [2, None]: + for d_w in [2, None]: + W, X, Y, T = [make_random(n, is_discrete, d) + for is_discrete, d in [(False, d_w), + (False, d_x), + (False, d_y), + (is_discrete, d_t)]] + T_test = np.hstack([(T.reshape(-1, 1) if d_t == -1 else T) for i in range(n_periods)]) + for featurizer, fit_cate_intercept in\ + [(None, True), + (PolynomialFeatures(degree=2, include_bias=False), True), + (PolynomialFeatures(degree=2, include_bias=True), False)]: + + d_t_final = (2 if is_discrete else max(d_t, 1)) * n_periods + + effect_shape = (n,) + ((d_y,) if d_y > 0 else ()) + effect_summaryframe_shape = (n * (d_y if d_y > 0 else 1), 6) + marginal_effect_shape = ((n,) + + ((d_y,) if d_y > 0 else ()) + + ((d_t_final,) if d_t_final > 0 else ())) + marginal_effect_summaryframe_shape = (n * (d_y if d_y > 0 else 1) * + (d_t_final if d_t_final > 0 else 1), 6) + + # since T isn't passed to const_marginal_effect, defaults to one row if X is None + const_marginal_effect_shape = ((n if d_x else 1,) + + ((d_y,) if d_y > 0 else ()) + + ((d_t_final,) if d_t_final > 0 else())) + const_marginal_effect_summaryframe_shape = ( + (n if d_x else 1) * (d_y if d_y > 0 else 1) * + (d_t_final if d_t_final > 0 else 1), 6) + + fd_x = featurizer.fit_transform(X).shape[1:] if featurizer and d_x\ + else ((d_x,) if d_x else (0,)) + coef_shape = Y.shape[1:] + (d_t_final, ) + fd_x + + coef_summaryframe_shape = ( + (d_y if d_y > 0 else 1) * (fd_x[0] if fd_x[0] > + 0 else 1) * (d_t_final), 6) + intercept_shape = Y.shape[1:] + (d_t_final, ) + intercept_summaryframe_shape = ( + (d_y if d_y > 0 else 1) * (d_t_final if d_t_final > 0 else 1), 6) + + all_infs = [None, 'auto', BootstrapInference(2)] + #all_infs = [None, 'auto'] + est = DynamicDML(model_y=Lasso() if d_y < 1 else MultiTaskLasso(), + model_t=LogisticRegression() if is_discrete else + (Lasso() if d_t < 1 else MultiTaskLasso()), + featurizer=featurizer, + fit_cate_intercept=fit_cate_intercept, + discrete_treatment=is_discrete) + + # ensure we can serialize the unfit estimator + pickle.dumps(est) + + for inf in all_infs: + with self.subTest(d_w=d_w, d_x=d_x, d_y=d_y, d_t=d_t, + is_discrete=is_discrete, est=est, inf=inf): + + if X is None and (not fit_cate_intercept): + with pytest.raises(AttributeError): + est.fit(Y, T, X=X, W=W, groups=groups, inference=inf) + continue + + est.fit(Y, T, X=X, W=W, groups=groups, inference=inf) + + # ensure we can pickle the fit estimator + pickle.dumps(est) + + # make sure we can call the marginal_effect and effect methods + const_marg_eff = est.const_marginal_effect(X) + marg_eff = est.marginal_effect(T_test, X) + self.assertEqual(shape(marg_eff), marginal_effect_shape) + self.assertEqual(shape(const_marg_eff), const_marginal_effect_shape) + + np.testing.assert_allclose( + marg_eff if d_x else marg_eff[0:1], const_marg_eff) + + # TODO: add score and nuisance scores + """ + assert isinstance(est.score_, float) + for score in est.nuisance_scores_y: + assert isinstance(score, float) + for score in est.nuisance_scores_t: + assert isinstance(score, float) + """ + + T0 = np.full_like(T_test, 'a') if is_discrete else np.zeros_like(T_test) + eff = est.effect(X, T0=T0, T1=T_test) + self.assertEqual(shape(eff), effect_shape) + + self.assertEqual(shape(est.coef_), coef_shape) + if fit_cate_intercept: + self.assertEqual(shape(est.intercept_), intercept_shape) + else: + with pytest.raises(AttributeError): + self.assertEqual(shape(est.intercept_), intercept_shape) + + if inf is not None: + const_marg_eff_int = est.const_marginal_effect_interval(X) + marg_eff_int = est.marginal_effect_interval(T_test, X) + self.assertEqual(shape(marg_eff_int), + (2,) + marginal_effect_shape) + self.assertEqual(shape(const_marg_eff_int), + (2,) + const_marginal_effect_shape) + self.assertEqual(shape(est.effect_interval(X, T0=T0, T1=T_test)), + (2,) + effect_shape) + self.assertEqual(shape(est.coef__interval()), + (2,) + coef_shape) + if fit_cate_intercept: + self.assertEqual(shape(est.intercept__interval()), + (2,) + intercept_shape) + else: + with pytest.raises(AttributeError): + self.assertEqual(shape(est.intercept__interval()), + (2,) + intercept_shape) + + const_marg_effect_inf = est.const_marginal_effect_inference(X) + T1 = np.full_like(T_test, 'b') if is_discrete else T_test + effect_inf = est.effect_inference(X, T0=T0, T1=T1) + marg_effect_inf = est.marginal_effect_inference(T_test, X) + # test const marginal inference + self.assertEqual(shape(const_marg_effect_inf.summary_frame()), + const_marginal_effect_summaryframe_shape) + self.assertEqual(shape(const_marg_effect_inf.point_estimate), + const_marginal_effect_shape) + self.assertEqual(shape(const_marg_effect_inf.stderr), + const_marginal_effect_shape) + self.assertEqual(shape(const_marg_effect_inf.var), + const_marginal_effect_shape) + self.assertEqual(shape(const_marg_effect_inf.pvalue()), + const_marginal_effect_shape) + self.assertEqual(shape(const_marg_effect_inf.zstat()), + const_marginal_effect_shape) + self.assertEqual(shape(const_marg_effect_inf.conf_int()), + (2,) + const_marginal_effect_shape) + np.testing.assert_array_almost_equal( + const_marg_effect_inf.conf_int()[0], + const_marg_eff_int[0], decimal=5) + const_marg_effect_inf.population_summary()._repr_html_() + + # test effect inference + self.assertEqual(shape(effect_inf.summary_frame()), + effect_summaryframe_shape) + self.assertEqual(shape(effect_inf.point_estimate), + effect_shape) + self.assertEqual(shape(effect_inf.stderr), + effect_shape) + self.assertEqual(shape(effect_inf.var), + effect_shape) + self.assertEqual(shape(effect_inf.pvalue()), + effect_shape) + self.assertEqual(shape(effect_inf.zstat()), + effect_shape) + self.assertEqual(shape(effect_inf.conf_int()), + (2,) + effect_shape) + np.testing.assert_array_almost_equal( + effect_inf.conf_int()[0], + est.effect_interval(X, T0=T0, T1=T1)[0], decimal=5) + effect_inf.population_summary()._repr_html_() + + # test marginal effect inference + self.assertEqual(shape(marg_effect_inf.summary_frame()), + marginal_effect_summaryframe_shape) + self.assertEqual(shape(marg_effect_inf.point_estimate), + marginal_effect_shape) + self.assertEqual(shape(marg_effect_inf.stderr), + marginal_effect_shape) + self.assertEqual(shape(marg_effect_inf.var), + marginal_effect_shape) + self.assertEqual(shape(marg_effect_inf.pvalue()), + marginal_effect_shape) + self.assertEqual(shape(marg_effect_inf.zstat()), + marginal_effect_shape) + self.assertEqual(shape(marg_effect_inf.conf_int()), + (2,) + marginal_effect_shape) + np.testing.assert_array_almost_equal( + marg_effect_inf.conf_int()[0], marg_eff_int[0], decimal=5) + marg_effect_inf.population_summary()._repr_html_() + + # test coef__inference and intercept__inference + if X is not None: + self.assertEqual( + shape(est.coef__inference().summary_frame()), + coef_summaryframe_shape) + np.testing.assert_array_almost_equal( + est.coef__inference().conf_int() + [0], est.coef__interval()[0], decimal=5) + + if fit_cate_intercept: + cm = ExitStack() + # ExitStack can be used as a "do nothing" ContextManager + else: + cm = pytest.raises(AttributeError) + with cm: + self.assertEqual(shape(est.intercept__inference(). + summary_frame()), + intercept_summaryframe_shape) + np.testing.assert_array_almost_equal( + est.intercept__inference().conf_int() + [0], est.intercept__interval()[0], decimal=5) + + est.summary() + + # TODO: add score to estimator + """ + est.score(Y, T, X, W) + """ + # make sure we can call effect with implied scalar treatments, + # no matter the dimensions of T, and also that we warn when there + # are multiple treatments + if d_t > 1: + cm = self.assertWarns(Warning) + else: + # ExitStack can be used as a "do nothing" ContextManager + cm = ExitStack() + with cm: + effect_shape2 = (n if d_x else 1,) + ((d_y,) if d_y > 0 else()) + eff = est.effect(X) if not is_discrete else est.effect( + X, T0='a', T1='b') + self.assertEqual(shape(eff), effect_shape2) From 1070aea9648d671c3eed82b4b592af4f57e7550a Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Wed, 7 Apr 2021 09:39:40 -0400 Subject: [PATCH 02/27] Add performance tests and an example notebook --- econml/dml/dynamic_dml.py | 2 +- econml/tests/dgp.py | 156 ++++ econml/tests/test_dynamic_dml.py | 39 +- ...mic Double Machine Learning Examples.ipynb | 677 ++++++++++++++++++ 4 files changed, 872 insertions(+), 2 deletions(-) create mode 100644 econml/tests/dgp.py create mode 100644 notebooks/Dynamic Double Machine Learning Examples.ipynb diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index b5cd6d917..9d10e33da 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -482,7 +482,7 @@ def _gen_ortho_learner_model_final(self, n_periods): return _LinearDynamicModelFinal(wrapped_final_model, n_periods=n_periods) def _prefit(self, Y, T, *args, groups=None, only_final=False, **kwargs): - u_periods = np.unique(np.bincount(groups.astype(int))) + u_periods = np.unique(np.unique(groups, return_counts=True)[1]) if len(u_periods) > 1: raise AttributeError( "Imbalanced panel. Method currently expects only panels with equal number of periods. Pad your data") diff --git a/econml/tests/dgp.py b/econml/tests/dgp.py new file mode 100644 index 000000000..16e1f1aae --- /dev/null +++ b/econml/tests/dgp.py @@ -0,0 +1,156 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import numpy as np +from econml.utilities import cross_product +from statsmodels.tools.tools import add_constant + + +class _BaseDynamicPanelDGP: + + def __init__(self, n_periods, n_treatments, n_x): + self.n_periods = n_periods + self.n_treatments = n_treatments + self.n_x = n_x + return + + def create_instance(self, *args, **kwargs): + pass + + def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): + pass + + def static_policy_data(self, n_units, tau, random_seed=123): + def policy_gen(Tpre, X, period): + return tau[period] + return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) + + def adaptive_policy_data(self, n_units, policy_gen, random_seed=123): + return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) + + def static_policy_effect(self, tau, mc_samples=1000): + Y_tau, _, _, _ = self.static_policy_data(mc_samples, tau) + Y_zero, _, _, _ = self.static_policy_data( + mc_samples, np.zeros((self.n_periods, self.n_treatments))) + return np.mean(Y_tau[np.arange(Y_tau.shape[0]) % self.n_periods == self.n_periods - 1]) - \ + np.mean(Y_zero[np.arange(Y_zero.shape[0]) % + self.n_periods == self.n_periods - 1]) + + def adaptive_policy_effect(self, policy_gen, mc_samples=1000): + Y_tau, _, _, _ = self.adaptive_policy_data(mc_samples, policy_gen) + Y_zero, _, _, _ = self.static_policy_data( + mc_samples, np.zeros((self.n_periods, self.n_treatments))) + return np.mean(Y_tau[np.arange(Y_tau.shape[0]) % self.n_periods == self.n_periods - 1]) - \ + np.mean(Y_zero[np.arange(Y_zero.shape[0]) % + self.n_periods == self.n_periods - 1]) + + +class DynamicPanelDGP(_BaseDynamicPanelDGP): + + def __init__(self, n_periods, n_treatments, n_x): + super().__init__(n_periods, n_treatments, n_x) + + def create_instance(self, s_x, sigma_x=.5, sigma_y=.5, conf_str=5, hetero_strength=0, hetero_inds=None, + autoreg=.5, state_effect=.5, random_seed=123): + np.random.seed(random_seed) + self.s_x = s_x + self.conf_str = conf_str + self.sigma_x = sigma_x + self.sigma_y = sigma_y + self.hetero_inds = hetero_inds.astype( + int) if hetero_inds is not None else hetero_inds + self.endo_inds = np.setdiff1d( + np.arange(self.n_x), hetero_inds).astype(int) + # The first s_x state variables are confounders. The final s_x variables are exogenous and can create + # heterogeneity + self.Alpha = np.random.uniform(-1, 1, + size=(self.n_x, self.n_treatments)) + self.Alpha /= np.linalg.norm(self.Alpha, axis=1, ord=1, keepdims=True) + self.Alpha *= state_effect + if self.hetero_inds is not None: + self.Alpha[self.hetero_inds] = 0 + + self.Beta = np.zeros((self.n_x, self.n_x)) + for t in range(self.n_x): + self.Beta[t, :] = autoreg * np.roll(np.random.uniform(low=4.0**(-np.arange( + 0, self.n_x)), high=4.0**(-np.arange(1, self.n_x + 1))), t) + if self.hetero_inds is not None: + self.Beta[np.ix_(self.endo_inds, self.hetero_inds)] = 0 + self.Beta[np.ix_(self.hetero_inds, self.endo_inds)] = 0 + + self.epsilon = np.random.uniform(-1, 1, size=self.n_treatments) + self.zeta = np.zeros(self.n_x) + self.zeta[:self.s_x] = self.conf_str / self.s_x + + self.y_hetero_effect = np.zeros(self.n_x) + self.x_hetero_effect = np.zeros(self.n_x) + if self.hetero_inds is not None: + self.y_hetero_effect[self.hetero_inds] = np.random.uniform(.5 * hetero_strength, 1.5 * hetero_strength) / \ + len(self.hetero_inds) + self.x_hetero_effect[self.hetero_inds] = np.random.uniform(.5 * hetero_strength, 1.5 * hetero_strength) / \ + len(self.hetero_inds) + + self.true_effect = np.zeros((self.n_periods, self.n_treatments)) + self.true_effect[0] = self.epsilon + for t in np.arange(1, self.n_periods): + self.true_effect[t, :] = (self.zeta.reshape( + 1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) @ self.Alpha) + + self.true_hetero_effect = np.zeros( + (self.n_periods, (self.n_x + 1) * self.n_treatments)) + self.true_hetero_effect[0, :] = cross_product( + add_constant(self.y_hetero_effect.reshape(1, -1), has_constant='add'), + self.epsilon.reshape(1, -1)) + for t in np.arange(1, self.n_periods): + self.true_hetero_effect[t, :] = cross_product( + add_constant(self.x_hetero_effect.reshape(1, -1), has_constant='add'), + self.zeta.reshape(1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) @ self.Alpha) + return self + + def hetero_effect_fn(self, t, x): + if t == 0: + return (np.dot(self.y_hetero_effect, x.flatten()) + 1) * self.epsilon + else: + return (np.dot(self.x_hetero_effect, x.flatten()) + 1) *\ + (self.zeta.reshape(1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) + @ self.Alpha).flatten() + + def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): + np.random.seed(random_seed) + Y = np.zeros(n_units * self.n_periods) + T = np.zeros((n_units * self.n_periods, self.n_treatments)) + X = np.zeros((n_units * self.n_periods, self.n_x)) + groups = np.zeros(n_units * self.n_periods) + for t in range(n_units * self.n_periods): + period = t % self.n_periods + if period == 0: + X[t] = np.random.normal(0, self.sigma_x, size=self.n_x) + T[t] = policy_gen(np.zeros(self.n_treatments), X[t], period) + else: + X[t] = (np.dot(self.x_hetero_effect, X[t - 1]) + 1) * np.dot(self.Alpha, T[t - 1]) + \ + np.dot(self.Beta, X[t - 1]) + \ + np.random.normal(0, self.sigma_x, size=self.n_x) + T[t] = policy_gen(T[t - 1], X[t], period) + Y[t] = (np.dot(self.y_hetero_effect, X[t]) + 1) * np.dot(self.epsilon, T[t]) + \ + np.dot(X[t], self.zeta) + \ + np.random.normal(0, self.sigma_y) + groups[t] = t // self.n_periods + + return Y, T, X[:, self.hetero_inds] if self.hetero_inds else None, X[:, self.endo_inds], groups + + def observational_data(self, n_units, gamma=0, s_t=1, sigma_t=0.5, random_seed=123): + """ Generated observational data with some observational treatment policy parameters + + Parameters + ---------- + n_units : how many units to observe + gamma : what is the degree of auto-correlation of the treatments across periods + s_t : sparsity of treatment policy; how many states does it depend on + sigma_t : what is the std of the exploration/randomness in the treatment + """ + Delta = np.zeros((self.n_treatments, self.n_x)) + Delta[:, :s_t] = self.conf_str / s_t + + def policy_gen(Tpre, X, period): + return gamma * Tpre + (1 - gamma) * np.dot(Delta, X) + \ + np.random.normal(0, sigma_t, size=self.n_treatments) + return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index bb5aa8eac..409cd231c 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -12,6 +12,7 @@ from econml.inference import BootstrapInference, EmpiricalInferenceResults, NormalInferenceResults from econml.utilities import shape, hstack, vstack, reshape, cross_product import econml.tests.utilities # bugfix for assertWarns +from econml.tests.dgp import DynamicPanelDGP class TestDynamicDML(unittest.TestCase): @@ -79,7 +80,6 @@ def make_random(n, is_discrete, d): (d_y if d_y > 0 else 1) * (d_t_final if d_t_final > 0 else 1), 6) all_infs = [None, 'auto', BootstrapInference(2)] - #all_infs = [None, 'auto'] est = DynamicDML(model_y=Lasso() if d_y < 1 else MultiTaskLasso(), model_t=LogisticRegression() if is_discrete else (Lasso() if d_t < 1 else MultiTaskLasso()), @@ -256,3 +256,40 @@ def make_random(n, is_discrete, d): eff = est.effect(X) if not is_discrete else est.effect( X, T0='a', T1='b') self.assertEqual(shape(eff), effect_shape2) + + def test_perf(self): + np.random.seed(123) + n_units = 400 + n_periods = 3 + n_treatments = 1 + n_x = 100 + s_x = 10 + s_t = 10 + hetero_strength = .5 + hetero_inds = np.arange(n_x - n_treatments, n_x) + alpha_regs = [1e-4, 1e-3, 1e-2, 5e-2, .1, 1] + + def lasso_model(): + return LassoCV(cv=3, alphas=alpha_regs, max_iter=500) + # No heterogeneity + dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( + s_x, random_seed=1) + Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12) + est = DynamicDML(model_y=lasso_model(), model_t=lasso_model(), cv=3) + est.fit(Y, T, X=X, W=W, groups=groups, inference="auto") + np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=1e-01) + np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) + np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) + # Heterogeneous effects + hetero_strength = .5 + hetero_inds = np.arange(n_x - n_treatments, n_x) + dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( + s_x, hetero_strength=hetero_strength, hetero_inds=hetero_inds, random_seed=1) + Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12) + est.fit(Y, T, X=X, W=W, groups=groups, inference="auto") + np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=0.2) + np.testing.assert_allclose(est.coef_, dgp.true_hetero_effect[:, hetero_inds + 1], atol=0.2) + np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) + np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) + np.testing.assert_array_less(est.coef__interval()[0], dgp.true_hetero_effect[:, hetero_inds + 1]) + np.testing.assert_array_less(dgp.true_hetero_effect[:, hetero_inds + 1], est.coef__interval()[1]) diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb new file mode 100644 index 000000000..268402dfb --- /dev/null +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -0,0 +1,677 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dynamic Double Machine Learning: Use Cases and Examples\n", + "\n", + "Dynamic DoubleML is an extension of the Double ML approach for treatments assigned sequentially over time periods. This estimator will account for treatments that can have causal effects on future outcomes. For more details, see [this paper](https://arxiv.org/abs/2002.07285) or the [EconML docummentation](https://econml.azurewebsites.net/).\n", + "\n", + "For example, the Dynamic DoubleML could be useful in estimating the following causal effects:\n", + "* the effect of investments on revenue at companies that receive investments at regular intervals ([see more](https://arxiv.org/abs/2103.08390))\n", + "* the effect of prices on demand in stores where prices of goods change over time\n", + "* the effect of income on health outcomes in people who receive yearly income\n", + "\n", + "The expected data format is balanced panel data. Each panel corresponds to one entity (e.g. company, store or person) and the different rows in a panel correspond to different time points. Example:\n", + "\n", + "||Company|Year|Features|Investment|Revenue|\n", + "|---|---|---|---|---|---|\n", + "|1|A|2018|...|\\$1,000|\\$10,000|\n", + "|2|A|2019|...|\\$2,000|\\$12,000|\n", + "|3|A|2020|...|\\$3,000|\\$15,000|\n", + "|4|B|2018|...|\\$0|\\$5,000|\n", + "|5|B|2019|...|\\$100|\\$10,000|\n", + "|6|B|2020|...|\\$1,200|\\$7,000|\n", + "|7|C|2018|...|\\$1,000|\\$20,000|\n", + "|8|C|2019|...|\\$1,500|\\$25,000|\n", + "|9|C|2020|...|\\$500|\\$15,000|\n", + "\n", + "(Note: when passing the data to the DynamicDML estimator, the \"Company\" column above corresponds to the `groups` argument at fit time. The \"Year\" column above should not be passed in as it will be inferred from the \"Company\" column)\n", + "\n", + "\n", + "In this notebook, we show the performance of the DynamicDML on synthetic and observational data. \n", + "\n", + "## Notebook Contents\n", + "\n", + "1. [Example Usage with Average Treatment Effects](#1.-Example-Usage-with-Average-Treatment-Effects)\n", + "2. [Example Usage with Heterogeneous Treatment Effects](#2.-Example-Usage-with-Heterogeneous-Treatment-Effects)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import econml" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Main imports\n", + "from econml.dml import DynamicDML\n", + "from econml.tests.dgp import DynamicPanelDGP\n", + "\n", + "# Helper imports\n", + "import numpy as np\n", + "from sklearn.linear_model import Lasso, LassoCV, LogisticRegression, LogisticRegressionCV, MultiTaskLassoCV\n", + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Example Usage with Average Treatment Effects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.1 DGP\n", + "\n", + "We consider a data generating process from a markovian treatment model. \n", + "\n", + "In the example bellow, $T_t\\rightarrow$ treatment(s) at time $t$, $Y_t\\rightarrow$outcome at time $t$, $X_t\\rightarrow$ features and controls at time $t$ (the coefficients $e, f'$ will pick the features and the controls).\n", + "\\begin{align}\n", + " X_t =& (\\pi'X_{t-1} + 1) \\cdot A\\, T_{t-1} + B X_{t-1} + \\epsilon_t\\\\\n", + " T_t =& \\gamma\\, T_{t-1} + (1-\\gamma) \\cdot D X_t + \\zeta_t\\\\\n", + " Y_t =& (\\sigma' X_{t} + 1) \\cdot e\\, T_{t} + f X_t + \\eta_t\n", + "\\end{align}\n", + "\n", + "with $X_0, T_0 = 0$ and $\\epsilon_t, \\zeta_t, \\eta_t \\sim N(0, \\sigma^2)$. Moreover, $X_t \\in R^{n_x}$, $B[:, 0:s_x] \\neq 0$ and $B[:, s_x:-1] = 0$, $\\gamma\\in [0, 1]$, $D[:, 0:s_x] \\neq 0$, $D[:, s_x:-1]=0$, $f[0:s_x]\\neq 0$, $f[s_x:-1]=0$. We draw a single time series of samples of length $n\\_panels \\cdot n\\_periods$." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Define DGP parameters\n", + "np.random.seed(123)\n", + "n_panels = 400 # number of panels\n", + "n_periods = 3 # number of time periods in each panel\n", + "n_treatments = 1 # number of treatments in each period\n", + "n_x = 100 # number of features + controls\n", + "s_x = 10 # number of controls (endogeneous variables)\n", + "s_t = 10 # treatment support size" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate data\n", + "dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance(\n", + " s_x, random_seed=12345)\n", + "Y, T, X, W, groups = dgp.observational_data(n_panels, s_t=s_t, random_seed=12345)\n", + "true_effect = dgp.true_effect" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.2 Train Estimator" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "alpha_regs = [1e-4, 1e-3, 1e-2, 5e-2, .1, 1]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "est = DynamicDML(\n", + " model_y=LassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", + " model_t=MultiTaskLassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", + " cv=3)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "est.fit(Y, T, X=None, W=W, groups=groups, inference=\"auto\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average effect of default policy: 2.26\n" + ] + } + ], + "source": [ + "# Average treatment effect of all periods on last period for unit treatments\n", + "print(f\"Average effect of default policy: {est.ate():0.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Effect of target policy over baseline policy: 4.33\n" + ] + } + ], + "source": [ + "# Effect of target policy over baseline policy\n", + "# Must specify a treatment for each period\n", + "baseline_policy = np.zeros((1, n_periods))\n", + "target_policy = np.array([[1, 2, 3]])\n", + "eff = est.effect(T0=baseline_policy, T1=target_policy)\n", + "print(f\"Effect of target policy over baseline policy: {eff[0]:0.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Effect of a treatment in period 1 on period 3 outcome: 0.71\n", + "Effect of a treatment in period 2 on period 3 outcome: 1.03\n", + "Effect of a treatment in period 3 on period 3 outcome: 0.52\n" + ] + } + ], + "source": [ + "# Period treatment effects + interpretation\n", + "for i, theta in enumerate(est.intercept_):\n", + " print(f\"Effect of a treatment in period {i+1} on period {n_periods} outcome: {theta:0.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Coefficient Results: X is None, please call intercept_inference to learn the constant!\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|$(T0)_0$ 0.711 0.041 17.224 0.0 0.643 0.779
cate_intercept|$(T0)_1$ 1.031 0.1 10.306 0.0 0.866 1.195
cate_intercept|$(T0)_2$ 0.518 0.139 3.739 0.0 0.29 0.746


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" + ], + "text/plain": [ + "\n", + "\"\"\"\n", + " CATE Intercept Results \n", + "=============================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------------------------\n", + "cate_intercept|$(T0)_0$ 0.711 0.041 17.224 0.0 0.643 0.779\n", + "cate_intercept|$(T0)_1$ 1.031 0.1 10.306 0.0 0.866 1.195\n", + "cate_intercept|$(T0)_2$ 0.518 0.139 3.739 0.0 0.29 0.746\n", + "-----------------------------------------------------------------------------\n", + "\n", + "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", + "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", + "where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:\n", + "$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$\n", + "where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.\n", + "\"\"\"" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Period treatment effects with confidence intervals\n", + "est.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "conf_ints = est.intercept__interval(alpha=0.05)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.3 Performance Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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F5xzo1u4ro+k95xzWS29Z15envt8FxoAaA9bPtknSRpxdrrnJPDl8/vvlt48wXMShITagq5my5YtGjFiRKuvX762RD96eoMO1x1RVmaq5k4brhnjW/xx/pR8/PHHmjRpkt5//3116xaP7USa+nsxs0LnXF5T13eWe/BukvSy7xAA4okfvgBEgdqC0M0Yn6Un13wkqWN+QfaHP/xBt99+ux588MHYNHdt4b3BM7PJSjR4F53gmlmSZknSwIEDOygZAAAAgBPpyJUP1113na677roO+35dldfW18zGSvp3SVc658qau845t8g5l+ecy+vfv3/HBQQQC69u3q1XN+/2HQNAYKgtAHzw1uCZ2UBJyyR9yzn3ga8cAAAAABCKyJZomtmTkiZJ6mdmxZLulJQsSc65RyXdIamvpF/Vb3ta29yNggAAAACAlkXW4Dnnrm3h/HckfSeq7w8AAAAAccP2MwAAAAA6hd27d2vmzJkaOnSocnNzNWHCBD377LMdmmHHjh0aPfr4p7zt2LFDixcvbtN7/uIXv9DBgwcbjnv37t3mfC2hwQMQe4P69tSgvj19xwAQGGoLcHKcc5oxY4YmTpyooqIiFRYWasmSJSouLj7u2tra2g7Pd6IGr6U8xzZ4UfL+mAQA8G3YmWm+IwAIELUFwSsulNYtlip2SpmDpJyZUnZum9/u9ddfV/fu3XXLLbc0jA0aNEi33nqrJOmJJ57QsmXLtH//ftXV1enZZ5/VjTfeqKKiIvXs2VOLFi3S2LFjtXDhQvXu3Vv//M//LEkaPXq0XnjhBUnS5ZdfrosuukhvvvmmsrKy9Nxzzyk1NVWFhYW68cYbJUmXXXZZk/nmzZunLVu2KCcnR9dff7369OlzVJ677rpL999/f8P3mjNnjvLy8rRv3z59/PHHmjx5svr166f8/HxJ0u23364XXnhBqampeu6553TmmWe2+c+uMWbwAMRebd0R1dYd8R0DQGCoLQhacaGUf7dUVSaln514zb87Md5GmzZt0he/+MUTXvPuu+/q6aef1htvvKE777xT48eP14YNG/Szn/2sVc/I27Ztm2bPnq1NmzYpMzNTzzzzjCTphhtu0C9/+UutX7++2a+95557dPHFF2vdunX6/ve/f1ye5tx2220655xzlJ+f39DcHThwQBdccIHWr1+viRMn6je/+U2L2VuLBg9A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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Some plotting boilerplate code\n", + "plt.figure(figsize=(15, 5))\n", + "plt.errorbar(np.arange(n_periods*n_treatments)-.04, est.intercept_, yerr=(conf_ints[1] - est.intercept_,\n", + " est.intercept_ - conf_ints[0]), fmt='o', label='DynamicDML')\n", + "plt.errorbar(np.arange(n_periods*n_treatments), true_effect.flatten(), fmt='o', alpha=.6, label='Ground truth')\n", + "for t in np.arange(1, n_periods):\n", + " plt.axvline(x=t * n_treatments - .5, linestyle='--', alpha=.4)\n", + "plt.xticks([t * n_treatments - .5 + n_treatments/2 for t in range(n_periods)],\n", + " [\"$\\\\theta_{}$\".format(t) for t in range(n_periods)])\n", + "plt.gca().set_xlim([-.5, n_periods*n_treatments - .5])\n", + "plt.ylabel(\"Effect\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Example Usage with Heterogeneous Treatment Effects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.1 DGP" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# Define additional DGP parameters\n", + "het_strength = .5\n", + "het_inds = np.arange(n_x - n_treatments, n_x)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate data\n", + "dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance(\n", + " s_x, hetero_strength=het_strength, hetero_inds=het_inds, random_seed=12)\n", + "Y, T, X, W, groups = dgp.observational_data(n_panels, s_t=s_t, random_seed=1)\n", + "ate_effect = dgp.true_effect\n", + "het_effect = dgp.true_hetero_effect[:, het_inds + 1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.2 Train Estimator" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "est = DynamicDML(\n", + " model_y=LassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", + " model_t=MultiTaskLassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", + " cv=3)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Objective did not converge. You might want to increase the number of iterations. Duality gap: 2.6550941575656566, tolerance: 1.6272718134380235\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "est.fit(Y, T, X=X, W=W, groups=groups, inference=\"auto\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
Coefficient Results
point_estimate stderr zstat pvalue ci_lower ci_upper
X0|$(T0)_0$ 0.394 0.103 3.838 0.0 0.225 0.563
X0|$(T0)_1$ -0.066 0.192 -0.343 0.732 -0.382 0.25
X0|$(T0)_2$ 0.04 0.201 0.198 0.843 -0.291 0.37
\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|$(T0)_0$ 0.579 0.052 11.242 0.0 0.495 0.664
cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.704 -0.108 0.173
cate_intercept|$(T0)_2$ -0.098 0.093 -1.049 0.294 -0.251 0.055


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" + ], + "text/plain": [ + "\n", + "\"\"\"\n", + " Coefficient Results \n", + "=================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------------\n", + "X0|$(T0)_0$ 0.394 0.103 3.838 0.0 0.225 0.563\n", + "X0|$(T0)_1$ -0.066 0.192 -0.343 0.732 -0.382 0.25\n", + "X0|$(T0)_2$ 0.04 0.201 0.198 0.843 -0.291 0.37\n", + " CATE Intercept Results \n", + "=============================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------------------------\n", + "cate_intercept|$(T0)_0$ 0.579 0.052 11.242 0.0 0.495 0.664\n", + "cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.704 -0.108 0.173\n", + "cate_intercept|$(T0)_2$ -0.098 0.093 -1.049 0.294 -0.251 0.055\n", + "-----------------------------------------------------------------------------\n", + "\n", + "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", + "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", + "where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:\n", + "$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$\n", + "where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.\n", + "\"\"\"" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "est.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average effect of default policy:0.48\n" + ] + } + ], + "source": [ + "# Average treatment effect for test points\n", + "X_test = X[:10]\n", + "print(f\"Average effect of default policy:{est.ate(X=X_test):0.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Effect of target policy over baseline policy for test set:\n", + " [ 0.48463458 0.52974258 0.21137445 0.36785476 0.37107783 0.6353013\n", + " -0.08878165 0.17427563 0.45723001 0.06398282]\n" + ] + } + ], + "source": [ + "# Effect of target policy over baseline policy\n", + "# Must specify a treatment for each period\n", + "baseline_policy = np.zeros((1, n_periods))\n", + "target_policy = np.array([[1, 2, 3]])\n", + "eff = est.effect(X=X_test, T0=baseline_policy, T1=target_policy)\n", + "print(\"Effect of target policy over baseline policy for test set:\\n\", eff)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([ 0.57932353, 0.03248502, -0.09764978]),\n", + " array([[ 0.39429592],\n", + " [-0.06583129],\n", + " [ 0.03975663]]))" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Coefficients\n", + "est.intercept_, est.coef_" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "# Confidence intervals\n", + "conf_ints_intercept = est.intercept__interval(alpha=0.05)\n", + "conf_ints_coef = est.coef__interval(alpha=0.05)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.3 Performance Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA4AAAAFWCAYAAAA4zPNMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAApxklEQVR4nO3de3hV9Z3v8c+XECBcw80LAbl0kOEOJTrSqkVFwVOnMu0pWHtGvI16qmWmFxw88nhrp9Vaa8fqaaGto53pCNQqXjseQegUS6tBLgpIsQgSVIiBIJcEQvieP7JJQ0hI9l7Z+5e91/v1PDzZ67dXko/b+JVP1tprmbsLAAAAAJD72oUOAAAAAADIDAogAAAAAMQEBRAAAAAAYoICCAAAAAAxQQEEAAAAgJigAAIAAABATLQPHaC19enTxwcNGhQ6BoA27OOqaklS9075gZMAQHKYXwBaYtWqVR+5e9/Gnsu5Ajho0CCVlJSEjgGgDVuzvUKSNG5AYdAcAJAs5heAljCzbU09l3MFEACaw1+cAGQr5heAqHgPIAAAAADEBAUQQOz8bnOZfre5LHQMAEga8wtAVJwCCiB2DlUfDR0BAFLC/EJI1dXVKi0tVVVVVegoSOjUqZP69++v/PyWXxiKAggAAACgWaWlperWrZsGDRokMwsdJ/bcXeXl5SotLdXgwYNb/HmcAgoAAACgWVVVVerduzflr40wM/Xu3TvpI7IUQAAAAAAtQvlrW1L590EBBBA7p/XopNN6dAodAwCSxvxCtpkxb6VmzFvZal8vLy9P48aN08iRIzV27Fg98MADOno0zHtjS0pKNGvWrJPuM2jQII0ePVqjR4/WiBEjNHfu3Lojdlu3bpWZae7cuXX7f/TRR8rPz9ctt9wiSbrrrrv0/e9/v1VzUwABxM6ooh4aVdQjdAwASBrzC3FXUFCgNWvWaP369Xr55Zf1m9/8RnfffXeQLMXFxXrooYea3W/ZsmV688039dprr2nLli268cYb654bPHiwXnjhhbrtX/3qVxo5cmRa8h5DAQQAAADQ6hav3qHV71Xoj+/u1qfvfUWLV+9o1a9/yimnaP78+Xr44Yfl7jr//PO1Zs2auufPPfdcrV27VnfddZeuvfZaTZo0SUOGDDmutE2bNk0TJkzQyJEjNX/+/Lr1rl27avbs2Ro5cqQmT56s1157re7zn332WUnS8uXLddlll0mS9u/fr2uuuUajR4/WmDFj9Otf//qEvF27dtVPfvITLV68WLt375Ykde7cWcOHD1dJSYkkaeHChZo+fXqrvk4NUQAzoLUPfQOIZtmmXVq2aVfoGACQNOYXssXi1Tt021Nv6nBN7emZOyoqddtTb7Z6CRwyZIhqamq0a9cuXXfddXrsscckSX/6059UVVWlsWPHSpLefvttvfTSS3rttdd09913q7q6WpL06KOPatWqVSopKdFDDz2k8vJySdKBAwd04YUXav369erWrZvmzp2rl19+WU8//bTuuOOOE3J861vfUo8ePfTmm29q3bp1uvDCCxvN2717dw0ePFibN2+uW7viiiu0YMECbd++XXl5eerXr19rvkQnoAACiJ2aGldNjYeOAQBJY34hW9z/0iZVVtcct1ZZXaP7X9qUtu/5xS9+Uc8//7yqq6v16KOP6uqrr6577rOf/aw6duyoPn366JRTTtHOnTslSQ899JDGjh2rc845R9u3b68rZh06dNDUqVMlSaNHj9ZnPvMZ5efna/To0dq6desJ33vJkiW6+eab67Z79uzZZE734/8bnjp1ql5++WUtWLBAM2bMSPUfv8W4DyAAAACAVvV+RWVS66nasmWL8vLydMopp8jMdPHFF+uZZ57RokWLtGrVqrr9OnbsWPc4Ly9PR44c0fLly7VkyRKtXLlSnTt31qRJk+ou0JKfn193hc127drVfX67du105MiRlPPu27dPW7du1Zlnnqm9e/dKqi2bEyZM0AMPPKANGzbUnWKaLhwBBAAAANCq+hUWJLWeirKyMt1000265ZZb6sra9ddfr1mzZumss8466VE4Sdq7d6969uypzp076+2339Yf/vCHlLNcfPHFeuSRR+q29+zZc8I++/fv11e+8hVNmzbthGzf+MY3dN9996lXr14pZ2gpCiAAAACAVjV7yjAV5Ocdt1aQn6fZU4ZF+rqVlZV1t4GYPHmyLrnkEt155511z0+YMEHdu3fXNddc0+zXmjp1qo4cOaLhw4drzpw5Ouecc1LONXfuXO3Zs0ejRo3S2LFjtWzZsrrnLrjgAo0aNUpnn322zjjjDM2bN++Ezx85cqRmzpzZ6Nf+9re/rf79+9f9icoanoOa7YqLi/3YVXTaimMXgFl448TASQBI0sYPPpYkDT+9e+AkAJAc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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Some plotting boilerplate code\n", + "plt.figure(figsize=(15, 5))\n", + "# Intercepts\n", + "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments), \n", + " est.intercept_, \n", + " yerr=(conf_ints_intercept[1] - est.intercept_, est.intercept_ - conf_ints_intercept[0]), \n", + " fmt='o', label='DynamicDML')\n", + "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments), ate_effect.flatten(), \n", + " fmt='o', label='Ground truth')\n", + "# Heterogeneous effects\n", + "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments)+1, est.coef_, \n", + " yerr=((conf_ints_coef[1] - est.coef_).flatten(), \n", + " (est.coef_ - conf_ints_coef[0]).flatten()), \n", + " fmt='o', color='C0')\n", + "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments)+1, het_effect, \n", + " fmt='o', color='C1')\n", + "for t in np.arange(2, (het_inds.shape[0]+1)*n_periods, 2):\n", + " plt.axvline(x=t * n_treatments - .5, linestyle='--', alpha=.4)\n", + "# Labels\n", + "x_range = np.arange(n_periods*n_treatments*(het_inds.shape[0]+1))\n", + "x_ticks = list(x_range)\n", + "x_labels = [1 if i%(n_treatments+1)==0 else f\"$X_\\u007b{het_inds[i%(n_treatments+1)-1]}\\u007d$\" for i in x_range]\n", + "x_ticks += [het_inds.shape[0]/2 + i*(n_treatments*(het_inds.shape[0]+1)) for i in range(n_periods)]\n", + "x_labels += [f\"\\n\\n\\n$\\\\theta_{i}$\" for i in range(n_periods)]\n", + "plt.xticks(x_ticks, x_labels)\n", + "plt.ylabel(\"Effect\")\n", + "plt.legend()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 5f6da4010b646d8c7b65356915b9216013ff6859 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Fri, 9 Apr 2021 18:20:52 -0400 Subject: [PATCH 03/27] Add scores. --- econml/dml/dynamic_dml.py | 89 +++++++++++++++++++++++++++++--- econml/tests/test_dynamic_dml.py | 16 +++--- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index 9d10e33da..cb68ad562 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -92,8 +92,6 @@ def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): return Y_res, T_res def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): - # TODO: implement scores - # TODO: fix correctness? assert Y.shape[0] % self.n_periods == 0, \ "Length of training data should be an integer multiple of time periods." inds_score = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] @@ -147,7 +145,7 @@ def __init__(self, model_final, n_periods): self._model_final_trained = {k: clone(self._model_final, safe=False) for k in np.arange(n_periods)} def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): - # TODO: handle sample weight, sample var + # NOTE: sample weight, sample var are not passed in Y_res, T_res = nuisances self._d_y = Y.shape[1:] for kappa in np.arange(self.n_periods): @@ -186,8 +184,29 @@ def predict(self, X=None): return preds def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): - # TODO: implement score - return None + assert Y.shape[0] % self.n_periods == 0, \ + "Length of training data should be an integer multiple of time periods." + Y_res, T_res = nuisances + + scores = np.full((self.n_periods, ), np.nan) + for kappa in np.arange(self.n_periods): + period = self.n_periods - 1 - kappa + period_filter = self.period_filter_gen(period, Y.shape[0]) + Y_adj = Y_res[period_filter].copy() + if kappa > 0: + Y_adj -= np.sum( + [self._model_final_trained[tau].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - tau, Y.shape[0])] if X is not None else None, + T_res[period_filter, ..., self.n_periods - 1 - tau] + ) for tau in np.arange(kappa)], axis=0) + Y_adj_pred = self._model_final_trained[kappa].predict_with_res( + X[period_filter] if X is not None else None, + T_res[period_filter, ..., period]) + if sample_weight is not None: + scores[kappa] = np.mean(np.average((Y_adj - Y_adj_pred)**2, weights=sample_weight, axis=0)) + else: + scores[kappa] = np.mean((Y_adj - Y_adj_pred) ** 2) + return scores def period_filter_gen(self, p, n): return (np.arange(n) % self.n_periods == p) @@ -548,12 +567,39 @@ def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, grou warn("This CATE estimator does not yet support sample weights and sample variance. " "These inputs will be ignored during fitting.", UserWarning) - # TODO: support sample_weight, sample_var? return super().fit(Y, T, X=X, W=W, sample_weight=None, sample_var=None, groups=groups, cache_values=cache_values, inference=inference) + def score(self, Y, T, X=None, W=None): + """ + Score the fitted CATE model on a new data set. Generates nuisance parameters + for the new data set based on the fitted residual nuisance models created at fit time. + It uses the mean prediction of the models fitted by the different crossfit folds. + Then calculates the MSE of the final residual Y on residual T regression. + + If model_final does not have a score method, then it raises an :exc:`.AttributeError` + + Parameters + ---------- + Y: (n, d_y) matrix or vector of length n + Outcomes for each sample (required: n = n_groups * n_periods) + T: (n, d_t) matrix or vector of length n + Treatments for each sample (required: n = n_groups * n_periods) + X: optional(n, d_x) matrix or None (Default=None) + Features for each sample (Required: n = n_groups * n_periods) + W: optional(n, d_w) matrix or None (Default=None) + Controls for each sample (Required: n = n_groups * n_periods) + + Returns + ------- + score: float + The MSE of the final CATE model on the new data. + """ + # Replacing score from _OrthoLearner, to enforce Z=None and improve the docstring + return super().score(Y, T, X=X, W=W) + def cate_treatment_names(self, treatment_names=None): """ Get treatment names for each time period. @@ -658,3 +704,34 @@ def model_final(self): def model_final(self, model): if model is not None: raise ValueError("Parameter `model_final` cannot be altered for this estimator!") + + @property + def models_y(self): + return [[mdl._model_y for mdl in mdls] for mdls in super().models_nuisance_] + + @property + def models_t(self): + return [[mdl._model_t for mdl in mdls] for mdls in super().models_nuisance_] + + @property + def nuisance_scores_y(self): + return self.nuisance_scores_[0] + + @property + def nuisance_scores_t(self): + return self.nuisance_scores_[1] + + @property + def residuals_(self): + """ + A tuple (y_res, T_res, X, W), of the residuals from the first stage estimation + along with the associated X and W. Samples are not guaranteed to be in the same + order as the input order. + """ + if not hasattr(self, '_cached_values'): + raise AttributeError("Estimator is not fitted yet!") + if self._cached_values is None: + raise AttributeError("`fit` was called with `cache_values=False`. " + "Set to `True` to enable residual storage.") + Y_res, T_res = self._cached_values.nuisances + return Y_res, T_res, self._cached_values.X, self._cached_values.W diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index 409cd231c..21a5aa7a9 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -113,14 +113,11 @@ def make_random(n, is_discrete, d): np.testing.assert_allclose( marg_eff if d_x else marg_eff[0:1], const_marg_eff) - # TODO: add score and nuisance scores - """ - assert isinstance(est.score_, float) - for score in est.nuisance_scores_y: - assert isinstance(score, float) - for score in est.nuisance_scores_t: - assert isinstance(score, float) - """ + assert len(est.score_) == n_periods + for score in est.nuisance_scores_y[0]: + assert score.shape == (n_periods, ) + for score in est.nuisance_scores_t[0]: + assert score.shape == (n_periods, n_periods) T0 = np.full_like(T_test, 'a') if is_discrete else np.zeros_like(T_test) eff = est.effect(X, T0=T0, T1=T_test) @@ -238,8 +235,7 @@ def make_random(n, is_discrete, d): [0], est.intercept__interval()[0], decimal=5) est.summary() - - # TODO: add score to estimator + # TODO: fix score """ est.score(Y, T, X, W) """ From d8bc1f30fc5e3d3e5f5d688d36fc78034dd7c2e7 Mon Sep 17 00:00:00 2001 From: Maggie Hei Date: Fri, 4 Jun 2021 18:34:53 -0400 Subject: [PATCH 04/27] store some internal variables to allow calling from diased inference function --- econml/dml/dynamic_dml.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index cb68ad562..c8bbc3ec5 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -269,6 +269,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index): self.n_periods * d_xt)) Sigma = np.zeros((self.n_periods * d_xt, self.n_periods * d_xt)) + self._res_epsilon = {} for kappa in np.arange(self.n_periods): # Calculating the (kappa, kappa) block entry (of size n_treatments x n_treatments) of matrix Sigma period = self.n_periods - 1 - kappa @@ -283,6 +284,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index): res_epsilon = (Y_res[period_filter] - (Y_diff[:, y_index] if y_index >= 0 else Y_diff) ).reshape(-1, 1, 1) + self._res_epsilon[period] = res_epsilon.flatten() cur_resT = XT_res[period][period] cov_cur_resT = cur_resT.reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt) sigma_kappa = np.mean((res_epsilon**2) * cov_cur_resT, axis=0) @@ -295,6 +297,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index): axis=0) M[kappa * d_xt:(kappa + 1) * d_xt, tau * d_xt:(tau + 1) * d_xt] = m_kappa_tau + self._M = M return np.linalg.inv(M) @ Sigma @ np.linalg.inv(M).T From 6daf315d220095d039fe74d564d20ccae9046e74 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 29 Jul 2021 13:13:02 -0400 Subject: [PATCH 05/27] Swap t and j indexes to match the paper --- econml/dml/dynamic_dml.py | 167 +++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 83 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index c8bbc3ec5..1c014c02b 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# TODO: add paper reference + import abc import numpy as np from warnings import warn @@ -39,19 +41,18 @@ def fit(self, Y, T, X=None, W=None, sample_weight=None, groups=None): "Length of training data should be an integer multiple of time periods." inds_train = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] self._model_y_trained = {} - self._model_t_trained = {} - for kappa in np.arange(self.n_periods): - self._model_y_trained[kappa] = clone(self._model_y, safe=False).fit( - self._filter_or_None(X, inds_train + kappa), + self._model_t_trained = {j: {} for j in np.arange(self.n_periods)} + for t in np.arange(self.n_periods): + self._model_y_trained[t] = clone(self._model_y, safe=False).fit( + self._filter_or_None(X, inds_train + t), self._filter_or_None( - W, inds_train + kappa), + W, inds_train + t), Y[inds_train + self.n_periods - 1]) - self._model_t_trained[kappa] = {} - for tau in np.arange(kappa, self.n_periods): - self._model_t_trained[kappa][tau] = clone(self._model_t, safe=False).fit( - self._filter_or_None(X, inds_train + kappa), - self._filter_or_None(W, inds_train + kappa), - T[inds_train + tau]) + for j in np.arange(t, self.n_periods): + self._model_t_trained[j][t] = clone(self._model_t, safe=False).fit( + self._filter_or_None(X, inds_train + t), + self._filter_or_None(W, inds_train + t), + T[inds_train + j]) return self def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): @@ -63,31 +64,32 @@ def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): Y residuals for each period in panel format. This shape is required for _OrthoLearner's crossfitting. T_res : (n, d_t, n_periods) matrix - T residuals for pairs of periods (kappa, tau), where the data is in panel format for kappa - and in index form for tau. For example, the residuals for (kappa, tau) can be retrieved via - T_res[np.arange(n) % n_periods == kappa, ..., tau]. For tau < kappa, the entries of this + T residuals for pairs of periods (t, j), where the data is in panel format for t + and in index form for j. For example, the residuals for (t, j) can be retrieved via + T_res[np.arange(n) % n_periods == t, ..., j]. For t < j, the entries of this matrix are np.nan. This shape is required for _OrthoLearner's crossfitting. """ + # TODO: update T_res docstring assert Y.shape[0] % self.n_periods == 0, \ "Length of training data should be an integer multiple of time periods." inds_predict = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] Y_res = np.full(Y.shape, np.nan) T_res = np.full(T.shape + (self.n_periods, ), np.nan) shape_formatter = self._get_shape_formatter(X, W) - for kappa in np.arange(self.n_periods): + for t in np.arange(self.n_periods): Y_slice = Y[inds_predict + self.n_periods - 1] - Y_pred = self._model_y_trained[kappa].predict( - self._filter_or_None(X, inds_predict + kappa), - self._filter_or_None(W, inds_predict + kappa)) - Y_res[np.arange(Y.shape[0]) % self.n_periods == kappa] = Y_slice\ + Y_pred = self._model_y_trained[t].predict( + self._filter_or_None(X, inds_predict + t), + self._filter_or_None(W, inds_predict + t)) + Y_res[np.arange(Y.shape[0]) % self.n_periods == t] = Y_slice\ - shape_formatter(Y_slice, Y_pred).reshape(Y_slice.shape) - for tau in np.arange(kappa, self.n_periods): - T_slice = T[inds_predict + tau] - T_pred = self._model_t_trained[kappa][tau].predict( - self._filter_or_None(X, inds_predict + kappa), - self._filter_or_None(W, inds_predict + kappa)) - T_res[np.arange(Y.shape[0]) % self.n_periods == kappa, ..., tau] = T_slice\ + for j in np.arange(t, self.n_periods): + T_slice = T[inds_predict + j] + T_pred = self._model_t_trained[j][t].predict( + self._filter_or_None(X, inds_predict + t), + self._filter_or_None(W, inds_predict + t)) + T_res[np.arange(Y.shape[0]) % self.n_periods == j, ..., t] = T_slice\ - shape_formatter(T_slice, T_pred).reshape(T_slice.shape) return Y_res, T_res @@ -97,21 +99,21 @@ def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): inds_score = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] if hasattr(self._model_y, 'score'): Y_score = np.full((self.n_periods, ), np.nan) - for kappa in np.arange(self.n_periods): - Y_score[kappa] = self._model_y_trained[kappa].score( - self._filter_or_None(X, inds_score + kappa), - self._filter_or_None(W, inds_score + kappa), + for t in np.arange(self.n_periods): + Y_score[t] = self._model_y_trained[t].score( + self._filter_or_None(X, inds_score + t), + self._filter_or_None(W, inds_score + t), Y[inds_score + self.n_periods - 1]) else: Y_score = None if hasattr(self._model_t, 'score'): T_score = np.full((self.n_periods, self.n_periods), np.nan) - for kappa in np.arange(self.n_periods): - for tau in np.arange(kappa, self.n_periods): - T_score[kappa][tau] = self._model_t_trained[kappa][tau].score( - self._filter_or_None(X, inds_score + kappa), - self._filter_or_None(W, inds_score + kappa), - T[inds_score + tau]) + for t in np.arange(self.n_periods): + for j in np.arange(t, self.n_periods): + T_score[j][t] = self._model_t_trained[j][t].score( + self._filter_or_None(X, inds_score + t), + self._filter_or_None(W, inds_score + t), + T[inds_score + j]) else: T_score = None return Y_score, T_score @@ -148,17 +150,17 @@ def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, # NOTE: sample weight, sample var are not passed in Y_res, T_res = nuisances self._d_y = Y.shape[1:] - for kappa in np.arange(self.n_periods): - period = self.n_periods - 1 - kappa + for t in np.arange(self.n_periods): + period = self.n_periods - 1 - t period_filter = self.period_filter_gen(period, Y.shape[0]) Y_adj = Y_res[period_filter].copy() - if kappa > 0: + if t > 0: Y_adj -= np.sum( - [self._model_final_trained[tau].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - tau, Y.shape[0])] if X is not None else None, - T_res[period_filter, ..., self.n_periods - 1 - tau] - ) for tau in np.arange(kappa)], axis=0) - self._model_final_trained[kappa].fit( + [self._model_final_trained[j].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0])] if X is not None else None, + T_res[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0]), ..., period] + ) for j in np.arange(t)], axis=0) + self._model_final_trained[t].fit( X[period_filter] if X is not None else None, T[period_filter], T_res[period_filter, ..., period], Y_adj) @@ -176,9 +178,9 @@ def predict(self, X=None): x_dy_shape + (self.n_periods * d_t, ) ) - for kappa in range(self.n_periods): - preds[..., kappa * d_t: (kappa + 1) * d_t] = \ - self._model_final_trained[kappa].predict(X).reshape( + for t in range(self.n_periods): + preds[..., t * d_t: (t + 1) * d_t] = \ + self._model_final_trained[t].predict(X).reshape( x_dy_shape + (d_t, ) ) return preds @@ -189,23 +191,23 @@ def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None Y_res, T_res = nuisances scores = np.full((self.n_periods, ), np.nan) - for kappa in np.arange(self.n_periods): - period = self.n_periods - 1 - kappa + for t in np.arange(self.n_periods): + period = self.n_periods - 1 - t period_filter = self.period_filter_gen(period, Y.shape[0]) Y_adj = Y_res[period_filter].copy() - if kappa > 0: + if t > 0: Y_adj -= np.sum( - [self._model_final_trained[tau].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - tau, Y.shape[0])] if X is not None else None, - T_res[period_filter, ..., self.n_periods - 1 - tau] - ) for tau in np.arange(kappa)], axis=0) - Y_adj_pred = self._model_final_trained[kappa].predict_with_res( + [self._model_final_trained[j].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0])] if X is not None else None, + T_res[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0]), ..., period] + ) for j in np.arange(t)], axis=0) + Y_adj_pred = self._model_final_trained[t].predict_with_res( X[period_filter] if X is not None else None, T_res[period_filter, ..., period]) if sample_weight is not None: - scores[kappa] = np.mean(np.average((Y_adj - Y_adj_pred)**2, weights=sample_weight, axis=0)) + scores[t] = np.mean(np.average((Y_adj - Y_adj_pred)**2, weights=sample_weight, axis=0)) else: - scores[kappa] = np.mean((Y_adj - Y_adj_pred) ** 2) + scores[t] = np.mean((Y_adj - Y_adj_pred) ** 2) return scores def period_filter_gen(self, p, n): @@ -233,7 +235,7 @@ def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, self.model_final_._param = coef.T if self.model_final_._n_out else coef def _get_coef_(self): - period_coefs = np.array([self._model_final_trained[kappa]._model.coef_ for kappa in range(self.n_periods)]) + period_coefs = np.array([self._model_final_trained[t]._model.coef_ for t in range(self.n_periods)]) if self._d_y: return np.array([ np.array([period_coefs[k, i, :] for k in range(self.n_periods)]).flatten() @@ -256,49 +258,48 @@ def _fit_single_output_cov(self, nuisances, X, y_index): XT_res = np.array([ [ self._model_final_trained[0]._combine( - X[self.period_filter_gen(tau, Y_res.shape[0])] if X is not None else None, - T_res[self.period_filter_gen(kappa, Y_res.shape[0]), ..., tau], + X[self.period_filter_gen(j, Y_res.shape[0])] if X is not None else None, + T_res[self.period_filter_gen(t, Y_res.shape[0]), ..., j], fitting=False ) - for tau in range(self.n_periods) + for j in range(self.n_periods) ] - for kappa in range(self.n_periods) + for t in range(self.n_periods) ]) d_xt = XT_res.shape[-1] - M = np.zeros((self.n_periods * d_xt, + J = np.zeros((self.n_periods * d_xt, self.n_periods * d_xt)) Sigma = np.zeros((self.n_periods * d_xt, self.n_periods * d_xt)) - self._res_epsilon = {} - for kappa in np.arange(self.n_periods): - # Calculating the (kappa, kappa) block entry (of size n_treatments x n_treatments) of matrix Sigma - period = self.n_periods - 1 - kappa + for t in np.arange(self.n_periods): + # Calculating the (t, t) block entry (of size n_treatments x n_treatments) of matrix Sigma + period = self.n_periods - 1 - t period_filter = self.period_filter_gen(period, Y_res.shape[0]) Y_diff = np.sum([ - self._model_final_trained[tau].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - tau, + self._model_final_trained[j].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - j, Y_res.shape[0])] if X is not None else None, - T_res[period_filter, ..., self.n_periods - 1 - tau]) - for tau in np.arange(kappa + 1) + T_res[self.period_filter_gen(self.n_periods - 1 - j, + Y_res.shape[0]), ..., period] + ) + for j in np.arange(t + 1) ], axis=0) res_epsilon = (Y_res[period_filter] - (Y_diff[:, y_index] if y_index >= 0 else Y_diff) ).reshape(-1, 1, 1) - self._res_epsilon[period] = res_epsilon.flatten() cur_resT = XT_res[period][period] cov_cur_resT = cur_resT.reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt) - sigma_kappa = np.mean((res_epsilon**2) * cov_cur_resT, axis=0) - Sigma[kappa * d_xt:(kappa + 1) * d_xt, - kappa * d_xt:(kappa + 1) * d_xt] = sigma_kappa - for tau in np.arange(kappa + 1): - # Calculating the (kappa, tau) block entry (of size n_treatments x n_treatments) of matrix M - m_kappa_tau = np.mean( - XT_res[period][self.n_periods - 1 - tau].reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt), + sigma_t = np.mean((res_epsilon**2) * cov_cur_resT, axis=0) + Sigma[t * d_xt:(t + 1) * d_xt, + t * d_xt:(t + 1) * d_xt] = sigma_t + for j in np.arange(t + 1): + # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix J + m_t_j = np.mean( + XT_res[self.n_periods - 1 - j][period].reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt), axis=0) - M[kappa * d_xt:(kappa + 1) * d_xt, - tau * d_xt:(tau + 1) * d_xt] = m_kappa_tau - self._M = M - return np.linalg.inv(M) @ Sigma @ np.linalg.inv(M).T + J[t * d_xt:(t + 1) * d_xt, + j * d_xt:(j + 1) * d_xt] = m_t_j + return np.linalg.inv(J) @ Sigma @ np.linalg.inv(J).T class _DynamicFinalWrapper(_FinalWrapper): @@ -668,7 +669,7 @@ def _expand_treatments(self, X, *Ts): if self.transformer: T = np.hstack([ base_expand_treatments( - X, T[:, [kappa]])[1] for kappa in range(self._n_periods) + X, T[:, [t]])[1] for t in range(self._n_periods) ]) outTs.append(T) return (X,) + tuple(outTs) From 0615d70c7e7e2bbb6a08eafd218a8bf321e54b67 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 29 Jul 2021 15:49:11 -0400 Subject: [PATCH 06/27] Update covariance matrix to include off-diagonal elements --- econml/dml/dynamic_dml.py | 67 +++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index 1c014c02b..61fa68834 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -255,6 +255,8 @@ def _fit_single_output_cov(self, nuisances, X, y_index): x (n_periods*n_treatments) matrix for a single outcome. """ Y_res, T_res = nuisances + # Calculate auxiliary quantities + # X ⨂ T_res XT_res = np.array([ [ self._model_final_trained[0]._combine( @@ -267,38 +269,49 @@ def _fit_single_output_cov(self, nuisances, X, y_index): for t in range(self.n_periods) ]) d_xt = XT_res.shape[-1] + # sum(model_final.predict(X, T_res)) + Y_diff = np.array([ + np.sum([ + self._model_final_trained[j].predict_with_res( + X[self.period_filter_gen(self.n_periods - 1 - j, + Y_res.shape[0])] if X is not None else None, + T_res[ + self.period_filter_gen(self.n_periods - 1 - j, Y_res.shape[0]), ..., self.n_periods - 1 - t] + ) for j in np.arange(t + 1)], + axis=0 + ) + for t in np.arange(self.n_periods) + ]) J = np.zeros((self.n_periods * d_xt, self.n_periods * d_xt)) Sigma = np.zeros((self.n_periods * d_xt, self.n_periods * d_xt)) for t in np.arange(self.n_periods): - # Calculating the (t, t) block entry (of size n_treatments x n_treatments) of matrix Sigma - period = self.n_periods - 1 - t - period_filter = self.period_filter_gen(period, Y_res.shape[0]) - Y_diff = np.sum([ - self._model_final_trained[j].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - j, - Y_res.shape[0])] if X is not None else None, - T_res[self.period_filter_gen(self.n_periods - 1 - j, - Y_res.shape[0]), ..., period] - ) - for j in np.arange(t + 1) - ], axis=0) - res_epsilon = (Y_res[period_filter] - - (Y_diff[:, y_index] if y_index >= 0 else Y_diff) - ).reshape(-1, 1, 1) - cur_resT = XT_res[period][period] - cov_cur_resT = cur_resT.reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt) - sigma_t = np.mean((res_epsilon**2) * cov_cur_resT, axis=0) - Sigma[t * d_xt:(t + 1) * d_xt, - t * d_xt:(t + 1) * d_xt] = sigma_t - for j in np.arange(t + 1): - # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix J - m_t_j = np.mean( - XT_res[self.n_periods - 1 - j][period].reshape(-1, d_xt, 1) @ cur_resT.reshape(-1, 1, d_xt), - axis=0) - J[t * d_xt:(t + 1) * d_xt, - j * d_xt:(j + 1) * d_xt] = m_t_j + period_t = self.n_periods - 1 - t + period_filter_t = self.period_filter_gen(period_t, Y_res.shape[0]) + res_epsilon_t = (Y_res[period_filter_t] - + (Y_diff[t][:, y_index] if y_index >= 0 else Y_diff[t]) + ).reshape(-1, 1, 1) + resT_t = XT_res[period_t][period_t] + for j in np.arange(self.n_periods): + # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix Sigma + period_j = self.n_periods - 1 - j + period_filter_j = self.period_filter_gen(period_j, Y_res.shape[0]) + res_epsilon_j = (Y_res[period_filter_j] - + (Y_diff[j][:, y_index] if y_index >= 0 else Y_diff[j]) + ).reshape(-1, 1, 1) + resT_j = XT_res[period_j][period_j] + cov_resT_tj = resT_t.reshape(-1, d_xt, 1) @ resT_j.reshape(-1, 1, d_xt) + sigma_tj = np.mean((res_epsilon_t * res_epsilon_j) * cov_resT_tj, axis=0) + Sigma[t * d_xt:(t + 1) * d_xt, + j * d_xt:(j + 1) * d_xt] = sigma_tj + if j <= t: + # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix J + m_tj = np.mean( + XT_res[period_j][period_t].reshape(-1, d_xt, 1) @ resT_t.reshape(-1, 1, d_xt), + axis=0) + J[t * d_xt:(t + 1) * d_xt, + j * d_xt:(j + 1) * d_xt] = m_tj return np.linalg.inv(J) @ Sigma @ np.linalg.inv(J).T From 7dc65b8b1e4a488f5cf4055be67d0b7474f1f7f4 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Fri, 30 Jul 2021 23:29:33 -0400 Subject: [PATCH 07/27] Add support for out of order groups --- doc/reference.rst | 1 + doc/spec/estimation/dml.rst | 42 ++++- doc/spec/references.rst | 7 +- econml/_ortho_learner.py | 19 ++- econml/dml/__init__.py | 2 +- econml/dml/_rlearner.py | 4 +- econml/dml/causal_forest.py | 2 +- econml/dml/dml.py | 2 +- econml/dml/dynamic_dml.py | 143 ++++++++++-------- econml/dr/_drlearner.py | 4 +- econml/iv/dml/_dml.py | 8 +- econml/iv/dr/_dr.py | 4 +- econml/policy/_drlearner.py | 4 +- econml/tests/test_dynamic_dml.py | 36 +++-- ...mic Double Machine Learning Examples.ipynb | 49 +++--- 15 files changed, 211 insertions(+), 116 deletions(-) diff --git a/doc/reference.rst b/doc/reference.rst index c7f9f3ca7..865cc52a4 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -17,6 +17,7 @@ Double Machine Learning (DML) econml.dml.SparseLinearDML econml.dml.CausalForestDML econml.dml.NonParamDML + econml.dml.DynamicDML .. _dr_api: diff --git a/doc/spec/estimation/dml.rst b/doc/spec/estimation/dml.rst index 3032f72c2..af65c4c92 100644 --- a/doc/spec/estimation/dml.rst +++ b/doc/spec/estimation/dml.rst @@ -34,7 +34,8 @@ What are the relevant estimator classes? This section describes the methodology implemented in the classes, :class:`._RLearner`, :class:`.DML`, :class:`.LinearDML`, :class:`.SparseLinearDML`, :class:`.KernelDML`, :class:`.NonParamDML`, -:class:`.CausalForestDML`. +:class:`.CausalForestDML`, +:class:`.DynamicDML`. Click on each of these links for a detailed module documentation and input parameters of each class. @@ -71,8 +72,10 @@ Most of the methods provided make a parametric form assumption on the heterogene linear on some pre-defined; potentially high-dimensional; featurization). These methods include: :class:`.DML`, :class:`.LinearDML`, :class:`.SparseLinearDML`, :class:`.KernelDML`. -For fullly non-parametric heterogeneous treatment effect models, checkout the :class:`.NonParamDML` -and the :class:`.CausalForestDML`. For more options of non-parametric CATE estimators, +For fullly non-parametric heterogeneous treatment effect models, check out the :class:`.NonParamDML` +and the :class:`.CausalForestDML`. +For treatments assigned sequentially over several time periods, see the class :class:`.DynamicDML`. +For more options of non-parametric CATE estimators, check out the :ref:`Forest Estimators User Guide ` and the :ref:`Meta Learners User Guide `. @@ -155,7 +158,7 @@ Class Hierarchy Structure In this library we implement variants of several of the approaches mentioned in the last section. The hierarchy structure of the implemented CATE estimators is as follows. - .. inheritance-diagram:: econml.dml.LinearDML econml.dml.SparseLinearDML econml.dml.KernelDML econml.dml.NonParamDML econml.dml.CausalForestDML + .. inheritance-diagram:: econml.dml.LinearDML econml.dml.SparseLinearDML econml.dml.KernelDML econml.dml.NonParamDML econml.dml.CausalForestDML econml.dml.DynamicDML :parts: 1 :private-bases: :top-classes: econml._rlearner._RLearner, econml._cate_estimator.StatsModelsCateEstimatorMixin, econml._cate_estimator.DebiasedLassoCateEstimatorMixin @@ -286,6 +289,37 @@ Below we give a brief description of each of these classes: Check out :ref:`Forest Estimators User Guide ` for more information on forest based CATE models and other alternatives to the :class:`.CausalForestDML`. + * **DynamicDML.** The class :class:`.DynamicDML` is an extension of the Double ML approach for treatments assigned sequentially over time periods. + This estimator will adjust for treatments that can have causal effects on future outcomes. The data corresponds to a Markov decision process :math:`\{X_t, W_t, T_t, Y_t\}_{t=1}^m`, + where :math:`X_t, W_t` corresponds to the state at time :math:`t`, :math:`T_t` is the treatment at time :math:`t` and :math:`Y_t` is the observed outcome at time :math:`t`. + + The model makes the following structural equation assumptions on the data generating process: + + .. math:: + + X_t =~& A \cdot T_{t-1} + B \cdot X_{t-1} + \eta_t\\ + T_t =~& p(T_{t-1}, X_t, \zeta_t) \\ + Y_t =~& \theta_0'T_t + \mu'X_t \epsilon_t + + For more details about this model and underlying assumptions, see [Lewis2021]_. + + To learn the treatment effects of treatments in the different periods on the last period outcome, one can simply call: + + .. testcode:: + + import numpy as np + from econml.dml import DynamicDML + + n_panels = 100 # number of panels + n_periods = 3 # number of time periods per panel + n = n_panels * n_periods + groups = np.repeat(a=np.arange(n_panels), repeats=n_periods, axis=0) + X = np.random.normal(size=(n, 1)) + T = np.random.normal(size=(n, 2)) + y = np.random.normal(size=(n, )) + est = DynamicDML() + est.fit(y, T, X=X, W=None, groups=groups, inference="auto") + * **_RLearner.** The internal private class :class:`._RLearner` is a parent of the :class:`.DML` and allows the user to specify any way of fitting a final model that takes as input the residual :math:`\tilde{T}`, the features :math:`X` and predicts the residual :math:`\tilde{Y}`. Moreover, the nuisance models take as input diff --git a/doc/spec/references.rst b/doc/spec/references.rst index 0692af351..dc9abe5bb 100644 --- a/doc/spec/references.rst +++ b/doc/spec/references.rst @@ -113,4 +113,9 @@ References .. [Lundberg2017] Lundberg, S., Lee, S. (2017). A Unified Approach to Interpreting Model Predictions. - URL https://arxiv.org/abs/1705.07874 \ No newline at end of file + URL https://arxiv.org/abs/1705.07874 + +.. [Lewis2021] + Lewis, G., Syrgkanis, V. (2021). + Double/Debiased Machine Learning for Dynamic Treatment Effects. + URL https://arxiv.org/abs/2002.07285 \ No newline at end of file diff --git a/econml/_ortho_learner.py b/econml/_ortho_learner.py index 2cee3da92..8134ffd88 100644 --- a/econml/_ortho_learner.py +++ b/econml/_ortho_learner.py @@ -664,7 +664,8 @@ def fit(self, Y, T, X=None, W=None, Z=None, *, sample_weight=None, sample_var=No X=X, W=W, Z=Z, nuisances=nuisances, sample_weight=sample_weight, - sample_var=sample_var) + sample_var=sample_var, + groups=groups) return self @@ -748,17 +749,20 @@ def _fit_nuisances(self, Y, T, X=None, W=None, Z=None, sample_weight=None, group sample_weight=sample_weight, groups=groups) return nuisances, fitted_models, fitted_inds, scores - def _fit_final(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def _fit_final(self, Y, T, X=None, W=None, Z=None, nuisances=None, + sample_weight=None, sample_var=None, groups=None): self._ortho_learner_model_final.fit(Y, T, **filter_none_kwargs(X=X, W=W, Z=Z, nuisances=nuisances, sample_weight=sample_weight, - sample_var=sample_var)) + sample_var=sample_var, + groups=groups)) self.score_ = None if hasattr(self._ortho_learner_model_final, 'score'): self.score_ = self._ortho_learner_model_final.score(Y, T, **filter_none_kwargs(X=X, W=W, Z=Z, nuisances=nuisances, sample_weight=sample_weight, - sample_var=sample_var)) + sample_var=sample_var, + groups=groups)) def const_marginal_effect(self, X=None): X, = check_input_arrays(X) @@ -793,7 +797,7 @@ def effect_inference(self, X=None, *, T0=0, T1=1): return super().effect_inference(X, T0=T0, T1=T1) effect_inference.__doc__ = LinearCateEstimator.effect_inference.__doc__ - def score(self, Y, T, X=None, W=None, Z=None, sample_weight=None): + def score(self, Y, T, X=None, W=None, Z=None, sample_weight=None, groups=None): """ Score the fitted CATE model on a new data set. Generates nuisance parameters for the new data set based on the fitted nuisance models created at fit time. @@ -817,6 +821,8 @@ def score(self, Y, T, X=None, W=None, Z=None, sample_weight=None): Instruments for each sample sample_weight: optional(n,) vector or None (Default=None) Weights for each samples + groups: (n,) vector, optional + All rows corresponding to the same group will be kept together during splitting. Returns ------- @@ -853,7 +859,8 @@ def score(self, Y, T, X=None, W=None, Z=None, sample_weight=None): nuisances[it] = np.mean(nuisances[it], axis=0) return self._ortho_learner_model_final.score(Y, T, nuisances=nuisances, - **filter_none_kwargs(X=X, W=W, Z=Z, sample_weight=sample_weight)) + **filter_none_kwargs(X=X, W=W, Z=Z, + sample_weight=sample_weight, groups=groups)) @property def ortho_learner_model_final_(self): diff --git a/econml/dml/__init__.py b/econml/dml/__init__.py index 2c3785d34..00035f2c1 100644 --- a/econml/dml/__init__.py +++ b/econml/dml/__init__.py @@ -8,7 +8,7 @@ Then estimates a CATE model by regressing the residual outcome on the residual treatment in a manner that accounts for heterogeneity in the regression coefficient, with respect to X. For the theoretical foundations of these methods see [dml]_, [rlearner]_, [paneldml]_, -[lassodml]_, [ortholearner]_. +[lassodml]_, [ortholearner]_, [dynamicdml]_. References ---------- diff --git a/econml/dml/_rlearner.py b/econml/dml/_rlearner.py index 5b954716a..d5989541a 100644 --- a/econml/dml/_rlearner.py +++ b/econml/dml/_rlearner.py @@ -91,7 +91,7 @@ class _ModelFinal: def __init__(self, model_final): self._model_final = model_final - def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): Y_res, T_res = nuisances self._model_final.fit(X, T, T_res, Y_res, sample_weight=sample_weight, sample_var=sample_var) return self @@ -99,7 +99,7 @@ def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, def predict(self, X=None): return self._model_final.predict(X) - def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): Y_res, T_res = nuisances if Y_res.ndim == 1: Y_res = Y_res.reshape((-1, 1)) diff --git a/econml/dml/causal_forest.py b/econml/dml/causal_forest.py index 4d4ac5eb8..84bb034d9 100644 --- a/econml/dml/causal_forest.py +++ b/econml/dml/causal_forest.py @@ -52,7 +52,7 @@ def _ate_and_stderr(self, drpreds, mask=None): stderr = (np.nanstd(drpreds, axis=0) / np.sqrt(nonnan)).reshape(self._d_y + self._d_t) return point, stderr - def fit(self, X, T, T_res, Y_res, sample_weight=None, sample_var=None): + def fit(self, X, T, T_res, Y_res, sample_weight=None, sample_var=None, groups=None): # Track training dimensions to see if Y or T is a vector instead of a 2-dimensional array self._d_t = shape(T_res)[1:] self._d_y = shape(Y_res)[1:] diff --git a/econml/dml/dml.py b/econml/dml/dml.py index 36d54f69c..defacd0cf 100644 --- a/econml/dml/dml.py +++ b/econml/dml/dml.py @@ -134,7 +134,7 @@ def _combine(self, X, T, fitting=True): F = np.ones((T.shape[0], 1)) return cross_product(F, T) - def fit(self, X, T, T_res, Y_res, sample_weight=None, sample_var=None): + def fit(self, X, T, T_res, Y_res, sample_weight=None, sample_var=None, groups=None): # Track training dimensions to see if Y or T is a vector instead of a 2-dimensional array self._d_t = shape(T_res)[1:] self._d_y = shape(Y_res)[1:] diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index 61fa68834..f39a25826 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -23,6 +23,17 @@ get_feature_names_or_default) +def _get_groups_period_filter(groups, n_periods): + group_counts = {} + group_period_filter = {i: [] for i in range(n_periods)} + for i, g in enumerate(groups): + if g not in group_counts: + group_counts[g] = 0 + group_period_filter[group_counts[g]].append(i) + group_counts[g] += 1 + return group_period_filter + + class _DynamicModelNuisance: """ Nuisance model fits the model_y and model_t at fit time and at predict time @@ -36,23 +47,23 @@ def __init__(self, model_y, model_t, n_periods): self.n_periods = n_periods def fit(self, Y, T, X=None, W=None, sample_weight=None, groups=None): - """Fit a series of nuisance models for each period or period pairs""" + """Fit a series of nuisance models for each period or period pairs.""" assert Y.shape[0] % self.n_periods == 0, \ "Length of training data should be an integer multiple of time periods." - inds_train = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] + period_filters = _get_groups_period_filter(groups, self.n_periods) self._model_y_trained = {} self._model_t_trained = {j: {} for j in np.arange(self.n_periods)} for t in np.arange(self.n_periods): self._model_y_trained[t] = clone(self._model_y, safe=False).fit( - self._filter_or_None(X, inds_train + t), + self._filter_or_None(X, period_filters[t]), self._filter_or_None( - W, inds_train + t), - Y[inds_train + self.n_periods - 1]) + W, period_filters[t]), + Y[period_filters[self.n_periods - 1]]) for j in np.arange(t, self.n_periods): self._model_t_trained[j][t] = clone(self._model_t, safe=False).fit( - self._filter_or_None(X, inds_train + t), - self._filter_or_None(W, inds_train + t), - T[inds_train + j]) + self._filter_or_None(X, period_filters[t]), + self._filter_or_None(W, period_filters[t]), + T[period_filters[j]]) return self def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): @@ -70,40 +81,39 @@ def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): matrix are np.nan. This shape is required for _OrthoLearner's crossfitting. """ - # TODO: update T_res docstring assert Y.shape[0] % self.n_periods == 0, \ "Length of training data should be an integer multiple of time periods." - inds_predict = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] + period_filters = _get_groups_period_filter(groups, self.n_periods) Y_res = np.full(Y.shape, np.nan) T_res = np.full(T.shape + (self.n_periods, ), np.nan) shape_formatter = self._get_shape_formatter(X, W) for t in np.arange(self.n_periods): - Y_slice = Y[inds_predict + self.n_periods - 1] + Y_slice = Y[period_filters[self.n_periods - 1]] Y_pred = self._model_y_trained[t].predict( - self._filter_or_None(X, inds_predict + t), - self._filter_or_None(W, inds_predict + t)) - Y_res[np.arange(Y.shape[0]) % self.n_periods == t] = Y_slice\ + self._filter_or_None(X, period_filters[t]), + self._filter_or_None(W, period_filters[t])) + Y_res[period_filters[t]] = Y_slice\ - shape_formatter(Y_slice, Y_pred).reshape(Y_slice.shape) for j in np.arange(t, self.n_periods): - T_slice = T[inds_predict + j] + T_slice = T[period_filters[j]] T_pred = self._model_t_trained[j][t].predict( - self._filter_or_None(X, inds_predict + t), - self._filter_or_None(W, inds_predict + t)) - T_res[np.arange(Y.shape[0]) % self.n_periods == j, ..., t] = T_slice\ + self._filter_or_None(X, period_filters[t]), + self._filter_or_None(W, period_filters[t])) + T_res[period_filters[j], ..., t] = T_slice\ - shape_formatter(T_slice, T_pred).reshape(T_slice.shape) return Y_res, T_res def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): assert Y.shape[0] % self.n_periods == 0, \ "Length of training data should be an integer multiple of time periods." - inds_score = np.arange(Y.shape[0])[np.arange(Y.shape[0]) % self.n_periods == 0] + period_filters = _get_groups_period_filter(groups, self.n_periods) if hasattr(self._model_y, 'score'): Y_score = np.full((self.n_periods, ), np.nan) for t in np.arange(self.n_periods): Y_score[t] = self._model_y_trained[t].score( - self._filter_or_None(X, inds_score + t), - self._filter_or_None(W, inds_score + t), - Y[inds_score + self.n_periods - 1]) + self._filter_or_None(X, period_filters[t]), + self._filter_or_None(W, period_filters[t]), + Y[period_filters[self.n_periods - 1]]) else: Y_score = None if hasattr(self._model_t, 'score'): @@ -111,9 +121,9 @@ def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): for t in np.arange(self.n_periods): for j in np.arange(t, self.n_periods): T_score[j][t] = self._model_t_trained[j][t].score( - self._filter_or_None(X, inds_score + t), - self._filter_or_None(W, inds_score + t), - T[inds_score + j]) + self._filter_or_None(X, period_filters[t]), + self._filter_or_None(W, period_filters[t]), + T[period_filters[j]]) else: T_score = None return Y_score, T_score @@ -146,23 +156,23 @@ def __init__(self, model_final, n_periods): self.n_periods = n_periods self._model_final_trained = {k: clone(self._model_final, safe=False) for k in np.arange(n_periods)} - def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): # NOTE: sample weight, sample var are not passed in + period_filters = _get_groups_period_filter(groups, self.n_periods) Y_res, T_res = nuisances self._d_y = Y.shape[1:] for t in np.arange(self.n_periods): period = self.n_periods - 1 - t - period_filter = self.period_filter_gen(period, Y.shape[0]) - Y_adj = Y_res[period_filter].copy() + Y_adj = Y_res[period_filters[period]].copy() if t > 0: Y_adj -= np.sum( [self._model_final_trained[j].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0])] if X is not None else None, - T_res[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0]), ..., period] + X[period_filters[self.n_periods - 1 - j]] if X is not None else None, + T_res[period_filters[self.n_periods - 1 - j], ..., period] ) for j in np.arange(t)], axis=0) self._model_final_trained[t].fit( - X[period_filter] if X is not None else None, T[period_filter], - T_res[period_filter, ..., period], Y_adj) + X[period_filters[period]] if X is not None else None, T[period_filters[period]], + T_res[period_filters[period], ..., period], Y_adj) return self @@ -185,25 +195,25 @@ def predict(self, X=None): ) return preds - def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): assert Y.shape[0] % self.n_periods == 0, \ "Length of training data should be an integer multiple of time periods." Y_res, T_res = nuisances - scores = np.full((self.n_periods, ), np.nan) + period_filters = _get_groups_period_filter(groups, self.n_periods) for t in np.arange(self.n_periods): period = self.n_periods - 1 - t - period_filter = self.period_filter_gen(period, Y.shape[0]) - Y_adj = Y_res[period_filter].copy() + # period_filter = self.period_filter_gen(period, Y.shape[0]) + Y_adj = Y_res[period_filters[period]].copy() if t > 0: Y_adj -= np.sum( [self._model_final_trained[j].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0])] if X is not None else None, - T_res[self.period_filter_gen(self.n_periods - 1 - j, Y.shape[0]), ..., period] + X[period_filters[self.n_periods - 1 - j]] if X is not None else None, + T_res[period_filters[self.n_periods - 1 - j], ..., period] ) for j in np.arange(t)], axis=0) Y_adj_pred = self._model_final_trained[t].predict_with_res( - X[period_filter] if X is not None else None, - T_res[period_filter, ..., period]) + X[period_filters[period]] if X is not None else None, + T_res[period_filters[period], ..., period]) if sample_weight is not None: scores[t] = np.mean(np.average((Y_adj - Y_adj_pred)**2, weights=sample_weight, axis=0)) else: @@ -225,10 +235,11 @@ def __init__(self, model_final, n_periods): super().__init__(model_final, n_periods) self.model_final_ = StatsModelsLinearRegression(fit_intercept=False) - def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): - super().fit(Y, T, X=X, W=W, Z=Z, nuisances=nuisances, sample_weight=sample_weight, sample_var=sample_var) + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): + super().fit(Y, T, X=X, W=W, Z=Z, nuisances=nuisances, + sample_weight=sample_weight, sample_var=sample_var, groups=groups) # Compose final model - cov = self._get_cov(nuisances, X) + cov = self._get_cov(nuisances, X, groups) coef = self._get_coef_() self.model_final_._n_out = self._d_y[0] if self._d_y else 0 self.model_final_._param_var = cov / (Y.shape[0] / self.n_periods) @@ -243,25 +254,28 @@ def _get_coef_(self): ]) return period_coefs.flatten() - def _get_cov(self, nuisances, X): + def _get_cov(self, nuisances, X, groups): if self._d_y: return np.array( - [self._fit_single_output_cov((nuisances[0][:, i], nuisances[1]), X, i) for i in range(self._d_y[0])] + [self._fit_single_output_cov((nuisances[0][:, i], nuisances[1]), X, i, groups) + for i in range(self._d_y[0])] ) - return self._fit_single_output_cov(nuisances, X, -1) + return self._fit_single_output_cov(nuisances, X, -1, groups) - def _fit_single_output_cov(self, nuisances, X, y_index): + def _fit_single_output_cov(self, nuisances, X, y_index, groups): """ Calculates the covariance (n_periods*n_treatments) x (n_periods*n_treatments) matrix for a single outcome. """ + # TODO: add group filters here Y_res, T_res = nuisances # Calculate auxiliary quantities + period_filters = _get_groups_period_filter(groups, self.n_periods) # X ⨂ T_res XT_res = np.array([ [ self._model_final_trained[0]._combine( - X[self.period_filter_gen(j, Y_res.shape[0])] if X is not None else None, - T_res[self.period_filter_gen(t, Y_res.shape[0]), ..., j], + X[period_filters[j]] if X is not None else None, + T_res[period_filters[t], ..., j], fitting=False ) for j in range(self.n_periods) @@ -273,10 +287,9 @@ def _fit_single_output_cov(self, nuisances, X, y_index): Y_diff = np.array([ np.sum([ self._model_final_trained[j].predict_with_res( - X[self.period_filter_gen(self.n_periods - 1 - j, - Y_res.shape[0])] if X is not None else None, + X[period_filters[self.n_periods - 1 - j]] if X is not None else None, T_res[ - self.period_filter_gen(self.n_periods - 1 - j, Y_res.shape[0]), ..., self.n_periods - 1 - t] + period_filters[self.n_periods - 1 - j], ..., self.n_periods - 1 - t] ) for j in np.arange(t + 1)], axis=0 ) @@ -288,7 +301,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index): self.n_periods * d_xt)) for t in np.arange(self.n_periods): period_t = self.n_periods - 1 - t - period_filter_t = self.period_filter_gen(period_t, Y_res.shape[0]) + period_filter_t = period_filters[period_t] res_epsilon_t = (Y_res[period_filter_t] - (Y_diff[t][:, y_index] if y_index >= 0 else Y_diff[t]) ).reshape(-1, 1, 1) @@ -296,7 +309,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index): for j in np.arange(self.n_periods): # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix Sigma period_j = self.n_periods - 1 - j - period_filter_j = self.period_filter_gen(period_j, Y_res.shape[0]) + period_filter_j = period_filters[period_j] res_epsilon_j = (Y_res[period_filter_j] - (Y_diff[j][:, y_index] if y_index >= 0 else Y_diff[j]) ).reshape(-1, 1, 1) @@ -545,11 +558,19 @@ def _strata(self, Y, T, X=None, W=None, Z=None, "we will disallow passing X and W by position.", ['X', 'W']) def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, groups, cache_values=False, inference=None): - """ - Estimate the counterfactual model from data, i.e. estimates function :math:`\\theta(\\cdot)`. + """Estimate the counterfactual model from data, i.e. estimates function :math:`\\theta(\\cdot)`. + + The input data must contain groups with the same size corresponding to the number + of time periods the treatments were assigned over. - The input data has to be in panel format, i.e. a sequence of groups, each with the same size corresponding - to the number of time periods the treatments were assigned over. + The data should be preferably in panel format, with groups clustered together. + If group members do not appear together, the following is assumed: + + * the first instance of a group in the dataset is assumed to correspond to the first period of that group + * the second instance of a group in the dataset is assumed to correspond to the + second period of that group + + ...etc. Parameters ---------- @@ -589,7 +610,7 @@ def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, grou cache_values=cache_values, inference=inference) - def score(self, Y, T, X=None, W=None): + def score(self, Y, T, X=None, W=None, *, groups): """ Score the fitted CATE model on a new data set. Generates nuisance parameters for the new data set based on the fitted residual nuisance models created at fit time. @@ -608,6 +629,8 @@ def score(self, Y, T, X=None, W=None): Features for each sample (Required: n = n_groups * n_periods) W: optional(n, d_w) matrix or None (Default=None) Controls for each sample (Required: n = n_groups * n_periods) + groups: (n,) vector, required + All rows corresponding to the same group will be kept together during splitting. Returns ------- @@ -615,7 +638,7 @@ def score(self, Y, T, X=None, W=None): The MSE of the final CATE model on the new data. """ # Replacing score from _OrthoLearner, to enforce Z=None and improve the docstring - return super().score(Y, T, X=X, W=W) + return super().score(Y, T, X=X, W=W, groups=groups) def cate_treatment_names(self, treatment_names=None): """ diff --git a/econml/dr/_drlearner.py b/econml/dr/_drlearner.py index 03edf8e62..5ec747e38 100644 --- a/econml/dr/_drlearner.py +++ b/econml/dr/_drlearner.py @@ -124,7 +124,7 @@ def __init__(self, model_final, featurizer, multitask_model_final): self._multitask_model_final = multitask_model_final return - def fit(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None, groups=None): Y_pred, = nuisances self.d_y = Y_pred.shape[1:-1] # track whether there's a Y dimension (must be a singleton) self.d_t = Y_pred.shape[-1] - 1 # track # of treatment (exclude baseline treatment) @@ -154,7 +154,7 @@ def predict(self, X=None): preds = np.array([mdl.predict(X).reshape((-1,) + self.d_y) for mdl in self.models_cate]) return np.moveaxis(preds, 0, -1) # move treatment dim to end - def score(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None, groups=None): if (X is not None) and (self._featurizer is not None): X = self._featurizer.transform(X) Y_pred, = nuisances diff --git a/econml/iv/dml/_dml.py b/econml/iv/dml/_dml.py index 23f0add53..98a85f274 100644 --- a/econml/iv/dml/_dml.py +++ b/econml/iv/dml/_dml.py @@ -33,7 +33,7 @@ def __init__(self): self._model_final = _FinalWrapper(LinearRegression(fit_intercept=False), fit_cate_intercept=True, featurizer=None, use_weight_trick=False) - def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): Y_res, T_res, Z_res = nuisances if Z_res.ndim == 1: Z_res = Z_res.reshape(-1, 1) @@ -49,7 +49,7 @@ def predict(self, X=None): # TODO: allow the final model to actually use X? return self._model_final.predict(X=None) - def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): Y_res, T_res, Z_res = nuisances if Y_res.ndim == 1: Y_res = Y_res.reshape((-1, 1)) @@ -379,7 +379,7 @@ class _BaseDMLIVModelFinal: def __init__(self, model_final): self._model_final = clone(model_final, safe=False) - def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): Y_res, T_res = nuisances self._model_final.fit(X, T, T_res, Y_res, sample_weight=sample_weight, sample_var=sample_var) return self @@ -387,7 +387,7 @@ def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, def predict(self, X=None): return self._model_final.predict(X) - def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): Y_res, T_res = nuisances if Y_res.ndim == 1: Y_res = Y_res.reshape((-1, 1)) diff --git a/econml/iv/dr/_dr.py b/econml/iv/dr/_dr.py index 003b1577e..9ef8c547a 100644 --- a/econml/iv/dr/_dr.py +++ b/econml/iv/dr/_dr.py @@ -75,7 +75,7 @@ def _effect_estimate(self, nuisances): self._cov_clip, np.inf) return prel_theta + (res_y - prel_theta * res_t) * res_z / clipped_cov, clipped_cov - def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): self.d_y = Y.shape[1:] self.d_t = nuisances[1].shape[1:] self.d_z = nuisances[3].shape[1:] @@ -114,7 +114,7 @@ def predict(self, X=None): X = self._featurizer.transform(X) return self._model_final.predict(X).reshape((-1,) + self.d_y + self.d_t) - def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, sample_var=None, groups=None): theta_dr, clipped_cov = self._effect_estimate(nuisances) if (X is not None) and (self._featurizer is not None): diff --git a/econml/policy/_drlearner.py b/econml/policy/_drlearner.py index 145a36fd9..d9bc7966d 100644 --- a/econml/policy/_drlearner.py +++ b/econml/policy/_drlearner.py @@ -14,7 +14,7 @@ class _PolicyModelFinal(_ModelFinal): - def fit(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None): + def fit(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None, groups=None): if sample_var is not None: warn('Parameter `sample_var` is ignored by the final estimator') sample_var = None @@ -38,7 +38,7 @@ def predict(self, X=None): return pred[:, np.newaxis, :] return pred - def score(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None): + def score(self, Y, T, X=None, W=None, *, nuisances, sample_weight=None, sample_var=None, groups=None): return 0 diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index 21a5aa7a9..b573be805 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -9,6 +9,7 @@ from sklearn.linear_model import (LinearRegression, LassoCV, Lasso, MultiTaskLasso, MultiTaskLassoCV, LogisticRegression) from econml.dml import DynamicDML +from econml.dml.dynamic_dml import _get_groups_period_filter from econml.inference import BootstrapInference, EmpiricalInferenceResults, NormalInferenceResults from econml.utilities import shape, hstack, vstack, reshape, cross_product import econml.tests.utilities # bugfix for assertWarns @@ -267,25 +268,34 @@ def test_perf(self): def lasso_model(): return LassoCV(cv=3, alphas=alpha_regs, max_iter=500) + # No heterogeneity dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( s_x, random_seed=1) Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12) est = DynamicDML(model_y=lasso_model(), model_t=lasso_model(), cv=3) - est.fit(Y, T, X=X, W=W, groups=groups, inference="auto") - np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=1e-01) - np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) - np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) + # Define indices to test + groups_filter = _get_groups_period_filter(groups, 3) + shuffled_idx = np.array([groups_filter[i] for i in range(n_periods)]).flatten() + test_indices = [np.arange(n_units * n_periods), shuffled_idx] + for test_idx in test_indices: + est.fit(Y[test_idx], T[test_idx], X=X[test_idx] if X is not None else None, W=W[test_idx], + groups=groups[test_idx], inference="auto") + np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=1e-01) + np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) + np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) + # Heterogeneous effects - hetero_strength = .5 - hetero_inds = np.arange(n_x - n_treatments, n_x) dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( s_x, hetero_strength=hetero_strength, hetero_inds=hetero_inds, random_seed=1) Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12) - est.fit(Y, T, X=X, W=W, groups=groups, inference="auto") - np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=0.2) - np.testing.assert_allclose(est.coef_, dgp.true_hetero_effect[:, hetero_inds + 1], atol=0.2) - np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) - np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) - np.testing.assert_array_less(est.coef__interval()[0], dgp.true_hetero_effect[:, hetero_inds + 1]) - np.testing.assert_array_less(dgp.true_hetero_effect[:, hetero_inds + 1], est.coef__interval()[1]) + for test_idx in test_indices: + hetero_strength = .5 + hetero_inds = np.arange(n_x - n_treatments, n_x) + est.fit(Y[test_idx], T[test_idx], X=X[test_idx], W=W[test_idx], groups=groups[test_idx], inference="auto") + np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=0.2) + np.testing.assert_allclose(est.coef_, dgp.true_hetero_effect[:, hetero_inds + 1], atol=0.2) + np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) + np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) + np.testing.assert_array_less(est.coef__interval()[0], dgp.true_hetero_effect[:, hetero_inds + 1]) + np.testing.assert_array_less(dgp.true_hetero_effect[:, hetero_inds + 1], est.coef__interval()[1]) diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb index 268402dfb..6eb2e2b91 100644 --- a/notebooks/Dynamic Double Machine Learning Examples.ipynb +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -28,7 +28,7 @@ "* the effect of prices on demand in stores where prices of goods change over time\n", "* the effect of income on health outcomes in people who receive yearly income\n", "\n", - "The expected data format is balanced panel data. Each panel corresponds to one entity (e.g. company, store or person) and the different rows in a panel correspond to different time points. Example:\n", + "The preferred data format is balanced panel data. Each panel corresponds to one entity (e.g. company, store or person) and the different rows in a panel correspond to different time points. Example:\n", "\n", "||Company|Year|Features|Investment|Revenue|\n", "|---|---|---|---|---|---|\n", @@ -44,6 +44,21 @@ "\n", "(Note: when passing the data to the DynamicDML estimator, the \"Company\" column above corresponds to the `groups` argument at fit time. The \"Year\" column above should not be passed in as it will be inferred from the \"Company\" column)\n", "\n", + "If group memebers do not appear together, it is assumed that the first instance of a group in the dataset corresponds to the first period of that group, the second instance of the group corresponds to the second period, etc. Example:\n", + "\n", + "||Company|Features|Investment|Revenue|\n", + "|---|---|---|---|---|\n", + "|1|A|...|\\$1,000|\\$10,000|\n", + "|2|B|...|\\$0|\\$5,000\n", + "|3|C|...|\\$1,000|\\$20,000|\n", + "|4|A|...|\\$2,000|\\$12,000|\n", + "|5|B|...|\\$100|\\$10,000|\n", + "|6|C|...|\\$1,500|\\$25,000|\n", + "|7|A|...|\\$3,000|\\$15,000|\n", + "|8|B|...|\\$1,200|\\$7,000|\n", + "|9|C|...|\\$500|\\$15,000|\n", + "\n", + "In this dataset, 1st row corresponds to the first period of group `A`, 4th row corresponds to the second period of group `A`, etc.\n", "\n", "In this notebook, we show the performance of the DynamicDML on synthetic and observational data. \n", "\n", @@ -172,7 +187,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -269,10 +284,10 @@ " cate_intercept|$(T0)_0$ 0.711 0.041 17.224 0.0 0.643 0.779 \n", "\n", "\n", - " cate_intercept|$(T0)_1$ 1.031 0.1 10.306 0.0 0.866 1.195 \n", + " cate_intercept|$(T0)_1$ 1.031 0.096 10.715 0.0 0.872 1.189 \n", "\n", "\n", - " cate_intercept|$(T0)_2$ 0.518 0.139 3.739 0.0 0.29 0.746 \n", + " cate_intercept|$(T0)_2$ 0.518 0.142 3.658 0.0 0.285 0.751 \n", "\n", "

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -284,8 +299,8 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", "cate_intercept|$(T0)_0$ 0.711 0.041 17.224 0.0 0.643 0.779\n", - "cate_intercept|$(T0)_1$ 1.031 0.1 10.306 0.0 0.866 1.195\n", - "cate_intercept|$(T0)_2$ 0.518 0.139 3.739 0.0 0.29 0.746\n", + "cate_intercept|$(T0)_1$ 1.031 0.096 10.715 0.0 0.872 1.189\n", + "cate_intercept|$(T0)_2$ 0.518 0.142 3.658 0.0 0.285 0.751\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -329,7 +344,7 @@ "outputs": [ { "data": { - "image/png": "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F5xzo1u4ro+k95xzWS29Z15envt8FxoAaA9bPtknSRpxdrrnJPDl8/vvlt48wXMShITagq5my5YtGjFiRKuvX762RD96eoMO1x1RVmaq5k4brhnjW/xx/pR8/PHHmjRpkt5//3116xaP7USa+nsxs0LnXF5T13eWe/BukvSy7xAA4okfvgBEgdqC0M0Yn6Un13wkqWN+QfaHP/xBt99+ux588MHYNHdt4b3BM7PJSjR4F53gmlmSZknSwIEDOygZAAAAgBPpyJUP1113na677roO+35dldfW18zGSvp3SVc658qau845t8g5l+ecy+vfv3/HBQQQC69u3q1XN+/2HQNAYKgtAHzw1uCZ2UBJyyR9yzn3ga8cAAAAABCKyJZomtmTkiZJ6mdmxZLulJQsSc65RyXdIamvpF/Vb3ta29yNggAAAACAlkXW4Dnnrm3h/HckfSeq7w8AAAAAccP2MwAAAAA6hd27d2vmzJkaOnSocnNzNWHCBD377LMdmmHHjh0aPfr4p7zt2LFDixcvbtN7/uIXv9DBgwcbjnv37t3mfC2hwQMQe4P69tSgvj19xwAQGGoLcHKcc5oxY4YmTpyooqIiFRYWasmSJSouLj7u2tra2g7Pd6IGr6U8xzZ4UfL+mAQA8G3YmWm+IwAIELUFwSsulNYtlip2SpmDpJyZUnZum9/u9ddfV/fu3XXLLbc0jA0aNEi33nqrJOmJJ57QsmXLtH//ftXV1enZZ5/VjTfeqKKiIvXs2VOLFi3S2LFjtXDhQvXu3Vv//M//LEkaPXq0XnjhBUnS5ZdfrosuukhvvvmmsrKy9Nxzzyk1NVWFhYW68cYbJUmXXXZZk/nmzZunLVu2KCcnR9dff7369OlzVJ677rpL999/f8P3mjNnjvLy8rRv3z59/PHHmjx5svr166f8/HxJ0u23364XXnhBqampeu6553TmmWe2+c+uMWbwAMRebd0R1dYd8R0DQGCoLQhacaGUf7dUVSaln514zb87Md5GmzZt0he/+MUTXvPuu+/q6aef1htvvKE777xT48eP14YNG/Szn/2sVc/I27Ztm2bPnq1NmzYpMzNTzzzzjCTphhtu0C9/+UutX7++2a+95557dPHFF2vdunX6/ve/f1ye5tx2220655xzlJ+f39DcHThwQBdccIHWr1+viRMn6je/+U2L2VuLBg9A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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3gAAAEzCAYAAABjbqHIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAlPklEQVR4nO3de3SW5Z3u8etHDCRAQhDwlHDsRgY5hSZaqUrBUcHWbdnTVi3uatWOuoo6qwe6YclSbGtHx0M7VlulU2s7qwhWEd0ehvEQbNmimMipgIgNRBIVQ0ggQBKScO8/3pgGSEII75Ob3M/3sxbrzXO/T95cQvtbXDwnc84JAAAAAND99fAdAAAAAACQHBQ8AAAAAAgEBQ8AAAAAAkHBAwAAAIBAUPAAAAAAIBAUPAAAAAAIxEm+AxyrgQMHumHDhvmOASAge2rrJUmZaamekwAICbMFQFSKiop2OucGtfZetyt4w4YNU2Fhoe8YAAKyZnuVJCl3cJbXHADCwmwBEBUzK2nrvW5X8AAg2fjLF4AoMFsA+MA1eAAAAAAQCAoegNj7y5Zy/WVLue8YAALDbAHgA6doAoi9uvqDviMACBCzBd1NfX29SktLVVtb6zsKmqSlpSknJ0epqR2/WRMFDwAAAIBKS0uVkZGhYcOGycx8x4k955wqKipUWlqq4cOHd/j7OEUTAAAAgGprazVgwADK3QnCzDRgwIBjPqJKwQMAAAAgSZS7E0xn/jwoeABi77R+aTqtX5rvGAACw2xBHFz52Epd+djKpH1eSkqKcnNzNWbMGE2YMEEPPPCADh70cz1rYWGhbrvttnb3GTZsmMaNG6dx48bprLPO0rx585qPuG3btk1mpnnz5jXvv3PnTqWmpuqWW26RJM2fP1/3339/UnNT8ADE3tjsfhqb3c93DACBYbYAxy49PV1r1qzRhg0b9Morr+jll1/WXXfd5SVLfn6+HnrooaPuV1BQoPXr12vVqlUqLi7WTTfd1Pze8OHD9eKLLzZv/+lPf9KYMWMiyfsZCh4AAACAY7Z0dZlWf1ilt7fu0nn3vK6lq8uS+vmnnHKKFixYoIcffljOOU2ePFlr1qxpfv/888/X2rVrNX/+fF1//fWaMmWKRowYcUgpmzFjhvLy8jRmzBgtWLCgeb1v376aPXu2xowZo4suukirVq1q/v7nn39ekrR8+XJddtllkqS9e/fquuuu07hx4zR+/Hg988wzR+Tt27evHn30US1dulS7du2SJPXu3VujR49WYWGhJGnx4sW64oorkvr7dDgKHoDYK9j8qQo2f+o7BoDAMFsQsqWryzR3yXodaEycPllWVaO5S9YnveSNGDFCjY2N+vTTT3XDDTfoiSeekCS9//77qq2t1YQJEyRJ7733npYtW6ZVq1bprrvuUn19vSTp8ccfV1FRkQoLC/XQQw+poqJCkrRv3z5deOGF2rBhgzIyMjRv3jy98sorevbZZ3XHHXcckeMnP/mJ+vXrp/Xr12vdunW68MILW82bmZmp4cOHa8uWLc1rV111lRYtWqTt27crJSVFZ5xxRjJ/i44QWcEzs8fN7FMz+2sb719tZuvMbL2ZvWlmE6LKAgDtaWx0amx0vmPAo2RfQwJIzBaE7b5lm1VT33jIWk19o+5btjmyn/mNb3xDL7zwgurr6/X444/r29/+dvN7X/nKV9SrVy8NHDhQp5xyinbs2CFJeuihhzRhwgSde+652r59e3Px6tmzp6ZPny5JGjdunL70pS8pNTVV48aN07Zt24742a+++qpmzZrVvN2/f/82czp36P/vp0+frldeeUWLFi3SlVde2dn//A6L8gjeE5Kmt/P+Vklfcs6Nk/QTSQva2RcAAADACeKjqppjWu+s4uJipaSk6JRTTlHv3r118cUX67nnntNTTz2lq6++unm/Xr16NX+dkpKihoYGLV++XK+++qpWrlyptWvXauLEic03QElNTW2+Q2WPHj2av79Hjx5qaGjodN7q6mpt27ZNZ555ZvNaz549lZeXpwceeEBf//rXO/3ZHRVZwXPO/VnSrnbef9M5V9m0+ZaknKiyAAAAAEieM7LSj2m9M8rLy3XzzTfrlltuaS5j3/nOd3Tbbbfp7LPPbvcomiTt3r1b/fv3V+/evfXee+/prbfe6nSWiy++WI888kjzdmVl5RH77N27V9/97nc1Y8aMI7L94Ac/0L333quTTz650xk66kS5Bu8GSS/7DgEAAADg6GZPG6X01JRD1tJTUzR72qjj+tyamprmxyRcdNFFuuSSS3TnnXc2v5+Xl6fMzExdd911R/2s6dOnq6GhQaNHj9acOXN07rnndjrXvHnzVFlZqbFjx2rChAkqKChofm/q1KkaO3aszjnnHA0ZMkSPPfbYEd8/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\n", "text/plain": [ "
" ] @@ -431,7 +446,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 17, @@ -460,10 +475,10 @@ " X0|$(T0)_0$ 0.394 0.103 3.838 0.0 0.225 0.563 \n", "\n", "\n", - " X0|$(T0)_1$ -0.066 0.192 -0.343 0.732 -0.382 0.25 \n", + " X0|$(T0)_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248 \n", "\n", "\n", - " X0|$(T0)_2$ 0.04 0.201 0.198 0.843 -0.291 0.37 \n", + " X0|$(T0)_2$ 0.04 0.2 0.199 0.843 -0.29 0.369 \n", "\n", "\n", "\n", @@ -475,10 +490,10 @@ " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
cate_intercept|$(T0)_0$ 0.579 0.052 11.242 0.0 0.495 0.664
cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.704 -0.108 0.173cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.705 -0.108 0.173
cate_intercept|$(T0)_2$ -0.098 0.093 -1.049 0.294 -0.251 0.055cate_intercept|$(T0)_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -490,15 +505,15 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------\n", "X0|$(T0)_0$ 0.394 0.103 3.838 0.0 0.225 0.563\n", - "X0|$(T0)_1$ -0.066 0.192 -0.343 0.732 -0.382 0.25\n", - "X0|$(T0)_2$ 0.04 0.201 0.198 0.843 -0.291 0.37\n", + "X0|$(T0)_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248\n", + "X0|$(T0)_2$ 0.04 0.2 0.199 0.843 -0.29 0.369\n", " CATE Intercept Results \n", "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", "cate_intercept|$(T0)_0$ 0.579 0.052 11.242 0.0 0.495 0.664\n", - "cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.704 -0.108 0.173\n", - "cate_intercept|$(T0)_2$ -0.098 0.093 -1.049 0.294 -0.251 0.055\n", + "cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.705 -0.108 0.173\n", + "cate_intercept|$(T0)_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -610,7 +625,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA4AAAAFWCAYAAAA4zPNMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAApxklEQVR4nO3de3hV9Z3v8c+XECBcw80LAbl0kOEOJTrSqkVFwVOnMu0pWHtGvI16qmWmFxw88nhrp9Vaa8fqaaGto53pCNQqXjseQegUS6tBLgpIsQgSVIiBIJcEQvieP7JJQ0hI9l7Z+5e91/v1PDzZ67dXko/b+JVP1tprmbsLAAAAAJD72oUOAAAAAADIDAogAAAAAMQEBRAAAAAAYoICCAAAAAAxQQEEAAAAgJigAAIAAABATLQPHaC19enTxwcNGhQ6BoA27OOqaklS9075gZMAQHKYXwBaYtWqVR+5e9/Gnsu5Ajho0CCVlJSEjgGgDVuzvUKSNG5AYdAcAJAs5heAljCzbU09l3MFEACaw1+cAGQr5heAqHgPIAAAAADEBAUQQOz8bnOZfre5LHQMAEga8wtAVJwCCiB2DlUfDR0BAFLC/EJI1dXVKi0tVVVVVegoSOjUqZP69++v/PyWXxiKAggAAACgWaWlperWrZsGDRokMwsdJ/bcXeXl5SotLdXgwYNb/HmcAgoAAACgWVVVVerduzflr40wM/Xu3TvpI7IUQAAAAAAtQvlrW1L590EBBBA7p/XopNN6dAodAwCSxvxCtpkxb6VmzFvZal8vLy9P48aN08iRIzV27Fg98MADOno0zHtjS0pKNGvWrJPuM2jQII0ePVqjR4/WiBEjNHfu3Lojdlu3bpWZae7cuXX7f/TRR8rPz9ctt9wiSbrrrrv0/e9/v1VzUwABxM6ooh4aVdQjdAwASBrzC3FXUFCgNWvWaP369Xr55Zf1m9/8RnfffXeQLMXFxXrooYea3W/ZsmV688039dprr2nLli268cYb654bPHiwXnjhhbrtX/3qVxo5cmRa8h5DAQQAAADQ6hav3qHV71Xoj+/u1qfvfUWLV+9o1a9/yimnaP78+Xr44Yfl7jr//PO1Zs2auufPPfdcrV27VnfddZeuvfZaTZo0SUOGDDmutE2bNk0TJkzQyJEjNX/+/Lr1rl27avbs2Ro5cqQmT56s1157re7zn332WUnS8uXLddlll0mS9u/fr2uuuUajR4/WmDFj9Otf//qEvF27dtVPfvITLV68WLt375Ykde7cWcOHD1dJSYkkaeHChZo+fXqrvk4NUQAzoLUPfQOIZtmmXVq2aVfoGACQNOYXssXi1Tt021Nv6nBN7emZOyoqddtTb7Z6CRwyZIhqamq0a9cuXXfddXrsscckSX/6059UVVWlsWPHSpLefvttvfTSS3rttdd09913q7q6WpL06KOPatWqVSopKdFDDz2k8vJySdKBAwd04YUXav369erWrZvmzp2rl19+WU8//bTuuOOOE3J861vfUo8ePfTmm29q3bp1uvDCCxvN2717dw0ePFibN2+uW7viiiu0YMECbd++XXl5eerXr19rvkQnoAACiJ2aGldNjYeOAQBJY34hW9z/0iZVVtcct1ZZXaP7X9qUtu/5xS9+Uc8//7yqq6v16KOP6uqrr6577rOf/aw6duyoPn366JRTTtHOnTslSQ899JDGjh2rc845R9u3b68rZh06dNDUqVMlSaNHj9ZnPvMZ5efna/To0dq6desJ33vJkiW6+eab67Z79uzZZE734/8bnjp1ql5++WUtWLBAM2bMSPUfv8W4DyAAAACAVvV+RWVS66nasmWL8vLydMopp8jMdPHFF+uZZ57RokWLtGrVqrr9OnbsWPc4Ly9PR44c0fLly7VkyRKtXLlSnTt31qRJk+ou0JKfn193hc127drVfX67du105MiRlPPu27dPW7du1Zlnnqm9e/dKqi2bEyZM0AMPPKANGzbUnWKaLhwBBAAAANCq+hUWJLWeirKyMt1000265ZZb6sra9ddfr1mzZumss8466VE4Sdq7d6969uypzp076+2339Yf/vCHlLNcfPHFeuSRR+q29+zZc8I++/fv11e+8hVNmzbthGzf+MY3dN9996lXr14pZ2gpCiAAAACAVjV7yjAV5Ocdt1aQn6fZU4ZF+rqVlZV1t4GYPHmyLrnkEt155511z0+YMEHdu3fXNddc0+zXmjp1qo4cOaLhw4drzpw5Ouecc1LONXfuXO3Zs0ejRo3S2LFjtWzZsrrnLrjgAo0aNUpnn322zjjjDM2bN++Ezx85cqRmzpzZ6Nf+9re/rf79+9f9icoanoOa7YqLi/3YVXTaimMXgFl448TASQBI0sYPPpYkDT+9e+AkAJAc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\n", + "image/png": "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\n", "text/plain": [ "
" ] From efd634da819a56afcf08a4d326f989c17b98ace2 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Fri, 30 Jul 2021 23:54:32 -0400 Subject: [PATCH 08/27] Implement score --- econml/_ortho_learner.py | 2 +- econml/dml/dynamic_dml.py | 40 +++++++++++++++++++++++++------- econml/tests/test_dynamic_dml.py | 3 --- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/econml/_ortho_learner.py b/econml/_ortho_learner.py index 8134ffd88..3c7afcbab 100644 --- a/econml/_ortho_learner.py +++ b/econml/_ortho_learner.py @@ -845,7 +845,7 @@ def score(self, Y, T, X=None, W=None, Z=None, sample_weight=None, groups=None): for i, models_nuisances in enumerate(self._models_nuisance): # for each model under cross fit setting for j, mdl in enumerate(models_nuisances): - nuisance_temp = mdl.predict(Y, T, **filter_none_kwargs(X=X, W=W, Z=Z)) + nuisance_temp = mdl.predict(Y, T, **filter_none_kwargs(X=X, W=W, Z=Z, groups=groups)) if not isinstance(nuisance_temp, tuple): nuisance_temp = (nuisance_temp,) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index f39a25826..0e67aa76f 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -20,7 +20,8 @@ cross_product, deprecated, fit_with_groups, hstack, inverse_onehot, ndim, reshape, reshape_treatmentwise_effects, shape, transpose, - get_feature_names_or_default) + get_feature_names_or_default, check_input_arrays, + filter_none_kwargs) def _get_groups_period_filter(groups, n_periods): @@ -203,7 +204,6 @@ def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None period_filters = _get_groups_period_filter(groups, self.n_periods) for t in np.arange(self.n_periods): period = self.n_periods - 1 - t - # period_filter = self.period_filter_gen(period, Y.shape[0]) Y_adj = Y_res[period_filters[period]].copy() if t > 0: Y_adj -= np.sum( @@ -220,9 +220,6 @@ def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None scores[t] = np.mean((Y_adj - Y_adj_pred) ** 2) return scores - def period_filter_gen(self, p, n): - return (np.arange(n) % self.n_periods == p) - class _LinearDynamicModelFinal(_DynamicModelFinal): """Wrapper for the DynamicModelFinal with StatsModelsLinearRegression final model. @@ -567,7 +564,7 @@ def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, grou If group members do not appear together, the following is assumed: * the first instance of a group in the dataset is assumed to correspond to the first period of that group - * the second instance of a group in the dataset is assumed to correspond to the + * the second instance of a group in the dataset is assumed to correspond to the second period of that group ...etc. @@ -610,7 +607,7 @@ def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, grou cache_values=cache_values, inference=inference) - def score(self, Y, T, X=None, W=None, *, groups): + def score(self, Y, T, X=None, W=None, sample_weight=None, *, groups): """ Score the fitted CATE model on a new data set. Generates nuisance parameters for the new data set based on the fitted residual nuisance models created at fit time. @@ -637,8 +634,33 @@ def score(self, Y, T, X=None, W=None, *, groups): score: float The MSE of the final CATE model on the new data. """ - # Replacing score from _OrthoLearner, to enforce Z=None and improve the docstring - return super().score(Y, T, X=X, W=W, groups=groups) + if not hasattr(self._ortho_learner_model_final, 'score'): + raise AttributeError("Final model does not have a score method!") + Y, T, X, W, groups = check_input_arrays(Y, T, X, W, groups) + self._check_fitted_dims(X) + X, T = super()._expand_treatments(X, T) + n_iters = len(self._models_nuisance) + n_splits = len(self._models_nuisance[0]) + + # for each mc iteration + for i, models_nuisances in enumerate(self._models_nuisance): + # for each model under cross fit setting + for j, mdl in enumerate(models_nuisances): + nuisance_temp = mdl.predict(Y, T, **filter_none_kwargs(X=X, W=W, groups=groups)) + if not isinstance(nuisance_temp, tuple): + nuisance_temp = (nuisance_temp,) + + if i == 0 and j == 0: + nuisances = [np.zeros((n_iters * n_splits,) + nuis.shape) for nuis in nuisance_temp] + + for it, nuis in enumerate(nuisance_temp): + nuisances[it][i * n_iters + j] = nuis + + for it in range(len(nuisances)): + nuisances[it] = np.mean(nuisances[it], axis=0) + return self._ortho_learner_model_final.score(Y, T, nuisances=nuisances, + **filter_none_kwargs(X=X, W=W, + sample_weight=sample_weight, groups=groups)) def cate_treatment_names(self, treatment_names=None): """ diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index b573be805..4fdca33d1 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -236,10 +236,7 @@ def make_random(n, is_discrete, d): [0], est.intercept__interval()[0], decimal=5) est.summary() - # TODO: fix score - """ est.score(Y, T, X, W) - """ # make sure we can call effect with implied scalar treatments, # no matter the dimensions of T, and also that we warn when there # are multiple treatments From ac4dd7081860594b04063759cc56842c6bf73bb1 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Mon, 2 Aug 2021 15:59:22 -0400 Subject: [PATCH 09/27] Update docstring test outputs --- econml/dml/dynamic_dml.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index 0e67aa76f..647986b24 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -451,16 +451,16 @@ class DynamicDML(LinearModelFinalCateEstimatorMixin, _OrthoLearner): >>> est.coef__interval() (array([[-0.333...], [-0.171...], - [-0.158...], - [-0.352...], - [-0.045...], - [ 0.049...]]), + [-0.154...], + [-0.336...], + [-0.051...], + [ 0.040...]]), array([[-0.050...], [ 0.056...], - [ 0.112...], - [ 0.079...], - [ 0.260...], - [ 0.405...]])) + [ 0.108...], + [ 0.064...], + [ 0.265...], + [ 0.415...]])) """ def __init__(self, *, From a44a9605f9a02a4847e9fe88f0e76f2fc1541cda Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Mon, 2 Aug 2021 16:13:12 -0400 Subject: [PATCH 10/27] Fix merge issues --- econml/dml/_rlearner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/econml/dml/_rlearner.py b/econml/dml/_rlearner.py index 621238faa..ebdbe1fda 100644 --- a/econml/dml/_rlearner.py +++ b/econml/dml/_rlearner.py @@ -92,7 +92,7 @@ def __init__(self, model_final): self._model_final = model_final def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, - sample_weight=None, freq_weight=None, sample_var=None): + sample_weight=None, freq_weight=None, sample_var=None, groups=None): Y_res, T_res = nuisances self._model_final.fit(X, T, T_res, Y_res, sample_weight=sample_weight, freq_weight=freq_weight, sample_var=sample_var) From 1950fd16242605997a193f8a2d4d6c335427d5fe Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Mon, 2 Aug 2021 19:08:53 -0400 Subject: [PATCH 11/27] Address PR suggestions --- doc/spec/estimation/dml.rst | 18 ++++++++---------- econml/dml/dynamic_dml.py | 30 +++++++++++++++--------------- econml/tests/dgp.py | 5 ++++- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/doc/spec/estimation/dml.rst b/doc/spec/estimation/dml.rst index af65c4c92..9fe1deb57 100644 --- a/doc/spec/estimation/dml.rst +++ b/doc/spec/estimation/dml.rst @@ -51,6 +51,7 @@ characteristics :math:`X` of the treated samples, then one can use this method. .. testsetup:: + # DML import numpy as np X = np.random.choice(np.arange(5), size=(100,3)) Y = np.random.normal(size=(100,2)) @@ -59,6 +60,12 @@ characteristics :math:`X` of the treated samples, then one can use this method. t = t0 = t1 = T[:,0] W = np.random.normal(size=(100,2)) + # DynamicDML + groups = np.repeat(a=np.arange(100), repeats=3, axis=0) + X_dyn = np.random.normal(size=(300, 1)) + T_dyn = np.random.normal(size=(300, 2)) + y_dyn = np.random.normal(size=(300, )) + .. testcode:: from econml.dml import LinearDML @@ -307,18 +314,9 @@ Below we give a brief description of each of these classes: .. testcode:: - import numpy as np from econml.dml import DynamicDML - - n_panels = 100 # number of panels - n_periods = 3 # number of time periods per panel - n = n_panels * n_periods - groups = np.repeat(a=np.arange(n_panels), repeats=n_periods, axis=0) - X = np.random.normal(size=(n, 1)) - T = np.random.normal(size=(n, 2)) - y = np.random.normal(size=(n, )) est = DynamicDML() - est.fit(y, T, X=X, W=None, groups=groups, inference="auto") + est.fit(y_dyn, T_dyn, X=X_dyn, W=None, groups=groups, inference="auto") * **_RLearner.** The internal private class :class:`._RLearner` is a parent of the :class:`.DML` and allows the user to specify any way of fitting a final model that takes as input the residual :math:`\tilde{T}`, diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index 647986b24..e2c822f0c 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -56,14 +56,14 @@ def fit(self, Y, T, X=None, W=None, sample_weight=None, groups=None): self._model_t_trained = {j: {} for j in np.arange(self.n_periods)} for t in np.arange(self.n_periods): self._model_y_trained[t] = clone(self._model_y, safe=False).fit( - self._filter_or_None(X, period_filters[t]), - self._filter_or_None( + self._index_or_None(X, period_filters[t]), + self._index_or_None( W, period_filters[t]), Y[period_filters[self.n_periods - 1]]) for j in np.arange(t, self.n_periods): self._model_t_trained[j][t] = clone(self._model_t, safe=False).fit( - self._filter_or_None(X, period_filters[t]), - self._filter_or_None(W, period_filters[t]), + self._index_or_None(X, period_filters[t]), + self._index_or_None(W, period_filters[t]), T[period_filters[j]]) return self @@ -91,15 +91,15 @@ def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): for t in np.arange(self.n_periods): Y_slice = Y[period_filters[self.n_periods - 1]] Y_pred = self._model_y_trained[t].predict( - self._filter_or_None(X, period_filters[t]), - self._filter_or_None(W, period_filters[t])) + self._index_or_None(X, period_filters[t]), + self._index_or_None(W, period_filters[t])) Y_res[period_filters[t]] = Y_slice\ - shape_formatter(Y_slice, Y_pred).reshape(Y_slice.shape) for j in np.arange(t, self.n_periods): T_slice = T[period_filters[j]] T_pred = self._model_t_trained[j][t].predict( - self._filter_or_None(X, period_filters[t]), - self._filter_or_None(W, period_filters[t])) + self._index_or_None(X, period_filters[t]), + self._index_or_None(W, period_filters[t])) T_res[period_filters[j], ..., t] = T_slice\ - shape_formatter(T_slice, T_pred).reshape(T_slice.shape) return Y_res, T_res @@ -112,8 +112,8 @@ def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): Y_score = np.full((self.n_periods, ), np.nan) for t in np.arange(self.n_periods): Y_score[t] = self._model_y_trained[t].score( - self._filter_or_None(X, period_filters[t]), - self._filter_or_None(W, period_filters[t]), + self._index_or_None(X, period_filters[t]), + self._index_or_None(W, period_filters[t]), Y[period_filters[self.n_periods - 1]]) else: Y_score = None @@ -122,8 +122,8 @@ def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): for t in np.arange(self.n_periods): for j in np.arange(t, self.n_periods): T_score[j][t] = self._model_t_trained[j][t].score( - self._filter_or_None(X, period_filters[t]), - self._filter_or_None(W, period_filters[t]), + self._index_or_None(X, period_filters[t]), + self._index_or_None(W, period_filters[t]), T[period_filters[j]]) else: T_score = None @@ -134,7 +134,7 @@ def _get_shape_formatter(self, X, W): return lambda x, x_pred: np.tile(x_pred.reshape(1, -1), (x.shape[0], 1)) return lambda x, x_pred: x_pred - def _filter_or_None(self, X, filter_idx): + def _index_or_None(self, X, filter_idx): return None if X is None else X[filter_idx] @@ -742,7 +742,7 @@ def fit_cate_intercept_(self): @property def original_featurizer(self): - # NOTE: important to use the rlearner_model_final_ attribute instead of the + # NOTE: important to use the _ortho_learner_model_final_ attribute instead of the # attribute so that the trained featurizer will be passed through return self.ortho_learner_model_final_._model_final_trained[0]._original_featurizer @@ -755,7 +755,7 @@ def featurizer_(self): @property def model_final_(self): # NOTE This is used by the inference methods and is more for internal use to the library - # We need to use the rlearner's copy to retain the information from fitting + # We need to use the _ortho_learner's copy to retain the information from fitting return self.ortho_learner_model_final_.model_final_ @property diff --git a/econml/tests/dgp.py b/econml/tests/dgp.py index 16e1f1aae..98d4e6a81 100644 --- a/econml/tests/dgp.py +++ b/econml/tests/dgp.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import abc import numpy as np from econml.utilities import cross_product from statsmodels.tools.tools import add_constant @@ -13,9 +14,11 @@ def __init__(self, n_periods, n_treatments, n_x): self.n_x = n_x return + @abc.abstractmethod def create_instance(self, *args, **kwargs): pass + @abc.abstractmethod def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): pass @@ -138,7 +141,7 @@ def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): return Y, T, X[:, self.hetero_inds] if self.hetero_inds else None, X[:, self.endo_inds], groups def observational_data(self, n_units, gamma=0, s_t=1, sigma_t=0.5, random_seed=123): - """ Generated observational data with some observational treatment policy parameters + """Generate observational data with some observational treatment policy parameters. Parameters ---------- From 4636257aa3a21828e1c0a4b7fe3ce780928b30fa Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Tue, 3 Aug 2021 13:08:09 -0400 Subject: [PATCH 12/27] Fix subscript printing in summary --- econml/dml/dynamic_dml.py | 2 +- ...mic Double Machine Learning Examples.ipynb | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index e2c822f0c..eca010c1a 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -682,7 +682,7 @@ def cate_treatment_names(self, treatment_names=None): slice_treatment_names = super().cate_treatment_names(treatment_names) treatment_names_out = [] for k in range(self._n_periods): - treatment_names_out += [f"$({t})_{k}$" for t in slice_treatment_names] + treatment_names_out += [f"({t})$_{k}$" for t in slice_treatment_names] return treatment_names_out def cate_feature_names(self, feature_names=None): diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb index 6eb2e2b91..38082d3eb 100644 --- a/notebooks/Dynamic Double Machine Learning Examples.ipynb +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -187,7 +187,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -281,13 +281,13 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "\n", "\n", - " cate_intercept|$(T0)_0$ 0.711 0.041 17.224 0.0 0.643 0.779 \n", + " cate_intercept|(T0)$_0$ 0.711 0.041 17.224 0.0 0.643 0.779 \n", "\n", "\n", - " cate_intercept|$(T0)_1$ 1.031 0.096 10.715 0.0 0.872 1.189 \n", + " cate_intercept|(T0)$_1$ 1.031 0.096 10.715 0.0 0.872 1.189 \n", "\n", "\n", - " cate_intercept|$(T0)_2$ 0.518 0.142 3.658 0.0 0.285 0.751 \n", + " cate_intercept|(T0)$_2$ 0.518 0.142 3.658 0.0 0.285 0.751 \n", "\n", "

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -298,9 +298,9 @@ "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|$(T0)_0$ 0.711 0.041 17.224 0.0 0.643 0.779\n", - "cate_intercept|$(T0)_1$ 1.031 0.096 10.715 0.0 0.872 1.189\n", - "cate_intercept|$(T0)_2$ 0.518 0.142 3.658 0.0 0.285 0.751\n", + "cate_intercept|(T0)$_0$ 0.711 0.041 17.224 0.0 0.643 0.779\n", + "cate_intercept|(T0)$_1$ 1.031 0.096 10.715 0.0 0.872 1.189\n", + "cate_intercept|(T0)$_2$ 0.518 0.142 3.658 0.0 0.285 0.751\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -446,7 +446,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 17, @@ -472,13 +472,13 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "\n", "\n", - " X0|$(T0)_0$ 0.394 0.103 3.838 0.0 0.225 0.563 \n", + " X0|(T0)$_0$ 0.394 0.103 3.838 0.0 0.225 0.563 \n", "\n", "\n", - " X0|$(T0)_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248 \n", + " X0|(T0)$_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248 \n", "\n", "\n", - " X0|$(T0)_2$ 0.04 0.2 0.199 0.843 -0.29 0.369 \n", + " X0|(T0)$_2$ 0.04 0.2 0.199 0.843 -0.29 0.369 \n", "\n", "\n", "\n", @@ -487,13 +487,13 @@ " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|$(T0)_0$ 0.579 0.052 11.242 0.0 0.495 0.664cate_intercept|(T0)$_0$ 0.579 0.052 11.242 0.0 0.495 0.664
cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.705 -0.108 0.173cate_intercept|(T0)$_1$ 0.032 0.086 0.379 0.705 -0.108 0.173
cate_intercept|$(T0)_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056cate_intercept|(T0)$_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -504,16 +504,16 @@ "=================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------\n", - "X0|$(T0)_0$ 0.394 0.103 3.838 0.0 0.225 0.563\n", - "X0|$(T0)_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248\n", - "X0|$(T0)_2$ 0.04 0.2 0.199 0.843 -0.29 0.369\n", + "X0|(T0)$_0$ 0.394 0.103 3.838 0.0 0.225 0.563\n", + "X0|(T0)$_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248\n", + "X0|(T0)$_2$ 0.04 0.2 0.199 0.843 -0.29 0.369\n", " CATE Intercept Results \n", "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|$(T0)_0$ 0.579 0.052 11.242 0.0 0.495 0.664\n", - "cate_intercept|$(T0)_1$ 0.032 0.086 0.379 0.705 -0.108 0.173\n", - "cate_intercept|$(T0)_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056\n", + "cate_intercept|(T0)$_0$ 0.579 0.052 11.242 0.0 0.495 0.664\n", + "cate_intercept|(T0)$_1$ 0.032 0.086 0.379 0.705 -0.108 0.173\n", + "cate_intercept|(T0)$_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", From 9328a2282732a6829950875c6bdc457f82c2f192 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 5 Aug 2021 13:59:53 -0400 Subject: [PATCH 13/27] Address PR suggestions --- econml/dml/dynamic_dml.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index eca010c1a..bb162e75d 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -1,8 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -# TODO: add paper reference - import abc import numpy as np from warnings import warn @@ -94,14 +92,14 @@ def predict(self, Y, T, X=None, W=None, sample_weight=None, groups=None): self._index_or_None(X, period_filters[t]), self._index_or_None(W, period_filters[t])) Y_res[period_filters[t]] = Y_slice\ - - shape_formatter(Y_slice, Y_pred).reshape(Y_slice.shape) + - shape_formatter(Y_slice, Y_pred) for j in np.arange(t, self.n_periods): T_slice = T[period_filters[j]] T_pred = self._model_t_trained[j][t].predict( self._index_or_None(X, period_filters[t]), self._index_or_None(W, period_filters[t])) T_res[period_filters[j], ..., t] = T_slice\ - - shape_formatter(T_slice, T_pred).reshape(T_slice.shape) + - shape_formatter(T_slice, T_pred) return Y_res, T_res def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): @@ -131,8 +129,8 @@ def score(self, Y, T, X=None, W=None, sample_weight=None, groups=None): def _get_shape_formatter(self, X, W): if (X is None) and (W is None): - return lambda x, x_pred: np.tile(x_pred.reshape(1, -1), (x.shape[0], 1)) - return lambda x, x_pred: x_pred + return lambda x, x_pred: np.tile(x_pred.reshape(1, -1), (x.shape[0], 1)).reshape(x.shape) + return lambda x, x_pred: x_pred.reshape(x.shape) def _index_or_None(self, X, filter_idx): return None if X is None else X[filter_idx] From 24ca086efc96c9e885ca27d637430f541fee735e Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 5 Aug 2021 15:02:39 -0400 Subject: [PATCH 14/27] Update nuisance models in notebook --- ...mic Double Machine Learning Examples.ipynb | 145 ++++++++---------- 1 file changed, 68 insertions(+), 77 deletions(-) diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb index 38082d3eb..0eb1f1d6e 100644 --- a/notebooks/Dynamic Double Machine Learning Examples.ipynb +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -128,7 +128,7 @@ "source": [ "# Define DGP parameters\n", "np.random.seed(123)\n", - "n_panels = 400 # number of panels\n", + "n_panels = 1000 # number of panels\n", "n_periods = 3 # number of time periods in each panel\n", "n_treatments = 1 # number of treatments in each period\n", "n_x = 100 # number of features + controls\n", @@ -161,36 +161,34 @@ "execution_count": 5, "metadata": {}, "outputs": [], - "source": [ - "alpha_regs = [1e-4, 1e-3, 1e-2, 5e-2, .1, 1]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], "source": [ "est = DynamicDML(\n", - " model_y=LassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", - " model_t=MultiTaskLassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", + " model_y=LassoCV(cv=3), \n", + " model_t=MultiTaskLassoCV(cv=3), \n", " cv=3)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "scrolled": true }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Objective did not converge. You might want to increase the number of iterations. Duality gap: 2.0609523330807065, tolerance: 0.3973271492025328\n" + ] + }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -201,14 +199,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Average effect of default policy: 2.26\n" + "Average effect of default policy: 2.35\n" ] } ], @@ -219,14 +217,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Effect of target policy over baseline policy: 4.33\n" + "Effect of target policy over baseline policy: 4.62\n" ] } ], @@ -241,16 +239,16 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Effect of a treatment in period 1 on period 3 outcome: 0.71\n", - "Effect of a treatment in period 2 on period 3 outcome: 1.03\n", - "Effect of a treatment in period 3 on period 3 outcome: 0.52\n" + "Effect of a treatment in period 1 on period 3 outcome: 0.69\n", + "Effect of a treatment in period 2 on period 3 outcome: 1.05\n", + "Effect of a treatment in period 3 on period 3 outcome: 0.61\n" ] } ], @@ -262,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -281,13 +279,13 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "\n", "\n", - " cate_intercept|(T0)$_0$ 0.711 0.041 17.224 0.0 0.643 0.779 \n", + " cate_intercept|(T0)$_0$ 0.692 0.031 22.308 0.0 0.641 0.743 \n", "\n", "\n", - " cate_intercept|(T0)$_1$ 1.031 0.096 10.715 0.0 0.872 1.189 \n", + " cate_intercept|(T0)$_1$ 1.05 0.067 15.565 0.0 0.939 1.161 \n", "\n", "\n", - " cate_intercept|(T0)$_2$ 0.518 0.142 3.658 0.0 0.285 0.751 \n", + " cate_intercept|(T0)$_2$ 0.61 0.09 6.74 0.0 0.461 0.758 \n", "\n", "

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -298,9 +296,9 @@ "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ 0.711 0.041 17.224 0.0 0.643 0.779\n", - "cate_intercept|(T0)$_1$ 1.031 0.096 10.715 0.0 0.872 1.189\n", - "cate_intercept|(T0)$_2$ 0.518 0.142 3.658 0.0 0.285 0.751\n", + "cate_intercept|(T0)$_0$ 0.692 0.031 22.308 0.0 0.641 0.743\n", + "cate_intercept|(T0)$_1$ 1.05 0.067 15.565 0.0 0.939 1.161\n", + "cate_intercept|(T0)$_2$ 0.61 0.09 6.74 0.0 0.461 0.758\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -311,7 +309,7 @@ "\"\"\"" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -323,7 +321,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -339,12 +337,12 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3gAAAEzCAYAAABjbqHIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAlPklEQVR4nO3de3SW5Z3u8etHDCRAQhDwlHDsRgY5hSZaqUrBUcHWbdnTVi3uatWOuoo6qwe6YclSbGtHx0M7VlulU2s7qwhWEd0ehvEQbNmimMipgIgNRBIVQ0ggQBKScO8/3pgGSEII75Ob3M/3sxbrzXO/T95cQvtbXDwnc84JAAAAAND99fAdAAAAAACQHBQ8AAAAAAgEBQ8AAAAAAkHBAwAAAIBAUPAAAAAAIBAUPAAAAAAIxEm+AxyrgQMHumHDhvmOASAge2rrJUmZaamekwAICbMFQFSKiop2OucGtfZetyt4w4YNU2Fhoe8YAAKyZnuVJCl3cJbXHADCwmwBEBUzK2nrvW5X8AAg2fjLF4AoMFsA+MA1eAAAAAAQCAoegNj7y5Zy/WVLue8YAALDbAHgA6doAoi9uvqDviMACBCzBd1NfX29SktLVVtb6zsKmqSlpSknJ0epqR2/WRMFDwAAAIBKS0uVkZGhYcOGycx8x4k955wqKipUWlqq4cOHd/j7OEUTAAAAgGprazVgwADK3QnCzDRgwIBjPqJKwQMAAAAgSZS7E0xn/jwoeABi77R+aTqtX5rvGAACw2xBHFz52Epd+djKpH1eSkqKcnNzNWbMGE2YMEEPPPCADh70cz1rYWGhbrvttnb3GTZsmMaNG6dx48bprLPO0rx585qPuG3btk1mpnnz5jXvv3PnTqWmpuqWW26RJM2fP1/3339/UnNT8ADE3tjsfhqb3c93DACBYbYAxy49PV1r1qzRhg0b9Morr+jll1/WXXfd5SVLfn6+HnrooaPuV1BQoPXr12vVqlUqLi7WTTfd1Pze8OHD9eKLLzZv/+lPf9KYMWMiyfsZCh4AAACAY7Z0dZlWf1ilt7fu0nn3vK6lq8uS+vmnnHKKFixYoIcffljOOU2ePFlr1qxpfv/888/X2rVrNX/+fF1//fWaMmWKRowYcUgpmzFjhvLy8jRmzBgtWLCgeb1v376aPXu2xowZo4suukirVq1q/v7nn39ekrR8+XJddtllkqS9e/fquuuu07hx4zR+/Hg988wzR+Tt27evHn30US1dulS7du2SJPXu3VujR49WYWGhJGnx4sW64oorkvr7dDgKHoDYK9j8qQo2f+o7BoDAMFsQsqWryzR3yXodaEycPllWVaO5S9YnveSNGDFCjY2N+vTTT3XDDTfoiSeekCS9//77qq2t1YQJEyRJ7733npYtW6ZVq1bprrvuUn19vSTp8ccfV1FRkQoLC/XQQw+poqJCkrRv3z5deOGF2rBhgzIyMjRv3jy98sorevbZZ3XHHXcckeMnP/mJ+vXrp/Xr12vdunW68MILW82bmZmp4cOHa8uWLc1rV111lRYtWqTt27crJSVFZ5xxRjJ/i44QWcEzs8fN7FMz+2sb719tZuvMbL2ZvWlmE6LKAgDtaWx0amx0vmPAo2RfQwJIzBaE7b5lm1VT33jIWk19o+5btjmyn/mNb3xDL7zwgurr6/X444/r29/+dvN7X/nKV9SrVy8NHDhQp5xyinbs2CFJeuihhzRhwgSde+652r59e3Px6tmzp6ZPny5JGjdunL70pS8pNTVV48aN07Zt24742a+++qpmzZrVvN2/f/82czp36P/vp0+frldeeUWLFi3SlVde2dn//A6L8gjeE5Kmt/P+Vklfcs6Nk/QTSQva2RcAAADACeKjqppjWu+s4uJipaSk6JRTTlHv3r118cUX67nnntNTTz2lq6++unm/Xr16NX+dkpKihoYGLV++XK+++qpWrlyptWvXauLEic03QElNTW2+Q2WPHj2av79Hjx5qaGjodN7q6mpt27ZNZ555ZvNaz549lZeXpwceeEBf//rXO/3ZHRVZwXPO/VnSrnbef9M5V9m0+ZaknKiyAAAAAEieM7LSj2m9M8rLy3XzzTfrlltuaS5j3/nOd3Tbbbfp7LPPbvcomiTt3r1b/fv3V+/evfXee+/prbfe6nSWiy++WI888kjzdmVl5RH77N27V9/97nc1Y8aMI7L94Ac/0L333quTTz650xk66kS5Bu8GSS/7DgEAAADg6GZPG6X01JRD1tJTUzR72qjj+tyamprmxyRcdNFFuuSSS3TnnXc2v5+Xl6fMzExdd911R/2s6dOnq6GhQaNHj9acOXN07rnndjrXvHnzVFlZqbFjx2rChAkqKChofm/q1KkaO3aszjnnHA0ZMkSPPfbYEd8/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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3gAAAEzCAYAAABjbqHIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAn5ElEQVR4nO3de3TV5Z3v8c+XGEi4BgFvBLnMQco9mOhIay04XrDtqUy76q1TrdpBV6HO6bT0wJKlaGtHl7adcWSqtGVsZxXBsYqMtYfjBdp6pEIiNwERJoAkWoiBQIAEQvieP3aMIQQSQn48ybPfr7Wy9v49v2fv/RHa7+Kb57efn7m7AAAAAAAdX6fQAQAAAAAAbYMGDwAAAAAiQYMHAAAAAJGgwQMAAACASNDgAQAAAEAkaPAAAAAAIBJnhQ5wqvr27euDBg0KHQNARPZV10iSemZlBk4CICbUFgBJKSoq+sjd+zV1rsM1eIMGDVJhYWHoGAAisnpHhSQpb0BO0BwA4kJtAZAUM9t+onMdrsEDgLbGP74AJIHaAiCExL6DZ2bzzGyXmb1zgvNfM7O1ZrbOzN40s7FJZQEAAACAdJDkJitPS5p0kvNbJX3O3UdL+oGkuQlmAYAT+tPmMv1pc1noGAAiQ20BEEJil2i6+x/NbNBJzr/Z4PDPknKTygIAJ3Oo5mjoCAAiRG1BR1NTU6OSkhJVV1eHjoI6WVlZys3NVWZmyzdrai/fwbtT0u9DhwAAAADSVUlJiXr06KFBgwbJzELHSXvurvLycpWUlGjw4MEtfl3w++CZ2USlGrz/fZI5U8ys0MwKy8q41AEAAABoa9XV1erTpw/NXTthZurTp88pr6gGbfDMbIykX0i63t3LTzTP3ee6e4G7F/Tr1+TtHgAAAACcJpq79qU1fx/BGjwzu1DS85K+7u7vhcoBAOf1ytJ5vbJCxwAQGWoL0sGNTy3XjU8tb7P3y8jIUF5enkaOHKmxY8fqxz/+sY4eDfN91sLCQt1zzz0nnTNo0CCNHj1ao0eP1ogRIzRr1qz6Fbdt27bJzDRr1qz6+R999JEyMzM1bdo0SdLs2bP12GOPtWnuJG+T8Iyk5ZKGmVmJmd1pZneb2d11U+6T1EfSv5nZajPj7uUAghjVv5dG9e8VOgaAyFBbgFOXnZ2t1atXa/369XrllVf0+9//Xg888ECQLAUFBXr88cebnbd06VKtW7dOK1asUHFxse666676c4MHD9bvfve7+uP//M//1MiRIxPJ+7HEGjx3v9ndz3f3THfPdfdfuvuT7v5k3flvuntvd8+r+ylIKgsAAACAtrVoValWvV+ht7bu1mcefl2LVpW26fufc845mjt3rp544gm5u6644gqtXr26/vzll1+uNWvWaPbs2brjjjs0YcIEDRky5JimbPLkycrPz9fIkSM1d+4nd2Xr3r27pk+frpEjR+qqq67SihUr6l+/ePFiSdKyZcv0xS9+UZK0f/9+3X777Ro9erTGjBmj3/72t8fl7d69u5588kktWrRIu3fvliR17dpVw4cPV2Fhai1r4cKFuuGGG9r0z6mx4JusAEBoSzft0tJNu0LHABAZagtitmhVqWY+v06Ha1OXT5ZWVGnm8+vavMkbMmSIamtrtWvXLt155516+umnJUnvvfeeqqurNXbsWEnSu+++qyVLlmjFihV64IEHVFNTI0maN2+eioqKVFhYqMcff1zl5altPw4cOKArr7xS69evV48ePTRr1iy98soreuGFF3Tfffcdl+MHP/iBevXqpXXr1mnt2rW68sorm8zbs2dPDR48WJs3b64fu+mmm7RgwQLt2LFDGRkZuuCCC9ryj+g4NHgA0l5trau21kPHQEBt/R0SQKK2IG6PLtmkqpraY8aqamr16JJNiX3mV7/6Vb300kuqqanRvHnz9I1vfKP+3Be+8AV16dJFffv21TnnnKOdO3dKkh5//HGNHTtWl112mXbs2FHfeHXu3FmTJk2SJI0ePVqf+9znlJmZqdGjR2vbtm3Hffarr76qqVOn1h/37t37hDndj/3//aRJk/TKK69owYIFuvHGG1v7n99i7eU+eAAAAAA6iA8qqk5pvLWKi4uVkZGhc845R2amq6++Wi+++KKeffZZFRUV1c/r0qVL/fOMjAwdOXJEy5Yt06uvvqrly5era9eumjBhQv0GKJmZmfU7VHbq1Kn+9Z06ddKRI0danbeyslLbtm3TRRddpL1790pKNZP5+fn68Y9/rA0bNtRfApoUVvAAAAAAnJILcrJPabw1ysrKdPfdd2vatGn1zdg3v/lN3XPPPbrkkktOuoomSXv37lXv3r3VtWtXvfvuu/rzn//c6ixXX3215syZU3+8Z8+e4+bs379f3/rWtzR58uTjsn33u9/VI488orPPPrvVGVqKBg8AAADAKZl+7TBlZ2YcM5admaHp1w47rfetqqqqv03CVVddpWuuuUb3339//fn8/Hz17NlTt99+e7PvNWnSJB05ckTDhw/XjBkzdNlll7U616xZs7Rnzx6NGjVKY8eO1dKlS+vPTZw4UaNGjdKll16qCy+8UE899dRxrx85cqRuu+22Jt/7hz/8oXJzc+t/Tpc1vka0vSsoKPCPd6EBgLaw8cN9kqTh5/cMnAShfPz9u4V3jQ+cBDGhtqCj2bhxo4YPH97i+YtWler7z63V4dqj6p+TrenXDtPkcf0TTCh98MEHmjBhgt5991116pQea1VN/b2YWdGJ7kLAd/AApD3+8QUgCdQWxG7yuP56ZsX7ks7ML8h+/etf695779VPfvKTtGnuWoMGDwAAAECrnMkrH2699VbdeuutZ+zzOipaXwBp79UNO/Xqhp2hYwCIDLUFQAg0eAAAAAAQCRo8AAAAAIgEDR4AAAAARIIGDwAAAEC7sHPnTt1yyy0aMmSI8vPzNX78eL3wwgtnNMO2bds0atSoJsfnz5/fqvf853/+Zx08eLD+uHv37q3O1xwaPABpb2CfrhrYp2voGAAiQ20BTo27a/LkybriiitUXFysoqIiLViwQCUlJcfNPXLkyBnPd7IGr7k8jRu8JHGbBABpb+i5PUJHABAhaguiV1IkrZ4vVWyXcgZKebdIufmtfrvXX39dnTt31t13310/NnDgQH3729+WJD399NN6/vnntX//ftXW1uqFF17QHXfcoeLiYnXt2lVz587VmDFjNHv2bHXv3l3f+973JEmjRo3SSy+9JEm67rrrdPnll+vNN99U//799eKLLyo7O1tFRUW64447JEnXXHNNk/lmzJihjRs3Ki8vT7fddpt69+59TJ4HHnhAjz32WP1nTZs2TQUFBdq3b58++OADTZw4UX379tXSpUslSffee69eeuklZWdn68UXX9S5557b6j+7hljBA5D2jtQe1ZHao6FjAIgMtQVRKymSlj4kVZVLPc9PPS59KDXeSuvXr9fFF1980jlvv/22nnvuOf3hD3/Q/fffr3Hjxmnt2rX60Y9+1KJ75G3evFlTp07V+vXrlZOTo9/+9reSpNtvv13/+q//qjVr1pzwtQ8//LA++9nPavXq1frOd75zXJ4Tueeee3TBBRdo6dKl9c3dgQMHdNlll2nNmjW64oor9POf/7zZ7C1Fgwcg7S3bVKZlm8pCxwAQGWoLorZ6vpTVU8rqJVmn1GNWz9R4G5k6darGjh2rSy65pH7s6quv1tlnny1JeuONN/T1r39dknTllVeqvLxc+/btO+l7Dh48WHl5eZKk/Px8bdu2TRUVFaqoqNAVV1whSfXv2RIN85yKzp0764tf/OIxOdoKDR4AAACAU1OxXerS6DLkLj1S4600cuRIvf322/XHc+bM0Wuvvaaysk9+UdKtW7dm3+ess87S0aOfrJ5XV1d/ErFLl/rnGRkZp/1dvoZ5Tva5jWVmZsrM2ixHQzR4AAAAAE5NzkDpUOWxY4cqU+OtdOWVV6q6ulo/+9nP6sdOtjHJZz/7Wf3mN7+RJC1btkx9+/ZVz549NWjQoPpG8e2339bWrVtP+rk5OTnKycnRG2+8IUn179lYjx49VFlZ2eQ5KfV9wQ0bNujQoUOqqKjQa6+91uLXtiUaPAAAAACnJu8WqXqfVL1X8qOpx+p9qfFWMjMtWrRIf/jDHzR48GBdeumluu222/TII480OX/27NkqKirSmDFjNGPGDP3qV7+SJH3lK1/R7t27NXLkSD3xxBO66KKLmv3sf//3f9fUqVOVl5cnd29yzpgxY5SRkaGxY8fqpz/96XHnBwwYoBtuuEGjRo3SDTfcoHHjxtWfmzJliiZNmqSJEye25I/itNiJ/gPaq4KCAi8sLAwdA0BEXt2wU5J01Yi22b0KHc+NTy2XJC28a3zgJIgJtQUdzcaNGzV8+PCWv6CNd9FE05r6ezGzIncvaGo+t0kAkPaG9Gv+en4AOFXUFkQvN5+Grh2iwQOQ9ob06x46AoAIUVsAhMB38ACkveqaWlXX1IaOASAy1BYAISTW4JnZPDPbZWbvnOD8p8xsuZkdMrPvJZUDAJrzxuaP9Mbmj0LHABAZags6oo62P0fsWvP3keQK3tOSJp3k/G5J90h6LMEMAAAAAFogKytL5eXlNHnthLurvLxcWVlZp/S6xL6D5+5/NLNBJzm/S9IuM/tCUhkAAAAAtExubq5KSkqOubE4wsrKylJubu4pvYZNVgAAAAAoMzNTgwcPDh0Dp6lDbLJiZlPMrNDMCvmNAgAAAAA0rUOs4Ln7XElzpdSNzgPHARCZoeeylTmAtkdtARBCh2jwACBJA/twM2IAbY/aAiCExBo8M3tG0gRJfc2sRNL9kjIlyd2fNLPzJBVK6inpqJn9L0kj3H1fUpkAoCkHDh2RJHXrwu+8ALQdaguAEJLcRfPmZs7/RdKpbQkDAAlY/t/lkqSrRpwbOAmAmFBbAITQITZZAQAAAAA0jwYPAAAAACJBgwcAAAAAkaDBAwAAAIBIsK0TgLT3qfN7hI4AIELUFgAh0OABSHu5vbuGjgAgQtQWACFwiSaAtLe3qkZ7q2pCxwAQGWoLgBBo8ACkvZVbd2vl1t2hYwCIDLUFQAg0eACAtLZoValWvV+ht7bu1mcefl2LVpWGjgQAQKvR4AEA0taiVaWa+fw6Ha49KkkqrajSzOfX0eQBADosGjwAQNp6dMkmVdXUHjNWVVOrR5dsCpQIAIDTQ4MHAEhbH1RUndI4AADtHbdJAJD2RvXvFToCArkgJ1ulTTRzF+RkB0iD2FBbAITACh6AtHderyyd1ysrdAwEMP3aYcrOzDhmLDszQ9OvHRYoEWJCbQEQAit4ANLengOHJUm9u3UOnARn2uRx/SVJ339urQ7XHlX/nGxNv3ZY/ThwOqgtAEKgwQOQ9oq275EkXTXi3MBJEMLkcf31zIr3JUkL7xofOA1iQm0BEAKXaAIAAABAJGjwAAAAACASNHgAAAAAEAkaPAAAAACIBJusAEh7YwfkhI4AIELUFgAh0OABSHv9enQJHQFAhKgtAELgEk0Aaa+s8pDKKg+FjgEgMtQWACHQ4AFIe2t2VGjNjorQMQBEhtoCIITEGjwzm2dmu8zsnROcNzN73My2mNlaM7s4qSwAAAAAkA6S/A7e05KekPTrE5y/TtLQup+/lvSzukcAANqnkiJp9XypYruUM1DKu0XKzQ+dCgCAeomt4Ln7HyXtPsmU6yX92lP+LCnHzM5PKg8AAKelpEha+pBUVS71PD/1uPSh1DgAAO1EyO/g9Ze0o8FxSd0YAADtz+r5UlZPKauXZJ1Sj1k9U+MAALQTHeI2CWY2RdIUSbrwwgsDpwEQm/yBvUNHQEdQsT21ctdQlx6pcaAJ1BYAIYRcwSuVNKDBcW7d2HHcfa67F7h7Qb9+/c5IOADpo3e3zurdrXPoGGjvcgZKhyqPHTtUmRoHmkBtARBCyAZvsaRb63bTvEzSXnf/MGAeAGnqL3ur9Ze91aFjoL3Lu0Wq3idV75X8aOqxel9qHGgCtQVACIldomlmz0iaIKmvmZVIul9SpiS5+5OSXpb0eUlbJB2UdHtSWQDgZN4p3StJOq9XVuAkaNdy86WJ9x67i+b4b7OLJk6I2gIghMQaPHe/uZnzLmlqUp8PAECby82noQMAtGshL9EEAAAAALQhGjwAAAAAiAQNHgAAAABEokPcBw8AknTJ4LNDRwAQIWoLgBBo8ACkvV7ZmaEjAIgQtQVACFyiCSDtlew5qJI9B0PHABAZaguAEFjBA5D23v2wUpKU27tr4CQAYkJtARACK3gAAAAAEAkaPAAAAACIBA0eAAAAAESCBg8AAAAAIsEmKwDS3vi/6hM6AoAIUVsAhECDByDtdetCKQTQ9qgtAELgEk0AaW97+QFtLz8QOgaAyFBbAITAr5YApL3NO/dLkgb26RY4CYCYUFsAhMAKHgAAAABEggYPAAAAACJBgwcAAAAAkaDBAwAAAIBIsMkKgLR3+dC+oSMAiBC1BUAINHgA0l5WZkboCAAiRG0BEAKXaAJIe8Vl+1Vctj90DACRobYACIEVPABpr7gsdSPiIf26B06CUBbeNT50BESI2gIgBFbwAAAAACASiTZ4ZjbJzDaZ2RYzm9HE+YFm9pqZrTWzZWaWm2QeAAAAAIhZYg2emWVImiPpOkkjJN1sZiMaTXtM0q/dfYykByX9U1J5AAAAACB2Sa7gXSppi7sXu/thSQskXd9ozghJr9c9X9rEeQAAAABACyW5yUp/STsaHJdI+utGc9ZI+rKkf5H0t5J6mFkfdy9PMBcAHGPCsH6hIwCIELUFQAihN1n5nqTPmdkqSZ+TVCqptvEkM5tiZoVmVlhWVnamMwKI3FkZnXRWRuhyCCA21BYAISRZdUolDWhwnFs3Vs/dP3D3L7v7OEn31o1VNH4jd5/r7gXuXtCvH78NA9C2Nu+s1OadlaFjAIgMtQVACEk2eCslDTWzwWbWWdJNkhY3nGBmfc3s4wwzJc1LMA8ANGl7+UFtLz8YOgaAyFBbAISQWIPn7kckTZO0RNJGSc+6+3oze9DMvlQ3bYKkTWb2nqRzJT2UVB4AAAAAiF2Sm6zI3V+W9HKjsfsaPH9O0nNJZgAAAACAdME3fwEAAAAgEjR4AAAAABCJRC/RBICO4KoR54aOACBC1BYAIbCCBwAAAACRoMEDkPY2frhPGz/cFzoGgMhQWwCEQIMHIO2V7qlS6Z6q0DEARIbaAiAEGjwAAAAAiAQNHgAAAABEggYPAAAAACLBbRIApL2MDAsdAUCEqC0AQqDBA5D2Jg47J3QEABGitgAIgUs0AQAAACASNHgA0t47pXv1Tune0DEARIbaAiCEFjV4ZvYfLRkDgI7oL3ur9Ze91aFjAIgMtQVACC1dwRvZ8MDMMiTlt30cAAAAAEBrnbTBM7OZZlYpaYyZ7av7qZS0S9KLZyQhAAAAAKBFTtrgufs/uXsPSY+6e8+6nx7u3sfdZ56hjAAAAACAFmjpJZorzKzXxwdmlmNmk5OJBABnVpfMTuqSyZ5TANoWtQVACObuzU8yW+3ueY3GVrn7uKSCnUhBQYEXFhae6Y8FAAAAgHbBzIrcvaCpcy39tVJT87hJOgAAAAC0Iy1t8ArN7Cdm9ld1Pz+RVJRkMAA4U1bvqNDqHRWhYwCIDLUFQAgtbfC+LemwpIWSFkiqljQ1qVAAcCZ9VHlIH1UeCh0DQGSoLQBCaNFllu5+QNIMM+tW9xwAAAAA0M60aAXPzD5tZhskbaw7Hmtm/5ZoMgAAAADAKWnpJZo/lXStpHJJcvc1kq5o7kVmNsnMNpnZFjOb0cT5C81sqZmtMrO1Zvb5UwkPAAAAAPhEi2/O4u47Gg3Vnmy+mWVImiPpOkkjJN1sZiMaTZsl6dm62y3cJIlVQQBnXNfOGeraOSN0DACRobYACKGltzrYYWafluRmlinpH1R3ueZJXCppi7sXS5KZLZB0vaQNDea4pJ51z3tJ+qClwQGgrXz6f/QNHQFAhKgtAEJo6Qre3UrtmtlfUqmkPDW/i2Z/SQ1X/UrqxhqaLenvzKxE0stK7dYJAAAAAGiFkzZ4ZvZI3dOJ7v41dz/X3c9x979z9/I2+PybJT3t7rmSPi/pP8zsuExmNsXMCs2ssKysrA0+FgA+UbR9t4q27w4dA0BkqC0AQmhuBe/zZmaSZrbivUslDWhwnFs31tCdkp6VJHdfLilL0nHXM7j7XHcvcPeCfv36tSIKAJzYngM12nOgJnQMAJGhtgAIobkG7/9I2iNpjJntM7PKho/NvHalpKFmNtjMOiu1icriRnPel/Q3kmRmw5Vq8FiiAwAAAIBWaK7Bm+XuOZJ+5+493b1Hw8eTvdDdj0iaJmmJUhuyPOvu683sQTP7Ut2070r6ezNbI+kZSd9wdz+t/yIAAAAASFPN7aK5XNLFkppbrWuSu7+s1OYpDcfua/B8g6TPtOa9AQAAgPbsxqeWS5IW3jU+cBKkk+YavM5mdoukT5vZlxufdPfnk4kFAGdOj6yW3jEGAFqO2gIghOYqz92SviYpR9L/bHTOJdHgAejw/npIn9ARAESI2gIghJM2eO7+hqQ3zKzQ3X95hjIBAAAAAFqhufvgfV+S3P2XZvbVRud+lGQwADhT3iou11vFbXFrTwD4BLUFQAjN7aJ5U4Pnje+FN6mNswBAEJXVR1RZfSR0DACRobYACKG5Bs9O8LypYwAAAABAQM01eH6C500dAwAAAAACam4XzbFmtk+p1brsuueqO85KNBkAAAAA4JQ0t4tmxpkKAgCh9O6WGToCgAhRWwCEwB04AaS9/IFnh44AIELUFgAhNPcdPKBdufGp5brxqeWhYwAAAADtEg0egLT35paP9OaWj0LHABAZaguAELhEE0DaO3i4NnQEABGitgAIgRU8AAAAAIgEDR4AAAAARIIGDwAAAAAiwXfwAKS9vj26hI4AIELUFgAh0OABSHt5A3JCRwAQIWoLgBC4RBMAAAAAIkGDByDt/Wlzmf60uSx0DACRobYACIFLNAGkvUM1R0NHABAhaguAEFjBAwAAAIBI0OABAAAAQCQSvUTTzCZJ+hdJGZJ+4e4PNzr/U0kT6w67SjrH3XOSzIQ0UVIkrZ4vVWyXcgZKebdIufmhUwEAAACJSqzBM7MMSXMkXS2pRNJKM1vs7hs+nuPu32kw/9uSxiWVB2mkpEha+pCU1VPqeb5UVZ46nngvTR6adF6vrNARAESI2gIghCRX8C6VtMXdiyXJzBZIul7ShhPMv1nS/QnmQbpYPT/V3GX1Sh1//Lh6Pg0emjSqf6/QEQBEiNoCIIQkv4PXX9KOBscldWPHMbOBkgZLej3BPEgXFdulLj2OHevSIzUOAAAARKy9bLJyk6Tn3L22qZNmNsXMCs2ssKyM+8mkq0WrSrXq/Qq9tXW3PvPw61q0qrTpiTkDpUOVx44dqkyNA01YummXlm7aFToGgMhQWwCEkGSDVyppQIPj3Lqxptwk6ZkTvZG7z3X3Ancv6NevXxtGREexaFWpZj6/TodrU/cUKq2o0szn1zXd5OXdIlXvk6r3Sn409Vi9LzUONKG21lVb66FjAIgMtQVACEk2eCslDTWzwWbWWakmbnHjSWb2KUm9JS1PMAs6uEeXbFJVzbELvFU1tXp0yabjJ+fmpzZUye4j7fsw9cgGKwAAAEgDiW2y4u5HzGyapCVK3SZhnruvN7MHJRW6+8fN3k2SFrg7v+LCCX1QUXVK48rNp6EDAABA2kn0Pnju/rKklxuN3dfoeHaSGRCHC3KyVdpEM3dBTnaANAAAAED71F42WQFOavq1w5SdmXHMWHZmhqZfOyxQIsSkf+9s9e/NLwsAtC1qC4AQEl3BA9rK5HGpO2x8/7m1Olx7VP1zsjX92mH148DpGH5+z9ARAESI2gIgBBo8dBiTx/XXMyvelyQtvGt84DQAAABA+8MlmgDS3qsbdurVDTtDxwAQGWoLgBBo8AAAAAAgEjR4AAAAABAJGjwAAAAAiAQNHgAAAABEgl00AaS9gX26ho4AIELUFgAh0OABSHtDz+0ROgKACFFbAITAJZoA0t6R2qM6Uns0dAwAkaG2AAiBBg9A2lu2qUzLNpWFjgEgMtQWACHQ4AEAAABAJGjwAAAAACASNHgAAAAAEAkaPAAAAACIBLdJAJD2hvTrFjoCgAhRWwCEQIMHIO0N6dc9dAQAEaK2AAiBBg8dysK7xoeOgAhV19RKkrIyMwInARATaguAEPgOHoC098bmj/TG5o9CxwAQGWoLgBBo8AAAAAAgEjR4AAAAABAJGjwAAACgjS1aVapV71fora279ZmHX9eiVaWhIyFN0OABAAAAbWjRqlLNfH6dDtcelSSVVlRp5vPraPJwRiTa4JnZJDPbZGZbzGzGCebcYGYbzGy9mc1PMg8ANGXoud019Fy2MwfQtqgt6evRJZtUVbeL6seqamr16JJNgRIhnSR2mwQzy5A0R9LVkkokrTSzxe6+ocGcoZJmSvqMu+8xs3OSygMAJzKwDzcjBtD2qC3p64OKqlMaB9pSkit4l0ra4u7F7n5Y0gJJ1zea8/eS5rj7Hkly910J5gGAJh04dEQHDh0JHQNAZKgt6euCnOxTGgfaUpINXn9JOxocl9SNNXSRpIvM7P+Z2Z/NbFKCeQCgScv/u1zL/7s8dAwAkaG2pK/p1w5TdqMb3GdnZmj6tcMCJUI6SewSzVP4/KGSJkjKlfRHMxvt7hUNJ5nZFElTJOnCCy88wxEBAACAlps8LrWm8f3n1upw7VH1z8nW9GuH1Y8DSUqywSuVNKDBcW7dWEMlkt5y9xpJW83sPaUavpUNJ7n7XElzJamgoMATSwwAAAC0gcnj+uuZFe9LkhbeNT5wGqSTJC/RXClpqJkNNrPOkm6StLjRnEVKrd7JzPoqdclmcYKZAAAAACBaia3gufsRM5smaYmkDEnz3H29mT0oqdDdF9edu8bMNkiqlTTd3blYHQAAAOmjpEhaPV+q2C7lDJTybpFy80OnQgeV6Hfw3P1lSS83GruvwXOX9I91PwAQxKfO7xE6AoAIUVvQIiVF0tKHpKyeUs/zpary1PHEe2ny0CqhN1kBgOBye3cNHQFAhKgtaJHV81PNXVav1PHHj6vn0+ChVZL8Dh4AdAh7q2q0t6omdAwAkaG2oEUqtktdGq32dumRGgdagQYPQNpbuXW3Vm7dHToGgMhQW9AiOQOlQ5XHjh2qTI0DrUCDBwAAAISSd4tUvU+q3iv50dRj9b7UONAKNHgAAABAKLn5qQ1VsvtI+z5MPbLBCk4Dm6wAAAAAIeXm09ChzbCCBwAAAACRYAUPQNob1b9X6AgAIkRtARACDR6AtHder6zQEQBEiNoCIAQu0QSQ9vYcOKw9Bw6HjgEgMtQWACHQ4AFIe0Xb96ho+57QMQBEhtoCIAQaPAAAAACIBA0eAAAAAESCBg8AAAAAIkGDBwAAAACR4DYJANLe2AE5oSMAiBC1BUAINHgA0l6/Hl1CRwAQIWoLgBC4RBNA2iurPKSyykOhYwCIDLUFQAg0eADS3podFVqzoyJ0DACRobYACIEGDwAAAAAiQYMHAAAAAJGgwQMAAACASNDgAQAAAEAkuE0CgLSXP7B36AgAIkRtARBCoit4ZjbJzDaZ2RYzm9HE+W+YWZmZra77+WaSeQCgKb27dVbvbp1DxwAQGWoLgBASW8EzswxJcyRdLalE0kozW+zuGxpNXeju05LKAQDN+cveaknSeb2yAicBEBNqC4AQkrxE81JJW9y9WJLMbIGk6yU1bvAAIKh3SvdK4h9hANoWtQVACEleotlf0o4GxyV1Y419xczWmtlzZjYgwTwAAAAAELXQu2j+l6RB7j5G0iuSftXUJDObYmaFZlZYVlZ2RgMCAAAAQEeRZINXKqnhilxu3Vg9dy9390N1h7+QlN/UG7n7XHcvcPeCfv36JRIWAAAAADq6JBu8lZKGmtlgM+ss6SZJixtOMLPzGxx+SdLGBPMAAAAAQNQS22TF3Y+Y2TRJSyRlSJrn7uvN7EFJhe6+WNI9ZvYlSUck7Zb0jaTyAMCJXDL47NARAESI2gIghERvdO7uL0t6udHYfQ2ez5Q0M8kMANCcXtmZoSMAiBC1BUAIoTdZAYDgSvYcVMmeg6FjAIgMtQVACImu4AFAR/Duh5WSpNzeXQMnARATaguAEFjBAwAAAIBI0OABAAAAQCRo8AAAAAAgEjR4AAAAABAJNlkBkPbG/1Wf0BEARIjaAiAEGjwAaa9bF0ohgLZHbQEQApdoAkh728sPaHv5gdAxAESG2gIgBH61BCDtbd65X5I0sE+3wEkAxITaAiAEVvAAAAAAIBI0eAAAAAAQCRo8AAAAAIgEDR4AAAAARIJNVgCkvcuH9g0dAUCEqC0AQqDBA5D2sjIzQkcAECFqC4AQuEQTQNorLtuv4rL9oWMAiAy1BUAINHgA0l5x2QEVl3EzYgBti9oCIAQu0QQAAAASsPCu8aEjIA2xggcAAAAAkaDBAwAAAIBI0OABAAAAQCT4Dh6AtDdhWL/QEQBEiNoCIAQaPABp76wMLmYA0PaoLQBCSLTymNkkM9tkZlvMbMZJ5n3FzNzMCpLMAwBN2byzUpt3VoaOASAy1BYAISTW4JlZhqQ5kq6TNELSzWY2ool5PST9g6S3ksoCACezvfygtpcfDB0DQGSoLQBCSHIF71JJW9y92N0PS1og6fom5v1A0iOSqhPMAgAAAADRS7LB6y9pR4PjkrqxemZ2saQB7v67BHMAAAAAQFoI9u1fM+sk6SeSvtuCuVPMrNDMCsvKypIPBwAAAAAdUJINXqmkAQ2Oc+vGPtZD0ihJy8xsm6TLJC1uaqMVd5/r7gXuXtCvH1sOAwAAAEBTzN2TeWOzsyS9J+lvlGrsVkq6xd3Xn2D+Mknfc/fCZt63TNL2tk2LiPWV9FHoEACiQ20BkARqC1pqoLs3ufKV2H3w3P2ImU2TtERShqR57r7ezB6UVOjui1v5vizhocXMrNDduf0GgDZFbQGQBGoL2kJiK3hAe0ChBJAEaguAJFBb0BaCbbICAAAAAGhbNHiI3dzQAQBEidoCIAnUFpw2LtEEAAAAgEiwggcAAAAAkaDBAwAAAIBI0OABAAAAQCRo8BAVM8sws38xs/Vmts7MhoTOBKDjo7YASAK1BUmgwUNsZkoqdveRkh6X9K3AeQDEgdoCIAnUFrS5s0IHANqKmXWT9Lfunl83tFXSFwJGAhABaguAJFBbkBQaPMTkKkkDzGx13fHZkl4NFwdAJKgtAJJAbUEiuEQTMcmTdJ+757l7nqT/K2m1mXUzs1+Z2c/N7GtBEwLoiPLUdG0ZYma/NLPngqYD0FHlqenaMrnu3ywLzeyaoAnRIdHgISa9JR2UJDM7S9I1kv5L0pclPefufy/pS+HiAeigmqwt7l7s7ncGTQagIztRbVlU92+WuyXdGDAfOigaPMTkPUmX1T3/jqTfuftWSbmSdtSN14YIBqBDO1FtAYDT0VxtmSVpzhlPhQ6PBg8xeUbSxWa2RdIYSf9YN16iVJMn8b95AKfuRLUFAE5Hk7XFUh6R9Ht3fztkQHRM5u6hMwCJqtul6glJ1ZLecPffBI4EIAJm1kfSQ5KulvQLd/+nwJEARMDM7pF0m6SVkla7+5OBI6GDocEDAAAAgEhwuRoAAAAARIIGDwAAAAAiQYMHAAAAAJGgwQMAAACASNDgAQAAAEAkaPAAAAAAIBI0eAAAAAAQCRo8AAAAAIgEDR4AAAAAROL/A2H9qvSiOxZyAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -387,7 +385,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -398,7 +396,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -419,37 +417,30 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "est = DynamicDML(\n", - " model_y=LassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", - " model_t=MultiTaskLassoCV(cv=3, alphas=alpha_regs, tol=1e-2), \n", + " model_y=LassoCV(cv=3), \n", + " model_t=MultiTaskLassoCV(cv=3), \n", " cv=3)" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": { "scrolled": true }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Objective did not converge. You might want to increase the number of iterations. Duality gap: 2.6550941575656566, tolerance: 1.6272718134380235\n" - ] - }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -460,7 +451,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -472,13 +463,13 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "\n", "\n", - " X0|(T0)$_0$ 0.394 0.103 3.838 0.0 0.225 0.563 \n", + " X0|(T0)$_0$ 0.392 0.073 5.366 0.0 0.272 0.512 \n", "\n", "\n", - " X0|(T0)$_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248 \n", + " X0|(T0)$_1$ -0.041 0.12 -0.339 0.735 -0.239 0.157 \n", "\n", "\n", - " X0|(T0)$_2$ 0.04 0.2 0.199 0.843 -0.29 0.369 \n", + " X0|(T0)$_2$ 0.115 0.119 0.968 0.333 -0.081 0.311 \n", "\n", "\n", "\n", @@ -487,13 +478,13 @@ " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|(T0)$_0$ 0.579 0.052 11.242 0.0 0.495 0.664cate_intercept|(T0)$_0$ 0.602 0.034 17.813 0.0 0.547 0.658
cate_intercept|(T0)$_1$ 0.032 0.086 0.379 0.705 -0.108 0.173cate_intercept|(T0)$_1$ -0.027 0.059 -0.462 0.644 -0.125 0.07
cate_intercept|(T0)$_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056cate_intercept|(T0)$_2$ -0.085 0.06 -1.411 0.158 -0.184 0.014


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -504,16 +495,16 @@ "=================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------\n", - "X0|(T0)$_0$ 0.394 0.103 3.838 0.0 0.225 0.563\n", - "X0|(T0)$_1$ -0.066 0.191 -0.345 0.73 -0.38 0.248\n", - "X0|(T0)$_2$ 0.04 0.2 0.199 0.843 -0.29 0.369\n", + "X0|(T0)$_0$ 0.392 0.073 5.366 0.0 0.272 0.512\n", + "X0|(T0)$_1$ -0.041 0.12 -0.339 0.735 -0.239 0.157\n", + "X0|(T0)$_2$ 0.115 0.119 0.968 0.333 -0.081 0.311\n", " CATE Intercept Results \n", "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ 0.579 0.052 11.242 0.0 0.495 0.664\n", - "cate_intercept|(T0)$_1$ 0.032 0.086 0.379 0.705 -0.108 0.173\n", - "cate_intercept|(T0)$_2$ -0.098 0.093 -1.048 0.294 -0.251 0.056\n", + "cate_intercept|(T0)$_0$ 0.602 0.034 17.813 0.0 0.547 0.658\n", + "cate_intercept|(T0)$_1$ -0.027 0.059 -0.462 0.644 -0.125 0.07\n", + "cate_intercept|(T0)$_2$ -0.085 0.06 -1.411 0.158 -0.184 0.014\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -524,7 +515,7 @@ "\"\"\"" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -535,14 +526,14 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Average effect of default policy:0.48\n" + "Average effect of default policy:0.45\n" ] } ], @@ -554,7 +545,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -562,8 +553,8 @@ "output_type": "stream", "text": [ "Effect of target policy over baseline policy for test set:\n", - " [ 0.48463458 0.52974258 0.21137445 0.36785476 0.37107783 0.6353013\n", - " -0.08878165 0.17427563 0.45723001 0.06398282]\n" + " [ 0.52203836 0.5995183 0.05267201 0.32145103 0.32698715 0.78083164\n", + " -0.46289232 -0.01105092 0.47496678 -0.20049582]\n" ] } ], @@ -578,19 +569,19 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(array([ 0.57932353, 0.03248502, -0.09764978]),\n", - " array([[ 0.39429592],\n", - " [-0.06583129],\n", - " [ 0.03975663]]))" + "(array([ 0.60246595, -0.02731669, -0.08491363]),\n", + " array([[ 0.39168147],\n", + " [-0.04079943],\n", + " [ 0.11529833]]))" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -602,7 +593,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -620,12 +611,12 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] From a39f1b5de1fb1442a32d310934271171698eea47 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 5 Aug 2021 20:16:06 -0400 Subject: [PATCH 15/27] Reverse effect indices to match paper --- econml/dml/dynamic_dml.py | 90 +++++++++---------- econml/tests/dgp.py | 19 ++-- ...mic Double Machine Learning Examples.ipynb | 48 +++++----- 3 files changed, 76 insertions(+), 81 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index bb162e75d..ce43834d0 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -160,18 +160,17 @@ def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, period_filters = _get_groups_period_filter(groups, self.n_periods) Y_res, T_res = nuisances self._d_y = Y.shape[1:] - for t in np.arange(self.n_periods): - period = self.n_periods - 1 - t - Y_adj = Y_res[period_filters[period]].copy() - if t > 0: + for t in np.arange(self.n_periods - 1, -1, -1): + Y_adj = Y_res[period_filters[t]].copy() + if t < self.n_periods - 1: Y_adj -= np.sum( [self._model_final_trained[j].predict_with_res( - X[period_filters[self.n_periods - 1 - j]] if X is not None else None, - T_res[period_filters[self.n_periods - 1 - j], ..., period] - ) for j in np.arange(t)], axis=0) + X[period_filters[j]] if X is not None else None, + T_res[period_filters[j], ..., t] + ) for j in np.arange(t + 1, self.n_periods)], axis=0) self._model_final_trained[t].fit( - X[period_filters[period]] if X is not None else None, T[period_filters[period]], - T_res[period_filters[period], ..., period], Y_adj) + X[period_filters[t]] if X is not None else None, T[period_filters[t]], + T_res[period_filters[t], ..., t], Y_adj) return self @@ -200,18 +199,17 @@ def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None Y_res, T_res = nuisances scores = np.full((self.n_periods, ), np.nan) period_filters = _get_groups_period_filter(groups, self.n_periods) - for t in np.arange(self.n_periods): - period = self.n_periods - 1 - t - Y_adj = Y_res[period_filters[period]].copy() - if t > 0: + for t in np.arange(self.n_periods - 1, -1, -1): + Y_adj = Y_res[period_filters[t]].copy() + if t < self.n_periods - 1: Y_adj -= np.sum( [self._model_final_trained[j].predict_with_res( - X[period_filters[self.n_periods - 1 - j]] if X is not None else None, - T_res[period_filters[self.n_periods - 1 - j], ..., period] - ) for j in np.arange(t)], axis=0) + X[period_filters[j]] if X is not None else None, + T_res[period_filters[j], ..., t] + ) for j in np.arange(t + 1, self.n_periods)], axis=0) Y_adj_pred = self._model_final_trained[t].predict_with_res( - X[period_filters[period]] if X is not None else None, - T_res[period_filters[period], ..., period]) + X[period_filters[t]] if X is not None else None, + T_res[period_filters[t], ..., t]) if sample_weight is not None: scores[t] = np.mean(np.average((Y_adj - Y_adj_pred)**2, weights=sample_weight, axis=0)) else: @@ -261,7 +259,6 @@ def _fit_single_output_cov(self, nuisances, X, y_index, groups): """ Calculates the covariance (n_periods*n_treatments) x (n_periods*n_treatments) matrix for a single outcome. """ - # TODO: add group filters here Y_res, T_res = nuisances # Calculate auxiliary quantities period_filters = _get_groups_period_filter(groups, self.n_periods) @@ -282,10 +279,9 @@ def _fit_single_output_cov(self, nuisances, X, y_index, groups): Y_diff = np.array([ np.sum([ self._model_final_trained[j].predict_with_res( - X[period_filters[self.n_periods - 1 - j]] if X is not None else None, - T_res[ - period_filters[self.n_periods - 1 - j], ..., self.n_periods - 1 - t] - ) for j in np.arange(t + 1)], + X[period_filters[j]] if X is not None else None, + T_res[period_filters[j], ..., t] + ) for j in np.arange(t, self.n_periods)], axis=0 ) for t in np.arange(self.n_periods) @@ -295,28 +291,24 @@ def _fit_single_output_cov(self, nuisances, X, y_index, groups): Sigma = np.zeros((self.n_periods * d_xt, self.n_periods * d_xt)) for t in np.arange(self.n_periods): - period_t = self.n_periods - 1 - t - period_filter_t = period_filters[period_t] - res_epsilon_t = (Y_res[period_filter_t] - + res_epsilon_t = (Y_res[period_filters[t]] - (Y_diff[t][:, y_index] if y_index >= 0 else Y_diff[t]) ).reshape(-1, 1, 1) - resT_t = XT_res[period_t][period_t] + resT_t = XT_res[t][t] for j in np.arange(self.n_periods): # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix Sigma - period_j = self.n_periods - 1 - j - period_filter_j = period_filters[period_j] - res_epsilon_j = (Y_res[period_filter_j] - + res_epsilon_j = (Y_res[period_filters[j]] - (Y_diff[j][:, y_index] if y_index >= 0 else Y_diff[j]) ).reshape(-1, 1, 1) - resT_j = XT_res[period_j][period_j] + resT_j = XT_res[j][j] cov_resT_tj = resT_t.reshape(-1, d_xt, 1) @ resT_j.reshape(-1, 1, d_xt) sigma_tj = np.mean((res_epsilon_t * res_epsilon_j) * cov_resT_tj, axis=0) Sigma[t * d_xt:(t + 1) * d_xt, j * d_xt:(j + 1) * d_xt] = sigma_tj - if j <= t: + if j >= t: # Calculating the (t, j) block entry (of size n_treatments x n_treatments) of matrix J m_tj = np.mean( - XT_res[period_j][period_t].reshape(-1, d_xt, 1) @ resT_t.reshape(-1, 1, d_xt), + XT_res[j][t].reshape(-1, d_xt, 1) @ resT_t.reshape(-1, 1, d_xt), axis=0) J[t * d_xt:(t + 1) * d_xt, j * d_xt:(j + 1) * d_xt] = m_tj @@ -431,34 +423,34 @@ class DynamicDML(LinearModelFinalCateEstimatorMixin, _OrthoLearner): est.fit(y, T, X=X, W=None, groups=groups, inference="auto") >>> est.const_marginal_effect(X[:2]) - array([[-0.012..., 0.031..., 0.069..., 0.111..., -0.349..., - -0.076...], - [-0.411..., -0.088..., 0.021..., -0.171..., -0.126... , - 0.397...]]) + array([[-0.349..., -0.076..., 0.069..., 0.111..., -0.012..., + 0.031...], + [-0.126... , 0.397..., 0.021..., -0.171..., -0.411..., + -0.088...]]) >>> est.effect(X[:2], T0=0, T1=1) array([-0.225..., -0.378...]) >>> est.effect(X[:2], T0=np.zeros((2, n_periods*T.shape[1])), T1=np.ones((2, n_periods*T.shape[1]))) array([-0.225..., -0.378...]) >>> est.coef_ - array([[-0.191...], - [-0.057...], + array([[ 0.107...], + [ 0.227...], [-0.023...], [-0.136...], - [ 0.107...], - [ 0.227...]]) + [-0.191...], + [-0.057...]]) >>> est.coef__interval() - (array([[-0.333...], - [-0.171...], + (array([[-0.051...], + [ 0.040...], [-0.154...], [-0.336...], - [-0.051...], - [ 0.040...]]), - array([[-0.050...], - [ 0.056...], + [-0.333...], + [-0.171...]]), + array([[ 0.265...], + [ 0.415...], [ 0.108...], [ 0.064...], - [ 0.265...], - [ 0.415...]])) + [-0.050...], + [ 0.056...]])) """ def __init__(self, *, diff --git a/econml/tests/dgp.py b/econml/tests/dgp.py index 98d4e6a81..333a326f2 100644 --- a/econml/tests/dgp.py +++ b/econml/tests/dgp.py @@ -93,28 +93,31 @@ def create_instance(self, s_x, sigma_x=.5, sigma_y=.5, conf_str=5, hetero_streng len(self.hetero_inds) self.true_effect = np.zeros((self.n_periods, self.n_treatments)) - self.true_effect[0] = self.epsilon - for t in np.arange(1, self.n_periods): + # Invert indices to match latest API + self.true_effect[self.n_periods - 1] = self.epsilon + for t in np.arange(self.n_periods - 2, -1, -1): self.true_effect[t, :] = (self.zeta.reshape( - 1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) @ self.Alpha) + 1, -1) @ np.linalg.matrix_power(self.Beta, (self.n_periods - 1 - t) - 1) @ self.Alpha) self.true_hetero_effect = np.zeros( (self.n_periods, (self.n_x + 1) * self.n_treatments)) - self.true_hetero_effect[0, :] = cross_product( + self.true_hetero_effect[self.n_periods - 1, :] = cross_product( add_constant(self.y_hetero_effect.reshape(1, -1), has_constant='add'), self.epsilon.reshape(1, -1)) - for t in np.arange(1, self.n_periods): + for t in np.arange(self.n_periods - 2, -1, -1): + # Invert indices to match latest API self.true_hetero_effect[t, :] = cross_product( add_constant(self.x_hetero_effect.reshape(1, -1), has_constant='add'), - self.zeta.reshape(1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) @ self.Alpha) + self.zeta.reshape(1, -1) @ np.linalg.matrix_power( + self.Beta, (self.n_periods - 1 - t) - 1) @ self.Alpha) return self def hetero_effect_fn(self, t, x): - if t == 0: + if t == self.n_periods - 1: return (np.dot(self.y_hetero_effect, x.flatten()) + 1) * self.epsilon else: return (np.dot(self.x_hetero_effect, x.flatten()) + 1) *\ - (self.zeta.reshape(1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) + (self.zeta.reshape(1, -1) @ np.linalg.matrix_power(self.Beta, (self.n_periods - 1 - t) - 1) @ self.Alpha).flatten() def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb index 0eb1f1d6e..e07d966f5 100644 --- a/notebooks/Dynamic Double Machine Learning Examples.ipynb +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -185,7 +185,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 6, @@ -224,7 +224,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Effect of target policy over baseline policy: 4.62\n" + "Effect of target policy over baseline policy: 4.79\n" ] } ], @@ -246,9 +246,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Effect of a treatment in period 1 on period 3 outcome: 0.69\n", + "Effect of a treatment in period 1 on period 3 outcome: 0.61\n", "Effect of a treatment in period 2 on period 3 outcome: 1.05\n", - "Effect of a treatment in period 3 on period 3 outcome: 0.61\n" + "Effect of a treatment in period 3 on period 3 outcome: 0.69\n" ] } ], @@ -279,13 +279,13 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "\n", "\n", - " cate_intercept|(T0)$_0$ 0.692 0.031 22.308 0.0 0.641 0.743 \n", + " cate_intercept|(T0)$_0$ 0.61 0.09 6.74 0.0 0.461 0.758 \n", "\n", "\n", " cate_intercept|(T0)$_1$ 1.05 0.067 15.565 0.0 0.939 1.161 \n", "\n", "\n", - " cate_intercept|(T0)$_2$ 0.61 0.09 6.74 0.0 0.461 0.758 \n", + " cate_intercept|(T0)$_2$ 0.692 0.031 22.308 0.0 0.641 0.743 \n", "\n", "

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -296,9 +296,9 @@ "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ 0.692 0.031 22.308 0.0 0.641 0.743\n", + "cate_intercept|(T0)$_0$ 0.61 0.09 6.74 0.0 0.461 0.758\n", "cate_intercept|(T0)$_1$ 1.05 0.067 15.565 0.0 0.939 1.161\n", - "cate_intercept|(T0)$_2$ 0.61 0.09 6.74 0.0 0.461 0.758\n", + "cate_intercept|(T0)$_2$ 0.692 0.031 22.308 0.0 0.641 0.743\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -342,7 +342,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3gAAAEzCAYAAABjbqHIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAn5ElEQVR4nO3de3TV5Z3v8c+XGEi4BgFvBLnMQco9mOhIay04XrDtqUy76q1TrdpBV6HO6bT0wJKlaGtHl7adcWSqtGVsZxXBsYqMtYfjBdp6pEIiNwERJoAkWoiBQIAEQvieP3aMIQQSQn48ybPfr7Wy9v49v2fv/RHa7+Kb57efn7m7AAAAAAAdX6fQAQAAAAAAbYMGDwAAAAAiQYMHAAAAAJGgwQMAAACASNDgAQAAAEAkaPAAAAAAIBJnhQ5wqvr27euDBg0KHQNARPZV10iSemZlBk4CICbUFgBJKSoq+sjd+zV1rsM1eIMGDVJhYWHoGAAisnpHhSQpb0BO0BwA4kJtAZAUM9t+onMdrsEDgLbGP74AJIHaAiCExL6DZ2bzzGyXmb1zgvNfM7O1ZrbOzN40s7FJZQEAAACAdJDkJitPS5p0kvNbJX3O3UdL+oGkuQlmAYAT+tPmMv1pc1noGAAiQ20BEEJil2i6+x/NbNBJzr/Z4PDPknKTygIAJ3Oo5mjoCAAiRG1BR1NTU6OSkhJVV1eHjoI6WVlZys3NVWZmyzdrai/fwbtT0u9DhwAAAADSVUlJiXr06KFBgwbJzELHSXvurvLycpWUlGjw4MEtfl3w++CZ2USlGrz/fZI5U8ys0MwKy8q41AEAAABoa9XV1erTpw/NXTthZurTp88pr6gGbfDMbIykX0i63t3LTzTP3ee6e4G7F/Tr1+TtHgAAAACcJpq79qU1fx/BGjwzu1DS85K+7u7vhcoBAOf1ytJ5vbJCxwAQGWoL0sGNTy3XjU8tb7P3y8jIUF5enkaOHKmxY8fqxz/+sY4eDfN91sLCQt1zzz0nnTNo0CCNHj1ao0eP1ogRIzRr1qz6Fbdt27bJzDRr1qz6+R999JEyMzM1bdo0SdLs2bP12GOPtWnuJG+T8Iyk5ZKGmVmJmd1pZneb2d11U+6T1EfSv5nZajPj7uUAghjVv5dG9e8VOgaAyFBbgFOXnZ2t1atXa/369XrllVf0+9//Xg888ECQLAUFBXr88cebnbd06VKtW7dOK1asUHFxse666676c4MHD9bvfve7+uP//M//1MiRIxPJ+7HEGjx3v9ndz3f3THfPdfdfuvuT7v5k3flvuntvd8+r+ylIKgsAAACAtrVoValWvV+ht7bu1mcefl2LVpW26fufc845mjt3rp544gm5u6644gqtXr26/vzll1+uNWvWaPbs2brjjjs0YcIEDRky5JimbPLkycrPz9fIkSM1d+4nd2Xr3r27pk+frpEjR+qqq67SihUr6l+/ePFiSdKyZcv0xS9+UZK0f/9+3X777Ro9erTGjBmj3/72t8fl7d69u5588kktWrRIu3fvliR17dpVw4cPV2Fhai1r4cKFuuGGG9r0z6mx4JusAEBoSzft0tJNu0LHABAZagtitmhVqWY+v06Ha1OXT5ZWVGnm8+vavMkbMmSIamtrtWvXLt155516+umnJUnvvfeeqqurNXbsWEnSu+++qyVLlmjFihV64IEHVFNTI0maN2+eioqKVFhYqMcff1zl5altPw4cOKArr7xS69evV48ePTRr1iy98soreuGFF3Tfffcdl+MHP/iBevXqpXXr1mnt2rW68sorm8zbs2dPDR48WJs3b64fu+mmm7RgwQLt2LFDGRkZuuCCC9ryj+g4NHgA0l5trau21kPHQEBt/R0SQKK2IG6PLtmkqpraY8aqamr16JJNiX3mV7/6Vb300kuqqanRvHnz9I1vfKP+3Be+8AV16dJFffv21TnnnKOdO3dKkh5//HGNHTtWl112mXbs2FHfeHXu3FmTJk2SJI0ePVqf+9znlJmZqdGjR2vbtm3Hffarr76qqVOn1h/37t37hDndj/3//aRJk/TKK69owYIFuvHGG1v7n99i7eU+eAAAAAA6iA8qqk5pvLWKi4uVkZGhc845R2amq6++Wi+++KKeffZZFRUV1c/r0qVL/fOMjAwdOXJEy5Yt06uvvqrly5era9eumjBhQv0GKJmZmfU7VHbq1Kn+9Z06ddKRI0danbeyslLbtm3TRRddpL1790pKNZP5+fn68Y9/rA0bNtRfApoUVvAAAAAAnJILcrJPabw1ysrKdPfdd2vatGn1zdg3v/lN3XPPPbrkkktOuoomSXv37lXv3r3VtWtXvfvuu/rzn//c6ixXX3215syZU3+8Z8+e4+bs379f3/rWtzR58uTjsn33u9/VI488orPPPrvVGVqKBg8AAADAKZl+7TBlZ2YcM5admaHp1w47rfetqqqqv03CVVddpWuuuUb3339//fn8/Hz17NlTt99+e7PvNWnSJB05ckTDhw/XjBkzdNlll7U616xZs7Rnzx6NGjVKY8eO1dKlS+vPTZw4UaNGjdKll16qCy+8UE899dRxrx85cqRuu+22Jt/7hz/8oXJzc+t/Tpc1vka0vSsoKPCPd6EBgLaw8cN9kqTh5/cMnAShfPz9u4V3jQ+cBDGhtqCj2bhxo4YPH97i+YtWler7z63V4dqj6p+TrenXDtPkcf0TTCh98MEHmjBhgt5991116pQea1VN/b2YWdGJ7kLAd/AApD3+8QUgCdQWxG7yuP56ZsX7ks7ML8h+/etf695779VPfvKTtGnuWoMGDwAAAECrnMkrH2699VbdeuutZ+zzOipaXwBp79UNO/Xqhp2hYwCIDLUFQAg0eAAAAAAQCRo8AAAAAIgEDR4AAAAARIIGDwAAAEC7sHPnTt1yyy0aMmSI8vPzNX78eL3wwgtnNMO2bds0atSoJsfnz5/fqvf853/+Zx08eLD+uHv37q3O1xwaPABpb2CfrhrYp2voGAAiQ20BTo27a/LkybriiitUXFysoqIiLViwQCUlJcfNPXLkyBnPd7IGr7k8jRu8JHGbBABpb+i5PUJHABAhaguiV1IkrZ4vVWyXcgZKebdIufmtfrvXX39dnTt31t13310/NnDgQH3729+WJD399NN6/vnntX//ftXW1uqFF17QHXfcoeLiYnXt2lVz587VmDFjNHv2bHXv3l3f+973JEmjRo3SSy+9JEm67rrrdPnll+vNN99U//799eKLLyo7O1tFRUW64447JEnXXHNNk/lmzJihjRs3Ki8vT7fddpt69+59TJ4HHnhAjz32WP1nTZs2TQUFBdq3b58++OADTZw4UX379tXSpUslSffee69eeuklZWdn68UXX9S5557b6j+7hljBA5D2jtQe1ZHao6FjAIgMtQVRKymSlj4kVZVLPc9PPS59KDXeSuvXr9fFF1980jlvv/22nnvuOf3hD3/Q/fffr3Hjxmnt2rX60Y9+1KJ75G3evFlTp07V+vXrlZOTo9/+9reSpNtvv13/+q//qjVr1pzwtQ8//LA++9nPavXq1frOd75zXJ4Tueeee3TBBRdo6dKl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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -437,7 +437,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 16, @@ -463,13 +463,13 @@ " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "\n", "\n", - " X0|(T0)$_0$ 0.392 0.073 5.366 0.0 0.272 0.512 \n", + " X0|(T0)$_0$ 0.115 0.119 0.968 0.333 -0.081 0.311 \n", "\n", "\n", " X0|(T0)$_1$ -0.041 0.12 -0.339 0.735 -0.239 0.157 \n", "\n", "\n", - " X0|(T0)$_2$ 0.115 0.119 0.968 0.333 -0.081 0.311 \n", + " X0|(T0)$_2$ 0.392 0.073 5.366 0.0 0.272 0.512 \n", "\n", "\n", "\n", @@ -478,13 +478,13 @@ " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", " \n", "\n", "\n", - " \n", + " \n", "\n", "
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|(T0)$_0$ 0.602 0.034 17.813 0.0 0.547 0.658cate_intercept|(T0)$_0$ -0.085 0.06 -1.411 0.158 -0.184 0.014
cate_intercept|(T0)$_1$ -0.027 0.059 -0.462 0.644 -0.125 0.07
cate_intercept|(T0)$_2$ -0.085 0.06 -1.411 0.158 -0.184 0.014cate_intercept|(T0)$_2$ 0.602 0.034 17.813 0.0 0.547 0.658


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], @@ -495,16 +495,16 @@ "=================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------\n", - "X0|(T0)$_0$ 0.392 0.073 5.366 0.0 0.272 0.512\n", + "X0|(T0)$_0$ 0.115 0.119 0.968 0.333 -0.081 0.311\n", "X0|(T0)$_1$ -0.041 0.12 -0.339 0.735 -0.239 0.157\n", - "X0|(T0)$_2$ 0.115 0.119 0.968 0.333 -0.081 0.311\n", + "X0|(T0)$_2$ 0.392 0.073 5.366 0.0 0.272 0.512\n", " CATE Intercept Results \n", "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ 0.602 0.034 17.813 0.0 0.547 0.658\n", + "cate_intercept|(T0)$_0$ -0.085 0.06 -1.411 0.158 -0.184 0.014\n", "cate_intercept|(T0)$_1$ -0.027 0.059 -0.462 0.644 -0.125 0.07\n", - "cate_intercept|(T0)$_2$ -0.085 0.06 -1.411 0.158 -0.184 0.014\n", + "cate_intercept|(T0)$_2$ 0.602 0.034 17.813 0.0 0.547 0.658\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -553,8 +553,8 @@ "output_type": "stream", "text": [ "Effect of target policy over baseline policy for test set:\n", - " [ 0.52203836 0.5995183 0.05267201 0.32145103 0.32698715 0.78083164\n", - " -0.46289232 -0.01105092 0.47496678 -0.20049582]\n" + " [2.08972181 2.23249102 1.22483907 1.72010749 1.73030869 2.56658993\n", + " 0.27482914 1.10741935 2.00298489 0.75833676]\n" ] } ], @@ -575,10 +575,10 @@ { "data": { "text/plain": [ - "(array([ 0.60246595, -0.02731669, -0.08491363]),\n", - " array([[ 0.39168147],\n", + "(array([-0.08491363, -0.02731669, 0.60246595]),\n", + " array([[ 0.11529833],\n", " [-0.04079943],\n", - " [ 0.11529833]]))" + " [ 0.39168147]]))" ] }, "execution_count": 20, @@ -616,7 +616,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA4AAAAFWCAYAAAA4zPNMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAApOklEQVR4nO3de3xV5Z3v8e+PGEi4BgFvgXKZQYY7lOgRtQyiCJ5aZdpTsM6MVO2op1LmtB08cOSFaDutHtraQ/W00NbaznQESjFeeziK4ClTFIPcCkixASRRIQQSbgnk8pw/EjMhBJK1V5Jn7zyf9+vFi72fvbL31+32J1/W2muZc04AAAAAgPavg+8AAAAAAIC2QQEEAAAAgEBQAAEAAAAgEBRAAAAAAAgEBRAAAAAAAkEBBAAAAIBAXOQ7QEvr3bu3GzBggO8YAJLYsfIKSVL3jHTPSQAgGuYXgObYtGnTYedcn8Yea3cFcMCAAcrLy/MdA0AS23KgRJI0pl+W1xwAEBXzC0BzmNn+8z3W7gogADSFPzgBSFXMLwBx8R1AAAAAAAgEBRBAcH6/p0i/31PkOwYARMb8AhAXh4ACCM7pimrfEQAgIcwv+FRRUaGCggKVl5f7joJaGRkZ6tu3r9LTm39iKAogAAAAgCYVFBSoW7duGjBggMzMd5zgOedUXFysgoICDRw4sNk/xyGgAAAAAJpUXl6uXr16Uf6ShJmpV69ekffIUgABAAAANAvlL7kk8u+DAgggOJf1yNBlPTJ8xwCAyJhfSDUzlmzQjCUbWuz50tLSNGbMGA0fPlyjR4/W97//fVVX+/lubF5enmbPnn3BbQYMGKCRI0dq5MiRGjZsmObPn1+3x27fvn0yM82fP79u+8OHDys9PV2zZs2SJC1cuFDf+973WjQ3BRBAcEZk99CI7B6+YwBAZMwvhC4zM1NbtmzRjh079Nprr+l3v/udHn30US9ZcnJytHjx4ia3W7t2rbZv366NGzcqPz9f999/f91jAwcO1CuvvFJ3/ze/+Y2GDx/eKnk/QQEEAAAA0OJyNxdq8wclenvvEV33+BvK3VzYos9/ySWXaOnSpXrqqafknNOECRO0ZcuWusevv/56bd26VQsXLtQ999yjiRMnatCgQWeVtmnTpmncuHEaPny4li5dWrfetWtXzZkzR8OHD9dNN92kjRs31v38iy++KElat26dbr31VknSiRMndPfdd2vkyJEaNWqUfvvb356Tt2vXrvrJT36i3NxcHTlyRJLUuXNnDR06VHl5eZKk5cuXa/r06S36PjVEAWwDLb3rG0A8a3cf0trdh3zHAIDImF9IFbmbCzVv1Xadqao5PLOwpEzzVm1v8RI4aNAgVVVV6dChQ7r33nv17LPPSpL+9Kc/qby8XKNHj5Ykvffee1q9erU2btyoRx99VBUVFZKkZ555Rps2bVJeXp4WL16s4uJiSdLJkyc1adIk7dixQ926ddP8+fP12muv6fnnn9eCBQvOyfGtb31LPXr00Pbt27Vt2zZNmjSp0bzdu3fXwIEDtWfPnrq1O+64Q8uWLdOBAweUlpamK664oiXfonNQAAEEp6rKqarK+Y4BAJExv5AqFq3erbKKqrPWyiqqtGj17lZ7zS9+8Yt6+eWXVVFRoWeeeUZf/vKX6x777Gc/q06dOql379665JJLdPDgQUnS4sWLNXr0aF1zzTU6cOBAXTHr2LGjpk6dKkkaOXKk/vqv/1rp6ekaOXKk9u3bd85rv/7663rwwQfr7vfs2fO8OZ07+7/hqVOn6rXXXtOyZcs0Y8aMRP/xm43rAAIAAABoUR+WlEVaT1R+fr7S0tJ0ySWXyMw0efJkvfDCC1qxYoU2bdpUt12nTp3qbqelpamyslLr1q3T66+/rg0bNqhz586aOHFi3Qla0tPT686w2aFDh7qf79ChgyorKxPOe/z4ce3bt09XXnmlSktLJdWUzXHjxun73/++du7cWXeIaWthDyAAAACAFnVFVmak9UQUFRXpgQce0KxZs+rK2le+8hXNnj1bV1111QX3wklSaWmpevbsqc6dO+u9997TW2+9lXCWyZMn6+mnn667f/To0XO2OXHihL761a9q2rRp52T75je/qSeeeEIXX3xxwhmaiwIIAAAAoEXNmTJEmelpZ61lpqdpzpQhsZ63rKys7jIQN910k26++WY98sgjdY+PGzdO3bt31913393kc02dOlWVlZUaOnSo5s6dq2uuuSbhXPPnz9fRo0c1YsQIjR49WmvXrq177IYbbtCIESN09dVX61Of+pSWLFlyzs8PHz5cM2fObPS5v/3tb6tv3751v+KyhsegprqcnBz3yVl0ksUnJ4BZfv94z0kASNKuj45JkoZe3t1zEgCIhvkFn3bt2qWhQ4c2e/vczYV6aOU2namqVnZWpuZMGaJpY7NbMaH04YcfauLEiXrvvffUoUMY+7oa+/diZpucczmNbc93AAEEhz84AUhVzC+kkmljs/Xcxg8ktc2OkF/96ld6+OGH9YMf/CCY8pcICiAAAACAVtGWR8Dddddduuuuu9rs9VIV1RhAcF7feVCv7zzoOwYARMb8AhAXBRAAAAAAAkEBBAAAAIBAUAABAAAAIBAUQAAAAAAp4eDBg7rzzjs1aNAgjRs3TuPHj9fzzz/fphn27dunESNGNLr+b//2bwk95w9/+EOdOnWq7n7Xrl0TztcUCiCA4PTv1Vn9e3X2HQMAImN+IWTOOU2bNk0TJkxQfn6+Nm3apGXLlqmgoOCcbSsrK9s834UKYFN5GhbA1sRlIAAEZ/Cl3XxHAICEML+QUratkNY8JpUWSD36SjcukEZNT/jp3njjDXXs2FEPPPBA3Vr//v31ta99TZL07LPPatWqVTpx4oSqqqr0/PPP65577lF+fr46d+6spUuXatSoUVq4cKG6du2qf/qnf5IkjRgxQi+//LIk6ZZbbtH111+vP/zhD8rOztYLL7ygzMxMbdq0Sffcc48k6eabb24039y5c7Vr1y6NGTNGM2fOVM+ePc/K8+ijj+p73/te3WvNmjVLOTk5OnbsmD788EPdcMMN6t27t9auXStJevjhh/Xyyy8rMzNTL7zwgi699NKE37v6vO4BNLOpZrbbzN43s7nn2Wa6me00sx1mltg+VY9yNxdq8wclenvvEV33+BvK3VzoOxIQvMqqalVWVfuOAQCRMb+QMratkF6aLZUekORqfn9pds16gnbs2KFPf/rTF9zm3Xff1cqVK/Xmm2/qkUce0dixY7Vt2zZ95zvfadY1Avfs2aMHH3xQO3bsUFZWln77299Kku6++2796Ec/0tatW8/7s48//rg+85nPaMuWLfr6179+Tp7zmT17tq644gqtXbu2rvydPHlS11xzjbZu3aoJEybopz/9aZPZm8tbATSzNElPS7pF0jBJXzKzYQ22GSxpnqTrnHPDJf23ts4ZR+7mQs1btV1nagd1YUmZ5q3aTgkEPFu3u0jrdhf5jgEAkTG/kDLWPCZVlJ29VlFWs95CHnzwQY0ePVpXXXVV3drkyZN18cUXS5LWr1+vv//7v5ckTZo0ScXFxTp27NgFn3PgwIEaM2aMJGncuHHat2+fSkpKVFJSogkTJkhS3XM2R/08UXTs2FG33nrrWTlais89gFdLet85l++cOyNpmaTbG2zzD5Keds4dlSTn3KE2zhjLotW7NbnqTa3vOFv5ne7U+o6zNbnqTS1avdt3NAAAAKD1lJ77vbwLrjfD8OHD9e6779bdf/rpp7VmzRoVFf3HX4p06dKlyee56KKLVF39H3vSy8vL62536tSp7nZaWlrs7xLWz3Oh120oPT1dZtZiOerzWQCzJR2od7+gdq2+KyVdaWb/bmZvmdnUxp7IzO4zszwzy6v/AfAt59hrejz9Z+rb4bA6mNS3w2E9nv4z5Rx7zXc0AAAAoPX06BttvRkmTZqk8vJy/fjHP65bu9CJUz7zmc/o17/+tSRp3bp16t27t7p3764BAwbUFcl3331Xe/fuveDrZmVlKSsrS+vXr5ekuudsqFu3bjp+/Ph5n6d///7auXOnTp8+rZKSEq1Zs6bZP9uSkv0soBdJGixpoqQvSfqpmWU13Mg5t9Q5l+Ocy+nTp0/bJryAeR1/o8525qy1znZG8zr+xlMiAAAAoA3cuEBKzzx7LT2zZj1BZqbc3Fy9+eabGjhwoK6++mrNnDlTTzzxRKPbL1y4UJs2bdKoUaM0d+5c/fKXv5QkfeELX9CRI0c0fPhwPfXUU7ryyiubfO1f/OIXevDBBzVmzBg55xrdZtSoUUpLS9Po0aP15JNPnvN4v379NH36dI0YMULTp0/X2LFj6x677777NHXqVN1www3NeStisfP9A7T6C5uNl7TQOTel9v48SXLOfbfeNj+R9LZz7he199dImuuce+d8z5uTk+Py8vJaNXtzuYVZMp37/jqZbGFJ2wcCIEl6fedBSdJNw1rmbFoA0FaYX/Bp165dGjp0aPN/oIXPAorGNfbvxcw2OedyGtve52Ug3pE02MwGSiqUdIekOxtsk6uaPX+/MLPeqjkkNL8tQ8ZhPfrWnvmokXUA3gzq0/T3AwAgGTG/kFJGTafwJSFvh4A65yolzZK0WtIuSSucczvM7DEzu612s9WSis1sp6S1kuY454r9JE5AK+z6BhDfoD5dNahPV98xACAy5heAuLxeCN4596qkVxusLah320n6Ru2v1FP7Nx5FuQ+rV3WROrDrG0gK5RVVkqSM9DTPSQAgGuYXgLi8FsAgjJquWRv6SZKW3z/ecxgAkrR+z2FJfIcGQOphfsE351zd5QngXyLnc0n2s4ACAAAASAIZGRkqLi5OqHSg5TnnVFxcrIyMjEg/xx5AAAAAAE3q27evCgoKlEzX3Q5dRkaG+vaNdoJJCiAAAACAJqWnp2vgwIG+YyAmDgEFAAAAgECwBxBAcAZfyinUAaQm5heAuCiAAILTvxcXUgaQmphfAOLiEFAAwTl5ulInT1f6jgEAkTG/AMRFAQQQnA1/LtaGPxf7jgEAkTG/AMRFAQQAAACAQFAAAQAAACAQnASmDSy/f7zvCGjnZizZIInPGgAAAC6MPYAAAAAAEAj2AAIIzl9d3s13BABICPMLQFwUQADB6duzs+8IAJAQ5heAuDgEFEBwSssqVFpW4TsGAETG/AIQFwUQQHDe2XtE7+w94jsGAETG/AIQFwUQAAAAAAJBAQQAAACAQFAAAQAAACAQFEAAAAAACASXgQAQnBHZPXxHAICEML8AxEUBBBCcy3pk+I4AAAlhfgGIi0NAAQTn6MkzOnryjO8YABAZ8wtAXBRAAMHZtP+oNu0/6jsGAETG/AIQFwUQAAAAAAJBAQQAAACAQFAAAQAAACAQFEAAAAAACASXgQAQnNH9snxHAICEML8AxEUBBBCcPt06+Y4AAAlhfgGIi0NAAQSn6PhpFR0/7TsGAETG/AIQFwUQQHC2HijR1gMlvmMAQGTMLwBxUQABAAAAIBAUQAAAAAAIBAUQAAAAAAJBAQQAAACAQHAZCADBGde/p+8IAJAQ5heAuCiAAILTs0tH3xEAICHMLwBxcQgogOB8XFquj0vLfccAgMiYXwDiYg8ggOD8sbBUknRZjwzPSQAgGuYXgLjYAwikum0r9NTBu/TcR7dIT46Qtq3wnQgAAABJij2AQCrbtkJ6abb6VJfV3C89IL00u+b2qOn+cgEAACAped0DaGZTzWy3mb1vZnMvsN0XzMyZWU5b5gOS3prHpIqys9cqymrWAQAAgAa8FUAzS5P0tKRbJA2T9CUzG9bIdt0k/aOkt9s2IZACSguirQMAACBoPg8BvVrS+865fEkys2WSbpe0s8F235L0hKQ5bRsPSAE9+tYc9tnYOs7rqoEX+44AAAlhfgGIy+choNmS6v/JtaB2rY6ZfVpSP+fcK20ZDEgZNy5QZdrZZ4KrTMuQblzgKVBq6JGZrh6Z6b5jAEBkzC8AcSXtWUDNrIOkH0j6ZjO2vc/M8swsr6ioqPXDAUkit+o6za34igqqe6vamQqqe2tuxVeUW3Wd72hJreDoKRUcPeU7BgBExvwCEJfPQ0ALJfWrd79v7donukkaIWmdmUnSZZJeNLPbnHN59Z/IObdU0lJJysnJca0ZGkgmi1bvVuGZa7VS1561vmH1bk0bm32en8J7Hx2XJPXt2dlzEgCIhvkFIC6fewDfkTTYzAaaWUdJd0h68ZMHnXOlzrnezrkBzrkBkt6SdE75A0L2YUlZpHUAAACEzVsBdM5VSpolabWkXZJWOOd2mNljZnabr1xAKrkiKzPSOgAAAMLm9ULwzrlXJb3aYK3Rs1c45ya2RSYglcyZMkTzVm1XWUVV3VpmeprmTBniMRUAAACSldcCCCCeT77n99DKbTpTVa3srEzNmTKE7/8BAACgURRAIMVNG5ut5zZ+IElafv94z2lSw/i/6OU7AgAkhPkFIC4KIIDgdOnE6AOQmphfAOJK2usAAkBr2V98UvuLT/qOAQCRMb8AxMVfIwEIzp6DJyRJ/Xt18ZwEAKJhfgGIiz2AAAAAABAICiAAAAAABIICCAAAAACBoAACAAAAQEQzlmzQjCUbfMeIjJPAAAjO9YN7+44AAAlhfgGIiwIIIDgZ6Wm+IwBAQphfAOLiEFAAwckvOqH8ohO+YwBAZMwvAHFRAAEEJ7/opPKLuJAygNTD/AIQFwUQAAAAAAJBAQQAAACAQFAAAQAAACAQFEAAAAAACASXgQAQnIlD+viOAAAJYX4BiIsCCCA4F6Vx8AOA1MT8AhAXUwRAcPYcPK49B4/7jgEAkTG/AMRFAQQQnP3Fp7S/+JTvGAAQGfMLQFwUQAAAAAAIBAUQAAAAAAJBAQQAAACAQFAAAQAAACAQXAYCQHBuGnap7wgAkBDmF4C42AMIAAAAAIGgAAIIzq6PjmnXR8d8xwCAyJhfAOKiAAIITuHRMhUeLfMdAwAiY34BiIsCCAAAAACBoAACAAAAQCAogAAAAAAQCC4DASA4aWnmOwIAJIT5BSAuCiCA4Nww5BLfEQAgIcwvAHFRAIF2YPn9431HAAAAQArgO4AAgvPHwlL9sbDUdwwAiIz5BSCuZhVAM/uX5qwBQCr4uLRcH5eW+44BAJExvwDE1dw9gMPr3zGzNEnjWj4OAAAAAKC1XLAAmtk8MzsuaZSZHav9dVzSIUkvtElCAAAAAECLuGABdM591znXTdIi51z32l/dnHO9nHPz2igjAAAAAKAFNPcQ0I1m1uOTO2aWZWbTWicSALSuTukd1Cmdc2ABSD3MLwBxNfcyEI84557/5I5zrsTMHpGU2yqpAKAVfWZwH98RACAhzC8AcTX3r5Aa245rCAIAAABACmluAcwzsx+Y2V/U/vqBpE1xX9zMpprZbjN738zmNvL4N8xsp5ltM7M1ZtY/7msCwJYDJdpyoMR3DACIjPkFIK7mFsCvSTojabmkZZLKJT0Y54VrLyXxtKRbJA2T9CUzG9Zgs82ScpxzoyStlPQ/47wmAEjS4eOndfj4ad8xACAy5heAuJp1GKdz7qSkuWbWpfZ2S7ha0vvOuXxJMrNlkm6XtLPe666tt/1bkv6uhV4bAAAAAILTrD2AZnatme2UtKv2/mgz+98xXztb0oF69wtq187nXkm/i/maAAAAABCs5h4C+qSkKZKKJck5t1XShNYK1ZCZ/Z2kHEmLzvP4fWaWZ2Z5RUVFbRULAAAAAFJKsy8k45w70GCpKuZrF0rqV+9+39q1s5jZTZIelnSbc67Rg96dc0udcznOuZw+fTg9MoAL69wxTZ07pvmOAUDSjCUbNGPJBt8xUgbzC0Bczb2UwwEzu1aSM7N0Sf+o2sNBY3hH0mAzG6ia4neHpDvrb2BmYyUtkTTVOXco5usBgCTp2r/s7TsCACSE+QUgrubuAXxANWf9zFZNWRujmGcBdc5VSpolabVqyuQK59wOM3vMzG6r3WyRpK6SfmNmW8zsxTivCQAAAAAhu+AeQDN7wjn33yXd4Jz725Z+cefcq5JebbC2oN7tm1r6NQFg0/4jkqRx/S/2nAQAomF+AYirqT2A/9nMTNK8tggDAG3h6MkKHT1Z4TsGAETG/AIQV1PfAfw/ko5K6mpmxySZJPfJ78657q2cDwAAAADQQpraAzjfOZcl6RXnXHfnXLf6v7dBPgAAAABAC2mqAH5yXuZjrR0EAAAAANC6mjoEtKOZ3SnpWjP7fMMHnXOrWicWALSebhnNvQIOACQX5heAuJqaIg9I+ltJWZI+1+AxJ4kCCCDl/KdBvXxHAICEML8AxHXBAuicWy9pvZnlOed+3kaZAAAAAACt4ILfATSzhyTJOfdzM/tig8e+05rBAKC1vJ1frLfzi33HAIDImF8A4mrqJDB31Lvd8FqAU1s4CwC0iePllTpeXuk7BgBExvwCEFdTBdDOc7ux+wAAAACAJNZUAXTnud3YfQAAAABAEmvqLKCjzeyYavb2ZdbeVu39jFZNBgAAAABoUU2dBTStrYIAQFvp2SXddwQASAjzC0BcXE0UQHDG9b/YdwQASAjzC0BcTX0HEAAAAABQT+7mQm3+oERv7z2i6x5/Q7mbC31HajYKIIDg/OH9w/rD+4d9xwCAyJhfgH+5mws1b9V2namqliQVlpRp3qrtKVMCKYAAgnPqTJVOnanyHQMAImN+Af4tWr1bZRVn/3dYVlGlRat3e0oUDQUQAAAAAJrpw5KySOvJhgIIAAAAAM10RVZmpPVkQwEEAAAAgGaaM2WIMtPPvlpeZnqa5kwZ4ilRNFwGAkBwenfr5DsCACSE+QX4N21stiTpoZXbdKaqWtlZmZozZUjderKjAAIIzph+Wb4jAEBCmF9Acpg2NlvPbfxAkrT8/vGe00TDIaAAAAAAEAgKIIDg/H5PkX6/p8h3DACIjPkFIC4OAQUQnNMV1b4jpJQZSzZISr1DXID2iPkFIC72AAIAAABAICiAAAAAABAICiAAAAAABILvAAIIzmU9MnxHAICEML8AxEUBBBCcEdk9fEcAgIQwvwDExSGgAAAAABAICiCA4KzdfUhrdx/yHQMAImN+AYiLQ0ABBKeqyvmOAAAJYX4BiIs9gAAAAAAQCAogAAAAAASCAggAAAAAgeA7gACCk90z03cEAEgI8wtAXBRAAMEZenl33xEAICHMLwBxcQgoAAAAAASCAgggOK/vPKjXdx70HQMAImN+RTNjyQbNWLLBdwwgqVAAAQAAACAQFEAAAAAACAQFEAAAAAAC4bUAmtlUM9ttZu+b2dxGHu9kZstrH3/bzAZ4iAkAAAAA7YK3AmhmaZKelnSLpGGSvmRmwxpsdq+ko865v5T0pKQn2jYlgPaof6/O6t+rs+8YqWHbCj118C4999Et0pMjpG0rfCdCe8LnKzLmF4C4fF4H8GpJ7zvn8iXJzJZJul3Sznrb3C5pYe3tlZKeMjNzzrm2DAqgfRl8aTffEVLDthXSS7PVp7qs5n7pAeml2TW3R033lwvtA5+vhDC/AMTl8xDQbEkH6t0vqF1rdBvnXKWkUkm92iQdgHarsqpalVXVvmMkvzWPSRVlZ69VlNWsA3Hx+UoI8wtAXO3iJDBmdp+Z5ZlZXlFRke84AJLcut1FWrebWdGk0oJo60AUfL4SwvwCEJfPAlgoqV+9+31r1xrdxswuktRDUnHDJ3LOLXXO5Tjncvr06dNKcQEgMD36RlsHouDzBQBe+CyA70gabGYDzayjpDskvdhgmxclzay9/V8kvcH3/wCgjdy4QErPPHstPbNmHYiLzxcAeOGtANZ+p2+WpNWSdkla4ZzbYWaPmdlttZv9XFIvM3tf0jcknXOpCABAKxk1XfrcYhV1uETVMqlHP+lzizlBB1oGny8A8MLnWUDlnHtV0qsN1hbUu10u6YttnQsAUCO36jo9dHqxzlRVKzsjU3Oqhmia71BoP0ZN16wNNd8GWX7/eM9hACAMXgsgAPgwqE8X3xFSQu7mQs1btV1nas84WFhSpnmrtkuSpo1teNJmAG2B+QUgrnZxFlAAiGJQn64a1Ker7xhJb9Hq3SqrqDprrayiSotW7/aUCADzC0BcFEAAwSmvqFJ5g2KDc31YUhZpHUDrY34BSWLbCj118C4999Et0pMjpG0rfCdqNgoggOCs33NY6/cc9h0j6V2RlRlpHYgqd3OhNn9Qorf3HtF1j7+h3M0NrwaFhphfQBLYtkJ6abb6VB9SBzmp9ID00uyUKYEUQABAo+ZMGaLM9LSz1jLT0zRnyhBPidCenO87ppRAAElvzWNSRYOjYSrKatZTAAUQANCoaWOz9d3Pj1THtJr/VWRnZeq7nx/JCWDQIviOKVpdCh+ihyRXWhBtPclwFlAAwHlNG5ut5zZ+IInT9KNl8R1TtKq6Q/RqP0+fHKInca1JxNejb81nqrH1FMAeQAAA0Ob4jilaVYofoockd+MCKb3BrErPrFlPARRAAMEZfGlXDb6U06gDPvEd08Qwv5opxQ/RQ5IbNV363GIVdbhE1TKpRz/pc4tTZu8yh4ACCE7/XlxIGfDtk++SPrRym85UVSs7K1NzpgzhO6ZNYH41U4ofoocUMGq6Zm3oJyn1viLBHkAAwTl5ulInT1f6jgEEb9rYbI39VJb+08CL9e9zJ1H+moH51Uwpfoge0JoogACCs+HPxdrw52LfMQAgMuZXM6X4IXpAa+IQUAAAALQ/KXyIHtCa2AMIAAAAAIGgAAIAAKDdyd1cqM0flOjtvUd03eNvKHdzoe9IQFKgAAIAAKBdyd1cqHmrtutMVbUkqbCkTPNWbacEAqIAAgjQX13eTX91eTffMQAgMuZX8yxavVtlFVVnrZVVVGnR6t2eEgHJg5PAAAhO356dfUcAgIQwv5rnw5KySOtASNgDCCA4pWUVKi2r8B0DACJjfjXPFVmZkdaBkFAAAQTnnb1H9M7eI75jAEBkzK/mmTNliDLT085ay0xP05wpQzwlApIHh4ACAACgXZk2NluS9NDKbTpTVa3srEzNmTKkbh0IGQUQAAAA7c60sdl6buMHkrgQPFAfh4ACAAAAQCAogAAAAAAQCA4BBRCcEdk9fEcAgIQwvwDERQEEEJzLemT4jgAACWF+AYiLAgggOEdPnpEk9ezS0XMSAIiG+QUkj1Q9uRDfAQQQnE37j2rT/qO+YwBAZMwvAHFRAAEAAAAgEBRAAAAAAAgEBRAAAAAAAkEBBAAAAIBAcBZQAMEZ3S/LdwQASAjzC0BcFEAAwenTrZPvCCklVU9zDbRHzC8AcXEIKIDgFB0/raLjp33HAIDImF8A4qIAAgjO1gMl2nqgxHcMAIiM+QUgLgogAAAAAASCAggAAAAAgaAAAgAAAEAgKIAAAAAAEAguAwEgOOP69/QdAQASwvwCEBcFEEBwenbp6DsCACSE+QUgLg4BBRCcj0vL9XFpue8YABAZ8wtAXOwBBBCcPxaWSpIu65HhOQkARMP8AhCXlz2AZnaxmb1mZntqfz/ngHYzG2NmG8xsh5ltM7MZPrICAAAAQHvh6xDQuZLWOOcGS1pTe7+hU5Lucs4NlzRV0g/NLKvtIgIAAABA++KrAN4u6Ze1t38paVrDDZxzf3LO7am9/aGkQ5L6tFVAAAAAAGhvfBXAS51zH9Xe/ljSpRfa2MyultRR0p/P8/h9ZpZnZnlFRUUtmxQAAAAA2olWOwmMmb0u6bJGHnq4/h3nnDMzd4HnuVzSv0ia6Zyrbmwb59xSSUslKScn57zPBQCSdNXAi31HAICEML+iWX7/eN8RgKTTagXQOXfT+R4zs4Nmdrlz7qPagnfoPNt1l/SKpIedc2+1UlQAgemRme47AgAkhPkFIC5fh4C+KGlm7e2Zkl5ouIGZdZT0vKRfOedWtmE2AO1cwdFTKjh6yncMAIiM+QUgLl8F8HFJk81sj6Sbau/LzHLM7Ge120yXNEHSl81sS+2vMV7SAmhX3vvouN776LjvGAAQGfMLQFxeLgTvnCuWdGMj63mSvlJ7+18l/WsbRwMAAACAdsvXHkAAAAAAQBujAAIAAABAICiAAAAAABAIL98BBACfxv9FL98RACAhzC8AcVEAAQSnSydGH4DUxPwCEBeHgAIIzv7ik9pffNJ3DACIjPkFIC7+GglAcPYcPCFJ6t+ri+ckABAN8wtAXOwBBAAAAIBAUAABAAAAIBAUQAAAAAAIBAUQAAAAAALBSWAABOf6wb19RwCAhDC/AMRFAQQQnIz0NN8RACAhzC8AcXEIKIDg5BedUH7RCd8xACAy5heAuNgDCCA4+UU1F1Ee1Ker5yQAlt8/3neElML8AhAXewABAAAAIBAUQAAAAAAIBAUQAAAAAAJBAQQAAACAQHASGADBmTikj+8IAJAQ5heAuCiAAIJzURoHPwBITcwvAHExRQAEZ8/B49pz8LjvGAAQGfMLQFwUQADB2V98SvuLT/mOAQCRMb8AxEUBBAAAAIBAUAABAAAAIBAUQAAAAAAIBAUQAAAAAAJhzjnfGVqUmRVJ2u87RyN6SzrsO0SK4T2LhvcLrYnPVzS8X9HwfqE18fmKhvcrmmR9v/o75xq9cGi7K4DJyszynHM5vnOkEt6zaHi/0Jr4fEXD+xUN7xdaE5+vaHi/oknF94tDQAEAAAAgEBRAAAAAAAgEBbDtLPUdIAXxnkXD+4XWxOcrGt6vaHi/0Jr4fEXD+xVNyr1ffAcQAAAAAALBHkAAAAAACAQFsJWZ2TNmdsjM/ug7CwBExQwDkKqYX0DjKICt71lJU32HAIAEPStmGIDU9KyYX8A5KICtzDn3/yQd8Z0D7YOZPWBmP653/9tm9i8+M6F9Y4ahpTC/0NaYX2hJ7WmGUQCB1PIrSbeaWZaZ3Srps5Lu85wJAJqD+QUglbWbGXaR7wAAms85d8rMnpP0z5JukTTZOVfmORYANIn5BSCVtacZRgEEUs8zknZJut0592ffYQAgAuYXgFTWLmYYBRBIPQskFanef79mNkzSQknFktZI2ln/vnNuZZunBIBzMb8ApLJ2McP4DmArq91VvEHSEDMrMLN7fWdC6jKzb0rKkDRd0j/We+gWST9yzv1XSXc1ch9ICDMMLYX5hbbG/EJLak8zzJxzvjMAaAYzmyRpsaTxzrnjZrZZ0t3OuS1mdomkRySdknStpL+pf985d52v3ADA/AKQytrbDKMAAinAzD4laZ2kzzrndtWufVnSROfcl+ttlyZplXPu9sbuA0BbY34BSGXtcYZRAIF2wMwGSPofkrpI+rGkgvr3nXPr/aUDgPNjfgFIZak4wyiAAAAAABAITgIDAAAAAIGgAAIAAABAICiAAAAAABAICiAAAAAABIICCAAAAACBoAACAAAAQCAogAAAAAAQCAogAAAAAASCAggAAAAAgaAAAgAAAEAgKIAAAAAAEAgKIAAAAAAEggIIAAAAAIGgAAIAAABAICiAAAAAABAICiAAAAAABIICCAAAAACBoAACAAAAQCAogAAAAAAQCAogAAAAAASCAggAAAAAgaAAAgAAAEAgKIAAAAAAEAgKIAAAAAAEggIIAAAAAIGgACJpmFmamf0vM9thZtvNbJDvTADQHMwvAKmK+RUeCiCSyTxJ+c654ZIWS/qq5zwA0FzMLwCpivkVmIt8BwAkycy6SPob59y42qW9kj7rMRIANAvzC0CqYn6FiQKIZHGTpH5mtqX2/sWSXvcXBwCajfkFIFUxvwLEIaBIFmMkLXDOjXHOjZH0fyVtMbMuZvZLM/upmf2t14QA0Lgxanx+DTKzn5vZSq/pAOD8xqjx+TWt9s9ey83sZq8J0eIogEgWPSWdkiQzu0jSzZJekvR5SSudc/8g6TZ/8QDgvBqdX865fOfcvV6TAcCFnW9+5db+2esBSTM85kMroAAiWfxJ0jW1t78u6RXn3F5JfSUdqF2v8hEMAJpwvvkFAMmuqfk1X9LTbZ4KrYoCiGTxnKRPm9n7kkZJ+kbteoFqSqDE5xVAcjrf/AKAZNfo/LIaT0j6nXPuXZ8B0fLMOec7A3BetWenekpSuaT1zrlfe44EAM1iZr0k/bOkyZJ+5pz7rudIANAsZjZb0kxJ70ja4pz7iedIaEEUQAAAAAAIBIfUAQAAAEAgKIAAAAAAEAgKIAAAAAAEggIIAAAAAIGgAAIAAABAICiAAAAAABAICiAAAAAABIICCAAAAACBoAACAAAAQCD+P/AalFhf9y9WAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ "
" ] From 4210d1db2d36df3dea4bfb97522622c299585577 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 5 Aug 2021 20:33:07 -0400 Subject: [PATCH 16/27] Add sample code to README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index f841b040a..cbc77cfdd 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ For information on use cases and background material on causal inference and het - [Interpretability](#interpretability) - [Causal Model Selection and Cross-Validation](#causal-model-selection-and-cross-validation) - [Inference](#inference) + - [Policy Learning](#policy-learning) - [For Developers](#for-developers) - [Running the tests](#running-the-tests) - [Generating the documentation](#generating-the-documentation) @@ -158,6 +159,25 @@ To install from source, see [For Developers](#for-developers) section below. +
+ Dynamic Double Machine Learning (click to expand) + + ```Python + from econml.dml import DynamicDML + # Use defaults + est = DynamicDML() + # Or specify hyperparameters + est = DynamicDML(model_y=LassoCV(cv=3), + model_t=LassoCV(cv=3), + cv=3) + est.fit(Y, T, X=X, W=None, groups=groups, inference="auto") + # Effects + treatment_effects = est.effect(X_test) + # Confidence intervals + lb, ub = est.effect_interval(X_test, alpha=0.05) + ``` +
+
Causal Forests (click to expand) From 4cc11560448993ba70c4f493cc24ab7b7066f483 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Thu, 5 Aug 2021 23:13:30 -0400 Subject: [PATCH 17/27] Adjust heterogeneity to depend only on features from the first period --- econml/dml/dynamic_dml.py | 60 +++++++++---------- econml/tests/dgp.py | 6 +- econml/tests/test_dynamic_dml.py | 20 +++---- ...mic Double Machine Learning Examples.ipynb | 58 +++++++++--------- 4 files changed, 74 insertions(+), 70 deletions(-) diff --git a/econml/dml/dynamic_dml.py b/econml/dml/dynamic_dml.py index ce43834d0..5ecd69ef2 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dml/dynamic_dml.py @@ -165,11 +165,11 @@ def fit(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None, if t < self.n_periods - 1: Y_adj -= np.sum( [self._model_final_trained[j].predict_with_res( - X[period_filters[j]] if X is not None else None, + X[period_filters[0]] if X is not None else None, T_res[period_filters[j], ..., t] ) for j in np.arange(t + 1, self.n_periods)], axis=0) self._model_final_trained[t].fit( - X[period_filters[t]] if X is not None else None, T[period_filters[t]], + X[period_filters[0]] if X is not None else None, T[period_filters[t]], T_res[period_filters[t], ..., t], Y_adj) return self @@ -204,11 +204,11 @@ def score(self, Y, T, X=None, W=None, Z=None, nuisances=None, sample_weight=None if t < self.n_periods - 1: Y_adj -= np.sum( [self._model_final_trained[j].predict_with_res( - X[period_filters[j]] if X is not None else None, + X[period_filters[0]] if X is not None else None, T_res[period_filters[j], ..., t] ) for j in np.arange(t + 1, self.n_periods)], axis=0) Y_adj_pred = self._model_final_trained[t].predict_with_res( - X[period_filters[t]] if X is not None else None, + X[period_filters[0]] if X is not None else None, T_res[period_filters[t], ..., t]) if sample_weight is not None: scores[t] = np.mean(np.average((Y_adj - Y_adj_pred)**2, weights=sample_weight, axis=0)) @@ -266,7 +266,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index, groups): XT_res = np.array([ [ self._model_final_trained[0]._combine( - X[period_filters[j]] if X is not None else None, + X[period_filters[0]] if X is not None else None, T_res[period_filters[t], ..., j], fitting=False ) @@ -279,7 +279,7 @@ def _fit_single_output_cov(self, nuisances, X, y_index, groups): Y_diff = np.array([ np.sum([ self._model_final_trained[j].predict_with_res( - X[period_filters[j]] if X is not None else None, + X[period_filters[0]] if X is not None else None, T_res[period_filters[j], ..., t] ) for j in np.arange(t, self.n_periods)], axis=0 @@ -423,34 +423,34 @@ class DynamicDML(LinearModelFinalCateEstimatorMixin, _OrthoLearner): est.fit(y, T, X=X, W=None, groups=groups, inference="auto") >>> est.const_marginal_effect(X[:2]) - array([[-0.349..., -0.076..., 0.069..., 0.111..., -0.012..., - 0.031...], - [-0.126... , 0.397..., 0.021..., -0.171..., -0.411..., - -0.088...]]) + array([[-0.336..., -0.048..., -0.061..., 0.042..., -0.204..., + 0.00667271], + [-0.101..., 0.433..., 0.054..., -0.217..., -0.101..., + -0.159...]]) >>> est.effect(X[:2], T0=0, T1=1) - array([-0.225..., -0.378...]) + array([-0.601..., -0.091...]) >>> est.effect(X[:2], T0=np.zeros((2, n_periods*T.shape[1])), T1=np.ones((2, n_periods*T.shape[1]))) - array([-0.225..., -0.378...]) + array([-0.601..., -0.091...]) >>> est.coef_ - array([[ 0.107...], - [ 0.227...], - [-0.023...], - [-0.136...], - [-0.191...], - [-0.057...]]) + array([[ 0.112...], + [ 0.231...], + [ 0.055...], + [-0.125...], + [ 0.049...], + [-0.079...]]) >>> est.coef__interval() - (array([[-0.051...], - [ 0.040...], - [-0.154...], - [-0.336...], - [-0.333...], - [-0.171...]]), - array([[ 0.265...], - [ 0.415...], - [ 0.108...], - [ 0.064...], - [-0.050...], - [ 0.056...]])) + (array([[-0.035...], + [ 0.029...], + [-0.087... ], + [-0.366... ], + [-0.090...], + [-0.233...]]), + array([[0.260...], + [0.433... ], + [0.198...], + [0.116...], + [0.189...], + [0.074...]])) """ def __init__(self, *, diff --git a/econml/tests/dgp.py b/econml/tests/dgp.py index 333a326f2..e4057b44a 100644 --- a/econml/tests/dgp.py +++ b/econml/tests/dgp.py @@ -130,13 +130,17 @@ def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): period = t % self.n_periods if period == 0: X[t] = np.random.normal(0, self.sigma_x, size=self.n_x) + const_x0 = X[t][self.hetero_inds] T[t] = policy_gen(np.zeros(self.n_treatments), X[t], period) else: X[t] = (np.dot(self.x_hetero_effect, X[t - 1]) + 1) * np.dot(self.Alpha, T[t - 1]) + \ np.dot(self.Beta, X[t - 1]) + \ np.random.normal(0, self.sigma_x, size=self.n_x) + # The feature for heterogeneity stays constant + X_t = X[t].copy() + X_t[self.hetero_inds] = const_x0 T[t] = policy_gen(T[t - 1], X[t], period) - Y[t] = (np.dot(self.y_hetero_effect, X[t]) + 1) * np.dot(self.epsilon, T[t]) + \ + Y[t] = (np.dot(self.y_hetero_effect, X_t if period != 0 else X[t]) + 1) * np.dot(self.epsilon, T[t]) + \ np.dot(X[t], self.zeta) + \ np.random.normal(0, self.sigma_y) groups[t] = t // self.n_periods diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index 7f26743ce..380c4a777 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -16,6 +16,7 @@ from econml.tests.dgp import DynamicPanelDGP +@pytest.mark.dml class TestDynamicDML(unittest.TestCase): def test_cate_api(self): @@ -253,7 +254,7 @@ def make_random(n, is_discrete, d): def test_perf(self): np.random.seed(123) - n_units = 400 + n_units = 1000 n_periods = 3 n_treatments = 1 n_x = 100 @@ -261,15 +262,14 @@ def test_perf(self): s_t = 10 hetero_strength = .5 hetero_inds = np.arange(n_x - n_treatments, n_x) - alpha_regs = [1e-4, 1e-3, 1e-2, 5e-2, .1, 1] def lasso_model(): - return LassoCV(cv=3, alphas=alpha_regs, max_iter=500) + return LassoCV(cv=3) # No heterogeneity dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( - s_x, random_seed=1) - Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12) + s_x, random_seed=12345) + Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12345) est = DynamicDML(model_y=lasso_model(), model_t=lasso_model(), cv=3) # Define indices to test groups_filter = _get_groups_period_filter(groups, 3) @@ -278,17 +278,17 @@ def lasso_model(): for test_idx in test_indices: est.fit(Y[test_idx], T[test_idx], X=X[test_idx] if X is not None else None, W=W[test_idx], groups=groups[test_idx], inference="auto") - np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=1e-01) + np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=0.2) np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) # Heterogeneous effects dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( - s_x, hetero_strength=hetero_strength, hetero_inds=hetero_inds, random_seed=1) - Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=12) + s_x, hetero_strength=hetero_strength, hetero_inds=hetero_inds, random_seed=12) + Y, T, X, W, groups = dgp.observational_data(n_units, s_t=s_t, random_seed=1) + hetero_strength = .5 + hetero_inds = np.arange(n_x - n_treatments, n_x) for test_idx in test_indices: - hetero_strength = .5 - hetero_inds = np.arange(n_x - n_treatments, n_x) est.fit(Y[test_idx], T[test_idx], X=X[test_idx], W=W[test_idx], groups=groups[test_idx], inference="auto") np.testing.assert_allclose(est.intercept_, dgp.true_effect.flatten(), atol=0.2) np.testing.assert_allclose(est.coef_, dgp.true_hetero_effect[:, hetero_inds + 1], atol=0.2) diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb index e07d966f5..ecfd78ee1 100644 --- a/notebooks/Dynamic Double Machine Learning Examples.ipynb +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -110,7 +110,7 @@ "\n", "We consider a data generating process from a markovian treatment model. \n", "\n", - "In the example bellow, $T_t\\rightarrow$ treatment(s) at time $t$, $Y_t\\rightarrow$outcome at time $t$, $X_t\\rightarrow$ features and controls at time $t$ (the coefficients $e, f'$ will pick the features and the controls).\n", + "In the example bellow, $T_t\\rightarrow$ treatment(s) at time $t$, $Y_t\\rightarrow$outcome at time $t$, $X_t\\rightarrow$ features and controls at time $t$ (the coefficients $e, f$ will pick the features and the controls).\n", "\\begin{align}\n", " X_t =& (\\pi'X_{t-1} + 1) \\cdot A\\, T_{t-1} + B X_{t-1} + \\epsilon_t\\\\\n", " T_t =& \\gamma\\, T_{t-1} + (1-\\gamma) \\cdot D X_t + \\zeta_t\\\\\n", @@ -185,7 +185,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 6, @@ -437,7 +437,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 16, @@ -460,16 +460,16 @@ "\n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
Coefficient Results
point_estimate stderr zstat pvalue ci_lower ci_upper point_estimate stderr zstat pvalue ci_lower ci_upper
X0|(T0)$_0$ 0.115 0.119 0.968 0.333 -0.081 0.311X0|(T0)$_0$ 0.112 0.119 0.947 0.344 -0.083 0.308
X0|(T0)$_1$ -0.041 0.12 -0.339 0.735 -0.239 0.157X0|(T0)$_1$ 0.092 0.128 0.719 0.472 -0.119 0.303
X0|(T0)$_2$ 0.392 0.073 5.366 0.0 0.272 0.512X0|(T0)$_2$ 0.443 0.076 5.85 0.0 0.318 0.567
\n", "\n", @@ -478,33 +478,33 @@ " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|(T0)$_0$ -0.085 0.06 -1.411 0.158 -0.184 0.014cate_intercept|(T0)$_0$ -0.082 0.061 -1.347 0.178 -0.182 0.018
cate_intercept|(T0)$_1$ -0.027 0.059 -0.462 0.644 -0.125 0.07cate_intercept|(T0)$_1$ -0.035 0.059 -0.591 0.554 -0.133 0.063
cate_intercept|(T0)$_2$ 0.602 0.034 17.813 0.0 0.547 0.658cate_intercept|(T0)$_2$ 0.592 0.035 17.006 0.0 0.535 0.65


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], "text/plain": [ "\n", "\"\"\"\n", - " Coefficient Results \n", - "=================================================================\n", - " point_estimate stderr zstat pvalue ci_lower ci_upper\n", - "-----------------------------------------------------------------\n", - "X0|(T0)$_0$ 0.115 0.119 0.968 0.333 -0.081 0.311\n", - "X0|(T0)$_1$ -0.041 0.12 -0.339 0.735 -0.239 0.157\n", - "X0|(T0)$_2$ 0.392 0.073 5.366 0.0 0.272 0.512\n", + " Coefficient Results \n", + "================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "----------------------------------------------------------------\n", + "X0|(T0)$_0$ 0.112 0.119 0.947 0.344 -0.083 0.308\n", + "X0|(T0)$_1$ 0.092 0.128 0.719 0.472 -0.119 0.303\n", + "X0|(T0)$_2$ 0.443 0.076 5.85 0.0 0.318 0.567\n", " CATE Intercept Results \n", "=============================================================================\n", " point_estimate stderr zstat pvalue ci_lower ci_upper\n", "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ -0.085 0.06 -1.411 0.158 -0.184 0.014\n", - "cate_intercept|(T0)$_1$ -0.027 0.059 -0.462 0.644 -0.125 0.07\n", - "cate_intercept|(T0)$_2$ 0.602 0.034 17.813 0.0 0.547 0.658\n", + "cate_intercept|(T0)$_0$ -0.082 0.061 -1.347 0.178 -0.182 0.018\n", + "cate_intercept|(T0)$_1$ -0.035 0.059 -0.591 0.554 -0.133 0.063\n", + "cate_intercept|(T0)$_2$ 0.592 0.035 17.006 0.0 0.535 0.65\n", "-----------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", @@ -533,13 +533,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Average effect of default policy:0.45\n" + "Average effect of default policy:0.42\n" ] } ], "source": [ "# Average treatment effect for test points\n", - "X_test = X[:10]\n", + "X_test = X[np.arange(0, 25, 3)]\n", "print(f\"Average effect of default policy:{est.ate(X=X_test):0.2f}\")" ] }, @@ -553,8 +553,8 @@ "output_type": "stream", "text": [ "Effect of target policy over baseline policy for test set:\n", - " [2.08972181 2.23249102 1.22483907 1.72010749 1.73030869 2.56658993\n", - " 0.27482914 1.10741935 2.00298489 0.75833676]\n" + " [ 2.1924814 1.69559976 -0.24732358 0.40266764 2.33464274 0.81404248\n", + " 2.25540586 1.52509443 2.44106892]\n" ] } ], @@ -575,10 +575,10 @@ { "data": { "text/plain": [ - "(array([-0.08491363, -0.02731669, 0.60246595]),\n", - " array([[ 0.11529833],\n", - " [-0.04079943],\n", - " [ 0.39168147]]))" + "(array([-0.08178444, -0.03511038, 0.59245168]),\n", + " array([[0.11235101],\n", + " [0.09227667],\n", + " [0.44267996]]))" ] }, "execution_count": 20, @@ -616,7 +616,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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3n7lxmJ9a/kWnaw5pbzcbM2ZMkTTxualMKH03nvvaeLEiXrrrbfUoUMY+7qa+vdiZhuccwVNbc85gACCwx+cAKQr5hfSybSxeXpq/buSWqeE/+pXv9K9996rH/7wh8GUv2RQAAEAAACkRGvufb3tttt02223tdr7pSuqMYDgvLL9gF7ZfsB3DACIjPkFIC4KIAAAAAAEggIIAAAAAIGgAAIAAABAICiAAAAAANLCgQMHdOutt2rw4MEaN26cxo8fr6effrpVM+zdu1cjRoxocv0///M/k/qZP/rRj3T8+PH65926dUs6X3MogACCM6BXFw3o1cV3DACIjPmFkDnnNG3aNE2YMEG7d+/Whg0btGTJEhUVFZ2xbXV1davnO1cBbC5P4wKYSl5vA2FmUyX9i6QMST93zj3YxDbTJS2U5CRtds7d2qohAbQ7F5/f3XcEAEgK8wtpZcsyafUDUnmRlJMvXb1AGjU96R/36quvqlOnTrr77rvr1wYMGKCvfvWrkqQnn3xSK1as0NGjR1VTU6Onn35ad9xxh3bv3q0uXbpo8eLFGjVqlBYuXKhu3brpm9/8piRpxIgRev755yVJ1113na688kr94Q9/UF5enp555hllZ2drw4YNuuOOOyRJ1157bZP55s6dqx07dmjMmDGaMWOGevbseVqe+++/X9///vfr32vWrFkqKCjQRx99pPfee09XXXWVevfurTVr1kiS7r33Xj3//PPKzs7WM888o/PPPz/pz64hb3sAzSxD0mOSrpM0TNIXzWxYo20uljRP0hXOueGS/rG1cwJof6prTqm65pTvGAAQGfMLaWPLMum52VL5fkmu9p/Pza5dT9K2bdv0yU9+8pzbvPnmm1q+fLl+97vf6b777tPYsWO1ZcsWffe7303oHoG7du3SPffco23btik3N1e//e1vJUm33367fvzjH2vz5s1n/b0PPvigPv3pT2vTpk362te+dkaes5k9e7b69eunNWvW1Je/Y8eO6fLLL9fmzZs1YcIE/exnP2s2e6J8HgJ6maS3nXO7nXMnJS2RdFOjbf5e0mPOucOS5Jw72MoZAbRDa3eWaO3OEt8xACAy5lcEW5bp0QO36an3r5MeGRGreCAJqx+QqipOX6uqqF1vIffcc49Gjx6tSy+9tH7tmmuu0XnnnSdJeu211/R3f/d3kqRJkyaptLRU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\n", "text/plain": [ "
" ] From e74067e27d49467ca6dbe54eac237ca3d224e989 Mon Sep 17 00:00:00 2001 From: Vasilis Syrgkanis Date: Fri, 6 Aug 2021 12:34:53 -0400 Subject: [PATCH 18/27] moved dynamic_dml to separate module. fixed remaining bugs in dgp. fixed docstring for X. fixed notebook. changed documentation. --- README.md | 2 +- doc/reference.rst | 16 +- doc/spec/estimation/dml.rst | 34 +- doc/spec/estimation/dynamic_dml.rst | 94 +++++ doc/spec/estimation_dynamic.rst | 11 + doc/spec/references.rst | 7 +- doc/spec/spec.rst | 1 + econml/dml/__init__.py | 10 +- econml/dynamic/__init__.py | 4 + econml/dynamic/dml/__init__.py | 20 + .../dynamic_dml.py => dynamic/dml/_dml.py} | 36 +- econml/tests/dgp.py | 59 ++- econml/tests/test_dynamic_dml.py | 4 +- ...mic Double Machine Learning Examples.ipynb | 345 +++++++++++------- 14 files changed, 455 insertions(+), 188 deletions(-) create mode 100644 doc/spec/estimation/dynamic_dml.rst create mode 100644 doc/spec/estimation_dynamic.rst create mode 100755 econml/dynamic/__init__.py create mode 100755 econml/dynamic/dml/__init__.py rename econml/{dml/dynamic_dml.py => dynamic/dml/_dml.py} (96%) mode change 100644 => 100755 notebooks/Dynamic Double Machine Learning Examples.ipynb diff --git a/README.md b/README.md index b6d58ec16..efd5dccf3 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ To install from source, see [For Developers](#for-developers) section below. Dynamic Double Machine Learning (click to expand) ```Python - from econml.dml import DynamicDML + from econml.dynamic.dml import DynamicDML # Use defaults est = DynamicDML() # Or specify hyperparameters diff --git a/doc/reference.rst b/doc/reference.rst index 865cc52a4..76b528925 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -17,7 +17,6 @@ Double Machine Learning (DML) econml.dml.SparseLinearDML econml.dml.CausalForestDML econml.dml.NonParamDML - econml.dml.DynamicDML .. _dr_api: @@ -105,6 +104,21 @@ Sieve Methods econml.iv.sieve.HermiteFeatures econml.iv.sieve.DPolynomialFeatures +.. _dynamic_api: + +Estimators for Dynamic Treatment Regimes +---------------------------------------- + +.. _dynamicdml_api: + +Dynamic Double Machine Learning +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + :toctree: _autosummary + + econml.dynamic.dml.DynamicDML + .. _policy_api: Policy Learning diff --git a/doc/spec/estimation/dml.rst b/doc/spec/estimation/dml.rst index 9fe1deb57..0ffe0718a 100644 --- a/doc/spec/estimation/dml.rst +++ b/doc/spec/estimation/dml.rst @@ -34,8 +34,7 @@ What are the relevant estimator classes? This section describes the methodology implemented in the classes, :class:`._RLearner`, :class:`.DML`, :class:`.LinearDML`, :class:`.SparseLinearDML`, :class:`.KernelDML`, :class:`.NonParamDML`, -:class:`.CausalForestDML`, -:class:`.DynamicDML`. +:class:`.CausalForestDML`. Click on each of these links for a detailed module documentation and input parameters of each class. @@ -60,12 +59,6 @@ characteristics :math:`X` of the treated samples, then one can use this method. t = t0 = t1 = T[:,0] W = np.random.normal(size=(100,2)) - # DynamicDML - groups = np.repeat(a=np.arange(100), repeats=3, axis=0) - X_dyn = np.random.normal(size=(300, 1)) - T_dyn = np.random.normal(size=(300, 2)) - y_dyn = np.random.normal(size=(300, )) - .. testcode:: from econml.dml import LinearDML @@ -81,7 +74,6 @@ linear on some pre-defined; potentially high-dimensional; featurization). These :class:`.SparseLinearDML`, :class:`.KernelDML`. For fullly non-parametric heterogeneous treatment effect models, check out the :class:`.NonParamDML` and the :class:`.CausalForestDML`. -For treatments assigned sequentially over several time periods, see the class :class:`.DynamicDML`. For more options of non-parametric CATE estimators, check out the :ref:`Forest Estimators User Guide ` and the :ref:`Meta Learners User Guide `. @@ -165,7 +157,7 @@ Class Hierarchy Structure In this library we implement variants of several of the approaches mentioned in the last section. The hierarchy structure of the implemented CATE estimators is as follows. - .. inheritance-diagram:: econml.dml.LinearDML econml.dml.SparseLinearDML econml.dml.KernelDML econml.dml.NonParamDML econml.dml.CausalForestDML econml.dml.DynamicDML + .. inheritance-diagram:: econml.dml.LinearDML econml.dml.SparseLinearDML econml.dml.KernelDML econml.dml.NonParamDML econml.dml.CausalForestDML :parts: 1 :private-bases: :top-classes: econml._rlearner._RLearner, econml._cate_estimator.StatsModelsCateEstimatorMixin, econml._cate_estimator.DebiasedLassoCateEstimatorMixin @@ -296,28 +288,6 @@ Below we give a brief description of each of these classes: Check out :ref:`Forest Estimators User Guide ` for more information on forest based CATE models and other alternatives to the :class:`.CausalForestDML`. - * **DynamicDML.** The class :class:`.DynamicDML` is an extension of the Double ML approach for treatments assigned sequentially over time periods. - This estimator will adjust for treatments that can have causal effects on future outcomes. The data corresponds to a Markov decision process :math:`\{X_t, W_t, T_t, Y_t\}_{t=1}^m`, - where :math:`X_t, W_t` corresponds to the state at time :math:`t`, :math:`T_t` is the treatment at time :math:`t` and :math:`Y_t` is the observed outcome at time :math:`t`. - - The model makes the following structural equation assumptions on the data generating process: - - .. math:: - - X_t =~& A \cdot T_{t-1} + B \cdot X_{t-1} + \eta_t\\ - T_t =~& p(T_{t-1}, X_t, \zeta_t) \\ - Y_t =~& \theta_0'T_t + \mu'X_t \epsilon_t - - For more details about this model and underlying assumptions, see [Lewis2021]_. - - To learn the treatment effects of treatments in the different periods on the last period outcome, one can simply call: - - .. testcode:: - - from econml.dml import DynamicDML - est = DynamicDML() - est.fit(y_dyn, T_dyn, X=X_dyn, W=None, groups=groups, inference="auto") - * **_RLearner.** The internal private class :class:`._RLearner` is a parent of the :class:`.DML` and allows the user to specify any way of fitting a final model that takes as input the residual :math:`\tilde{T}`, the features :math:`X` and predicts the residual :math:`\tilde{Y}`. Moreover, the nuisance models take as input diff --git a/doc/spec/estimation/dynamic_dml.rst b/doc/spec/estimation/dynamic_dml.rst new file mode 100644 index 000000000..b11dad886 --- /dev/null +++ b/doc/spec/estimation/dynamic_dml.rst @@ -0,0 +1,94 @@ +.. _dynamicdmluserguide: + +=============================== +Dynamic Double Machine Learning +=============================== + +What is it? +================================== + +Dynamic Double Machine Learning is a method for estimating (heterogeneous) treatment effects when +treatments are offered over time via an adaptive dynamic policy. It applies to the case when +all potential dynamic confounders/controls (factors that simultaneously had a direct effect on the adaptive treatment +decision in the collected data and the observed outcome) are observed, but are either too many (high-dimensional) for +classical statistical approaches to be applicable or their effect on +the treatment and outcome cannot be satisfactorily modeled by parametric functions (non-parametric). +Both of these latter problems can be addressed via machine learning techniques (see e.g. [Lewis2021]_). + + +What are the relevant estimator classes? +======================================== + +This section describes the methodology implemented in the class +:class:`.DynamicDML`. +Click on each of these links for a detailed module documentation and input parameters of each class. + + +When should you use it? +================================== + +Suppose you have observational (or experimental from an A/B test) historical data, where multiple treatment(s)/intervention(s)/action(s) +:math:`T` were offered over time to each of the units and some final outcome(s) :math:`Y` was observed and all the variables :math:`W` that could have +potentially gone into the choice of :math:`T`, and simultaneously could have had a direct effect on the outcome :math:`Y` (aka controls or confounders) are also recorder in the dataset. + +If your goal is to understand what was the effect of the treatment on the outcome as a function of a set of observable +characteristics :math:`X` of the treated samples, then one can use this method. For instance call: + +.. testsetup:: + + # DynamicDML + import numpy as np + groups = np.repeat(a=np.arange(100), repeats=3, axis=0) + W_dyn = np.random.normal(size=(300, 1)) + X_dyn = np.random.normal(size=(300, 1)) + T_dyn = np.random.normal(size=(300, 2)) + y_dyn = np.random.normal(size=(300, )) + +.. testcode:: + + from econml.dml import DynamicDML + est = DynamicDML() + est.fit(y_dyn, T_dyn, X=X_dyn, W=W_dyn, groups=groups) + + +Class Hierarchy Structure +================================== + +In this library we implement variants of several of the approaches mentioned in the last section. The hierarchy +structure of the implemented CATE estimators is as follows. + + .. inheritance-diagram:: econml.dml.DynamicDML + :parts: 1 + :private-bases: + :top-classes: econml._OrthoLearner, econml._cate_estimator.LinearModelFinalCateEstimatorMixin + +Below we give a brief description of each of these classes: + + * **DynamicDML.** The class :class:`.DynamicDML` is an extension of the Double ML approach for treatments assigned sequentially over time periods. + This estimator will adjust for treatments that can have causal effects on future outcomes. The data corresponds to a Markov decision process :math:`\{X_t, W_t, T_t, Y_t\}_{t=1}^m`, + where :math:`X_t, W_t` corresponds to the state at time :math:`t`, :math:`T_t` is the treatment at time :math:`t` and :math:`Y_t` is the observed outcome at time :math:`t`. + + The model makes the following structural equation assumptions on the data generating process: + + .. math:: + + X_t =~& A \cdot T_{t-1} + B \cdot X_{t-1} + \eta_t\\ + T_t =~& p(T_{t-1}, X_t, \zeta_t) \\ + Y_t =~& \theta_0'T_t + \mu'X_t \epsilon_t + + For more details about this model and underlying assumptions, see [Lewis2021]_. + + To learn the treatment effects of treatments in the different periods on the last period outcome, one can simply call: + + .. testcode:: + + from econml.dml import DynamicDML + est = DynamicDML() + est.fit(y_dyn, T_dyn, X=X_dyn, W=W_dyn, groups=groups) + + + +Usage FAQs +========== + +See our FAQ section in `_dmluserguide`_ diff --git a/doc/spec/estimation_dynamic.rst b/doc/spec/estimation_dynamic.rst new file mode 100644 index 000000000..6e7b47cc0 --- /dev/null +++ b/doc/spec/estimation_dynamic.rst @@ -0,0 +1,11 @@ +Estimation Methods for Dynamic Treatment Regimes +================================================ + +This section contains methods for estimating (heterogeneous) treatment effects, +even when treatments are offered over time and the treatments were chosen based on a dynamic +adaptive policy. This is referred to as the dynamic treatment regime (see e.g. [Hernan2010]_) + +.. toctree:: + :maxdepth: 2 + + estimation/dynamic_dml diff --git a/doc/spec/references.rst b/doc/spec/references.rst index dc9abe5bb..5f0213ac9 100644 --- a/doc/spec/references.rst +++ b/doc/spec/references.rst @@ -118,4 +118,9 @@ References .. [Lewis2021] Lewis, G., Syrgkanis, V. (2021). Double/Debiased Machine Learning for Dynamic Treatment Effects. - URL https://arxiv.org/abs/2002.07285 \ No newline at end of file + URL https://arxiv.org/abs/2002.07285 + +.. [Hernan2010] + Hernán, Miguel A., and James M. Robins (2010). + Causal inference. + URL https://www.hsph.harvard.edu/miguel-hernan/causal-inference-book/ diff --git a/doc/spec/spec.rst b/doc/spec/spec.rst index 693854193..649dba98c 100644 --- a/doc/spec/spec.rst +++ b/doc/spec/spec.rst @@ -19,6 +19,7 @@ The EconML Python SDK, developed by the ALICE team at MSR New England, incorpora comparison estimation estimation_iv + estimation_dynamic inference interpretability references diff --git a/econml/dml/__init__.py b/econml/dml/__init__.py index 00035f2c1..a83428cf7 100644 --- a/econml/dml/__init__.py +++ b/econml/dml/__init__.py @@ -8,7 +8,7 @@ Then estimates a CATE model by regressing the residual outcome on the residual treatment in a manner that accounts for heterogeneity in the regression coefficient, with respect to X. For the theoretical foundations of these methods see [dml]_, [rlearner]_, [paneldml]_, -[lassodml]_, [ortholearner]_, [dynamicdml]_. +[lassodml]_, [ortholearner]_. References ---------- @@ -32,15 +32,10 @@ .. [ortholearner] Dylan Foster, Vasilis Syrgkanis (2019). Orthogonal Statistical Learning. ACM Conference on Learning Theory. ``_ - -.. [dynamicdml] Greg Lewis and Vasilis Syrgkanis. - Double/Debiased Machine Learning for Dynamic Treatment Effects. - ``_, 2021. """ from .dml import (DML, LinearDML, SparseLinearDML, KernelDML, NonParamDML, ForestDML) -from .dynamic_dml import DynamicDML from .causal_forest import CausalForestDML __all__ = ["DML", @@ -49,5 +44,4 @@ "KernelDML", "NonParamDML", "ForestDML", - "CausalForestDML", - "DynamicDML"] + "CausalForestDML"] diff --git a/econml/dynamic/__init__.py b/econml/dynamic/__init__.py new file mode 100755 index 000000000..8e4ecd538 --- /dev/null +++ b/econml/dynamic/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +__all__ = ["dml"] diff --git a/econml/dynamic/dml/__init__.py b/econml/dynamic/dml/__init__.py new file mode 100755 index 000000000..a95579da7 --- /dev/null +++ b/econml/dynamic/dml/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Double Machine Learning for Dynamic Treatment Effects. + +A Double/Orthogonal machine learning approach to estimation of heterogeneous +treatment effect in the dynamic treatment regime. For the theoretical +foundations of these methods see: [dynamicdml]_. + +References +---------- + +.. [dynamicdml] Greg Lewis and Vasilis Syrgkanis. + Double/Debiased Machine Learning for Dynamic Treatment Effects. + ``_, 2021. +""" + +from ._dml import DynamicDML + +__all__ = ["DynamicDML"] diff --git a/econml/dml/dynamic_dml.py b/econml/dynamic/dml/_dml.py similarity index 96% rename from econml/dml/dynamic_dml.py rename to econml/dynamic/dml/_dml.py index 5ecd69ef2..9453b16e5 100644 --- a/econml/dml/dynamic_dml.py +++ b/econml/dynamic/dml/_dml.py @@ -8,18 +8,18 @@ from sklearn.model_selection import GroupKFold from scipy.stats import norm from sklearn.linear_model import (ElasticNetCV, LassoCV, LogisticRegressionCV) -from ..sklearn_extensions.linear_model import (StatsModelsLinearRegression, WeightedLassoCVWrapper) -from ..sklearn_extensions.model_selection import WeightedStratifiedKFold -from .dml import _FirstStageWrapper, _FinalWrapper -from .._cate_estimator import TreatmentExpansionMixin, LinearModelFinalCateEstimatorMixin -from .._ortho_learner import _OrthoLearner -from ..utilities import (_deprecate_positional, add_intercept, - broadcast_unit_treatments, check_high_dimensional, - cross_product, deprecated, fit_with_groups, - hstack, inverse_onehot, ndim, reshape, - reshape_treatmentwise_effects, shape, transpose, - get_feature_names_or_default, check_input_arrays, - filter_none_kwargs) +from ...sklearn_extensions.linear_model import (StatsModelsLinearRegression, WeightedLassoCVWrapper) +from ...sklearn_extensions.model_selection import WeightedStratifiedKFold +from ...dml.dml import _FirstStageWrapper, _FinalWrapper +from ..._cate_estimator import TreatmentExpansionMixin, LinearModelFinalCateEstimatorMixin +from ..._ortho_learner import _OrthoLearner +from ...utilities import (_deprecate_positional, add_intercept, + broadcast_unit_treatments, check_high_dimensional, + cross_product, deprecated, fit_with_groups, + hstack, inverse_onehot, ndim, reshape, + reshape_treatmentwise_effects, shape, transpose, + get_feature_names_or_default, check_input_arrays, + filter_none_kwargs) def _get_groups_period_filter(groups, n_periods): @@ -408,7 +408,7 @@ class DynamicDML(LinearModelFinalCateEstimatorMixin, _OrthoLearner): .. testcode:: - from econml.dml import DynamicDML + from econml.dynamic.dml import DynamicDML np.random.seed(123) @@ -544,7 +544,7 @@ def _strata(self, Y, T, X=None, W=None, Z=None, @_deprecate_positional("X, and should be passed by keyword only. In a future release " "we will disallow passing X and W by position.", ['X', 'W']) def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, groups, - cache_values=False, inference=None): + cache_values=False, inference='auto'): """Estimate the counterfactual model from data, i.e. estimates function :math:`\\theta(\\cdot)`. The input data must contain groups with the same size corresponding to the number @@ -559,6 +559,10 @@ def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, grou ...etc. + Only the value of the features X at the first period of each unit are used for + heterogeneity. The value of X in subseuqnet periods is used as a time-varying control + but not for heterogeneity. + Parameters ---------- Y: (n, d_y) matrix or vector of length n @@ -566,7 +570,9 @@ def fit(self, Y, T, X=None, W=None, *, sample_weight=None, sample_var=None, grou T: (n, d_t) matrix or vector of length n Treatments for each sample (required: n = n_groups * n_periods) X: optional(n, d_x) matrix or None (Default=None) - Features for each sample (Required: n = n_groups * n_periods) + Features for each sample (Required: n = n_groups * n_periods). Only first + period features from each unit are used for heterogeneity, the rest are + used as time-varying controls together with W W: optional(n, d_w) matrix or None (Default=None) Controls for each sample (Required: n = n_groups * n_periods) sample_weight: optional(n,) vector or None (Default=None) diff --git a/econml/tests/dgp.py b/econml/tests/dgp.py index e4057b44a..403783447 100644 --- a/econml/tests/dgp.py +++ b/econml/tests/dgp.py @@ -4,6 +4,16 @@ import numpy as np from econml.utilities import cross_product from statsmodels.tools.tools import add_constant +try: + import matplotlib + import matplotlib.pyplot as plt +except ImportError as exn: + from .utilities import MissingModule + + # make any access to matplotlib or plt throw an exception + matplotlib = plt = MissingModule("matplotlib is no longer a dependency of the main econml package; " + "install econml[plt] or econml[all] to require it, or install matplotlib " + "separately, to use the tree interpreters", exn) class _BaseDynamicPanelDGP: @@ -52,8 +62,8 @@ class DynamicPanelDGP(_BaseDynamicPanelDGP): def __init__(self, n_periods, n_treatments, n_x): super().__init__(n_periods, n_treatments, n_x) - def create_instance(self, s_x, sigma_x=.5, sigma_y=.5, conf_str=5, hetero_strength=0, hetero_inds=None, - autoreg=.5, state_effect=.5, random_seed=123): + def create_instance(self, s_x, sigma_x=.8, sigma_y=.1, conf_str=5, hetero_strength=.5, hetero_inds=None, + autoreg=.25, state_effect=.25, random_seed=123): np.random.seed(random_seed) self.s_x = s_x self.conf_str = conf_str @@ -145,7 +155,7 @@ def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): np.random.normal(0, self.sigma_y) groups[t] = t // self.n_periods - return Y, T, X[:, self.hetero_inds] if self.hetero_inds else None, X[:, self.endo_inds], groups + return Y, T, X[:, self.hetero_inds] if (self.hetero_inds is not None) else None, X[:, self.endo_inds], groups def observational_data(self, n_units, gamma=0, s_t=1, sigma_t=0.5, random_seed=123): """Generate observational data with some observational treatment policy parameters. @@ -164,3 +174,46 @@ def policy_gen(Tpre, X, period): return gamma * Tpre + (1 - gamma) * np.dot(Delta, X) + \ np.random.normal(0, sigma_t, size=self.n_treatments) return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) + + +# Auxiliary function for adding xticks and vertical lines when plotting results +# for dynamic dml vs ground truth parameters. +def add_vlines(n_periods, n_treatments, hetero_inds): + locs, labels = plt.xticks([], []) + locs += [- .501 + (len(hetero_inds) + 1) / 2] + labels += ["\n\n$\\tau_{{{}}}$".format(0)] + locs += [qx for qx in np.arange(len(hetero_inds) + 1)] + labels += ["$1$"] + ["$x_{{{}}}$".format(qx) for qx in hetero_inds] + for q in np.arange(1, n_treatments): + plt.axvline(x=q * (len(hetero_inds) + 1) - .5, + linestyle='--', color='red', alpha=.2) + locs += [q * (len(hetero_inds) + 1) - .501 + (len(hetero_inds) + 1) / 2] + labels += ["\n\n$\\tau_{{{}}}$".format(q)] + locs += [(q * (len(hetero_inds) + 1) + qx) + for qx in np.arange(len(hetero_inds) + 1)] + labels += ["$1$"] + ["$x_{{{}}}$".format(qx) for qx in hetero_inds] + locs += [- .501 + (len(hetero_inds) + 1) * n_treatments / 2] + labels += ["\n\n\n\n$\\theta_{{{}}}$".format(0)] + for t in np.arange(1, n_periods): + plt.axvline(x=t * (len(hetero_inds) + 1) * + n_treatments - .5, linestyle='-', alpha=.6) + locs += [t * (len(hetero_inds) + 1) * n_treatments - .501 + + (len(hetero_inds) + 1) * n_treatments / 2] + labels += ["\n\n\n\n$\\theta_{{{}}}$".format(t)] + locs += [t * (len(hetero_inds) + 1) * + n_treatments - .501 + (len(hetero_inds) + 1) / 2] + labels += ["\n\n$\\tau_{{{}}}$".format(0)] + locs += [t * (len(hetero_inds) + 1) * n_treatments + + qx for qx in np.arange(len(hetero_inds) + 1)] + labels += ["$1$"] + ["$x_{{{}}}$".format(qx) for qx in hetero_inds] + for q in np.arange(1, n_treatments): + plt.axvline(x=t * (len(hetero_inds) + 1) * n_treatments + q * (len(hetero_inds) + 1) - .5, + linestyle='--', color='red', alpha=.2) + locs += [t * (len(hetero_inds) + 1) * n_treatments + q * + (len(hetero_inds) + 1) - .501 + (len(hetero_inds) + 1) / 2] + labels += ["\n\n$\\tau_{{{}}}$".format(q)] + locs += [t * (len(hetero_inds) + 1) * n_treatments + (q * (len(hetero_inds) + 1) + qx) + for qx in np.arange(len(hetero_inds) + 1)] + labels += ["$1$"] + ["$x_{{{}}}$".format(qx) for qx in hetero_inds] + plt.xticks(locs, labels) + plt.tight_layout() diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index 380c4a777..be2437a7c 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -8,8 +8,8 @@ from sklearn.preprocessing import OneHotEncoder, FunctionTransformer, PolynomialFeatures from sklearn.linear_model import (LinearRegression, LassoCV, Lasso, MultiTaskLasso, MultiTaskLassoCV, LogisticRegression) -from econml.dml import DynamicDML -from econml.dml.dynamic_dml import _get_groups_period_filter +from econml.dynamic.dml import DynamicDML +from econml.dynamic.dml._dml import _get_groups_period_filter from econml.inference import BootstrapInference, EmpiricalInferenceResults, NormalInferenceResults from econml.utilities import shape, hstack, vstack, reshape, cross_product import econml.tests.utilities # bugfix for assertWarns diff --git a/notebooks/Dynamic Double Machine Learning Examples.ipynb b/notebooks/Dynamic Double Machine Learning Examples.ipynb old mode 100644 new mode 100755 index ecfd78ee1..b5d48e3a2 --- a/notebooks/Dynamic Double Machine Learning Examples.ipynb +++ b/notebooks/Dynamic Double Machine Learning Examples.ipynb @@ -74,7 +74,8 @@ "metadata": {}, "outputs": [], "source": [ - "import econml" + "%load_ext autoreload\n", + "%autoreload 2" ] }, { @@ -82,10 +83,19 @@ "execution_count": 2, "metadata": {}, "outputs": [], + "source": [ + "import econml" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], "source": [ "# Main imports\n", - "from econml.dml import DynamicDML\n", - "from econml.tests.dgp import DynamicPanelDGP\n", + "from econml.dynamic.dml import DynamicDML\n", + "from econml.tests.dgp import DynamicPanelDGP, add_vlines\n", "\n", "# Helper imports\n", "import numpy as np\n", @@ -122,15 +132,15 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Define DGP parameters\n", "np.random.seed(123)\n", - "n_panels = 1000 # number of panels\n", + "n_panels = 5000 # number of panels\n", "n_periods = 3 # number of time periods in each panel\n", - "n_treatments = 1 # number of treatments in each period\n", + "n_treatments = 2 # number of treatments in each period\n", "n_x = 100 # number of features + controls\n", "s_x = 10 # number of controls (endogeneous variables)\n", "s_t = 10 # treatment support size" @@ -138,7 +148,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -158,55 +168,55 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "est = DynamicDML(\n", - " model_y=LassoCV(cv=3), \n", - " model_t=MultiTaskLassoCV(cv=3), \n", + " model_y=LassoCV(cv=3, max_iter=1000), \n", + " model_t=MultiTaskLassoCV(cv=3, max_iter=1000), \n", " cv=3)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": { "scrolled": true }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Objective did not converge. You might want to increase the number of iterations. Duality gap: 2.0609523330807065, tolerance: 0.3973271492025328\n" - ] - }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "est.fit(Y, T, X=None, W=W, groups=groups, inference=\"auto\")" + "est.fit(Y, T, X=None, W=W, groups=groups)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Average effect of default policy: 2.35\n" + "Average effect of default policy: 1.40\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A scalar was specified but there are multiple treatments; the same value will be used for each treatment. Consider specifyingall treatments, or using the const_marginal_effect method.\n" ] } ], @@ -217,50 +227,50 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Effect of target policy over baseline policy: 4.79\n" + "Effect of target policy over baseline policy: 1.40\n" ] } ], "source": [ "# Effect of target policy over baseline policy\n", "# Must specify a treatment for each period\n", - "baseline_policy = np.zeros((1, n_periods))\n", - "target_policy = np.array([[1, 2, 3]])\n", + "baseline_policy = np.zeros((1, n_periods * n_treatments))\n", + "target_policy = np.ones((1, n_periods * n_treatments))\n", "eff = est.effect(T0=baseline_policy, T1=target_policy)\n", "print(f\"Effect of target policy over baseline policy: {eff[0]:0.2f}\")" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Effect of a treatment in period 1 on period 3 outcome: 0.61\n", - "Effect of a treatment in period 2 on period 3 outcome: 1.05\n", - "Effect of a treatment in period 3 on period 3 outcome: 0.69\n" + "Marginal effect of a treatments in period 1 on period 3 outcome: [0.04000235 0.0701606 ]\n", + "Marginal effect of a treatments in period 2 on period 3 outcome: [0.31611764 0.23714736]\n", + "Marginal effect of a treatments in period 3 on period 3 outcome: [0.13108411 0.60656886]\n" ] } ], "source": [ "# Period treatment effects + interpretation\n", - "for i, theta in enumerate(est.intercept_):\n", - " print(f\"Effect of a treatment in period {i+1} on period {n_periods} outcome: {theta:0.2f}\")" + "for i, theta in enumerate(est.intercept_.reshape(-1, n_treatments)):\n", + " print(f\"Marginal effect of a treatments in period {i+1} on period {n_periods} outcome: {theta}\")" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -276,30 +286,42 @@ "\n", "\n", "\n", - " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|(T0)$_0$ 0.04 0.041 0.977 0.328 -0.027 0.107
cate_intercept|(T1)$_0$ 0.07 0.04 1.74 0.082 0.004 0.136
cate_intercept|(T0)$_1$ 0.316 0.036 8.848 0.0 0.257 0.375
cate_intercept|(T0)$_0$ 0.61 0.09 6.74 0.0 0.461 0.758cate_intercept|(T1)$_1$ 0.237 0.036 6.608 0.0 0.178 0.296
cate_intercept|(T0)$_1$ 1.05 0.067 15.565 0.0 0.939 1.161cate_intercept|(T0)$_2$ 0.131 0.003 45.665 0.0 0.126 0.136
cate_intercept|(T0)$_2$ 0.692 0.031 22.308 0.0 0.641 0.743cate_intercept|(T1)$_2$ 0.607 0.003 210.244 0.0 0.602 0.611


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], "text/plain": [ "\n", "\"\"\"\n", - " CATE Intercept Results \n", - "=============================================================================\n", - " point_estimate stderr zstat pvalue ci_lower ci_upper\n", - "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ 0.61 0.09 6.74 0.0 0.461 0.758\n", - "cate_intercept|(T0)$_1$ 1.05 0.067 15.565 0.0 0.939 1.161\n", - "cate_intercept|(T0)$_2$ 0.692 0.031 22.308 0.0 0.641 0.743\n", - "-----------------------------------------------------------------------------\n", + " CATE Intercept Results \n", + "==============================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "------------------------------------------------------------------------------\n", + "cate_intercept|(T0)$_0$ 0.04 0.041 0.977 0.328 -0.027 0.107\n", + "cate_intercept|(T1)$_0$ 0.07 0.04 1.74 0.082 0.004 0.136\n", + "cate_intercept|(T0)$_1$ 0.316 0.036 8.848 0.0 0.257 0.375\n", + "cate_intercept|(T1)$_1$ 0.237 0.036 6.608 0.0 0.178 0.296\n", + "cate_intercept|(T0)$_2$ 0.131 0.003 45.665 0.0 0.126 0.136\n", + "cate_intercept|(T1)$_2$ 0.607 0.003 210.244 0.0 0.602 0.611\n", + "------------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", @@ -309,7 +331,7 @@ "\"\"\"" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -321,7 +343,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -337,12 +359,12 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -373,7 +395,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 2. Example Usage with Heterogeneous Treatment Effects" + "# 2. Example Usage with Heterogeneous Treatment Effects on Time-Invariant Unit Characteristics\n", + "\n", + "We can also estimate treatment effect heterogeneity with respect to the value of some subset of features $X$ in the initial period. Heterogeneity is currently only supported with respect to such initial state features. This for instance can support heterogeneity with respect to time-invariant unit characteristics. In that case you can simply pass as $X$ a repetition of some unit features that stay constant in all periods. You can also pass time-varying features, and their time varying component will be used as a time-varying control. However, heterogeneity will only be estimated with respect to the initial state." ] }, { @@ -385,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -396,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -417,7 +441,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -429,7 +453,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": { "scrolled": true }, @@ -437,10 +461,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -451,7 +475,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -460,52 +484,100 @@ "\n", "\n", "\n", - " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
Coefficient Results
point_estimate stderr zstat pvalue ci_lower ci_upper point_estimate stderr zstat pvalue ci_lower ci_upper
X0|(T0)$_0$ 0.009 0.045 0.203 0.839 -0.065 0.083
X0|(T1)$_0$ 0.017 0.042 0.416 0.677 -0.051 0.086
X0|(T0)$_1$ -0.001 0.041 -0.035 0.972 -0.069 0.067
X0|(T1)$_1$ -0.031 0.041 -0.76 0.447 -0.099 0.036
X0|(T0)$_2$ -0.306 0.008 -36.667 0.0 -0.32 -0.292
X0|(T1)$_2$ 0.158 0.008 19.656 0.0 0.145 0.171
X1|(T0)$_0$ 0.017 0.044 0.378 0.706 -0.056 0.09
X1|(T1)$_0$ -0.007 0.045 -0.164 0.87 -0.082 0.067
X1|(T0)$_1$ -0.034 0.042 -0.821 0.412 -0.103 0.034
X0|(T0)$_0$ 0.112 0.119 0.947 0.344 -0.083 0.308X1|(T1)$_1$ -0.025 0.042 -0.6 0.549 -0.095 0.044
X0|(T0)$_1$ 0.092 0.128 0.719 0.472 -0.119 0.303X1|(T0)$_2$ -0.302 0.008 -35.72 0.0 -0.316 -0.288
X0|(T0)$_2$ 0.443 0.076 5.85 0.0 0.318 0.567X1|(T1)$_2$ 0.156 0.008 18.801 0.0 0.142 0.169
\n", "\n", "\n", "\n", - " \n", + " \n", + "\n", + "\n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", "\n", "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|(T0)$_0$ 0.024 0.036 0.653 0.513 -0.036 0.084
cate_intercept|(T0)$_0$ -0.082 0.061 -1.347 0.178 -0.182 0.018cate_intercept|(T1)$_0$ -0.033 0.036 -0.929 0.353 -0.092 0.025
cate_intercept|(T0)$_1$ -0.035 0.059 -0.591 0.554 -0.133 0.063cate_intercept|(T0)$_1$ -0.105 0.034 -3.067 0.002 -0.162 -0.049
cate_intercept|(T0)$_2$ 0.592 0.035 17.006 0.0 0.535 0.65cate_intercept|(T1)$_1$ 0.037 0.034 1.079 0.281 -0.019 0.093
cate_intercept|(T0)$_2$ -0.743 0.005 -140.503 0.0 -0.752 -0.734
cate_intercept|(T1)$_2$ 0.48 0.005 91.061 0.0 0.472 0.489


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], "text/plain": [ "\n", "\"\"\"\n", - " Coefficient Results \n", - "================================================================\n", - " point_estimate stderr zstat pvalue ci_lower ci_upper\n", - "----------------------------------------------------------------\n", - "X0|(T0)$_0$ 0.112 0.119 0.947 0.344 -0.083 0.308\n", - "X0|(T0)$_1$ 0.092 0.128 0.719 0.472 -0.119 0.303\n", - "X0|(T0)$_2$ 0.443 0.076 5.85 0.0 0.318 0.567\n", - " CATE Intercept Results \n", - "=============================================================================\n", - " point_estimate stderr zstat pvalue ci_lower ci_upper\n", - "-----------------------------------------------------------------------------\n", - "cate_intercept|(T0)$_0$ -0.082 0.061 -1.347 0.178 -0.182 0.018\n", - "cate_intercept|(T0)$_1$ -0.035 0.059 -0.591 0.554 -0.133 0.063\n", - "cate_intercept|(T0)$_2$ 0.592 0.035 17.006 0.0 0.535 0.65\n", - "-----------------------------------------------------------------------------\n", + " Coefficient Results \n", + "==================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "------------------------------------------------------------------\n", + "X0|(T0)$_0$ 0.009 0.045 0.203 0.839 -0.065 0.083\n", + "X0|(T1)$_0$ 0.017 0.042 0.416 0.677 -0.051 0.086\n", + "X0|(T0)$_1$ -0.001 0.041 -0.035 0.972 -0.069 0.067\n", + "X0|(T1)$_1$ -0.031 0.041 -0.76 0.447 -0.099 0.036\n", + "X0|(T0)$_2$ -0.306 0.008 -36.667 0.0 -0.32 -0.292\n", + "X0|(T1)$_2$ 0.158 0.008 19.656 0.0 0.145 0.171\n", + "X1|(T0)$_0$ 0.017 0.044 0.378 0.706 -0.056 0.09\n", + "X1|(T1)$_0$ -0.007 0.045 -0.164 0.87 -0.082 0.067\n", + "X1|(T0)$_1$ -0.034 0.042 -0.821 0.412 -0.103 0.034\n", + "X1|(T1)$_1$ -0.025 0.042 -0.6 0.549 -0.095 0.044\n", + "X1|(T0)$_2$ -0.302 0.008 -35.72 0.0 -0.316 -0.288\n", + "X1|(T1)$_2$ 0.156 0.008 18.801 0.0 0.142 0.169\n", + " CATE Intercept Results \n", + "===============================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-------------------------------------------------------------------------------\n", + "cate_intercept|(T0)$_0$ 0.024 0.036 0.653 0.513 -0.036 0.084\n", + "cate_intercept|(T1)$_0$ -0.033 0.036 -0.929 0.353 -0.092 0.025\n", + "cate_intercept|(T0)$_1$ -0.105 0.034 -3.067 0.002 -0.162 -0.049\n", + "cate_intercept|(T1)$_1$ 0.037 0.034 1.079 0.281 -0.019 0.093\n", + "cate_intercept|(T0)$_2$ -0.743 0.005 -140.503 0.0 -0.752 -0.734\n", + "cate_intercept|(T1)$_2$ 0.48 0.005 91.061 0.0 0.472 0.489\n", + "-------------------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", @@ -515,7 +587,7 @@ "\"\"\"" ] }, - "execution_count": 17, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -526,14 +598,22 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Average effect of default policy:0.42\n" + "Average effect of default policy:-0.42\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A scalar was specified but there are multiple treatments; the same value will be used for each treatment. Consider specifyingall treatments, or using the const_marginal_effect method.\n", + "A scalar was specified but there are multiple treatments; the same value will be used for each treatment. Consider specifyingall treatments, or using the const_marginal_effect method.\n" ] } ], @@ -545,7 +625,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -553,47 +633,54 @@ "output_type": "stream", "text": [ "Effect of target policy over baseline policy for test set:\n", - " [ 2.1924814 1.69559976 -0.24732358 0.40266764 2.33464274 0.81404248\n", - " 2.25540586 1.52509443 2.44106892]\n" + " [-0.37368525 -0.30896804 -0.43030363 -0.52252401 -0.42849622 -0.48790877\n", + " -0.34417987 -0.51804937 -0.36806744]\n" ] } ], "source": [ "# Effect of target policy over baseline policy\n", "# Must specify a treatment for each period\n", - "baseline_policy = np.zeros((1, n_periods))\n", - "target_policy = np.array([[1, 2, 3]])\n", + "baseline_policy = np.zeros((1, n_periods * n_treatments))\n", + "target_policy = np.ones((1, n_periods * n_treatments))\n", "eff = est.effect(X=X_test, T0=baseline_policy, T1=target_policy)\n", "print(\"Effect of target policy over baseline policy for test set:\\n\", eff)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(array([-0.08178444, -0.03511038, 0.59245168]),\n", - " array([[0.11235101],\n", - " [0.09227667],\n", - " [0.44267996]]))" + "(array([ 0.02374269, -0.03302781, -0.10526464, 0.03675719, -0.74294675,\n", + " 0.48025068]),\n", + " array([[ 0.00914226, 0.01675409],\n", + " [ 0.01732804, -0.00741467],\n", + " [-0.00143705, -0.03431712],\n", + " [-0.03136295, -0.02536834],\n", + " [-0.30581311, -0.30189654],\n", + " [ 0.15773252, 0.15564665]]))" ] }, - "execution_count": 20, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Coefficients\n", + "# Coefficients: intercept is of shape n_treatments*n_periods\n", + "# coef_ is of shape (n_treatments*n_periods, n_hetero_inds).\n", + "# first n_treatment rows are from first period, next n_treatment\n", + "# from second period, etc.\n", "est.intercept_, est.coef_" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -611,12 +698,39 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "# parse true parameters in array of shape (n_treatments*n_periods, 1 + n_hetero_inds)\n", + "# first column is the intercept\n", + "true_effect_inds = []\n", + "for t in range(n_treatments):\n", + " true_effect_inds += [t * (1 + n_x)] + (list(t * (1 + n_x) + 1 + het_inds) if len(het_inds)>0 else [])\n", + "true_effect_params = dgp.true_hetero_effect[:, true_effect_inds]\n", + "true_effect_params = true_effect_params.reshape((n_treatments*n_periods, 1 + het_inds.shape[0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "# concatenating intercept and coef_\n", + "param_hat = np.hstack([est.intercept_.reshape(-1, 1), est.coef_])\n", + "lower = np.hstack([conf_ints_intercept[0].reshape(-1, 1), conf_ints_coef[0]])\n", + "upper = np.hstack([conf_ints_intercept[1].reshape(-1, 1), conf_ints_coef[1]])" + ] + }, + { + "cell_type": "code", + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { - "image/png": "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3n7lxmJ9a/kWnaw5pbzcbM2ZMkTTxualMKH03nvvaeLEiXrrrbfUoUMY+7qa+vdiZhuccwVNbc85gACCwx+cAKQr5hfSybSxeXpq/buSWqeE/+pXv9K9996rH/7wh8GUv2RQAAEAAACkRGvufb3tttt02223tdr7pSuqMYDgvLL9gF7ZfsB3DACIjPkFIC4KIAAAAAAEggIIAAAAAIGgAAIAAABAICiAAAAAANLCgQMHdOutt2rw4MEaN26cxo8fr6effrpVM+zdu1cjRoxocv0///M/k/qZP/rRj3T8+PH65926dUs6X3MogACCM6BXFw3o1cV3DACIjPmFkDnnNG3aNE2YMEG7d+/Whg0btGTJEhUVFZ2xbXV1davnO1cBbC5P4wKYSl5vA2FmUyX9i6QMST93zj3YxDbTJS2U5CRtds7d2qohAbQ7F5/f3XcEAEgK8wtpZcsyafUDUnmRlJMvXb1AGjU96R/36quvqlOnTrr77rvr1wYMGKCvfvWrkqQnn3xSK1as0NGjR1VTU6Onn35ad9xxh3bv3q0uXbpo8eLFGjVqlBYuXKhu3brpm9/8piRpxIgRev755yVJ1113na688kr94Q9/UF5enp555hllZ2drw4YNuuOOOyRJ1157bZP55s6dqx07dmjMmDGaMWOGevbseVqe+++/X9///vfr32vWrFkqKCjQRx99pPfee09XXXWVevfurTVr1kiS7r33Xj3//PPKzs7WM888o/PPPz/pz64hb3sAzSxD0mOSrpM0TNIXzWxYo20uljRP0hXOueGS/rG1cwJof6prTqm65pTvGAAQGfMLaWPLMum52VL5fkmu9p/Pza5dT9K2bdv0yU9+8pzbvPnmm1q+fLl+97vf6b777tPYsWO1ZcsWffe7303oHoG7du3SPffco23btik3N1e//e1vJUm33367fvzjH2vz5s1n/b0PPvigPv3pT2vTpk362te+dkaes5k9e7b69eunNWvW1Je/Y8eO6fLLL9fmzZs1YcIE/exnP2s2e6J8HgJ6maS3nXO7nXMnJS2RdFOjbf5e0mPOucOS5Jw72MoZAbRDa3eWaO3OEt8xACAy5lcEW5bp0QO36an3r5MeGRGreCAJqx+QqipOX6uqqF1vIffcc49Gjx6tSy+9tH7tmmuu0XnnnSdJeu211/R3f/d3kqRJkyaptLRU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\n", + "image/png": "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SpL8+te/zjHHHJNbbrklU6ZMyfnnn5/rrrsuX/3qV3PBBRcMq+OP//iPM23atNx6661ZtmxZXvnKV2623qlTp+bggw/OnXfeuXHZm970plx55ZVZsWJFxo8fn5kzZ47kP9FzjMQRGEcn+Vmt9a5a61NJrkxyymbW+8MkVyd5cAS2CQAAADvH6hXbt7wHtdYkyRve8IZ885vfzLp16/LZz342b3/72zeu85rXvCaTJk3KPvvskxkzZuSBBx5IkixcuDBHHnlkXvrSl+aee+7Z2FjYZZddctJJJyVJjjjiiLziFa/IxIkTc8QRR2T58uXDarj++utz5plnbpyf3uVCsc/U+4yTTjop1113Xb785S/njW984w79G2yrkWhgHJDknmfNrxhatlEp5YAk/yHJJ0dgewAAALDzTJu1fct30F133ZXx48dnxowZ2W233XLCCSfkmmuuyaJFi3LaaadtXG/SpEkb748fPz7r16/P97///Vx//fW56aab8i//8i958YtfnDVr1iRJJk6cmFJKkmTcuHEbf37cuHFZv379sDpqrRvX7+axxx7L8uXL88IXvnDjsl122SVHHXVU/uIv/iKvf/3rd+wfYhuNRANjc3tZN5n/H0neX2t9eqtPVsq7SilLSilLVq5cOQLlAQAAwHY47oJk4q7PXTZx187yEbJy5cq8+93vzllnnbWxeXDGGWfk7LPPzkte8pLstddeXX9+9erVmT59enbbbbf85Cc/yT/90z/tcC0nnnhiPvGJT2ycf+SRR4at8/jjj+c973lPTj311GFHaJxzzjn5yEc+kr333nuHa9gWI9HAWJHkwGfNz0py3ybrzE9yZSlleZL/lOSvSimnbu7Jaq2X1Vrn11rn7+urgehXe+/dmQAYTkYCdCcnd765C5LXLkzGDx35MO3Azvzc3i5Q+eSTT278GtXjjz8+J554Yi688MKNjx911FGZOnVqTj/99K0+10knnZT169dn7ty5+eAHP5iXvvSlO1zX+eefn0ceeSSHH354jjzyyHzve9/b+Nixxx6bww8/PEcffXQOOuigfOpTnxr284cddlje9ra3bfa5L7nkksyaNWvj1Iuy6fkr2/0EpUxIckeS45Lcm+SHSU6rtd62hfUvT/LNWutVW3vu+fPn12euZgrAzvE3/3BXkuSdL5/TcCUA7SQnoT/cfvvtOeSQ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\n", "text/plain": [ "
" ] @@ -628,32 +742,13 @@ } ], "source": [ - "# Some plotting boilerplate code\n", "plt.figure(figsize=(15, 5))\n", - "# Intercepts\n", - "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments), \n", - " est.intercept_, \n", - " yerr=(conf_ints_intercept[1] - est.intercept_, est.intercept_ - conf_ints_intercept[0]), \n", - " fmt='o', label='DynamicDML')\n", - "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments), ate_effect.flatten(), \n", - " fmt='o', label='Ground truth')\n", - "# Heterogeneous effects\n", - "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments)+1, est.coef_, \n", - " yerr=((conf_ints_coef[1] - est.coef_).flatten(), \n", - " (est.coef_ - conf_ints_coef[0]).flatten()), \n", - " fmt='o', color='C0')\n", - "plt.errorbar((het_inds.shape[0]+1)*np.arange(n_periods*n_treatments)+1, het_effect, \n", - " fmt='o', color='C1')\n", - "for t in np.arange(2, (het_inds.shape[0]+1)*n_periods, 2):\n", - " plt.axvline(x=t * n_treatments - .5, linestyle='--', alpha=.4)\n", - "# Labels\n", - "x_range = np.arange(n_periods*n_treatments*(het_inds.shape[0]+1))\n", - "x_ticks = list(x_range)\n", - "x_labels = [1 if i%(n_treatments+1)==0 else f\"$X_\\u007b{het_inds[i%(n_treatments+1)-1]}\\u007d$\" for i in x_range]\n", - "x_ticks += [het_inds.shape[0]/2 + i*(n_treatments*(het_inds.shape[0]+1)) for i in range(n_periods)]\n", - "x_labels += [f\"\\n\\n\\n$\\\\theta_{i}$\" for i in range(n_periods)]\n", - "plt.xticks(x_ticks, x_labels)\n", - "plt.ylabel(\"Effect\")\n", + "plt.errorbar(np.arange(n_periods * (len(het_inds) + 1) * n_treatments),\n", + " true_effect_params.flatten(), fmt='*', label='Ground Truth')\n", + "plt.errorbar(np.arange(n_periods * (len(het_inds) + 1) * n_treatments),\n", + " param_hat.flatten(), yerr=((upper - param_hat).flatten(),\n", + " (param_hat - lower).flatten()), fmt='o', label='DynamicDML')\n", + "add_vlines(n_periods, n_treatments, het_inds)\n", "plt.legend()\n", "plt.show()" ] @@ -675,7 +770,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" } }, "nbformat": 4, From 99e62e7b1e367279c24b32387f696bbdb6c8f2ff Mon Sep 17 00:00:00 2001 From: Vasilis Syrgkanis Date: Fri, 6 Aug 2021 12:51:49 -0400 Subject: [PATCH 19/27] fixed ref in doc --- doc/spec/estimation/dynamic_dml.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/spec/estimation/dynamic_dml.rst b/doc/spec/estimation/dynamic_dml.rst index b11dad886..db6487c4f 100644 --- a/doc/spec/estimation/dynamic_dml.rst +++ b/doc/spec/estimation/dynamic_dml.rst @@ -57,7 +57,7 @@ Class Hierarchy Structure In this library we implement variants of several of the approaches mentioned in the last section. The hierarchy structure of the implemented CATE estimators is as follows. - .. inheritance-diagram:: econml.dml.DynamicDML + .. inheritance-diagram:: econml.dynamic.dml.DynamicDML :parts: 1 :private-bases: :top-classes: econml._OrthoLearner, econml._cate_estimator.LinearModelFinalCateEstimatorMixin From 1cf663ac222a5ddb7895233e0064eee9c4f0b9fe Mon Sep 17 00:00:00 2001 From: Vasilis Syrgkanis Date: Fri, 6 Aug 2021 14:00:31 -0400 Subject: [PATCH 20/27] doc bug --- doc/spec/estimation/dynamic_dml.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/spec/estimation/dynamic_dml.rst b/doc/spec/estimation/dynamic_dml.rst index db6487c4f..0003c76f6 100644 --- a/doc/spec/estimation/dynamic_dml.rst +++ b/doc/spec/estimation/dynamic_dml.rst @@ -72,10 +72,11 @@ Below we give a brief description of each of these classes: .. math:: - X_t =~& A \cdot T_{t-1} + B \cdot X_{t-1} + \eta_t\\ - T_t =~& p(T_{t-1}, X_t, \zeta_t) \\ - Y_t =~& \theta_0'T_t + \mu'X_t \epsilon_t + XW_t =~& A \cdot T_{t-1} + B \cdot XW_{t-1} + \eta_t\\ + T_t =~& p(T_{t-1}, XW_t, \zeta_t) \\ + Y_t =~& \theta_0(X_0)'T_t + \mu'XW_t + \epsilon_t + where :math:`XW` is the concatenation of the :math:`X` and :math:`W` variables. For more details about this model and underlying assumptions, see [Lewis2021]_. To learn the treatment effects of treatments in the different periods on the last period outcome, one can simply call: @@ -91,4 +92,4 @@ Below we give a brief description of each of these classes: Usage FAQs ========== -See our FAQ section in `_dmluserguide`_ +See our FAQ section in :ref:`DML User Guide ` From 669c284ac2c60cf1ec2bfc1ed7c372e38faca114 Mon Sep 17 00:00:00 2001 From: Vasilis Syrgkanis Date: Fri, 6 Aug 2021 14:05:12 -0400 Subject: [PATCH 21/27] relaxed dynamci dml tests --- econml/tests/test_dynamic_dml.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/econml/tests/test_dynamic_dml.py b/econml/tests/test_dynamic_dml.py index be2437a7c..7539c18f9 100644 --- a/econml/tests/test_dynamic_dml.py +++ b/econml/tests/test_dynamic_dml.py @@ -294,5 +294,5 @@ def lasso_model(): np.testing.assert_allclose(est.coef_, dgp.true_hetero_effect[:, hetero_inds + 1], atol=0.2) np.testing.assert_array_less(est.intercept__interval()[0], dgp.true_effect.flatten()) np.testing.assert_array_less(dgp.true_effect.flatten(), est.intercept__interval()[1]) - np.testing.assert_array_less(est.coef__interval()[0], dgp.true_hetero_effect[:, hetero_inds + 1]) - np.testing.assert_array_less(dgp.true_hetero_effect[:, hetero_inds + 1], est.coef__interval()[1]) + np.testing.assert_array_less(est.coef__interval()[0] - .05, dgp.true_hetero_effect[:, hetero_inds + 1]) + np.testing.assert_array_less(dgp.true_hetero_effect[:, hetero_inds + 1] - .05, est.coef__interval()[1]) From 0420656ba52c451054177f928c8668704d3a8e62 Mon Sep 17 00:00:00 2001 From: Vasilis Syrgkanis Date: Fri, 6 Aug 2021 15:55:09 -0400 Subject: [PATCH 22/27] fixed doctest --- doc/spec/estimation/dynamic_dml.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/spec/estimation/dynamic_dml.rst b/doc/spec/estimation/dynamic_dml.rst index 0003c76f6..b92f319d8 100644 --- a/doc/spec/estimation/dynamic_dml.rst +++ b/doc/spec/estimation/dynamic_dml.rst @@ -46,7 +46,7 @@ characteristics :math:`X` of the treated samples, then one can use this method. .. testcode:: - from econml.dml import DynamicDML + from econml.dynamic.dml import DynamicDML est = DynamicDML() est.fit(y_dyn, T_dyn, X=X_dyn, W=W_dyn, groups=groups) @@ -83,7 +83,7 @@ Below we give a brief description of each of these classes: .. testcode:: - from econml.dml import DynamicDML + from econml.dynamic.dml import DynamicDML est = DynamicDML() est.fit(y_dyn, T_dyn, X=X_dyn, W=W_dyn, groups=groups) From 42c65dd89af0234c9afb774a47a5417e335aa509 Mon Sep 17 00:00:00 2001 From: Maggie Hei Date: Sun, 8 Aug 2021 01:02:33 -0400 Subject: [PATCH 23/27] add ROI notebook --- econml/data/dynamic_panel_dgp.py | 457 +++++++++++++ econml/data/input_dynamicdgp/cov_new.jbl | Bin 0 -> 63587 bytes econml/data/input_dynamicdgp/gm_0.jbl | Bin 0 -> 1067 bytes econml/data/input_dynamicdgp/gm_1.jbl | Bin 0 -> 1067 bytes econml/data/input_dynamicdgp/gm_2.jbl | Bin 0 -> 1067 bytes econml/data/input_dynamicdgp/gm_3.jbl | Bin 0 -> 1067 bytes econml/data/input_dynamicdgp/gm_4.jbl | Bin 0 -> 1067 bytes econml/data/input_dynamicdgp/gm_5.jbl | Bin 0 -> 1067 bytes econml/data/input_dynamicdgp/gm_6.jbl | Bin 0 -> 1067 bytes .../data/input_dynamicdgp/lognorm_neg_0.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_neg_1.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_neg_2.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_neg_3.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_neg_4.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_neg_5.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_0.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_1.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_2.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_3.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_4.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_5.jbl | Bin 0 -> 164 bytes .../data/input_dynamicdgp/lognorm_pos_6.jbl | Bin 0 -> 164 bytes econml/data/input_dynamicdgp/n_0.jbl | Bin 0 -> 139 bytes econml/data/input_dynamicdgp/n_1.jbl | Bin 0 -> 139 bytes econml/data/input_dynamicdgp/n_2.jbl | Bin 0 -> 139 bytes econml/data/input_dynamicdgp/n_3.jbl | Bin 0 -> 139 bytes econml/data/input_dynamicdgp/n_4.jbl | Bin 0 -> 139 bytes econml/data/input_dynamicdgp/n_5.jbl | Bin 0 -> 139 bytes econml/data/input_dynamicdgp/n_6.jbl | Bin 0 -> 139 bytes ... at Microsoft via Short-Term Proxies.ipynb | 606 ++++++++++++++++++ 30 files changed, 1063 insertions(+) create mode 100644 econml/data/dynamic_panel_dgp.py create mode 100644 econml/data/input_dynamicdgp/cov_new.jbl create mode 100644 econml/data/input_dynamicdgp/gm_0.jbl create mode 100644 econml/data/input_dynamicdgp/gm_1.jbl create mode 100644 econml/data/input_dynamicdgp/gm_2.jbl create mode 100644 econml/data/input_dynamicdgp/gm_3.jbl create mode 100644 econml/data/input_dynamicdgp/gm_4.jbl create mode 100644 econml/data/input_dynamicdgp/gm_5.jbl create mode 100644 econml/data/input_dynamicdgp/gm_6.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_neg_0.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_neg_1.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_neg_2.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_neg_3.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_neg_4.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_neg_5.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_0.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_1.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_2.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_3.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_4.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_5.jbl create mode 100644 econml/data/input_dynamicdgp/lognorm_pos_6.jbl create mode 100644 econml/data/input_dynamicdgp/n_0.jbl create mode 100644 econml/data/input_dynamicdgp/n_1.jbl create mode 100644 econml/data/input_dynamicdgp/n_2.jbl create mode 100644 econml/data/input_dynamicdgp/n_3.jbl create mode 100644 econml/data/input_dynamicdgp/n_4.jbl create mode 100644 econml/data/input_dynamicdgp/n_5.jbl create mode 100644 econml/data/input_dynamicdgp/n_6.jbl create mode 100644 notebooks/CustomerScenarios/Case Study - Long-Term Return-on-Investment at Microsoft via Short-Term Proxies.ipynb diff --git a/econml/data/dynamic_panel_dgp.py b/econml/data/dynamic_panel_dgp.py new file mode 100644 index 000000000..07c280083 --- /dev/null +++ b/econml/data/dynamic_panel_dgp.py @@ -0,0 +1,457 @@ +import numpy as np +from econml.utilities import cross_product +from statsmodels.tools.tools import add_constant +import pandas as pd +import scipy as sp +from scipy.stats import expon +from sklearn.linear_model import LinearRegression +import matplotlib.pyplot as plt +import joblib +import os + + +dir = os.path.dirname(__file__) + +# covariance matrix + + +def new_cov_matrix(cov): + p = cov.shape[0] + # get eigen value and eigen vectors + e_val, e_vec = sp.linalg.eigh(cov) + start = [0, 35, 77, 86] + end = [35, 77, 86, p] + e_val_new = np.array([]) + for i, j in zip(start, end): + e_val_new = np.append(e_val_new, linear_approximation(i, j, e_val)) + # simulate eigen vectors + e_vec_new = np.zeros_like(e_vec) + for i in range(p): + w = np.zeros(p) # , np.random.normal(0.01, 0.01, size=p) + w[np.random.choice(p, 6)] += np.random.normal(0.01, 0.06, size=(6)) + e_vec_new[:, i] = w / np.linalg.norm(w) + # keep the top 4 eigen value and corresponding eigen vector + e_vec_new[:, -4:] = e_vec[:, -4:] + e_val_new[-4:] = e_val[-4:] + # replace the negative eigen values + e_val_new[np.where(e_val_new < 0)] = e_val[np.where(e_val_new < 0)] + # generate a new covariance matrix + cov_new = e_vec_new.dot(np.diag(e_val_new)).dot(e_vec_new.T) + return cov_new + +# get linear approximation of eigen values + + +def linear_approximation(start, end, e_val): + est = LinearRegression() + X = np.arange(start, end).reshape(-1, 1) + est.fit(X, e_val[start:end]) + pred = est.predict(X) + return pred + + +# coefs +def generate_coefs(index, columns): + simulated_coefs_df = pd.DataFrame(0, index=index, columns=columns) + # get the indices of each group of features + ind_demo = [columns.index(col) for col in columns if "demo" in col] + ind_proxy = [columns.index(col) for col in columns if "proxy" in col] + ind_investment = [columns.index(col) + for col in columns if "investment" in col] + + for i in range(7): + outcome_name = simulated_coefs_df.index[i] + if "proxy" in outcome_name: + ind_same_proxy = [ + ind for ind in ind_proxy if outcome_name in columns[ind]] + # print(ind_same_proxy) + random_proxy_name = np.random.choice( + [proxy for proxy in index[:4] if proxy != outcome_name] + ) + ind_random_other_proxy = [ + ind for ind in ind_proxy if random_proxy_name in columns[ind] + ] + # demo + simulated_coefs_df.iloc[ + i, np.random.choice(ind_demo, 2) + ] = np.random.uniform(0.004, 0.05) + # same proxy + simulated_coefs_df.iloc[i, ind_same_proxy] = sorted( + np.random.choice(expon.pdf(np.arange(10)) * + 5e-1, 6, replace=False) + ) + simulated_coefs_df.iloc[i, ind_random_other_proxy] = sorted( + np.random.choice(expon.pdf(np.arange(10)) * + 5e-2, 6, replace=False) + ) + elif "investment" in outcome_name: + ind_same_invest = [ + ind for ind in ind_investment if outcome_name in columns[ind] + ] + random_proxy_name = np.random.choice(index[:4]) + ind_random_other_proxy = [ + ind for ind in ind_proxy if random_proxy_name in columns[ind] + ] + simulated_coefs_df.iloc[ + i, np.random.choice(ind_demo, 2) + ] = np.random.uniform(0.001, 0.05) + simulated_coefs_df.iloc[i, ind_same_invest] = sorted( + np.random.choice(expon.pdf(np.arange(10)) * + 5e-1, 6, replace=False) + ) + simulated_coefs_df.iloc[i, ind_random_other_proxy] = sorted( + np.random.choice(expon.pdf(np.arange(10)) * + 1e-1, 6, replace=False) + ) + return simulated_coefs_df + + +# residuals + + +def simulate_residuals(ind): + n, n_pos, n_neg = joblib.load(os.path.join(dir, f"input_dynamicdgp/n_{ind}.jbl")) + # gmm + est = joblib.load(os.path.join(dir, f"input_dynamicdgp/gm_{ind}.jbl")) + x_new = est.sample(n - n_pos - n_neg)[0].flatten() + + # log normal on outliers + if n_pos > 0: + # positive outliers + s, loc, scale = joblib.load(os.path.join(dir, f"input_dynamicdgp/lognorm_pos_{ind}.jbl")) + fitted_pos_outliers = sp.stats.lognorm( + s, loc=loc, scale=scale).rvs(size=n_pos) + else: + fitted_pos_outliers = np.array([]) + # negative outliers + if n_neg > 0: + s, loc, scale = joblib.load(os.path.join(dir, f"input_dynamicdgp/lognorm_neg_{ind}.jbl")) + fitted_neg_outliers = - \ + sp.stats.lognorm(s, loc=loc, scale=scale).rvs(size=n_neg) + else: + fitted_neg_outliers = np.array([]) + x_new = np.concatenate((x_new, fitted_pos_outliers, fitted_neg_outliers)) + return x_new + + +def simulate_residuals_all(res_df): + res_df_new = res_df.copy(deep=True) + for i in range(res_df.shape[1]): + res_df_new.iloc[:, i] = simulate_residuals(i) + # demean the new residual again + res_df_new = res_df_new - res_df_new.mean(axis=0) + return res_df_new + +# generate data + + +def get_prediction(df, coef_matrix, residuals, thetas, n, intervention, columns, index, counterfactual): + data_matrix = df[columns].values + # sample residuals + sample_residuals = residuals + preds = np.matmul(data_matrix, coef_matrix.T) + + # get prediction for current investment + if counterfactual: + pred_inv = np.zeros(preds[:, 4:].shape) + else: + pred_inv = preds[:, 4:] + sample_residuals[:, 4:] + intervention + df[index[4:]] = pd.DataFrame(pred_inv, index=df.index) + + # get prediction for current proxy + pred_proxy = preds[:, :4] + sample_residuals[:, :4] + \ + np.matmul(pred_inv, thetas.T) + df[index[:4]] = pd.DataFrame(pred_proxy, index=df.index) + return df + + +def generate_dgp( + cov_matrix, + n_tpid, + t_period, + coef_matrix, + residual_matrix, + thetas, + intervention, + columns, + index, + counterfactual +): + df_all = pd.DataFrame() + # get first period prediction + m = cov_matrix.shape[0] + x = np.random.multivariate_normal(np.repeat(0, m), cov_matrix, size=n_tpid) + df = pd.DataFrame( + np.hstack( + (np.arange(n_tpid).reshape(-1, 1), + np.repeat(1, n_tpid).reshape(-1, 1), x) + ), + columns=["id", "datetime"] + columns, + ) + df = get_prediction(df, coef_matrix, residual_matrix[0], + thetas, n_tpid, intervention, columns, index, False) + df_all = pd.concat([df_all, df], axis=0) + + # iterate the step ahead contruction + for t in range(2, t_period + 1): + # prepare new x + new_df = df.copy(deep=True) + new_df["datetime"] = np.repeat(t, n_tpid) + for name in index: + for i in range(-6, -1): + new_df[f"{name}_{i}"] = df[f"{name}_{i+1}"] + new_df[f"{name}_-1"] = df[name] + df = get_prediction(new_df, coef_matrix, residual_matrix[t - 1], + thetas, n_tpid, [0, 0, 0], columns, index, counterfactual) + df_all = pd.concat([df_all, df]) + df_all = df_all.sort_values(["id", "datetime"]) + return df_all + + +class AbstracDynamicPanelDGP: + + def __init__(self, n_periods, n_treatments, n_x): + self.n_periods = n_periods + self.n_treatments = n_treatments + self.n_x = n_x + return + + def create_instance(self, *args, **kwargs): + pass + + def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): + pass + + def static_policy_data(self, n_units, tau, random_seed=123): + def policy_gen(Tpre, X, period): + return tau[period] + return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) + + def adaptive_policy_data(self, n_units, policy_gen, random_seed=123): + return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) + + def static_policy_effect(self, tau, mc_samples=1000): + Y_tau, _, _, _ = self.static_policy_data(mc_samples, tau) + Y_zero, _, _, _ = self.static_policy_data( + mc_samples, np.zeros((self.n_periods, self.n_treatments))) + return np.mean(Y_tau[np.arange(Y_tau.shape[0]) % self.n_periods == self.n_periods - 1]) - \ + np.mean(Y_zero[np.arange(Y_zero.shape[0]) % + self.n_periods == self.n_periods - 1]) + + def adaptive_policy_effect(self, policy_gen, mc_samples=1000): + Y_tau, _, _, _ = self.adaptive_policy_data(mc_samples, policy_gen) + Y_zero, _, _, _ = self.static_policy_data( + mc_samples, np.zeros((self.n_periods, self.n_treatments))) + return np.mean(Y_tau[np.arange(Y_tau.shape[0]) % self.n_periods == self.n_periods - 1]) - \ + np.mean(Y_zero[np.arange(Y_zero.shape[0]) % + self.n_periods == self.n_periods - 1]) + + +class DynamicPanelDGP(AbstracDynamicPanelDGP): + + def __init__(self, n_periods, n_treatments, n_x): + super().__init__(n_periods, n_treatments, n_x) + + def create_instance(self, s_x, sigma_x, sigma_y, conf_str, epsilon, Alpha_unnormalized, + hetero_strength=0, hetero_inds=None, + autoreg=.5, state_effect=.5, random_seed=123): + random_state = np.random.RandomState(random_seed) + self.s_x = s_x + self.conf_str = conf_str + self.sigma_x = sigma_x + self.sigma_y = sigma_y + self.hetero_inds = hetero_inds.astype( + int) if hetero_inds is not None else hetero_inds + self.hetero_strength = hetero_strength + self.autoreg = autoreg + self.state_effect = state_effect + self.random_seed = random_seed + self.endo_inds = np.setdiff1d( + np.arange(self.n_x), hetero_inds).astype(int) + # The first s_x state variables are confounders. The final s_x variables are exogenous and can create + # heterogeneity + self.Alpha = Alpha_unnormalized + self.Alpha /= np.linalg.norm(self.Alpha, axis=1, ord=1, keepdims=True) + self.Alpha *= state_effect + if self.hetero_inds is not None: + self.Alpha[self.hetero_inds] = 0 + + self.Beta = np.zeros((self.n_x, self.n_x)) + for t in range(self.n_x): + self.Beta[t, :] = autoreg * np.roll(random_state.uniform(low=4.0**(-np.arange( + 0, self.n_x)), high=4.0**(-np.arange(1, self.n_x + 1))), t) + if self.hetero_inds is not None: + self.Beta[np.ix_(self.endo_inds, self.hetero_inds)] = 0 + self.Beta[np.ix_(self.hetero_inds, self.endo_inds)] = 0 + + self.epsilon = epsilon + self.zeta = np.zeros(self.n_x) + self.zeta[:self.s_x] = self.conf_str / self.s_x + + self.y_hetero_effect = np.zeros(self.n_x) + self.x_hetero_effect = np.zeros(self.n_x) + if self.hetero_inds is not None: + self.y_hetero_effect[self.hetero_inds] = random_state.uniform(.5 * hetero_strength, + 1.5 * hetero_strength) /\ + len(self.hetero_inds) + self.x_hetero_effect[self.hetero_inds] = random_state.uniform(.5 * hetero_strength, + 1.5 * hetero_strength) / \ + len(self.hetero_inds) + + self.true_effect = np.zeros((self.n_periods, self.n_treatments)) + self.true_effect[0] = self.epsilon + for t in np.arange(1, self.n_periods): + self.true_effect[t, :] = (self.zeta.reshape( + 1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) @ self.Alpha) + + self.true_hetero_effect = np.zeros( + (self.n_periods, (self.n_x + 1) * self.n_treatments)) + self.true_hetero_effect[0, :] = cross_product(add_constant(self.y_hetero_effect.reshape(1, -1), + has_constant='add'), + self.epsilon.reshape(1, -1)) + for t in np.arange(1, self.n_periods): + self.true_hetero_effect[t, :] = cross_product(add_constant(self.x_hetero_effect.reshape(1, -1), + has_constant='add'), + self.zeta.reshape(1, -1) @ + np.linalg.matrix_power(self.Beta, t - 1) @ self.Alpha) + + return self + + def hetero_effect_fn(self, t, x): + if t == 0: + return (np.dot(self.y_hetero_effect, x.flatten()) + 1) * self.epsilon + else: + return (np.dot(self.x_hetero_effect, x.flatten()) + 1) *\ + (self.zeta.reshape(1, -1) @ np.linalg.matrix_power(self.Beta, t - 1) + @ self.Alpha).flatten() + + def _gen_data_with_policy(self, n_units, policy_gen, random_seed=123): + random_state = np.random.RandomState(random_seed) + Y = np.zeros(n_units * self.n_periods) + T = np.zeros((n_units * self.n_periods, self.n_treatments)) + X = np.zeros((n_units * self.n_periods, self.n_x)) + groups = np.zeros(n_units * self.n_periods) + for t in range(n_units * self.n_periods): + period = t % self.n_periods + if period == 0: + X[t] = random_state.normal(0, self.sigma_x, size=self.n_x) + T[t] = policy_gen(np.zeros(self.n_treatments), X[t], period, random_state) + else: + X[t] = (np.dot(self.x_hetero_effect, X[t - 1]) + 1) * np.dot(self.Alpha, T[t - 1]) + \ + np.dot(self.Beta, X[t - 1]) + \ + random_state.normal(0, self.sigma_x, size=self.n_x) + T[t] = policy_gen(T[t - 1], X[t], period, random_state) + Y[t] = (np.dot(self.y_hetero_effect, X[t]) + 1) * np.dot(self.epsilon, T[t]) + \ + np.dot(X[t], self.zeta) + \ + random_state.normal(0, self.sigma_y) + groups[t] = t // self.n_periods + + return Y, T, X, groups + + def observational_data(self, n_units, gamma, s_t, sigma_t, random_seed=123): + """ Generated observational data with some observational treatment policy parameters + + Parameters + ---------- + n_units : how many units to observe + gamma : what is the degree of auto-correlation of the treatments across periods + s_t : sparsity of treatment policy; how many states does it depend on + sigma_t : what is the std of the exploration/randomness in the treatment + """ + Delta = np.zeros((self.n_treatments, self.n_x)) + Delta[:, :s_t] = self.conf_str / s_t + + def policy_gen(Tpre, X, period, random_state): + return gamma * Tpre + (1 - gamma) * np.dot(Delta, X) + \ + random_state.normal(0, sigma_t, size=self.n_treatments) + return self._gen_data_with_policy(n_units, policy_gen, random_seed=random_seed) + + +class SemiSynthetic: + + def create_instance(self): + # get new covariance matrix + self.cov_new = joblib.load(os.path.join(dir, f"input_dynamicdgp/cov_new.jbl")) + + # get coefs + self.index = ["proxy1", "proxy2", "proxy3", "proxy4", + "investment1", "investment2", "investment3", ] + self.columns = [f"{ind}_{i}" for ind in self.index for i in range(-6, 0)] +\ + [f"demo_{i}" for i in range(47)] + + self.coef_df = generate_coefs(self.index, self.columns) + self.n_proxies = 4 + self.n_treatments = 3 + + # get residuals + res_df = pd.DataFrame(columns=self.index) + self.new_res_df = simulate_residuals_all(res_df) + + def gen_data(self, n, n_periods, thetas, random_seed): + random_state = np.random.RandomState(random_seed) + n_proxies = self.n_proxies + n_treatments = self.n_treatments + coef_matrix = self.coef_df.values + residual_matrix = self.new_res_df.values + n_x = len(self.columns) + # proxy 1 is the outcome + outcome = "proxy1" + + # make fixed residuals + all_residuals = [] + for t in range(n_periods): + sample_residuals = [] + for i in range(7): + sample_residuals.append( + random_state.choice(residual_matrix[:, i], n)) + sample_residuals = np.array(sample_residuals).T + all_residuals.append(sample_residuals) + all_residuals = np.array(all_residuals) + + fn_df_control = generate_dgp(self.cov_new, n, n_periods, + coef_matrix, all_residuals, thetas, + [0, 0, 0], self.columns, self.index, False) + + fn_df_cf_control = generate_dgp(self.cov_new, n, n_periods, + coef_matrix, all_residuals, thetas, + [0, 0, 0], self.columns, self.index, True) + true_effect = np.zeros((n_periods, n_treatments)) + for i in range(n_treatments): + intervention = [0, 0, 0] + intervention[i] = 1 + fn_df_treated = generate_dgp(self.cov_new, n, n_periods, + coef_matrix, all_residuals, thetas, + intervention, self.columns, self.index, True) + for t in range(n_periods): + ate_control = fn_df_cf_control.loc[ + fn_df_control["datetime"] == t + 1, outcome + ].mean() + ate_treated = fn_df_treated.loc[ + fn_df_treated["datetime"] == t + 1, outcome + ].mean() + true_effect[t, i] = ate_treated - ate_control + + panelX = fn_df_control[self.columns].values.reshape(-1, n_periods, n_x) + panelT = fn_df_control[self.index[n_proxies:] + ].values.reshape(-1, n_periods, n_treatments) + panelY = fn_df_control[outcome].values.reshape(-1, n_periods) + panelGroups = fn_df_control["id"].values.reshape(-1, n_periods) + return panelX, panelT, panelY, panelGroups, true_effect + + def plot_coefs(self): + coef_df = self.coef_df + plt.figure(figsize=(20, 20)) + for i in range(7): + outcome = coef_df.index[i] + plt.subplot(2, 4, i + 1) + coef_list = coef_df.iloc[i] + coef_list = coef_list[coef_list != 0] + plt.plot(coef_list) + plt.xticks(rotation=90) + plt.title(f"outcome:{outcome}") + plt.show() + + def plot_cov(self): + plt.imshow(self.cov_new) + plt.colorbar() + plt.show() diff --git a/econml/data/input_dynamicdgp/cov_new.jbl b/econml/data/input_dynamicdgp/cov_new.jbl new file mode 100644 index 0000000000000000000000000000000000000000..8d0bb38d911ff888185cb3a988dc370b93442e65 GIT binary patch literal 63587 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 literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/gm_0.jbl b/econml/data/input_dynamicdgp/gm_0.jbl new file mode 100644 index 0000000000000000000000000000000000000000..e4e262d9d950fe9c4bbf5c7060b615062a558f93 GIT binary patch literal 1067 zcmZ8hJ4_To7{22`V0kM(KmidShvEr{g7P>BCYQKyD2Z{qadvOGUD?+^v&*BnL=r?O zv``xYF~&|S8*H(%(!^L8(GVL8W3(_9SeWSS+!-EQZ1R8e|KI=k{+U}5jg6ZGQ#)oE zQ=U&r45hKpn1OASL!?9w7|wb~5Ne)*a0c?CN@*w*vXSOku45D1V-S(^ln4hs$AtVu z{E9mK?&s4#N->8hNpsH$Orapr_x#ny{dye+CXjMLhFW&BTmBjV1ymJB0&x32K!Bo{{O8 zH+N9ZyA6i~;LsG8piT+%!$GJQSV~NwA;%yr2@WfH+-0l_8s(52z5_=_p(!dyL|_jb zO(v6<|ME}#-7e`Ne>9tzkPY#5nftd{xtjYC;6q8jSmG^&a8~eiWZ>Kmp7YyyF3l}7=(ykCo4hg7-W}~1du)rVf}m3_ z6CLxR5{gvExB2dLWtEC)4!_8jZ+b>nbEaVv^A)+EHw_m3UVHLvdGS{qF3v&sDD;Ty afO5|O;%gXeE%XXOT4U`Uu{P-Q)!08I#a%xD literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/gm_1.jbl b/econml/data/input_dynamicdgp/gm_1.jbl new file mode 100644 index 0000000000000000000000000000000000000000..02c721f9040763c3d12402efcb444c054f63a89c GIT binary patch literal 1067 zcmZ8hNlX+$6rCAVpivgZ1r;|02bU2L1veOjaS~f5%Ei)4rMjn>Zt1S>U)2qAm_!dE z95|S0;>qn`;z=(?Jz+d}&={Aq@xaA{8n+mOnrL;+7{NYt@?ZV`{_=j+nKDxhIh1gg za_!SW$Y~1aNhpL(7%m}NrUdjCJv4~5o`rY;5=pc$7K<@#damy=$^rppdIFV6Fz_6h zm+3uf^uPLi>ldn&5T|+RIZQar&-C1R{QcV950j`u9=PN*wgbvxfu2JP6r;`T0Lt|^ zs*+}8V4rZ}3aC&HLoOvuK&8sh3Cnpd76Az;Xj!DmkP*Ta!UL$9K;;q{rrhuZg@t+y z)&8@w&1B|b;=m$ZLpAb1l_=XcDGo+`3e{+yLgxYy45HeEMSX<1e>M0_1 zSw-V>YTCl~Sjlm57FA0gh6gE$<{ZHomdF;PdQFeX0`oM7p?+KquGJYp@c{ml=gc zWOJ+Z#5q{40FURS1dS*z5Bp$^!g6W?YfB8u(qJ7*NSDEGSg*(Q_<7hc08L4~Oa<

k=PY#zTX=J8?PF^|lwa9(nFxc-gCTEsha3Sy3;^ z0<vz;CMzPi-S=R!|Ko+JLo?Ix4?A!6qI%ihEcr~!bEp^^#jsrwwiTfLKf;bVgin7A z(nlw)^!=HZH@`ZUq}3A7B7_}^r?UV%|Kr&;hiB)L_C)iS6b~cQ&$B; zmtLnjmP4f+8(zrd?sONGN|~O#$ZqHac2RR-5{JkY*=;rTRj|hEUtc^+q+!n(>>Yr8 c>N?=kGl1n9Mq3N}l^|`Yj?Pp&^n^z059)|uGXMYp literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/gm_2.jbl b/econml/data/input_dynamicdgp/gm_2.jbl new file mode 100644 index 0000000000000000000000000000000000000000..91737d308e5459eb8a96f1e818e080f4dc631403 GIT binary patch literal 1067 zcmZ8hJ4_To7{24+fCc3tqM(2Z;vu^7Qc;n^5G8TLX|Qk$lgaGfaJ#ave`c41;u1B= z!)T$EB`s>9A!4Bkory6f#LCLb#EL{4X-IH(?hKDDHu=B#|L=c%|IEFZ&L%8^sUzFw zlpjzcqYMrhGqHmTh%{0FM)MvLL^|$6GzUcqwK5WkI7oMG&vl67Gl(fgYK()vYeDfy z@}@TW?a$(dTFxO#GTd_#Qz#i3eDi62;oaI(wTvIwcnX<5p%7QnYFsdyj_E_G5>+d> z85+pL6x$5Sgu{eQu*0BSc&$JbjyD&m&ZVdalZW92`uDcot~7g&BheT?9%_8i(!@G^!;$BQr2> z?vPUS2o4LtrD-lflN#lR!_X|Sw3t9kfk9Xj98rt7%XmK=RU%4s7LJWUYeI>MzyWAW zrBc^7`6qr##E0XWXc1eMn?<&bJ!qc`w7P-L_J+-0$@SUArLWP|q|h}#MmS-Fy(Dtb zkuVyJMuS(WL}%cn(PT6mEk>&m5Usk%WsgaA^z%#C1T0 YX8`dv47V083qiW%o<6x71_Mq02k^aI>;M1& literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/gm_3.jbl b/econml/data/input_dynamicdgp/gm_3.jbl new file mode 100644 index 0000000000000000000000000000000000000000..4c3ce9289069d56c2cb71d1601323b7fe39df462 GIT binary patch literal 1067 zcmZ8hNoy2A6rP!ElU9>x5;bm7<1%q+lNdE>%(MswY%)kLDG)5Gdn)NR-PQf7x-;f5 z5RteX1b=`K5d;MhFWv+ZLA`j;9K0#$O;pIilu7+PhENxgQ!WPua3h@tJ88p^*c&~9G{EYG!c z7W?5e4qaOcrWF$7BnO0XHF<>4vVqOhWxKxdUfSsH9b8R;@U0Nc!znZ63!N1-cc)~LWi z*ik4H&is*|`kAQ+c3P@M;(0*{`#uR_*R|OCWW1|=HS{_E=I5O+A8KFbm9CXB!fq$& zC0B;-oYUcKaAcKc`U>oEHalCKPN&OB2tLjmx6buhPoK|Uy?4mtd8N&N@dSpwie_IK z_AjDID%O_}9hn)u{4ldHG&UOrZ0=?rwaNAt$!9x(N5$kch69SQrwqNz2>X^0{wX<6 zzGnlY^y}G!R@!N(C0;}b2Nh3$84fMuIlP3Y`Qh-~`HpYK9sa{=e_b%t9y4`SFdQ+P zRL63tlw&K1ncSU$ic-2A$cya9UgTCZ7dG*TT#=(r*Qspk?n&eH`>X-SuE6n87*y8* ZSDyha*D%>yIH3gT(fj)KUKolk{V%=8T>Jn4 literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/gm_4.jbl b/econml/data/input_dynamicdgp/gm_4.jbl new file mode 100644 index 0000000000000000000000000000000000000000..77af014faee8d2036d9892ad9d4e21f1dad3e8e5 GIT binary patch literal 1067 zcmZ8hNoy2A6rRaC>BJhr(X~Ls%0FaB*Q(YF@@sc6HneQ-+un%h8p7sHl9VMPbicqX|+T!nvUs1sS;Jo zxfvSB!xY;LV!~m{CfH$6Ch}A4q+J^^ANvHPlq&I%8B7I2eJH=CmU3j8P|aloDwK#? zy=o)V;hCqg1)CH}t>OowMBctmkU!@Us8owMIuTeFREc~u#LO5zRdiiPY&6f z5;Ptqy2(t}$vbXIsg<0^K@$Xra}HqyHN3^JUfp$gfftO zZ>~WpxB`s=aA}%L(41UuCN?lRE_yOf9$y$ZX>pf#=(iogNb zlS-vVR`@4=O2mh~nrIPQmOF!N8+)*CAcU0!@If7TN#AHPpF^uKr|v{@C8Avh>#4rSr+ z8k(?Tc^%R6yPBn?JFN-aa{lJ^ulEvaEpKm)e7fOUgiXyMI3ft!v(T}DuyY;Z-O6Xp zu6;H+)APl?HC~exOT2~v-ejPu!and+dm-f}mHa z5FPWO5{fi8aQN=@<&=_jmtSN%uzWM8In%L)`HJ)#twXW8u2adyKPw41HV?gU&C-~VNeLtE_Zgz9dIJhFrl_CmCTt z55_3utY{!!)k`jdw;Y6k;>m+ZE*^rQ2M-ZNTtKU9MzZvw;lKL-{pJ0tn{k7U*#uKp zajeN8q(nh^95QBM7nKots0<93JYfxh>|?_9LE$k4W;H6e>{HvWk##v2M(S@RzN6JsX47mFj}q^K(!jv z>bMyh$j1~r3~GeKm_xA3pjPCk*v)wkVgU{a$XQM1ArqJiga%M|SF7g8IH9`72-K^R zmRPls@Y?>-L`ba z4IRILe9Pd1$=S#lpcEIbQSAGK%CM8Gva1NYlOmplx?y9+pve${vYW$^y9|4@O*|tr zF>h|KT5%hi1>n&fm!L(9@xwuA6jopF$SD+)Q#zo)- zIFQX|FaG79_^DC=2X)aRwry_$IS%&W&~&Ib=U)E&`1?~jWqsXkCf=lluB9=;VKeF_ zS%l7{*<|i9d6jBx3cAb|v(;=fJIsjSM}Je>!>M3lesuBkyV<7;LYr0b7=ms=bEF7I z*U&^2E9;0ZpFVp1`;A|zQ_J@syqFBrS|e|7jeLgb*@TT15F8VPJw@om(^pbTF+6^eozM=flIG07Hs&jG*6bLpS?a1=`rclhfpb%E{t8?W b*8!EE0mRoZ+FIxrg7hfIPb$5T3U%clBwk*a literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/gm_6.jbl b/econml/data/input_dynamicdgp/gm_6.jbl new file mode 100644 index 0000000000000000000000000000000000000000..fbdfd1f70169bb966dbef2f3c10dfbb88606f956 GIT binary patch literal 1067 zcmZ8hJ4_To7`{8+3ko7WKprXx2g>mf6%@HM6ht>%l*G7+#W1@!uq(T>`_JqGS6qS^ z9wypY8Dl|VjCO`#E0tQ=5KCjJu^`6E#zbf5&hXe`lmDCl|Nh7K&ny%fe8wi2(S&2o z`vD^fG>rq!Elg1XQN{|uc-}*SNauZs<{%c=sv?mHMTYBmE+y3GP^8ARA_4lY4aH;0 zoBH_Huh)OH3IQ=PEj(v2gOah4Hy?hlz1@DSm5T!h&m+qx3`*6ERw@}SYWYy6Mzu;| zh6eI5!w!dX=`iCEOgU7@{0!5K>mcr9pMa9q6cI9qnM9Zml@GNtfy@%7yPQCk8qsQY zY-CZ9c^2DnKvlGAaUe?+?HdI7S&zU$tyrK_fo(&z%r{4@Y18K-Qq)+J&nN52BZtzG z#$&{=xaHD9L2qQQwS`6zoTq+8jCK&{Et8ys+v%nGKo~zrIo}hu_ zWszqYLNFyA8rK-Z**gq-9$^X`6{;M|!ST3^=Yek6m~*H%WS~MbICNK_K`Rj%xrs${ zC)C&yG)ll_86iQF78Qr1&@8cxoIp!~L0Sr&)MCPAY7kDT5jDC3rzfB_t`^C_VQ5RI z(>MN#PyW=14`+1QBDQUJ4ml3?pnWyayI0@iy2hVL{viF4=bk5}uK6)ShZ*(~&p~J0 ztT!7>QKcGPhAy+oY&KiWRx>21rjIt7GE19V9p5K!#ouj8ZFa=t2+m5Hb2;eVMH5z> z*hBQ9wWI56Pb@h(`fY7vdoiiiiS~BMXPB-{_)Hc-k0k8PLEk>Y{yl_awfcI`5J}x$ zesz8QQ(HYZJ+yAQB?{qFT<4y7?#%o6`lbk W)-c>!xGDwdP4o{W`d}o`6aN5t@mQ4r literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/lognorm_neg_0.jbl b/econml/data/input_dynamicdgp/lognorm_neg_0.jbl new file mode 100644 index 0000000000000000000000000000000000000000..21e84be89efa9f7d19a00c07f17255034af16e3a GIT binary patch literal 164 zcmZo*PR=XMEvVE>&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge0(t;q CfI2P! literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/lognorm_neg_2.jbl b/econml/data/input_dynamicdgp/lognorm_neg_2.jbl new file mode 100644 index 0000000000000000000000000000000000000000..f4f1cc696565109f642e42809d06643f6497d3c9 GIT binary patch literal 164 zcmZo*PR=XMEvVE>&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge>!oDNTt3(ra&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge~#d{Vmn+P;vtt&lIMkiWf9Ko0N(y3 literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/lognorm_pos_1.jbl b/econml/data/input_dynamicdgp/lognorm_pos_1.jbl new file mode 100644 index 0000000000000000000000000000000000000000..3e1fa82367247d794c5f2f3ec899315494630f5e GIT binary patch literal 164 zcmZo*PR=XMEvVE>&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge(FXU}2&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Gej;_Yqi>)zWJa&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&7r$lqQlpa%d* C4mnT& literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/lognorm_pos_4.jbl b/econml/data/input_dynamicdgp/lognorm_pos_4.jbl new file mode 100644 index 0000000000000000000000000000000000000000..4728440281e2251d55ded4904a0e5f588ee671dc GIT binary patch literal 164 zcmZo*PR=XMEvVE>&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&7r$lqQlpa%e4 CBROFJ literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/lognorm_pos_6.jbl b/econml/data/input_dynamicdgp/lognorm_pos_6.jbl new file mode 100644 index 0000000000000000000000000000000000000000..ebed90e543979a438e97a2b118b66456876d09c5 GIT binary patch literal 164 zcmZo*PR=XMEvVE>&M!*U%Pq|*$xJLNO0486PEO28EaEC;fXH*DlvEa^auqU0FflMN zq*)X)c{6x3wihx76|!h}Ge&Px$kSfPs|NsaeJdpZ literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/n_2.jbl b/econml/data/input_dynamicdgp/n_2.jbl new file mode 100644 index 0000000000000000000000000000000000000000..d9a912dda9ac6a45877843fb379db7d2c9c4bf93 GIT binary patch literal 139 zcmZo*_L??}i6J?!G`FBqFFC&`RWG+RrzA76s3@_Lt2j9^C$WgD5Gv1=Qc_uv%2mi1 z!NkD8kZDrL(nWYsZ!qryD9aPBEUdXEl0E*Kr2LJ#7 literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/n_4.jbl b/econml/data/input_dynamicdgp/n_4.jbl new file mode 100644 index 0000000000000000000000000000000000000000..de5e2691a4d017b6fe3bf8f00aec23d3921fb8bb GIT binary patch literal 139 zcmZo*_L??}i6J?!G`FBqFFC&`RWG+RrzA76s3@_Lt2j9^C$WgD5Gv1=Qc_uv%2mi1 z!NkD8kZDrL(ndKu;c_CL@A$L$APkSM+9sq-`EDHbt literal 0 HcmV?d00001 diff --git a/econml/data/input_dynamicdgp/n_5.jbl b/econml/data/input_dynamicdgp/n_5.jbl new file mode 100644 index 0000000000000000000000000000000000000000..e60b88978b95b303f53c8b690784c55a3073a743 GIT binary patch literal 139 zcmZo*_L??}i6J?!G`FBqFFC&`RWG+RrzA76s3@_Lt2j9^C$WgD5Gv1=Qc_uv%2mi1 z!NkD8kZDrL\n", + "\n", + "# Long-Term Return-on-Investment at Microsoft via Short-Term Proxies\n", + "\n", + "\n", + "Policy makers typically face the problem of wanting to estimate the treatment effect of some new incentives on long-run downstream interests. However, we only have historical data of older treatment options, and we haven't seen the long-run play out yet. We assume access to a long-term dataset where only past treatments were administered and a short-term dataset where novel treatments have been administered. We propose a surrogate based approach where we assume that the long-term effect is channeled through a multitude of available short-term proxies. Our work combines three major recent techniques in the causal machine learning literature: **surrogate indices**, **dynamic treatment effect estimation** and **double machine learning**, in a unified\n", + "pipeline. For more details, see this paper [here](https://arxiv.org/pdf/2103.08390.pdf).\n", + "\n", + "In this case study, we will show you how to apply this unified pipeline to a ROI estimation problem at Microsoft. These methodologies have already been implemented into our [EconML](https://aka.ms/econml) library and you could do it with only a few lines of code." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Summary\n", + "\n", + "1. [Background](#Background)\n", + "2. [Data](#Data)\n", + "3. [Do Dynamic Adjustment with EconML](#Do-Dynamic-Adjustment-with-EconML)\n", + "4. [Train Surrogate Index](#Train-Surrogate-Index)\n", + "5. [Run DML to learn ROI with EconML](#Run-DML-to-learn-ROI-with-EconML)\n", + "6. [Model Evaluation](#Model-Evaluation)\n", + "7. [Conclusions](#Conclusions)" + ] + }, + { + "attachments": { + "causal_graph.PNG": { + "image/png": "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" + }, + "pipeline.PNG": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Background\n", + "\n", + "Microsoft provides multiple montetary and resource investments to enterprice customers in support of products adoption, the sales manager would like to know which of these programs (\"investments\") are more successful than others? Specifically, we are interested in identifying the average treatment effect of each investment at some period $t$, on the cumulative outcome in the subsequent $m$ months. \n", + "\n", + "There are a few challenges to answer this question. First of all, we haven't fully observed the long-term revenue yet and we don't want to wait that long to evaluate a program. In addition, a careful causal modeling is required to correctly attribute the long-term ROI of multiple programs in a holistic manner, avoiding the biased estimate coming from confounding effect or double counting issues. \n", + "\n", + "The causal graph below shows how to frame this problem:\n", + "\n", + "![causal_graph.PNG](attachment:causal_graph.PNG)\n", + "\n", + "**Methodology:** Our proposed adjusted surrogate index approach could address all the chanllenges above by assuming the long-term effect is channeled through some short-term observed surrogates and employing a dynamic adjustment step (`DynamicDML`) to the surrogate model in order to get rid of the effect from future investment, finally applying double machine learning (`DML`) techniques to estimate the ROI. \n", + "\n", + "The pipeline below tells you how to solve this problem step by step:\n", + "![pipeline.PNG](attachment:pipeline.PNG)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# imports\n", + "from econml.data.dynamic_panel_dgp import SemiSynthetic\n", + "from sklearn.linear_model import LassoCV, MultiTaskLassoCV\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "The **semi-synthetic data*** is comprised of 4 components:\n", + " * **Surrogates:** short-term metrics that could represent long-term revenue\n", + " * **Treatments:** different types of monetary investments to the end customers\n", + " * **Outcomes:** cumulative long-term revenue\n", + " * **Controls:** lagged surrogates and treatments, other time-invariant controls (e.g. demographics)\n", + "\n", + "To build the semi-synthetic data we estimate a series of moments from a real-world dataset: a full covariance matrix of\n", + "all surrogates, treatments, and controls in one period and a series of linear prediction models (lassoCV) of each surrogate and\n", + "treatment on a set of 6 lags of each treatment, 6 lags of each surrogate, and time-invariant controls. Using these values, we draw new parameters from distributions matching the key characteristics of each family of parameters. Finally, we use these new\n", + "parameters to simulate surrogates, treatments, and controls by drawing a set of initial values from the covariance matrix and\n", + "forward simulating to match intertemporal relationships from the transformed prediction models. We use one surrogate to be the outcome of interests. Then we consider the effect of each treatment in period $t$ on the cumulative sum of outcome from following 4 periods. We can calculate the true treatment effects in the semi-synthetic data as a function of parameters from the linear prediction models.\n", + "\n", + "The input data is in a **panel format**. Each panel corresponds to one company and the different rows in a panel correspond to different time period. \n", + "\n", + "Example:\n", + "\n", + "||Company|Year|Features|Controls/Surrogates|T1|T2|T3|AdjRev|\n", + "|---|---|---|---|---|---|---|---|---|\n", + "|1|A|2018|...|...|\\$1,000|...|...|\\$10,000|\n", + "|2|A|2019|...|...|\\$2,000|...|...|\\$12,000|\n", + "|3|A|2020|...|...|\\$3,000|...|...|\\$15,000|\n", + "|4|A|2021|...|...|\\$3,000|...|...|\\$18,000|\n", + "|5|B|2018|...|...|\\$0|...|...|\\$5,000|\n", + "|6|B|2019|...|...|\\$1,000|...|...|\\$10,000|\n", + "|7|B|2020|...|...|\\$0|...|...|\\$7,000|\n", + "|8|B|2021|...|...|\\$1,200|...|...|\\$12,000|\n", + "|9|C|2018|...|...|\\$1,000|...|...|\\$20,000|\n", + "|10|C|2019|...|...|\\$1,500|...|...|\\$25,000|\n", + "|11|C|2020|...|...|\\$500|...|...|\\$18,000|\n", + "|12|C|2021|...|...|\\$500|...|...|\\$20,000|\n", + " \n", + " **For confidentiality reason, the data used in this case study is synthetically generated and the feature distributions don't exactly correspond to real distributions.*" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# generate historical dataset (training purpose)\n", + "np.random.seed(43)\n", + "dgp = SemiSynthetic()\n", + "dgp.create_instance()\n", + "n_periods = 4\n", + "n_units = 5000\n", + "n_treatments = dgp.n_treatments\n", + "random_seed = 43\n", + "thetas = np.random.uniform(0, 2, size=(dgp.n_proxies, n_treatments))\n", + "\n", + "panelX, panelT, panelY, panelGroups, true_effect = dgp.gen_data(\n", + " n_units, n_periods, thetas, random_seed\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Outcome shape: (5000, 4)\n", + "Treatment shape: (5000, 4, 3)\n", + "Controls shape: (5000, 4, 89)\n" + ] + } + ], + "source": [ + "# print panel data shape\n", + "print(\"Outcome shape: \", panelY.shape)\n", + "print(\"Treatment shape: \", panelT.shape)\n", + "print(\"Controls shape: \", panelX.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# generate new dataset (testing purpose)\n", + "panelXnew, panelTnew, panelYnew, panelGroupsnew, true_effect_new = dgp.gen_data(\n", + " n_units, n_periods, thetas, random_seed\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True Long-term Effect for each investment: [2.78577429 3.93764671 0.74981862]\n" + ] + } + ], + "source": [ + "# print true long term effect\n", + "true_longterm_effect = np.sum(true_effect_new, axis=0)\n", + "print(\"True Long-term Effect for each investment: \", true_longterm_effect)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Do Dynamic Adjustment with EconML\n", + "From the causal graph above, we could see we want to first remove the effects of future incentives from the historical outcomes to create an **adjusted long-term revenue** as if those future incentives never happened.\n", + "\n", + "EconML's `DynamicDML` estimator is an extension of Double Machine Learning approach to **dynamically estimate the period effect of treatments assigned sequentially over time period**. In this scenario, it could help us to adjust the cumulative revenue by subtracting the period effect of all of the investments after the target investment.\n", + "\n", + "For more details about `DynamicDML`, please read this [paper](https://arxiv.org/pdf/2002.07285.pdf). " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Helper function to reshape the panel data\n", + "def long(x): # reshape the panel data to (n_units * n_periods, -1)\n", + " n_units = x.shape[0]\n", + " n_periods = x.shape[1]\n", + " return (\n", + " x.reshape(n_units * n_periods)\n", + " if np.ndim(x) == 2\n", + " else x.reshape(n_units * n_periods, -1)\n", + " )\n", + "\n", + "\n", + "def wide(x): # reshape the panel data to (n_units, n_periods * d_x)\n", + " n_units = x.shape[0]\n", + " return x.reshape(n_units, -1)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# on historical data construct adjusted outcomes\n", + "from econml.dynamic.dml import DynamicDML\n", + "\n", + "panelYadj = panelY.copy()\n", + "\n", + "est = DynamicDML(\n", + " model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=2\n", + ")\n", + "for t in range(1, n_periods): # for each target period 1...m\n", + " # learn period effect for each period treatment on target period t\n", + " est.fit(\n", + " long(panelY[:, 1 : t + 1]),\n", + " long(panelT[:, 1 : t + 1, :]), # reshape data to long format\n", + " X=None,\n", + " W=long(panelX[:, 1 : t + 1, :]),\n", + " groups=long(panelGroups[:, 1 : t + 1]),\n", + " )\n", + " # remove effect of observed treatments\n", + " T1 = wide(panelT[:, 1 : t + 1, :])\n", + " panelYadj[:, t] = panelY[:, t] - est.effect(\n", + " T0=np.zeros_like(T1), T1=T1\n", + " ) # reshape data to wide format" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train Surrogate Index\n", + "Once we have the adjusted outcome, we'd like to train any ML model to learn the relationship between short-term surrogates and long-term revenue from the historical dataset, assuming the treatment effect of investments on long-term revenue could **only** go through short-term surrogates, and the **relationship keeps the same** between the historical dataset and the new dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# train surrogate index on historical dataset\n", + "S=panelX[:,1,[6*(i+1)-1 for i in range(4)]] # surrogates columns\n", + "XS = np.hstack(\n", + " [panelX[:, 0], S]\n", + ") # concatenate controls and surrogates from historical dataset\n", + "TotalYadj = np.sum(panelYadj, axis=1) # total revenue from historical dataset\n", + "adjusted_proxy_model = LassoCV().fit(\n", + " XS, TotalYadj\n", + ") # train proxy model from historical dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# predict new long term revenue\n", + "Snew=panelXnew[:,1,[6*(i+1)-1 for i in range(4)]] # surrogates columns\n", + "XSnew = np.hstack(\n", + " [panelXnew[:, 0], Snew]\n", + ") # concatenate controls and surrogates from new dataset\n", + "sindex_adj = adjusted_proxy_model.predict(XSnew)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run DML to learn ROI with EconML\n", + "Finally we will call `LinearDML` estimator from EconML to learn the treatment effect of multiple investments on the adjusted surrogate index in new dataset. `LinearDML` is a two stage machine learning models for estimating **(heterogeneous) treatment effects** when all potential confounders are observed, it leverages the machine learning power to deal with **high dimensional dataset** and still be able to construct **confidence intervals**. \n", + "\n", + "For more details, please read this [paper](https://arxiv.org/pdf/1608.00060.pdf). " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True Long-term Effect for each investment: [2.78577429 3.93764671 0.74981862]\n", + "Coefficient Results: X is None, please call intercept_inference to learn the constant!\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|T0 2.826 0.007 405.552 0.0 2.812 2.84
cate_intercept|T1 4.03 0.023 174.383 0.0 3.985 4.075
cate_intercept|T2 0.676 0.035 19.407 0.0 0.608 0.745


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" + ], + "text/plain": [ + "\n", + "\"\"\"\n", + " CATE Intercept Results \n", + "========================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "------------------------------------------------------------------------\n", + "cate_intercept|T0 2.826 0.007 405.552 0.0 2.812 2.84\n", + "cate_intercept|T1 4.03 0.023 174.383 0.0 3.985 4.075\n", + "cate_intercept|T2 0.676 0.035 19.407 0.0 0.608 0.745\n", + "------------------------------------------------------------------------\n", + "\n", + "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", + "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", + "where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:\n", + "$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$\n", + "where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.\n", + "\"\"\"" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# learn treatment effect on surrogate index on new dataset\n", + "from econml.dml import LinearDML\n", + "\n", + "adjsurr_est = LinearDML(\n", + " model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=3\n", + ")\n", + "# fit treatment_0 on total revenue from new dataset\n", + "adjsurr_est.fit(sindex_adj, panelTnew[:, 0], X=None, W=panelXnew[:, 0])\n", + "# print treatment effect summary\n", + "print(\"True Long-term Effect for each investment: \", true_longterm_effect)\n", + "adjsurr_est.summary(alpha=0.05)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# save the treatment effect and confidence interval\n", + "adjsurr_point_est = adjsurr_est.intercept_\n", + "adjsurr_conf_int_lb, adjsurr_conf_int_ub = adjsurr_est.intercept__interval(alpha=0.05)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Model Evaluation\n", + "Now we want to compare the proposed **adjusted surrogate index** approach with estimation from realized long-term outcome. Below we train another `LinearDML` model on the realized cumulative revenue directly, without any adjustment. And then we visualize the two models output, comparing with the ground truth." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True Long-term Effect for each investment: [2.78577429 3.93764671 0.74981862]\n", + "Coefficient Results: X is None, please call intercept_inference to learn the constant!\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|T0 6.73 0.052 129.741 0.0 6.628 6.832
cate_intercept|T1 9.754 0.132 73.643 0.0 9.494 10.014
cate_intercept|T2 3.781 0.12 31.484 0.0 3.545 4.016


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" + ], + "text/plain": [ + "\n", + "\"\"\"\n", + " CATE Intercept Results \n", + "========================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "------------------------------------------------------------------------\n", + "cate_intercept|T0 6.73 0.052 129.741 0.0 6.628 6.832\n", + "cate_intercept|T1 9.754 0.132 73.643 0.0 9.494 10.014\n", + "cate_intercept|T2 3.781 0.12 31.484 0.0 3.545 4.016\n", + "------------------------------------------------------------------------\n", + "\n", + "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", + "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", + "where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:\n", + "$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$\n", + "where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.\n", + "\"\"\"" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# learn treatment effect on direct outcome\n", + "from econml.dml import LinearDML\n", + "\n", + "direct_est = LinearDML(\n", + " model_y=LassoCV(max_iter=1000), model_t=MultiTaskLassoCV(max_iter=1000), cv=3\n", + ")\n", + "# fit treatment_0 on total revenue from new dataset\n", + "direct_est.fit(np.sum(panelYnew, axis=1), panelTnew[:, 0], X=None, W=panelXnew[:, 0])\n", + "# print treatment effect summary\n", + "print(\"True Long-term Effect for each investment: \", true_longterm_effect)\n", + "direct_est.summary(alpha=0.05)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# save the treatment effect and confidence interval\n", + "direct_point_est = direct_est.intercept_\n", + "direct_conf_int_lb, direct_conf_int_ub = direct_est.intercept__interval(alpha=0.05)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0.98, 'Error bar plot of treatment effect from different models')" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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fvjhftCrYCKCww8/3F+7JKkTsz+iQtuX/ojJB0lqJT/oVWi+BiVJfhao0Fr7GjT5IEkdzOuvv86nPvUpjjnmGE444QROOukkfvOb37RpDKtXr2bUqFENlv/yl7/cpzZ//OMfs23btprlXr167XN8kiQpW/ZH1FomHyQpQ2VrNvAfv32Wz93+N/7jt89StmbDfrWXUuKcc87h1FNPZdWqVSxbtox77rmHtWvX7lV3586d+7WvfdHUh31z8dT/sJckSQeG/ZF32B8pHJMPkpSRsjUbmLVoJZVbd3Bkn2Iqt+5g1qKV+/WB/6c//Ylu3brxla98paZs6NChfO1rXwNg7ty5nH322UycOJHTTz+dyspKzjnnHEpKSpgwYQLLly8HYObMmfzoRz+qaWPUqFGsXr2a1atXM3LkSL70pS9x7LHHMnnyZLZv3w7AsmXLGD16NKNHj+bmm29uML4rrriCxx57jDFjxnDDDTfsFc/ixYs588wza+pfcsklzJ07l9mzZ/PKK6/woQ99iA996EM167/97W8zevRoJkyYwOuvv77Px02SpM7K/oj9kfbC5IMkZWRB6Tp6FxdxWPciDongsO5F9C4uYkHpun1u87nnnmPs2LFN1iktLWX+/Pn8+c9/5uqrr+b4449n+fLlXHfddXz2s59tdh//+Mc/uPjii3nuuefo27cvCxYsAGD69OncdNNNPP30041ue/3113PKKadQVlbGZZddtlc8jbn00kt597vfzSOPPMIjjzwCwNatW5kwYQJPP/00p556Kj/96U+bjV1qTkTcHhFvRMSztcr6R8SiiPhH/m+/QsYoSQeS/RH7I+2FyQdJykh55TZ6Fde9onGv4q6UVx64oXwXX3wxo0ePZvz48TVlkyZNon///gAsWbKECy64AICJEyeyfv16Nm3a1GSbw4cPZ8yYMQCccMIJrF69mrfeeou33nqLU089FaCmzZaoHU9rdOvWreZXiT1xSAfAXOCMemVXAA+nlN4DPJxflqSDgv2RveNpDfsjB47JB0kHzh1TcjcBMLh/D7ZU1T2vcEvVTgb377HPbR577LGUlpbWLN988808/PDDVFRU1JT17Nmz2Xa6du3K7t27a5ZrXybp0EMPrbnfpUuX/T5Xs3Y8Te23vqKioppZlA9EHBJASulRoLJe8ceAO/P37wTOacuYJGVn+h+nM/2P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Y4b04z/PGcXcz5/If54zysRDS51+FRR1r1tW1D1Xvo/mz5/PBRdcwMsvv8zq1aspLy9n+PDhPPbYY5x66qn88pe/BODZZ5+tOd+wtiOPPJI33niD9evX8/bbb/P73/8eyH2Il5eX86EPfYgf/OAHbNy4kS1bttC7d282b95cs/3kyZO56aabapbLysoA6uz7D3/4Axs27H01lFdeeYXS0tI62w4dOhTInQO6Z1jm/PnzW3w8TjnllJrhh4sXL+aII47gsMMO4+STT+bee3Pnsj744IM18WzcuJF+/frRo0cPXnjhBZ588smatoqKiqiurgbg9NNPZ/78+bzxxhtArpP18ssvtziu2k4++eSac2FrD5X8yEc+wu23386WLVsAWLduHW+88QY7d+7k85//PPPmzWPkyJHMmjVrn/YrSRJgf6Qe+yOF64848kGSslIyLff3vktykzz1GZz7oN9Tvg/mzZvHN7/5zTplU6dOZd68ecyaNYvp06czcuRIRo4cyQknnFCnXkRQVFTEVVddxYknnsjAgQN53/veB8CuXbv4zGc+w8aNG0kpcemll9K3b1/OOussPv7xj3Pfffdx0003MXv2bC6++GJKSkrYuXMnp556KrfeeitXX301559/Psceeywf+MAHGDJkyF6xV1dX8/Wvf51XXnmF4uJiBgwYwK233grA17/+daZNm8acOXOYMqXl5+TOnDmTz3/+85SUlNCjRw/uvPNOgJp4fvGLX3DSSSdx1FFH0bt3b8444wxuvfVWRo4cyXvf+14mTJhQ09ZFF11ESUkJY8eO5e677+a73/0ukydPZvfu3RQVFXHzzTfXdE5a48Ybb+RTn/oUP/jBD/jYxz5WUz558mRWrFjBSSedBOQmiLrrrru49dZbOeWUU/jgBz/I6NGjGT9+PFOmTGHkyJGt3rckdXZ3nHFHoUMoPPsjddgfKVx/JBqaAbQ9GzduXGrtNUwl6UBZsWJF6//p3pH/8Jq+8MAH1AK7du3iXe96F6+99hpFRUUFiaGtvf3223Tp0oWuXbvyl7/8ha9+9as1v4ocLBp6LUbEspTSuAKF1KnYH5FUSPZHOgb7I3U58kGSslagD/k99lzeqbN80AOsWbOGadOmsXv3brp161Yz07MkSZ2W/ZE2Z3+kLpMPknSQe+GFFwodQpt7z3vew9///vdChyFJkvLs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\n", + "text/plain": [ + "

" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# plot the error bar plot of different models\n", + "plt.figure(figsize=(18, 6))\n", + "plt.subplot(1, 2, 1)\n", + "\n", + "plt.errorbar(\n", + " np.arange(n_treatments) - 0.04,\n", + " true_longterm_effect,\n", + " fmt=\"o\",\n", + " alpha=0.6,\n", + " label=\"Ground truth\",\n", + ")\n", + "plt.errorbar(\n", + " np.arange(n_treatments),\n", + " adjsurr_point_est,\n", + " yerr=(\n", + " adjsurr_conf_int_ub - adjsurr_point_est,\n", + " adjsurr_point_est - adjsurr_conf_int_lb,\n", + " ),\n", + " fmt=\"o\",\n", + " label=\"Adjusted Surrogate Index\",\n", + ")\n", + "plt.xticks(np.arange(n_treatments), [\"T0\", \"T1\", \"T2\"])\n", + "plt.ylabel(\"Effect\")\n", + "plt.legend()\n", + "\n", + "plt.subplot(1, 2, 2)\n", + "plt.errorbar(\n", + " np.arange(n_treatments) - 0.04,\n", + " true_longterm_effect,\n", + " fmt=\"o\",\n", + " alpha=0.6,\n", + " label=\"Ground truth\",\n", + ")\n", + "plt.errorbar(\n", + " np.arange(n_treatments),\n", + " adjsurr_point_est,\n", + " yerr=(\n", + " adjsurr_conf_int_ub - adjsurr_point_est,\n", + " adjsurr_point_est - adjsurr_conf_int_lb,\n", + " ),\n", + " fmt=\"o\",\n", + " label=\"Adjusted Surrogate Index\",\n", + ")\n", + "plt.errorbar(\n", + " np.arange(n_treatments) + 0.04,\n", + " direct_point_est,\n", + " yerr=(direct_conf_int_ub - direct_point_est, direct_point_est - direct_conf_int_lb),\n", + " fmt=\"o\",\n", + " label=\"Direct Model\",\n", + ")\n", + "plt.xticks(np.arange(n_treatments), [\"T0\", \"T1\", \"T2\"])\n", + "plt.ylabel(\"Effect\")\n", + "plt.legend()\n", + "plt.suptitle(\"Error bar plot of treatment effect from different models\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could see the **adjusted surrogate index** approach does a good job overcomes a common data limitation when considering long-term effects of novel treatments and expands the surrogate approach to consider a common, and previously\n", + "problematic, pattern of serially correlated treatments." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conclusions\n", + "\n", + "In this notebook, we have demonstrated the power of using EconML to:\n", + "\n", + "* estimate treatment effects in settings when multiple treatments are assigned over time and treatments can have a causal effect on future outcomes\n", + "* correct the bias coming from auto-correlation of the historical treatment policy\n", + "* use Machine Learning to enable estimation with high-dimensional surrogates and controls\n", + "* solve a complex problem using an unified pipeline with only a few lines of code\n", + "\n", + "To learn more about what EconML can do for you, visit our [website](https://aka.ms/econml), our [GitHub page](https://github.com/microsoft/EconML) or our [documentation](https://econml.azurewebsites.net/). " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From a5bd8e9c5c5283ebaf7388f0de48583cb3553f97 Mon Sep 17 00:00:00 2001 From: Maggie Hei Date: Mon, 9 Aug 2021 14:07:39 -0400 Subject: [PATCH 24/27] update setup to install jbl file --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 266aa9d16..185fdd26e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,6 +82,7 @@ exclude = [options.package_data] ; include all CSV files as data * = *.csv +* = *.jbl ; coverage configuration [coverage:run] From da8085d585c8eaa3e4ad90460b8e16bb4ae3b964 Mon Sep 17 00:00:00 2001 From: Maggie Hei Date: Mon, 9 Aug 2021 14:21:32 -0400 Subject: [PATCH 25/27] update setup to install jbl file --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 185fdd26e..0d9f2db65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,7 +82,7 @@ exclude = [options.package_data] ; include all CSV files as data * = *.csv -* = *.jbl + *.jbl ; coverage configuration [coverage:run] From ebef40b7d8898f0bc66b732b97c06809c512b612 Mon Sep 17 00:00:00 2001 From: Maggie Hei Date: Mon, 9 Aug 2021 19:54:28 -0400 Subject: [PATCH 26/27] update roi notebook --- econml/data/dynamic_panel_dgp.py | 5 +- ... at Microsoft via Short-Term Proxies.ipynb | 612 ++++++++++++++++-- 2 files changed, 552 insertions(+), 65 deletions(-) diff --git a/econml/data/dynamic_panel_dgp.py b/econml/data/dynamic_panel_dgp.py index 07c280083..82b842912 100644 --- a/econml/data/dynamic_panel_dgp.py +++ b/econml/data/dynamic_panel_dgp.py @@ -431,7 +431,10 @@ def gen_data(self, n, n_periods, thetas, random_seed): ].mean() true_effect[t, i] = ate_treated - ate_control - panelX = fn_df_control[self.columns].values.reshape(-1, n_periods, n_x) + new_index = ["proxy1", "proxy2", "proxy3", "proxy4"] + new_columns = [f"{ind}_{i}" for ind in new_index for i in range(-6, 0)] +\ + [f"demo_{i}" for i in range(47)] + panelX = fn_df_control[new_columns].values.reshape(-1, n_periods, len(new_columns)) panelT = fn_df_control[self.index[n_proxies:] ].values.reshape(-1, n_periods, n_treatments) panelY = fn_df_control[outcome].values.reshape(-1, n_periods) diff --git a/notebooks/CustomerScenarios/Case Study - Long-Term Return-on-Investment at Microsoft via Short-Term Proxies.ipynb b/notebooks/CustomerScenarios/Case Study - Long-Term Return-on-Investment at Microsoft via Short-Term Proxies.ipynb index 26b74c7e2..4c6ca7658 100644 --- a/notebooks/CustomerScenarios/Case Study - Long-Term Return-on-Investment at Microsoft via Short-Term Proxies.ipynb +++ b/notebooks/CustomerScenarios/Case Study - Long-Term Return-on-Investment at Microsoft via Short-Term Proxies.ipynb @@ -25,9 +25,10 @@ "2. [Data](#Data)\n", "3. [Do Dynamic Adjustment with EconML](#Do-Dynamic-Adjustment-with-EconML)\n", "4. [Train Surrogate Index](#Train-Surrogate-Index)\n", - "5. [Run DML to learn ROI with EconML](#Run-DML-to-learn-ROI-with-EconML)\n", + "5. [Run DML to Learn ROI with EconML](#Run-DML-to-Learn-ROI-with-EconML)\n", "6. [Model Evaluation](#Model-Evaluation)\n", - "7. [Conclusions](#Conclusions)" + "7. [Extensions -- Including Heterogeneity in Effect](#Extensions----Including-Heterogeneity-in-Effect)\n", + "8. [Conclusions](#Conclusions)" ] }, { @@ -60,9 +61,34 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 2;\n", + " var nbb_formatted_code = \"# imports\\nfrom econml.data.dynamic_panel_dgp import SemiSynthetic\\nfrom sklearn.linear_model import LassoCV, MultiTaskLassoCV\\nimport numpy as np\\nimport matplotlib.pyplot as plt\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# imports\n", "from econml.data.dynamic_panel_dgp import SemiSynthetic\n", @@ -113,9 +139,34 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 3;\n", + " var nbb_formatted_code = \"# generate historical dataset (training purpose)\\nnp.random.seed(43)\\ndgp = SemiSynthetic()\\ndgp.create_instance()\\nn_periods = 4\\nn_units = 5000\\nn_treatments = dgp.n_treatments\\nrandom_seed = 43\\nthetas = np.random.uniform(0, 2, size=(dgp.n_proxies, n_treatments))\\n\\npanelX, panelT, panelY, panelGroups, true_effect = dgp.gen_data(\\n n_units, n_periods, thetas, random_seed\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# generate historical dataset (training purpose)\n", "np.random.seed(43)\n", @@ -134,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -143,8 +194,32 @@ "text": [ "Outcome shape: (5000, 4)\n", "Treatment shape: (5000, 4, 3)\n", - "Controls shape: (5000, 4, 89)\n" + "Controls shape: (5000, 4, 71)\n" ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 4;\n", + " var nbb_formatted_code = \"# print panel data shape\\nprint(\\\"Outcome shape: \\\", panelY.shape)\\nprint(\\\"Treatment shape: \\\", panelT.shape)\\nprint(\\\"Controls shape: \\\", panelX.shape)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -156,27 +231,77 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 5;\n", + " var nbb_formatted_code = \"# generate new dataset (testing purpose)\\nthetas_new = np.random.uniform(0, 2, size=(dgp.n_proxies, n_treatments))\\npanelXnew, panelTnew, panelYnew, panelGroupsnew, true_effect_new = dgp.gen_data(\\n n_units, n_periods, thetas_new, random_seed\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# generate new dataset (testing purpose)\n", + "thetas_new = np.random.uniform(0, 2, size=(dgp.n_proxies, n_treatments))\n", "panelXnew, panelTnew, panelYnew, panelGroupsnew, true_effect_new = dgp.gen_data(\n", - " n_units, n_periods, thetas, random_seed\n", + " n_units, n_periods, thetas_new, random_seed\n", ")" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "True Long-term Effect for each investment: [2.78577429 3.93764671 0.74981862]\n" + "True Long-term Effect for each investment: [0.90994672 0.709811 2.45310877]\n" ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 6;\n", + " var nbb_formatted_code = \"# print true long term effect\\ntrue_longterm_effect = np.sum(true_effect_new, axis=0)\\nprint(\\\"True Long-term Effect for each investment: \\\", true_longterm_effect)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -199,9 +324,34 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 7;\n", + " var nbb_formatted_code = \"# Helper function to reshape the panel data\\ndef long(x): # reshape the panel data to (n_units * n_periods, -1)\\n n_units = x.shape[0]\\n n_periods = x.shape[1]\\n return (\\n x.reshape(n_units * n_periods)\\n if np.ndim(x) == 2\\n else x.reshape(n_units * n_periods, -1)\\n )\\n\\n\\ndef wide(x): # reshape the panel data to (n_units, n_periods * d_x)\\n n_units = x.shape[0]\\n return x.reshape(n_units, -1)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# Helper function to reshape the panel data\n", "def long(x): # reshape the panel data to (n_units * n_periods, -1)\n", @@ -221,9 +371,34 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 8;\n", + " var nbb_formatted_code = \"# on historical data construct adjusted outcomes\\nfrom econml.dynamic.dml import DynamicDML\\n\\npanelYadj = panelY.copy()\\n\\nest = DynamicDML(\\n model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=2\\n)\\nfor t in range(1, n_periods): # for each target period 1...m\\n # learn period effect for each period treatment on target period t\\n est.fit(\\n long(panelY[:, 1 : t + 1]),\\n long(panelT[:, 1 : t + 1, :]), # reshape data to long format\\n X=None,\\n W=long(panelX[:, 1 : t + 1, :]),\\n groups=long(panelGroups[:, 1 : t + 1]),\\n )\\n # remove effect of observed treatments\\n T1 = wide(panelT[:, 1 : t + 1, :])\\n panelYadj[:, t] = panelY[:, t] - est.effect(\\n T0=np.zeros_like(T1), T1=T1\\n ) # reshape data to wide format\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# on historical data construct adjusted outcomes\n", "from econml.dynamic.dml import DynamicDML\n", @@ -259,14 +434,38 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 9;\n", + " var nbb_formatted_code = \"# train surrogate index on historical dataset\\nXS = np.hstack(\\n [panelX[:, 1], panelYadj[:, :1]]\\n) # concatenate controls and surrogates from historical dataset\\nTotalYadj = np.sum(panelYadj, axis=1) # total revenue from historical dataset\\nadjusted_proxy_model = LassoCV().fit(\\n XS, TotalYadj\\n) # train proxy model from historical dataset\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# train surrogate index on historical dataset\n", - "S=panelX[:,1,[6*(i+1)-1 for i in range(4)]] # surrogates columns\n", "XS = np.hstack(\n", - " [panelX[:, 0], S]\n", + " [panelX[:, 1], panelYadj[:, :1]]\n", ") # concatenate controls and surrogates from historical dataset\n", "TotalYadj = np.sum(panelYadj, axis=1) # total revenue from historical dataset\n", "adjusted_proxy_model = LassoCV().fit(\n", @@ -276,14 +475,38 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 10;\n", + " var nbb_formatted_code = \"# predict new long term revenue\\nXSnew = np.hstack(\\n [panelXnew[:, 1], panelYnew[:, :1]]\\n) # concatenate controls and surrogates from new dataset\\nsindex_adj = adjusted_proxy_model.predict(XSnew)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# predict new long term revenue\n", - "Snew=panelXnew[:,1,[6*(i+1)-1 for i in range(4)]] # surrogates columns\n", "XSnew = np.hstack(\n", - " [panelXnew[:, 0], Snew]\n", + " [panelXnew[:, 1], panelYnew[:, :1]]\n", ") # concatenate controls and surrogates from new dataset\n", "sindex_adj = adjusted_proxy_model.predict(XSnew)" ] @@ -292,7 +515,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Run DML to learn ROI with EconML\n", + "# Run DML to Learn ROI with EconML\n", "Finally we will call `LinearDML` estimator from EconML to learn the treatment effect of multiple investments on the adjusted surrogate index in new dataset. `LinearDML` is a two stage machine learning models for estimating **(heterogeneous) treatment effects** when all potential confounders are observed, it leverages the machine learning power to deal with **high dimensional dataset** and still be able to construct **confidence intervals**. \n", "\n", "For more details, please read this [paper](https://arxiv.org/pdf/1608.00060.pdf). " @@ -300,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": { "scrolled": true }, @@ -309,7 +532,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "True Long-term Effect for each investment: [2.78577429 3.93764671 0.74981862]\n", + "True Long-term Effect for each investment: [0.90994672 0.709811 2.45310877]\n", "Coefficient Results: X is None, please call intercept_inference to learn the constant!\n" ] }, @@ -319,30 +542,30 @@ "\n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|T0 2.826 0.007 405.552 0.0 2.812 2.84cate_intercept|T0 0.83 0.015 57.214 0.0 0.802 0.858
cate_intercept|T1 4.03 0.023 174.383 0.0 3.985 4.075cate_intercept|T1 0.677 0.028 23.767 0.0 0.621 0.733
cate_intercept|T2 0.676 0.035 19.407 0.0 0.608 0.745cate_intercept|T2 2.438 0.035 69.711 0.0 2.369 2.507


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], "text/plain": [ "\n", "\"\"\"\n", - " CATE Intercept Results \n", - "========================================================================\n", - " point_estimate stderr zstat pvalue ci_lower ci_upper\n", - "------------------------------------------------------------------------\n", - "cate_intercept|T0 2.826 0.007 405.552 0.0 2.812 2.84\n", - "cate_intercept|T1 4.03 0.023 174.383 0.0 3.985 4.075\n", - "cate_intercept|T2 0.676 0.035 19.407 0.0 0.608 0.745\n", - "------------------------------------------------------------------------\n", + " CATE Intercept Results \n", + "=======================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------------------\n", + "cate_intercept|T0 0.83 0.015 57.214 0.0 0.802 0.858\n", + "cate_intercept|T1 0.677 0.028 23.767 0.0 0.621 0.733\n", + "cate_intercept|T2 2.438 0.035 69.711 0.0 2.369 2.507\n", + "-----------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", @@ -352,9 +575,33 @@ "\"\"\"" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 11;\n", + " var nbb_formatted_code = \"# learn treatment effect on surrogate index on new dataset\\nfrom econml.dml import LinearDML\\n\\nadjsurr_est = LinearDML(\\n model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=3\\n)\\n# fit treatment_0 on total revenue from new dataset\\nadjsurr_est.fit(sindex_adj, panelTnew[:, 0], X=None, W=panelXnew[:, 0])\\n# print treatment effect summary\\nprint(\\\"True Long-term Effect for each investment: \\\", true_longterm_effect)\\nadjsurr_est.summary(alpha=0.05)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -373,9 +620,34 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 12;\n", + " var nbb_formatted_code = \"# save the treatment effect and confidence interval\\nadjsurr_point_est = adjsurr_est.intercept_\\nadjsurr_conf_int_lb, adjsurr_conf_int_ub = adjsurr_est.intercept__interval(alpha=0.05)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# save the treatment effect and confidence interval\n", "adjsurr_point_est = adjsurr_est.intercept_\n", @@ -392,14 +664,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "True Long-term Effect for each investment: [2.78577429 3.93764671 0.74981862]\n", + "True Long-term Effect for each investment: [0.90994672 0.709811 2.45310877]\n", "Coefficient Results: X is None, please call intercept_inference to learn the constant!\n" ] }, @@ -409,30 +681,30 @@ "\n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|T0 6.73 0.052 129.741 0.0 6.628 6.832cate_intercept|T0 2.227 0.039 56.865 0.0 2.15 2.304
cate_intercept|T1 9.754 0.132 73.643 0.0 9.494 10.014cate_intercept|T1 1.561 0.226 6.911 0.0 1.118 2.004
cate_intercept|T2 3.781 0.12 31.484 0.0 3.545 4.016cate_intercept|T2 4.335 0.209 20.748 0.0 3.926 4.745


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" ], "text/plain": [ "\n", "\"\"\"\n", - " CATE Intercept Results \n", - "========================================================================\n", - " point_estimate stderr zstat pvalue ci_lower ci_upper\n", - "------------------------------------------------------------------------\n", - "cate_intercept|T0 6.73 0.052 129.741 0.0 6.628 6.832\n", - "cate_intercept|T1 9.754 0.132 73.643 0.0 9.494 10.014\n", - "cate_intercept|T2 3.781 0.12 31.484 0.0 3.545 4.016\n", - "------------------------------------------------------------------------\n", + " CATE Intercept Results \n", + "=======================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------------------\n", + "cate_intercept|T0 2.227 0.039 56.865 0.0 2.15 2.304\n", + "cate_intercept|T1 1.561 0.226 6.911 0.0 1.118 2.004\n", + "cate_intercept|T2 4.335 0.209 20.748 0.0 3.926 4.745\n", + "-----------------------------------------------------------------------\n", "\n", "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", @@ -442,9 +714,33 @@ "\"\"\"" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 13;\n", + " var nbb_formatted_code = \"# learn treatment effect on direct outcome\\nfrom econml.dml import LinearDML\\n\\ndirect_est = LinearDML(\\n model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=3\\n)\\n# fit treatment_0 on total revenue from new dataset\\ndirect_est.fit(np.sum(panelYnew, axis=1), panelTnew[:, 0], X=None, W=panelXnew[:, 0])\\n# print treatment effect summary\\nprint(\\\"True Long-term Effect for each investment: \\\", true_longterm_effect)\\ndirect_est.summary(alpha=0.05)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -452,7 +748,7 @@ "from econml.dml import LinearDML\n", "\n", "direct_est = LinearDML(\n", - " model_y=LassoCV(max_iter=1000), model_t=MultiTaskLassoCV(max_iter=1000), cv=3\n", + " model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=3\n", ")\n", "# fit treatment_0 on total revenue from new dataset\n", "direct_est.fit(np.sum(panelYnew, axis=1), panelTnew[:, 0], X=None, W=panelXnew[:, 0])\n", @@ -463,9 +759,34 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 14;\n", + " var nbb_formatted_code = \"# save the treatment effect and confidence interval\\ndirect_point_est = direct_est.intercept_\\ndirect_conf_int_lb, direct_conf_int_ub = direct_est.intercept__interval(alpha=0.05)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# save the treatment effect and confidence interval\n", "direct_point_est = direct_est.intercept_\n", @@ -474,7 +795,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -483,13 +804,13 @@ "Text(0.5, 0.98, 'Error bar plot of treatment effect from different models')" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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fvjhftCrYCKCww8/3F+7JKkTsz+iQtuX/ojJB0lqJT/oVWi+BiVJfhao0Fr7GjT5IEkdzOuvv86nPvUpjjnmGE444QROOukkfvOb37RpDKtXr2bUqFENlv/yl7/cpzZ//OMfs23btprlXr167XN8kiQpW/ZH1FomHyQpQ2VrNvAfv32Wz93+N/7jt89StmbDfrWXUuKcc87h1FNPZdWqVSxbtox77rmHtWvX7lV3586d+7WvfdHUh31z8dT/sJckSQeG/ZF32B8pHJMPkpSRsjUbmLVoJZVbd3Bkn2Iqt+5g1qKV+/WB/6c//Ylu3brxla98paZs6NChfO1rXwNg7ty5nH322UycOJHTTz+dyspKzjnnHEpKSpgwYQLLly8HYObMmfzoRz+qaWPUqFGsXr2a1atXM3LkSL70pS9x7LHHMnnyZLZv3w7AsmXLGD16NKNHj+bmm29uML4rrriCxx57jDFjxnDDDTfsFc/ixYs588wza+pfcsklzJ07l9mzZ/PKK6/woQ99iA996EM167/97W8zevRoJkyYwOuvv77Px02SpM7K/oj9kfbC5IMkZWRB6Tp6FxdxWPciDongsO5F9C4uYkHpun1u87nnnmPs2LFN1iktLWX+/Pn8+c9/5uqrr+b4449n+fLlXHfddXz2s59tdh//+Mc/uPjii3nuuefo27cvCxYsAGD69OncdNNNPP30041ue/3113PKKadQVlbGZZddtlc8jbn00kt597vfzSOPPMIjjzwCwNatW5kwYQJPP/00p556Kj/96U+bjV1qTkTcHhFvRMSztcr6R8SiiPhH/m+/QsYoSQeS/RH7I+2FyQdJykh55TZ6Fde9onGv4q6UVx64oXwXX3wxo0ePZvz48TVlkyZNon///gAsWbKECy64AICJEyeyfv16Nm3a1GSbw4cPZ8yYMQCccMIJrF69mrfeeou33nqLU089FaCmzZaoHU9rdOvWreZXiT1xSAfAXOCMemVXAA+nlN4DPJxflqSDgv2RveNpDfsjB47JB0kHzh1TcjcBMLh/D7ZU1T2vcEvVTgb377HPbR577LGUlpbWLN988808/PDDVFRU1JT17Nmz2Xa6du3K7t27a5ZrXybp0EMPrbnfpUuX/T5Xs3Y8Te23vqKioppZlA9EHBJASulRoLJe8ceAO/P37wTOacuYJGVn+h+nM/2P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Y4b04z/PGcXcz5/If54zysRDS51+FRR1r1tW1D1Xvo/mz5/PBRdcwMsvv8zq1aspLy9n+PDhPPbYY5x66qn88pe/BODZZ5+tOd+wtiOPPJI33niD9evX8/bbb/P73/8eyH2Il5eX86EPfYgf/OAHbNy4kS1bttC7d282b95cs/3kyZO56aabapbLysoA6uz7D3/4Axs27H01lFdeeYXS0tI62w4dOhTInQO6Z1jm/PnzW3w8TjnllJrhh4sXL+aII47gsMMO4+STT+bee3Pnsj744IM18WzcuJF+/frRo0cPXnjhBZ588smatoqKiqiurgbg9NNPZ/78+bzxxhtArpP18ssvtziu2k4++eSac2FrD5X8yEc+wu23386WLVsAWLduHW+88QY7d+7k85//PPPmzWPkyJHMmjVrn/YrSRJgf6Qe+yOF64848kGSslIyLff3vktykzz1GZz7oN9Tvg/mzZvHN7/5zTplU6dOZd68ecyaNYvp06czcuRIRo4cyQknnFCnXkRQVFTEVVddxYknnsjAgQN53/veB8CuXbv4zGc+w8aNG0kpcemll9K3b1/OOussPv7xj3Pfffdx0003MXv2bC6++GJKSkrYuXMnp556KrfeeitXX301559/Psceeywf+MAHGDJkyF6xV1dX8/Wvf51XXnmF4uJiBgwYwK233grA17/+daZNm8acOXOYMqXl5+TOnDmTz3/+85SUlNCjRw/uvPNOgJp4fvGLX3DSSSdx1FFH0bt3b8444wxuvfVWRo4cyXvf+14mTJhQ09ZFF11ESUkJY8eO5e677+a73/0ukydPZvfu3RQVFXHzzTfXdE5a48Ybb+RTn/oUP/jBD/jYxz5WUz558mRWrFjBSSedBOQmiLrrrru49dZbOeWUU/jgBz/I6NGjGT9+PFOmTGHkyJGt3rckdXZ3nHFHoUMoPPsjddgfKVx/JBqaAbQ9GzduXGrtNUwl6UBZsWJF6//p3pH/8Jq+8MAH1AK7du3iXe96F6+99hpFRUUFiaGtvf3223Tp0oWuXbvyl7/8ha9+9as1v4ocLBp6LUbEspTSuAKF1KnYH5FUSPZHOgb7I3U58kGSslagD/k99lzeqbN80AOsWbOGadOmsXv3brp161Yz07MkSZ2W/ZE2Z3+kLpMPknSQe+GFFwodQpt7z3vew9///vdChyFJkvLs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\n", + "image/png": "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Yx/5yEd46623mryf+fPn89RTTzV5vUGDBvHmm2/u037XXXdx4oknUlZWxtChQ/nNb37T5G0Xy4033tjkdQqPcWP16NGjyfuRpEL9e3dja+Wuam1bK3fRv3e33MSj34Kd26uvtHN7rl1qIfZFWp59EUlqGQYTNRXpVNpZs2Zx+umnM2vWrEYt/+CDD3LYYYc1eT/N7QzUZu3atXz3u99l4cKFLF26lKeffpqysrJGr79r1656p/dXczoDkpSF8SP7sqVyJ5u372RPSmzevpMtlTsZP7JvboG319a+Yl3tat/si1SxLyJJHYPBRKEinUq7detWFi5cyE9/+lPuueeeqvbt27dzySWXMGTIEC6++GK2b3/v27K93xqsWrWKoUOHVrX/8Ic/ZOrUqQBMnz6d448/nrKyMi655BJWrVrFHXfcwc0338zw4cN54okn2LBhA+PHj+fkk0/m5JNP5sknnwRg48aNjB07lhNOOIErr7yS3F1aq1u/fj09e/asSul79OjB4MGDgepp/5tvvsmgQYMAmDlzJh/96Ec555xzOPfcc/eZrqio4KKLLqKsrIzRo0ezdOlSADZs2MCYMWOq6hk4cGDVtyYXXXQRJ510EieccAJ33nknANdddx3bt29n+PDhXHbZZQD84he/4JRTTmH48OF87nOfY/fu3fX+Xnr06ME3vvENhg0bxujRo3njjTcAeOWVVzj11FM58cQT+eY3v1ltnR/84AecfPLJlJWVcf311wNw//33c+6555JS4rXXXuO4447j9ddfr3ffkjqW4QN68aUxx9G7exfeeLuS3t278KUxx713V45D+9W+Yl3tar/si1RjX8S+iKSOwWCiUJFOpf3Nb37DuHHjOO644zj88MNZvHgxALfffjvdunVj+fLl3HDDDVXtjXXTTTfx17/+laVLl3LHHXcwaNAgrrrqKq699lrKy8s544wzmDx5Mtdeey3PPPMMc+bM4corrwTghhtu4PTTT+eFF17g4osvZvXq1ftsf9iwYRx11FEMHjyYiRMn8sADDzSqriVLljB79mwef/zxfaavv/56RowYwdKlS7nxxhv59Kc/XVXPOeecwwsvvMDHP/7xavXcddddLF68mEWLFjF9+nQ2btzITTfdRNeuXSkvL+fuu+9m+fLl/OpXv+LJJ5+kvLycTp06cffdd9db57Zt2xg9ejTPPvssZ555Jj/+8Y8BmDx5Mp///Od57rnnOOaYY6qWf/jhh3nppZf4y1/+Qnl5OYsXL2bBggVcfPHFHHPMMdx222189rOf5YYbbuDoo49u1LGS1HEMH9CLb180lJn/dArfvmho9VuFnjsFSrpWX6Gka65dHYt9kWrsi9gXkdQxdG54kQ6kSKfSzpo1i8mTJwNwySWXMGvWLE466SQWLFjANddcA0BZWVmTTk3cu85ll13GRRddxEUXXVTrMo888gjLli2rmt68eTNbt25lwYIF3HfffQCcf/759OrVa591O3XqxEMPPcQzzzzDo48+yrXXXsvixYurviWpy5gxY+jdu3et0wsXLmTOnDkAnHPOOWzcuJHNmzezcOFC7r//fgDGjRtXrZ7p06dXzVuzZg0vvfQShx9+eLV9PvrooyxevJiTTz4ZyH0DdOSRR9ZbZ5cuXbjgggsAOOmkk5g3bx4ATz75ZFWNl19+OV/96leBXGfg4YcfZsSIEUDu26eXXnqJM888k1tvvZWhQ4cyevRoLr300nr3K0n7KJuQ+/mbL+S+KT+0fy6U2NuujsO+SDX2ReyLSOoYDCYKHdovd8pkbe3NVFFRwR//+Eeee+45IoLdu3cTEfzgBz9o1PqdO3dmz549VdOVlZVVz+fOncuCBQt44IEH+O53v8tzzz23z/p79uzh6aefprS0tFn1RwSnnHIKp5xyCmPGjGHixIlMnTq1Wl2FNQF079693ummmD9/Po888gh/+tOf6NatG2edddY++wNIKXHFFVfwve99r9HbLikpISKAXMen8LrTve019/G1r32Nz33uc/vMW7t2LQcddBBvvPEGe/bs4aCDPBlJUhOVTYDFP8s9nzg321qUHfsi+7AvUn0f9kUktUf+i1WoCKfSzp49m8svv5xXX32VVatWsWbNGgYPHswTTzzBmWeeyS9/+UsAnn/++aprHAsdddRRrF+/no0bN/Luu+/yu9/9Dsj9J79mzRrOPvtsvv/97/P222+zdetWevbsyZYtW6rWHzt2LLfeemvVdHl5OUC1ff/+979n06ZN++z7b3/7G0uWLKm27sCBA4Hcdad7T/ecPXt2o4/HGWecUXVa4/z58zniiCM45JBDOO2007j33tz1sw8//HBVPW+//Ta9evWiW7duvPjiizz99NNV2yopKWHnzp0AnHvuucyePZv1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\n", "text/plain": [ "
" ] @@ -498,6 +819,30 @@ "needs_background": "light" }, "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 15;\n", + " var nbb_formatted_code = \"# plot the error bar plot of different models\\nplt.figure(figsize=(18, 6))\\nplt.subplot(1, 2, 1)\\n\\nplt.errorbar(\\n np.arange(n_treatments) - 0.04,\\n true_longterm_effect,\\n fmt=\\\"o\\\",\\n alpha=0.6,\\n label=\\\"Ground truth\\\",\\n)\\nplt.errorbar(\\n np.arange(n_treatments),\\n adjsurr_point_est,\\n yerr=(\\n adjsurr_conf_int_ub - adjsurr_point_est,\\n adjsurr_point_est - adjsurr_conf_int_lb,\\n ),\\n fmt=\\\"o\\\",\\n label=\\\"Adjusted Surrogate Index\\\",\\n)\\nplt.xticks(np.arange(n_treatments), [\\\"T0\\\", \\\"T1\\\", \\\"T2\\\"])\\nplt.ylabel(\\\"Effect\\\")\\nplt.legend()\\n\\nplt.subplot(1, 2, 2)\\nplt.errorbar(\\n np.arange(n_treatments) - 0.04,\\n true_longterm_effect,\\n fmt=\\\"o\\\",\\n alpha=0.6,\\n label=\\\"Ground truth\\\",\\n)\\nplt.errorbar(\\n np.arange(n_treatments),\\n adjsurr_point_est,\\n yerr=(\\n adjsurr_conf_int_ub - adjsurr_point_est,\\n adjsurr_point_est - adjsurr_conf_int_lb,\\n ),\\n fmt=\\\"o\\\",\\n label=\\\"Adjusted Surrogate Index\\\",\\n)\\nplt.errorbar(\\n np.arange(n_treatments) + 0.04,\\n direct_point_est,\\n yerr=(direct_conf_int_ub - direct_point_est, direct_point_est - direct_conf_int_lb),\\n fmt=\\\"o\\\",\\n label=\\\"Direct Model\\\",\\n)\\nplt.xticks(np.arange(n_treatments), [\\\"T0\\\", \\\"T1\\\", \\\"T2\\\"])\\nplt.ylabel(\\\"Effect\\\")\\nplt.legend()\\nplt.suptitle(\\\"Error bar plot of treatment effect from different models\\\")\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -565,6 +910,145 @@ "problematic, pattern of serially correlated treatments." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Extensions -- Including Heterogeneity in Effect\n", + "\n", + "Finally, I will show that our EconML's `DynamicDML` and `LinearDML` estimators could not only learn Average Treatment Effect (ATE), but also **Heterogeneous Treatment Effect (CATE)**, which will return the treatment effect as a function of interested characteristics. In the example below, I will use first control variable as feature to learn effect heterogeneity, and retrain the final `LinearDML` model. Similarly, you could train `DynamicDML` with feature $X$ as well." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True Long-term Effect for each investment: [0.90994672 0.709811 2.45310877]\n", + "Average treatment effect for each investment: [0.82738185 0.71610965 2.56087599]\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
Coefficient Results
point_estimate stderr zstat pvalue ci_lower ci_upper
X0|T0 0.009 0.011 0.76 0.447 -0.014 0.031
X0|T1 0.037 0.031 1.218 0.223 -0.023 0.098
X0|T2 -0.072 0.151 -0.478 0.633 -0.369 0.224
\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "
CATE Intercept Results
point_estimate stderr zstat pvalue ci_lower ci_upper
cate_intercept|T0 0.827 0.015 56.625 0.0 0.799 0.856
cate_intercept|T1 0.716 0.032 22.466 0.0 0.654 0.779
cate_intercept|T2 2.56 0.237 10.82 0.0 2.096 3.024


A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:
$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$
where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.
" + ], + "text/plain": [ + "\n", + "\"\"\"\n", + " Coefficient Results \n", + "===========================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------\n", + "X0|T0 0.009 0.011 0.76 0.447 -0.014 0.031\n", + "X0|T1 0.037 0.031 1.218 0.223 -0.023 0.098\n", + "X0|T2 -0.072 0.151 -0.478 0.633 -0.369 0.224\n", + " CATE Intercept Results \n", + "=======================================================================\n", + " point_estimate stderr zstat pvalue ci_lower ci_upper\n", + "-----------------------------------------------------------------------\n", + "cate_intercept|T0 0.827 0.015 56.625 0.0 0.799 0.856\n", + "cate_intercept|T1 0.716 0.032 22.466 0.0 0.654 0.779\n", + "cate_intercept|T2 2.56 0.237 10.82 0.0 2.096 3.024\n", + "-----------------------------------------------------------------------\n", + "\n", + "A linear parametric conditional average treatment effect (CATE) model was fitted:\n", + "$Y = \\Theta(X)\\cdot T + g(X, W) + \\epsilon$\n", + "where for every outcome $i$ and treatment $j$ the CATE $\\Theta_{ij}(X)$ has the form:\n", + "$\\Theta_{ij}(X) = \\phi(X)' coef_{ij} + cate\\_intercept_{ij}$\n", + "where $\\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\\_intercept_{ij}$ parameter.\n", + "\"\"\"" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 16;\n", + " var nbb_formatted_code = \"# learn treatment effect on surrogate index on new dataset\\nfrom econml.dml import LinearDML\\n\\nadjsurr_est = LinearDML(\\n model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=3\\n)\\n# fit treatment_0 on total revenue from new dataset\\nadjsurr_est.fit(\\n sindex_adj, panelTnew[:, 0], X=panelXnew[:, 0, :1], W=panelXnew[:, 0, 1:]\\n)\\n# print treatment effect summary\\nprint(\\\"True Long-term Effect for each investment: \\\", true_longterm_effect)\\nprint(\\n \\\"Average treatment effect for each investment: \\\",\\n adjsurr_est.const_marginal_ate(panelXnew[:, 0, :1]),\\n)\\nadjsurr_est.summary(alpha=0.05)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# learn treatment effect on surrogate index on new dataset\n", + "from econml.dml import LinearDML\n", + "\n", + "adjsurr_est = LinearDML(\n", + " model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=3\n", + ")\n", + "# fit treatment_0 on total revenue from new dataset\n", + "adjsurr_est.fit(\n", + " sindex_adj, panelTnew[:, 0], X=panelXnew[:, 0, :1], W=panelXnew[:, 0, 1:]\n", + ")\n", + "# print treatment effect summary\n", + "print(\"True Long-term Effect for each investment: \", true_longterm_effect)\n", + "print(\n", + " \"Average treatment effect for each investment: \",\n", + " adjsurr_est.const_marginal_ate(panelXnew[:, 0, :1]),\n", + ")\n", + "adjsurr_est.summary(alpha=0.05)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the summary table above, none of the coefficient for feature $X0$ is significant, that means there is no effect heterogeneity identified, which is consistent with the data generation process." + ] + }, { "cell_type": "markdown", "metadata": {}, From e77c5685802db1eb3e62bca4f64484f72a625b6a Mon Sep 17 00:00:00 2001 From: Keith Battocchi Date: Mon, 9 Aug 2021 07:53:47 -0400 Subject: [PATCH 27/27] Limit test paralellization --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4bc7d52b5..bedbbd8de 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -169,7 +169,7 @@ jobs: - script: 'pip install pytest-runner && python setup.py pytest' displayName: 'Unit tests' env: - PYTEST_ADDOPTS: '-m "not (notebook or automl or dml or causal)"' + PYTEST_ADDOPTS: '-m "not (notebook or automl or dml or causal)" -n 2' COVERAGE_PROCESS_START: 'setup.cfg' - task: PublishTestResults@2 displayName: 'Publish Test Results **/test-results.xml' @@ -223,7 +223,7 @@ jobs: - script: 'pip install pytest-runner && python setup.py pytest' displayName: 'Unit tests' env: - PYTEST_ADDOPTS: '-m "causal"' + PYTEST_ADDOPTS: '-m "causal" -n 1' COVERAGE_PROCESS_START: 'setup.cfg' - task: PublishTestResults@2 displayName: 'Publish Test Results **/test-results.xml'