Conditional Average Treatment Effects (CATE) with DoWhy and EconML

This is an experimental feature where we use EconML methods from DoWhy. Using EconML allows CATE estimation using different methods.

All four steps of causal inference in DoWhy remain the same: model, identify, estimate, and refute. The key difference is that we now call econml methods in the estimation step. There is also a simpler example using linear regression to understand the intuition behind CATE estimators.

[1]:
import os, sys
sys.path.insert(1, os.path.abspath("../../../"))  # for dowhy source code
[2]:
import numpy as np
import pandas as pd
import logging

import dowhy
from dowhy import CausalModel
import dowhy.datasets

import econml
import warnings
warnings.filterwarnings('ignore')
[3]:
data = dowhy.datasets.linear_dataset(10, num_common_causes=4, num_samples=10000,
                                    num_instruments=2, num_effect_modifiers=2,
                                     num_treatments=1,
                                    treatment_is_binary=False)
df=data['df']
df.head()
[3]:
X0 X1 Z0 Z1 W0 W1 W2 W3 v0 y
0 -0.423069 -0.783912 1.0 0.595267 -1.719039 1.133622 2.154847 2.093053 24.336479 168.434975
1 -0.671984 -0.517552 1.0 0.840097 -0.871639 1.240345 2.513988 1.144102 32.005832 244.808533
2 -0.408754 -1.224234 1.0 0.841418 -2.992127 1.947631 1.178079 0.364007 20.560389 105.083930
3 -1.797735 -0.810235 1.0 0.469800 -1.097293 -0.831213 0.490426 -0.229491 8.713406 34.353637
4 -0.579162 -3.119641 1.0 0.691993 -0.117981 0.009197 0.169698 2.553771 17.375187 -28.550935
[4]:
model = CausalModel(data=data["df"],
                    treatment=data["treatment_name"], outcome=data["outcome_name"],
                    graph=data["gml_graph"])
INFO:dowhy.causal_model:Model to find the causal effect of treatment ['v0'] on outcome ['y']
[5]:
model.view_model()
from IPython.display import Image, display
display(Image(filename="causal_model.png"))
../_images/example_notebooks_dowhy-conditional-treatment-effects_5_0.png
[6]:
identified_estimand= model.identify_effect()
print(identified_estimand)
INFO:dowhy.causal_identifier:Common causes of treatment and outcome:['W0', 'W3', 'W1', 'Unobserved Confounders', 'W2']
WARNING:dowhy.causal_identifier:If this is observed data (not from a randomized experiment), there might always be missing confounders. Causal effect cannot be identified perfectly.
WARN: Do you want to continue by ignoring any unobserved confounders? (use proceed_when_unidentifiable=True to disable this prompt) [y/n] y
INFO:dowhy.causal_identifier:Instrumental variables for treatment and outcome:['Z0', 'Z1']
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

Linear Model

First, let us build some intuition using a linear model for estimating CATE. The effect modifiers (that lead to a heterogeneous treatment effect) can be modeled as interaction terms with the treatment. Thus, their value modulates the effect of treatment.

Below the estimated effect of changing treatment from 0 to 1.

[7]:
linear_estimate = model.estimate_effect(identified_estimand,
                                        method_name="backdoor.linear_regression",
                                       control_value=0,
                                       treatment_value=1)
print(linear_estimate)
INFO:dowhy.causal_estimator:INFO: Using Linear Regression Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2+v0*X1+v0*X0
*** Causal Estimate ***

## Target estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

## Realized estimand
b: y~v0+W0+W3+W1+W2+v0*X1+v0*X0
## Estimate
Value: 10.000000000000007

EconML methods

We now move to the more advanced methods from the EconML package for estimating CATE.

First, let us look at the double machine learning estimator. Method_name corresponds to the fully qualified name of the class that we want to use. For double ML, it is “econml.dml.DMLCateEstimator”.

Target units defines the units over which the causal estimate is to be computed. This can be a lambda function filter on the original dataframe, a new Pandas dataframe, or a string corresponding to the three main kinds of target units (“ate”, “att” and “atc”). Below we show an example of a lambda function.

Method_params are passed directly to EconML. For details on allowed parameters, refer to the EconML documentation.

[8]:
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LassoCV
from sklearn.ensemble import GradientBoostingRegressor
dml_estimate = model.estimate_effect(identified_estimand, method_name="backdoor.econml.dml.DMLCateEstimator",
                                     control_value = 0,
                                     treatment_value = 1,
                                 target_units = lambda df: df["X0"]>1,  # condition used for CATE
                                 confidence_intervals=False,
                                method_params={"init_params":{'model_y':GradientBoostingRegressor(),
                                                              'model_t': GradientBoostingRegressor(),
                                                              "model_final":LassoCV(),
                                                              'featurizer':PolynomialFeatures(degree=1, include_bias=True)},
                                               "fit_params":{}})
print(dml_estimate)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
*** Causal Estimate ***

## Target estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

## Realized estimand
b: y~v0+W0+W3+W1+W2
## Estimate
Value: 8.578440374266151

[9]:
print("True causal estimate is", data["ate"])
True causal estimate is 6.531368733675461
[10]:
dml_estimate = model.estimate_effect(identified_estimand, method_name="backdoor.econml.dml.DMLCateEstimator",
                                     control_value = 0,
                                     treatment_value = 1,
                                 target_units = 1,  # condition used for CATE
                                 confidence_intervals=False,
                                method_params={"init_params":{'model_y':GradientBoostingRegressor(),
                                                              'model_t': GradientBoostingRegressor(),
                                                              "model_final":LassoCV(),
                                                              'featurizer':PolynomialFeatures(degree=1, include_bias=True)},
                                               "fit_params":{}})
print(dml_estimate)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
*** Causal Estimate ***

## Target estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

## Realized estimand
b: y~v0+W0+W3+W1+W2
## Estimate
Value: 6.478668642785378

CATE Object and Confidence Intervals

[11]:
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LassoCV
from sklearn.ensemble import GradientBoostingRegressor
dml_estimate = model.estimate_effect(identified_estimand,
                                     method_name="backdoor.econml.dml.DMLCateEstimator",
                                     target_units = lambda df: df["X0"]>1,
                                     confidence_intervals=True,
                                     method_params={"init_params":{'model_y':GradientBoostingRegressor(),
                                                              'model_t': GradientBoostingRegressor(),
                                                              "model_final":LassoCV(),
                                                              'featurizer':PolynomialFeatures(degree=1, include_bias=True)},
                                               "fit_params":{
                                                               'inference': 'bootstrap',
                                                            }
                                              })
print(dml_estimate)
print(dml_estimate.cate_estimates[:10])
print(dml_estimate.effect_intervals)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
[Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 32 concurrent workers.
[Parallel(n_jobs=-1)]: Done  71 out of 100 | elapsed:  1.1min remaining:   27.4s
[Parallel(n_jobs=-1)]: Done 100 out of 100 | elapsed:  1.2min finished
[Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 32 concurrent workers.
*** Causal Estimate ***

## Target estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

## Realized estimand
b: y~v0+W0+W3+W1+W2
## Estimate
Value: 8.59526782752452

[[11.26396103]
 [10.54322021]
 [ 8.16997949]
 [ 4.86061276]
 [11.09707173]
 [-2.36778738]
 [ 4.65848744]
 [ 6.6349523 ]
 [ 7.01785881]
 [ 9.61921864]]
(array([[10.7209148 ],
       [10.05703784],
       [ 7.82636093],
       ...,
       [10.37314163],
       [ 5.89648783],
       [14.28637593]]), array([[10.98146476],
       [10.28543916],
       [ 8.04793421],
       ...,
       [10.63228411],
       [ 6.08169175],
       [14.69742395]]))
[Parallel(n_jobs=-1)]: Done  71 out of 100 | elapsed:    0.1s remaining:    0.0s
[Parallel(n_jobs=-1)]: Done 100 out of 100 | elapsed:    0.1s finished

Can provide a new inputs as target units and estimate CATE on them.

[12]:
test_cols= data['effect_modifier_names'] # only need effect modifiers' values
test_arr = [np.random.uniform(0,1, 10) for _ in range(len(test_cols))] # all variables are sampled uniformly, sample of 10
test_df = pd.DataFrame(np.array(test_arr).transpose(), columns=test_cols)
dml_estimate = model.estimate_effect(identified_estimand,
                                     method_name="backdoor.econml.dml.DMLCateEstimator",
                                     target_units = test_df,
                                     confidence_intervals=False,
                                     method_params={"init_params":{'model_y':GradientBoostingRegressor(),
                                                              'model_t': GradientBoostingRegressor(),
                                                              "model_final":LassoCV(),
                                                              'featurizer':PolynomialFeatures(degree=1, include_bias=True)},
                                               "fit_params":{}
                                              })
print(dml_estimate.cate_estimates)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
[[10.98805765]
 [13.07006079]
 [10.63717349]
 [11.88142705]
 [12.24428257]
 [14.27632508]
 [13.19014829]
 [11.06457798]
 [11.51427196]
 [12.50204303]]

Can also retrieve the raw EconML estimator object for any further operations

[13]:
print(dml_estimate._estimator_object)
dml_estimate
<econml.dml.DMLCateEstimator object at 0x7fff577ce940>
[13]:
<dowhy.causal_estimator.CausalEstimate at 0x7fff577ce828>

Works with any EconML method

In addition to double machine learning, below we example analyses using orthogonal forests, DRLearner (bug to fix), and neural network-based instrumental variables.

Continuous treatment, Continuous outcome

[14]:
from sklearn.linear_model import LogisticRegression
orthoforest_estimate = model.estimate_effect(identified_estimand, method_name="backdoor.econml.ortho_forest.ContinuousTreatmentOrthoForest",
                                 target_units = lambda df: df["X0"]>1,
                                 confidence_intervals=False,
                                method_params={"init_params":{
                                                    'n_trees':2, # not ideal, just as an example to speed up computation
                                                    },
                                               "fit_params":{}
                                              })
print(orthoforest_estimate)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 32 concurrent workers.
[Parallel(n_jobs=-1)]: Done   2 out of   2 | elapsed:   38.9s remaining:    0.0s
[Parallel(n_jobs=-1)]: Done   2 out of   2 | elapsed:   38.9s finished
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 32 concurrent workers.
[Parallel(n_jobs=-1)]: Done   2 out of   2 | elapsed:   11.7s remaining:    0.0s
[Parallel(n_jobs=-1)]: Done   2 out of   2 | elapsed:   11.7s finished
[Parallel(n_jobs=-1)]: Using backend ThreadingBackend with 32 concurrent workers.
[Parallel(n_jobs=-1)]: Done  64 tasks      | elapsed:   34.5s
[Parallel(n_jobs=-1)]: Done 224 tasks      | elapsed:  1.7min
[Parallel(n_jobs=-1)]: Done 448 tasks      | elapsed:  3.5min
[Parallel(n_jobs=-1)]: Done 736 tasks      | elapsed:  5.8min
[Parallel(n_jobs=-1)]: Done 1088 tasks      | elapsed:  8.6min
*** Causal Estimate ***

## Target estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

## Realized estimand
b: y~v0+W0+W3+W1+W2
## Estimate
Value: 8.331353134942322

[Parallel(n_jobs=-1)]: Done 1483 out of 1483 | elapsed: 11.6min finished

Binary treatment, Binary outcome

[15]:
data_binary = dowhy.datasets.linear_dataset(10, num_common_causes=4, num_samples=10000,
                                    num_instruments=2, num_effect_modifiers=2,
                                    treatment_is_binary=True, outcome_is_binary=True)
print(data_binary['df'])

model_binary = CausalModel(data=data_binary["df"],
                    treatment=data_binary["treatment_name"], outcome=data_binary["outcome_name"],
                    graph=data_binary["gml_graph"])
identified_estimand_binary = model_binary.identify_effect(proceed_when_unidentifiable=True)
INFO:dowhy.causal_model:Model to find the causal effect of treatment ['v0'] on outcome ['y']
INFO:dowhy.causal_identifier:Common causes of treatment and outcome:['W0', 'W3', 'W1', 'Unobserved Confounders', 'W2']
WARNING:dowhy.causal_identifier:If this is observed data (not from a randomized experiment), there might always be missing confounders. Causal effect cannot be identified perfectly.
INFO:dowhy.causal_identifier:Continuing by ignoring these unobserved confounders because proceed_when_unidentifiable flag is True.
INFO:dowhy.causal_identifier:Instrumental variables for treatment and outcome:['Z0', 'Z1']
            X0        X1   Z0        Z1        W0        W1        W2  \
0     1.170983 -0.463676  0.0  0.999261  1.265824 -0.591487  0.187607
1     1.874968 -1.237442  0.0  0.372351  2.055680 -0.470702 -1.089608
2    -0.231365  0.227272  0.0  0.517914  1.004229 -1.196373 -2.495002
3     1.313117  0.065614  0.0  0.793337 -0.316458 -0.459159 -0.455055
4     0.012586  0.194818  0.0  0.683885 -0.322924 -1.523299 -0.816566
...        ...       ...  ...       ...       ...       ...       ...
9995  2.170496 -0.544734  0.0  0.251869  0.903261 -1.776711 -0.312277
9996  0.359327  0.190037  0.0  0.877978  1.832255 -1.413621 -1.064130
9997 -0.515581 -2.971678  0.0  0.482088  0.136553  0.119679  0.081736
9998  0.105968 -1.541644  0.0  0.095357  3.035971 -1.195416 -0.069832
9999  1.180787 -0.944992  0.0  0.265338  0.176723  1.442231 -1.669103

            W3     v0      y
0     0.281102   True   True
1    -0.175620   True  False
2    -1.024748  False   True
3     2.365471   True  False
4    -1.955707  False   True
...        ...    ...    ...
9995 -0.112627  False   True
9996 -2.130779   True   True
9997 -0.567688   True   True
9998  0.280326   True  False
9999 -0.242490   True   True

[10000 rows x 10 columns]

NOTE: DRLearner throws an error since it expects the outcome in a 1-D array. This will be fixed soon in the EconML package.

[17]:
from sklearn.linear_model import LogisticRegressionCV
#todo needs binary y
drlearner_estimate = model_binary.estimate_effect(identified_estimand_binary,
                                method_name="backdoor.econml.drlearner.LinearDRLearner",
                                target_units = lambda df: df["X0"]>1,
                                confidence_intervals=False,
                                method_params={"init_params":{
                                                    'model_propensity': LogisticRegressionCV(cv=3, solver='lbfgs', multi_class='auto')
                                                    },
                                               "fit_params":{}
                                              })
print(drlearner_estimate)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-17-f77b6b495b1d> in <module>
      8                                                     'model_propensity': LogisticRegressionCV(cv=3, solver='lbfgs', multi_class='auto')
      9                                                     },
---> 10                                                "fit_params":{}
     11                                               })
     12 print(drlearner_estimate)

/mnt/c/Users/amshar/code/dowhy/dowhy/causal_model.py in estimate_effect(self, identified_estimand, method_name, control_value, treatment_value, test_significance, evaluate_effect_strength, confidence_intervals, target_units, effect_modifiers, method_params)
    205                 params=method_params
    206             )
--> 207             estimate = causal_estimator.estimate_effect()
    208             # Store parameters inside estimate object for refutation methods
    209             estimate.add_params(

/mnt/c/Users/amshar/code/dowhy/dowhy/causal_estimator.py in estimate_effect(self)
     88         """
     89
---> 90         est = self._estimate_effect()
     91         self._estimate = est
     92

/mnt/c/Users/amshar/code/dowhy/dowhy/causal_estimators/econml_cate_estimator.py in _estimate_effect(self)
     66         # Calling the econml estimator's fit method
     67         if self.identifier_method == "backdoor":
---> 68             self.estimator.fit(Y, T, X, W, **self.method_params["fit_params"])
     69         else:
     70             self.estimator.fit(Y, T, X, Z, **self.method_params["fit_params"])

~/python-environments/vpy36/lib/python3.6/site-packages/econml/drlearner.py in fit(self, Y, T, X, W, sample_weight, sample_var, inference)
    656         """
    657         # Replacing fit from DRLearner, to add statsmodels inference in docstring
--> 658         return super().fit(Y, T, X=X, W=W, sample_weight=sample_weight, sample_var=sample_var, inference=inference)
    659
    660     @property

~/python-environments/vpy36/lib/python3.6/site-packages/econml/drlearner.py in fit(self, Y, T, X, W, sample_weight, sample_var, inference)
    364         """
    365         # Replacing fit from _OrthoLearner, to enforce Z=None and improve the docstring
--> 366         return super().fit(Y, T, X=X, W=W, sample_weight=sample_weight, sample_var=sample_var, inference=inference)
    367
    368     def score(self, Y, T, X=None, W=None):

~/python-environments/vpy36/lib/python3.6/site-packages/econml/cate_estimator.py in call(self, Y, T, inference, *args, **kwargs)
     87                 inference.prefit(self, Y, T, *args, **kwargs)
     88             # call the wrapped fit method
---> 89             m(self, Y, T, *args, **kwargs)
     90             if inference is not None:
     91                 # NOTE: we call inference fit *after* calling the main fit method

~/python-environments/vpy36/lib/python3.6/site-packages/econml/_ortho_learner.py in fit(self, Y, T, X, W, Z, sample_weight, sample_var, inference)
    478         """
    479         self._check_input_dims(Y, T, X, W, Z, sample_weight, sample_var)
--> 480         nuisances, fitted_inds = self._fit_nuisances(Y, T, X, W, Z, sample_weight=sample_weight)
    481         self._fit_final(self._subinds_check_none(Y, fitted_inds),
    482                         self._subinds_check_none(T, fitted_inds),

~/python-environments/vpy36/lib/python3.6/site-packages/econml/_ortho_learner.py in _fit_nuisances(self, Y, T, X, W, Z, sample_weight)
    516
    517         nuisances, fitted_models, fitted_inds = _crossfit(self._model_nuisance, folds,
--> 518                                                           Y, T, X=X, W=W, Z=Z, sample_weight=sample_weight)
    519         self._models_nuisance = fitted_models
    520         return nuisances, fitted_inds

~/python-environments/vpy36/lib/python3.6/site-packages/econml/_ortho_learner.py in _crossfit(model, folds, *args, **kwargs)
    143                 kwargs_test[key] = var[test_idxs]
    144
--> 145         model_list[idx].fit(*args_train, **kwargs_train)
    146
    147         nuisance_temp = model_list[idx].predict(*args_test, **kwargs_test)

~/python-environments/vpy36/lib/python3.6/site-packages/econml/drlearner.py in fit(self, Y, T, X, W, sample_weight)
    260             def fit(self, Y, T, X=None, W=None, *, sample_weight=None):
    261                 # TODO Allow for non-vector y, i.e. of shape (n, 1)
--> 262                 assert np.ndim(Y) == 1, "Can only accept single dimensional outcomes Y! Use Y.ravel()."
    263                 if (X is None) and (W is None):
    264                     raise AttributeError("At least one of X or W has to not be None!")

AssertionError: Can only accept single dimensional outcomes Y! Use Y.ravel().

Instrumental Variable Method

[18]:
import keras
from econml.deepiv import DeepIVEstimator
dims_zx = len(model._instruments)+len(model._effect_modifiers)
dims_tx = len(model._treatment)+len(model._effect_modifiers)
treatment_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_shape=(dims_zx,)), # sum of dims of Z and X
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(64, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(32, activation='relu'),
                                    keras.layers.Dropout(0.17)])
response_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_shape=(dims_tx,)), # sum of dims of T and X
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(64, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(32, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(1)])

deepiv_estimate = model.estimate_effect(identified_estimand,
                                        method_name="iv.econml.deepiv.DeepIVEstimator",
                                        target_units = lambda df: df["X0"]>-1,
                                        confidence_intervals=False,
                                method_params={"init_params":{'n_components': 10, # Number of gaussians in the mixture density networks
                                                              'm': lambda z, x: treatment_model(keras.layers.concatenate([z, x])), # Treatment model,
                                                              "h": lambda t, x: response_model(keras.layers.concatenate([t, x])), # Response model
                                                              'n_samples': 1, # Number of samples used to estimate the response
                                                              'first_stage_options': {'epochs':25},
                                                              'second_stage_options': {'epochs':25}
                                                             },
                                               "fit_params":{}})
print(deepiv_estimate)
Using TensorFlow backend.
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
WARNING:tensorflow:From /home/amshar/python-environments/vpy36/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:2403: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
WARNING:tensorflow:From /home/amshar/python-environments/vpy36/lib/python3.6/site-packages/keras/backend/tensorflow_backend.py:422: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.

Epoch 1/25
10000/10000 [==============================] - 1s 125us/step - loss: 5.7914
Epoch 2/25
10000/10000 [==============================] - 1s 69us/step - loss: 2.7293
Epoch 3/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.5698
Epoch 4/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.5207
Epoch 5/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4888
Epoch 6/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4770
Epoch 7/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4734
Epoch 8/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4526
Epoch 9/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4543
Epoch 10/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4469
Epoch 11/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4347
Epoch 12/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4246
Epoch 13/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4259
Epoch 14/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4241
Epoch 15/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4149
Epoch 16/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4150
Epoch 17/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4078
Epoch 18/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.4015
Epoch 19/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4001
Epoch 20/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4029
Epoch 21/25
10000/10000 [==============================] - 1s 67us/step - loss: 2.4025
Epoch 22/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.3970
Epoch 23/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.3930
Epoch 24/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.3982
Epoch 25/25
10000/10000 [==============================] - 1s 66us/step - loss: 2.3940
Epoch 1/25
10000/10000 [==============================] - 2s 205us/step - loss: 8526.7076
Epoch 2/25
10000/10000 [==============================] - 1s 111us/step - loss: 4659.4835
Epoch 3/25
10000/10000 [==============================] - 1s 107us/step - loss: 4231.5569
Epoch 4/25
10000/10000 [==============================] - 1s 104us/step - loss: 4215.2266
Epoch 5/25
10000/10000 [==============================] - 1s 102us/step - loss: 4090.3670
Epoch 6/25
10000/10000 [==============================] - 1s 102us/step - loss: 4030.0446
Epoch 7/25
10000/10000 [==============================] - 1s 100us/step - loss: 4137.3973
Epoch 8/25
10000/10000 [==============================] - 1s 98us/step - loss: 4091.2221
Epoch 9/25
10000/10000 [==============================] - 1s 97us/step - loss: 4003.9576
Epoch 10/25
10000/10000 [==============================] - 1s 97us/step - loss: 4013.2564
Epoch 11/25
10000/10000 [==============================] - 1s 98us/step - loss: 4035.7388
Epoch 12/25
10000/10000 [==============================] - 1s 97us/step - loss: 3965.2224
Epoch 13/25
10000/10000 [==============================] - 1s 98us/step - loss: 4054.5777
Epoch 14/25
10000/10000 [==============================] - 1s 97us/step - loss: 4014.5290
Epoch 15/25
10000/10000 [==============================] - 1s 97us/step - loss: 3984.4700
Epoch 16/25
10000/10000 [==============================] - 1s 96us/step - loss: 3926.2307
Epoch 17/25
10000/10000 [==============================] - 1s 94us/step - loss: 4015.3016
Epoch 18/25
10000/10000 [==============================] - 1s 94us/step - loss: 4034.1578
Epoch 19/25
10000/10000 [==============================] - 1s 94us/step - loss: 3974.5770
Epoch 20/25
10000/10000 [==============================] - 1s 94us/step - loss: 3985.2165
Epoch 21/25
10000/10000 [==============================] - 1s 94us/step - loss: 3977.9142
Epoch 22/25
10000/10000 [==============================] - 1s 94us/step - loss: 3950.7200
Epoch 23/25
10000/10000 [==============================] - 1s 94us/step - loss: 4011.0488
Epoch 24/25
10000/10000 [==============================] - 1s 93us/step - loss: 3986.2898
Epoch 25/25
10000/10000 [==============================] - 1s 94us/step - loss: 4029.8864
*** Causal Estimate ***

## Target estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
  d
─────(Expectation(y|W0,W3,W1,W2))
d[v₀]
Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W0,W3,W1,W2,U) = P(y|v0,W0,W3,W1,W2)
### Estimand : 2
Estimand name: iv
Estimand expression:
Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1))
Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1})
Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y)

## Realized estimand
b: y~v0+W0+W3+W1+W2
## Estimate
Value: 6.484096527099609

Refuting the estimate

Random

[19]:
res_random=model.refute_estimate(identified_estimand, dml_estimate, method_name="random_common_cause")
print(res_random)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2+w_random
Refute: Add a Random Common Cause
Estimated effect:(12.13683678878986,)
New effect:(13.248034250593435,)

Adding an unobserved common cause variable

[20]:
res_unobserved=model.refute_estimate(identified_estimand, dml_estimate, method_name="add_unobserved_common_cause",
                                     confounders_effect_on_treatment="linear", confounders_effect_on_outcome="linear",
                                    effect_strength_on_treatment=0.01, effect_strength_on_outcome=0.02)
print(res_unobserved)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
Refute: Add an Unobserved Common Cause
Estimated effect:(12.13683678878986,)
New effect:(13.196059198878597,)

Replacing treatment with a random (placebo) variable

[21]:
res_placebo=model.refute_estimate(identified_estimand, dml_estimate,
        method_name="placebo_treatment_refuter", placebo_type="permute")
print(res_placebo)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~placebo+W0+W3+W1+W2
Refute: Use a Placebo Treatment
Estimated effect:(12.13683678878986,)
New effect:(0.0,)

Removing a random subset of the data

[22]:
res_subset=model.refute_estimate(identified_estimand, dml_estimate,
        method_name="data_subset_refuter", subset_fraction=0.8)
print(res_subset)
INFO:dowhy.causal_estimator:INFO: Using EconML Estimator
INFO:dowhy.causal_estimator:b: y~v0+W0+W3+W1+W2
Refute: Use a subset of data
Estimated effect:(12.13683678878986,)
New effect:(13.150007335670214,)

More refutation methods to come, especially specific to the CATE estimators.