def test_parameter_passing(self): for gen in [DML, NonParamDML]: est = gen(model_y=LinearRegression(), model_t=LinearRegression(), model_final=LinearRegression(), mc_iters=2, mc_agg='median') assert est.mc_iters == 2 assert est.mc_agg == 'median' for gen in [LinearDML, SparseLinearDML, KernelDML, ForestDML]: est = gen(model_y=LinearRegression(), model_t=LinearRegression(), mc_iters=2, mc_agg='median') assert est.mc_iters == 2 assert est.mc_agg == 'median' for gen in [DRLearner, LinearDRLearner, SparseLinearDRLearner, ForestDRLearner]: est = gen(mc_iters=2, mc_agg='median') assert est.mc_iters == 2 assert est.mc_agg == 'median' for gen in [DMLATEIV(model_Y_W=LinearRegression(), model_T_W=LinearRegression(), model_Z_W=LinearRegression(), mc_iters=2, mc_agg='median'), ProjectedDMLATEIV(model_Y_W=LinearRegression(), model_T_W=LinearRegression(), model_T_WZ=LinearRegression(), mc_iters=2, mc_agg='median'), DMLIV(model_Y_X=LinearRegression(), model_T_X=LinearRegression(), model_T_XZ=LinearRegression(), model_final=LinearRegression(), mc_iters=2, mc_agg='median'), NonParamDMLIV(model_Y_X=LinearRegression(), model_T_X=LinearRegression(), model_T_XZ=LinearRegression(), model_final=LinearRegression(), mc_iters=2, mc_agg='median'), IntentToTreatDRIV(model_Y_X=LinearRegression(), model_T_XZ=LinearRegression(), flexible_model_effect=LinearRegression(), mc_iters=2, mc_agg='median'), LinearIntentToTreatDRIV(model_Y_X=LinearRegression(), model_T_XZ=LinearRegression(), flexible_model_effect=LinearRegression(), mc_iters=2, mc_agg='median')]: assert est.mc_iters == 2 assert est.mc_agg == 'median'
def test_orthoiv_random_state(self): Y, T, X, W, X_test = self._make_data(500, 2) for est in [ DMLATEIV(model_Y_W=RandomForestRegressor(n_estimators=10, max_depth=4, random_state=123), model_T_W=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), model_Z_W=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), discrete_treatment=True, discrete_instrument=True, cv=2, random_state=123), ProjectedDMLATEIV( model_Y_W=RandomForestRegressor(n_estimators=10, max_depth=4, random_state=123), model_T_W=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), model_T_WZ=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), discrete_treatment=True, discrete_instrument=True, cv=2, random_state=123) ]: TestRandomState._test_random_state(est, None, Y, T, W=W, Z=T) for est in [ DMLIV(model_Y_X=RandomForestRegressor(n_estimators=10, max_depth=4, random_state=123), model_T_X=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), model_T_XZ=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), model_final=LinearRegression(fit_intercept=False), discrete_treatment=True, discrete_instrument=True, cv=2, random_state=123), NonParamDMLIV( model_Y_X=RandomForestRegressor(n_estimators=10, max_depth=4, random_state=123), model_T_X=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), model_T_XZ=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), model_final=LinearRegression(), discrete_treatment=True, discrete_instrument=True, cv=2, random_state=123) ]: TestRandomState._test_random_state(est, X_test, Y, T, X=X, Z=T) for est in [ IntentToTreatDRIV( model_Y_X=RandomForestRegressor(n_estimators=10, max_depth=4, random_state=123), model_T_XZ=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), flexible_model_effect=RandomForestRegressor( n_estimators=10, max_depth=4, random_state=123), cv=2, random_state=123), LinearIntentToTreatDRIV( model_Y_X=RandomForestRegressor(n_estimators=10, max_depth=4, random_state=123), model_T_XZ=RandomForestClassifier(n_estimators=10, max_depth=4, random_state=123), flexible_model_effect=RandomForestRegressor( n_estimators=10, max_depth=4, random_state=123), cv=2, random_state=123) ]: TestRandomState._test_random_state(est, X_test, Y, T, X=X, W=W, Z=T)
def test_orthoiv(self): y, T, X, W = self._get_data() Z = T.copy() est = DMLATEIV(model_Y_W=LinearRegression(), model_T_W=LinearRegression(), model_Z_W=LinearRegression(), mc_iters=2) est.fit(y, T, W=W, Z=Z, cache_values=True) est.refit_final() est.model_Y_W = Lasso() est.model_T_W = ElasticNet() est.model_Z_W = WeightedLasso() est.fit(y, T, W=W, Z=Z, cache_values=True) assert isinstance(est.models_nuisance_[0]._model_Y_W._model, Lasso) assert isinstance(est.models_nuisance_[0]._model_T_W._model, ElasticNet) assert isinstance(est.models_nuisance_[0]._model_Z_W._model, WeightedLasso) est = ProjectedDMLATEIV(model_Y_W=LinearRegression(), model_T_W=LinearRegression(), model_T_WZ=LinearRegression(), mc_iters=2) est.fit(y, T, W=W, Z=Z, cache_values=True) est.refit_final() est.model_Y_W = Lasso() est.model_T_W = ElasticNet() est.model_T_WZ = WeightedLasso() est.fit(y, T, W=W, Z=Z, cache_values=True) assert isinstance(est.models_nuisance_[0]._model_Y_W._model, Lasso) assert isinstance(est.models_nuisance_[0]._model_T_W._model, ElasticNet) assert isinstance(est.models_nuisance_[0]._model_T_WZ._model, WeightedLasso) est = DMLIV(model_Y_X=LinearRegression(), model_T_X=LinearRegression(), model_T_XZ=LinearRegression(), model_final=LinearRegression(fit_intercept=False), mc_iters=2) est.fit(y, T, X=X, Z=Z, cache_values=True) np.testing.assert_equal(len(est.coef_), X.shape[1]) est.featurizer = PolynomialFeatures(degree=2, include_bias=False) est.refit_final() np.testing.assert_equal(len(est.coef_), X.shape[1]**2) est.intercept_ est.fit_cate_intercept = False est.intercept_ est.refit_final() with pytest.raises(AttributeError): est.intercept_ est.model_Y_X = Lasso() est.model_T_X = ElasticNet() est.model_T_XZ = WeightedLasso() est.fit(y, T, X=X, Z=Z, cache_values=True) assert isinstance(est.models_Y_X[0], Lasso) assert isinstance(est.models_T_X[0], ElasticNet) assert isinstance(est.models_T_XZ[0], WeightedLasso) est = DMLIV(model_Y_X=LinearRegression(), model_T_X=LinearRegression(), model_T_XZ=LinearRegression(), model_final=LinearRegression(fit_intercept=False), mc_iters=2) est.fit(y, T, X=X, Z=Z, cache_values=True) np.testing.assert_equal(len(est.coef_), X.shape[1]) est.featurizer = PolynomialFeatures(degree=2, include_bias=False) est.refit_final() np.testing.assert_equal(len(est.coef_), X.shape[1]**2) est.intercept_ est.fit_cate_intercept = False est.intercept_ est.refit_final() with pytest.raises(AttributeError): est.intercept_ est.model_Y_X = Lasso() est.model_T_X = ElasticNet() est.model_T_XZ = WeightedLasso() est.fit(y, T, X=X, Z=Z, cache_values=True) assert isinstance(est.models_nuisance_[0]._model_Y_X._model, Lasso) assert isinstance(est.models_nuisance_[0]._model_T_X._model, ElasticNet) assert isinstance(est.models_nuisance_[0]._model_T_XZ._model, WeightedLasso) est = NonParamDMLIV(model_Y_X=LinearRegression(), model_T_X=LinearRegression(), model_T_XZ=LinearRegression(), model_final=LinearRegression(fit_intercept=True), mc_iters=2) est.fit(y, T, X=X, Z=Z, cache_values=True) est.featurizer = PolynomialFeatures(degree=2, include_bias=False) est.model_final = WeightedLasso() est.refit_final() assert isinstance(est.model_cate, WeightedLasso) assert isinstance(est.featurizer_, PolynomialFeatures) est = IntentToTreatDRIV(model_Y_X=LinearRegression(), model_T_XZ=LogisticRegression(), flexible_model_effect=LinearRegression()) est.fit(y, T, X=X, W=W, Z=Z, cache_values=True) assert est.model_final is None assert isinstance(est.model_final_, LinearRegression) est.flexible_model_effect = Lasso() est.refit_final() assert est.model_final is None assert isinstance(est.model_final_, Lasso) est.model_final = Lasso() est.refit_final() assert isinstance(est.model_final, Lasso) assert isinstance(est.model_final_, Lasso) assert isinstance(est.models_nuisance_[0]._prel_model_effect.model_final_, LinearRegression) est.fit(y, T, X=X, W=W, Z=Z, cache_values=True) assert isinstance(est.models_nuisance_[0]._prel_model_effect.model_final_, Lasso) est = LinearIntentToTreatDRIV(model_Y_X=LinearRegression(), model_T_XZ=LogisticRegression(), flexible_model_effect=LinearRegression()) est.fit(y, T, X=X, W=W, Z=Z, cache_values=True) est.fit_cate_intercept = False est.intercept_ est.intercept__interval() est.refit_final() with pytest.raises(AttributeError): est.intercept_ with pytest.raises(AttributeError): est.intercept__interval() with pytest.raises(ValueError): est.model_final = LinearRegression() est.flexible_model_effect = Lasso() est.fit(y, T, X=X, W=W, Z=Z, cache_values=True) assert isinstance(est.models_nuisance_[0]._prel_model_effect.model_final_, Lasso)
def test_bad_splits_discrete(self): """ Tests that when some training splits in a crossfit fold don't contain all treatments then an error is raised. """ Y = np.array([2, 3, 1, 3, 2, 1, 1, 1]) bad = np.array([2, 2, 1, 2, 1, 1, 1, 1]) W = np.ones((8, 1)) ok = np.array([1, 2, 3, 1, 2, 3, 1, 2]) models = [Lasso(), Lasso(), Lasso()] est = DMLATEIV(*models, n_splits=[(np.arange(4, 8), np.arange(4))]) est.fit(Y, T=bad, Z=bad, W=W) # imbalance ok with continuous instrument/treatment models = [Lasso(), LogisticRegression(), Lasso()] est = DMLATEIV(*models, n_splits=[(np.arange(4, 8), np.arange(4))], discrete_treatment=True) with pytest.raises(AttributeError): est.fit(Y, T=bad, Z=ok, W=W) models = [Lasso(), Lasso(), LogisticRegression()] est = DMLATEIV(*models, n_splits=[(np.arange(4, 8), np.arange(4))], discrete_instrument=True) with pytest.raises(AttributeError): est.fit(Y, T=ok, Z=bad, W=W)
def test_cate_api(self): """Test that we correctly implement the CATE API.""" n = 30 def size(n, d): return (n, d) if d >= 0 else (n, ) def make_random(is_discrete, d): if d is None: return None sz = size(n, d) if is_discrete: while True: arr = np.random.choice(['a', 'b', 'c'], size=sz) # ensure that we've got at least two of every row _, counts = np.unique(arr, return_counts=True, axis=0) if len(counts) == 3**(d if d > 0 else 1) and counts.min() > 1: return arr else: return np.random.normal(size=sz) def eff_shape(n, d_y): return (n, ) + ((d_y, ) if d_y > 0 else ()) def marg_eff_shape(n, d_y, d_t_final): return ((n, ) + ((d_y, ) if d_y > 0 else ()) + ((d_t_final, ) if d_t_final > 0 else ())) # since T isn't passed to const_marginal_effect, defaults to one row if X is None def const_marg_eff_shape(n, d_x, d_y, d_t_final): return ((n if d_x else 1, ) + ((d_y, ) if d_y > 0 else ()) + ((d_t_final, ) if d_t_final > 0 else ())) for d_t in [2, 1, -1]: n_t = d_t if d_t > 0 else 1 for discrete_t in [True, False] if n_t == 1 else [False]: for d_y in [3, 1, -1]: for d_q in [2, None]: for d_z in [2, 1]: if d_z < n_t: continue for discrete_z in [True, False ] if d_z == 1 else [False]: Z1, Q, Y, T1 = [ make_random(is_discrete, d) for is_discrete, d in [( discrete_z, d_z), (False, d_q), (False, d_y), (discrete_t, d_t)] ] if discrete_t and discrete_z: # need to make sure we get all *joint* combinations arr = make_random(True, 2) Z1 = arr[:, 0].reshape(size(n, d_z)) T1 = arr[:, 0].reshape(size(n, d_t)) d_t_final1 = 2 if discrete_t else d_t if discrete_t: # IntentToTreat only supports binary treatments/instruments T2 = T1.copy() T2[T1 == 'c'] = np.random.choice( ['a', 'b'], size=np.count_nonzero(T1 == 'c')) d_t_final2 = 1 if discrete_z: # IntentToTreat only supports binary treatments/instruments Z2 = Z1.copy() Z2[Z1 == 'c'] = np.random.choice( ['a', 'b'], size=np.count_nonzero(Z1 == 'c')) effect_shape = eff_shape(n, d_y) model_t = LogisticRegression( ) if discrete_t else Lasso() model_z = LogisticRegression( ) if discrete_z else Lasso() # TODO: add stratification to bootstrap so that we can use it # even with discrete treatments all_infs = [None] if not (discrete_t or discrete_z): all_infs.append(BootstrapInference(1)) estimators = [ (DMLATEIV(model_Y_W=Lasso(), model_T_W=model_t, model_Z_W=model_z, discrete_treatment=discrete_t, discrete_instrument=discrete_z), True, all_infs), (ProjectedDMLATEIV( model_Y_W=Lasso(), model_T_W=model_t, model_T_WZ=model_t, discrete_treatment=discrete_t, discrete_instrument=discrete_z), False, all_infs), (DMLIV(model_Y_X=Lasso(), model_T_X=model_t, model_T_XZ=model_t, model_final=Lasso(), discrete_treatment=discrete_t, discrete_instrument=discrete_z), False, all_infs) ] if d_q and discrete_t and discrete_z: # IntentToTreat requires X estimators.append((LinearIntentToTreatDRIV( model_Y_X=Lasso(), model_T_XZ=model_t, flexible_model_effect=WeightedLasso(), n_splits=2), False, all_infs + ['statsmodels'])) for est, multi, infs in estimators: if not ( multi ) and d_y > 1 or d_t > 1 or d_z > 1: continue # ensure we can serialize unfit estimator pickle.dumps(est) d_ws = [None] if isinstance(est, LinearIntentToTreatDRIV): d_ws.append(2) for d_w in d_ws: W = make_random(False, d_w) for inf in infs: with self.subTest( d_z=d_z, d_x=d_q, d_y=d_y, d_t=d_t, discrete_t=discrete_t, discrete_z=discrete_z, est=est, inf=inf): Z = Z1 T = T1 d_t_final = d_t_final1 X = Q d_x = d_q if isinstance( est, (DMLATEIV, ProjectedDMLATEIV)): # these support only W but not X W = Q X = None d_x = None def fit(): return est.fit( Y, T, Z=Z, W=W, inference=inf) def score(): return est.score(Y, T, Z=Z, W=W) else: # these support only binary, not general discrete T and Z if discrete_t: T = T2 d_t_final = d_t_final2 if discrete_z: Z = Z2 if isinstance( est, LinearIntentToTreatDRIV ): def fit(): return est.fit( Y, T, Z=Z, X=X, W=W, inference=inf) def score(): return est.score( Y, T, Z=Z, X=X, W=W) else: def fit(): return est.fit( Y, T, Z=Z, X=X, inference=inf) def score(): return est.score( Y, T, Z=Z, X=X) marginal_effect_shape = marg_eff_shape( n, d_y, d_t_final) const_marginal_effect_shape = const_marg_eff_shape( n, d_x, d_y, d_t_final) fit() # ensure we can serialize 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, X) self.assertEqual( shape(marg_eff), marginal_effect_shape) self.assertEqual( shape(const_marg_eff), const_marginal_effect_shape ) np.testing.assert_array_equal( marg_eff if d_x else marg_eff[0:1], const_marg_eff) T0 = np.full_like( T, 'a' ) if discrete_t else np.zeros_like( T) eff = est.effect(X, T0=T0, T1=T) self.assertEqual( shape(eff), effect_shape) # TODO: add tests for extra properties like coef_ where they exist if inf is not None: const_marg_eff_int = est.const_marginal_effect_interval( X) marg_eff_int = est.marginal_effect_interval( T, 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)), (2, ) + effect_shape) # TODO: add tests for extra properties like coef_ where they exist score() # 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 discrete_t else est.effect( X, T0='a', T1='b') self.assertEqual( shape(eff), effect_shape2)