diff --git a/causallib/estimation/__init__.py b/causallib/estimation/__init__.py index 26c6c26e..74e1648e 100644 --- a/causallib/estimation/__init__.py +++ b/causallib/estimation/__init__.py @@ -1,4 +1,4 @@ -from .doubly_robust import DoublyRobustIpFeature, DoublyRobustJoffe, DoublyRobustVanilla +from .doubly_robust import PropensityFeatureStandardization, WeightedStandardization, ResidualCorrectedStandardization from .ipw import IPW from .overlap_weights import OverlapWeights from .standardization import Standardization, StratifiedStandardization diff --git a/causallib/estimation/doubly_robust.py b/causallib/estimation/doubly_robust.py index 90f847b1..b7aca5cb 100644 --- a/causallib/estimation/doubly_robust.py +++ b/causallib/estimation/doubly_robust.py @@ -26,13 +26,14 @@ import warnings import pandas as pd +import numpy as np from .base_estimator import IndividualOutcomeEstimator -from .base_weight import WeightEstimator +from .base_weight import WeightEstimator, PropensityEstimator from ..utils import general_tools as g_tools -class DoublyRobust(IndividualOutcomeEstimator): +class BaseDoublyRobust(IndividualOutcomeEstimator): """ Abstract class defining the interface and general initialization of specific doubly-robust methods. """ @@ -52,7 +53,7 @@ def __init__(self, outcome_model, weight_model, If None - all covariates passed will be used. Either list of column names or boolean mask. """ - super(DoublyRobust, self).__init__(lambda **x: None) # Dummy initialization + super(BaseDoublyRobust, self).__init__(lambda **x: None) # Dummy initialization delattr(self, "learner") # To remove the learner attribute a IndividualOutcomeEstimator has self.outcome_model = outcome_model self.weight_model = weight_model @@ -98,25 +99,23 @@ def __repr__(self): return repr_string -class DoublyRobustVanilla(DoublyRobust): +class ResidualCorrectedStandardization(BaseDoublyRobust): """ - Given the measured outcome Y, the assignment Y, and the coefficients X calculate a doubly-robust estimator - of the effect of treatment + Calculates a doubly-robust estimate of the treatment effect by performing + potential-outcome prediction (`outcome_model`) and then correcting its + prediction-residuals using re-weighting from a treatment model (`weight_model`, like IPW). Let e(X) be the estimated propensity score and m(X, A) is the estimated effect by an estimator, then the individual estimates are: - | Y + (A-e(X))*(Y-m(X,1)) / e(X) if A==1, and - | Y + (e(X)-A)*(Y-m(X,0)) / (1-e(X)) if A==0 + | m(X,1) + A*(Y-m(X,1))/e(X), and + | m(X,0) + (1-A)*(Y-m(X,0))/(1-e(X)) These expressions show that when e(X) is an unbiased estimator of A, or when m is an unbiased estimator of Y then the resulting estimator is unbiased. Note that the term for A==0 is derived from (1-A)-(1-e(X)) - Another way of writing these equation is by "correcting" the individual prediction rather than the individual - outcome: - - | m(X,1) + A*(Y-m(X,1))/e(X), and - | m(X,0) + (1-A)*(Y-m(X,0))/(1-e(X)) + Kang and Schafer (https://dx.doi.org/10.1214/07-STS227) attribute this method to + Cassel, Särndal and Wretman. """ def fit(self, X, a, y, refit_weight_model=True, **kwargs): @@ -247,15 +246,50 @@ def estimate_effect(self, outcome1, outcome2, agg="population", effect_types="di "not corrected for population effect.\n" "In case you want individual effect use agg='individual', or in case you want population" "effect use the estimate_population_effect() output as your input to this function.") - effect = super(DoublyRobustVanilla, self).estimate_effect(outcome1, outcome2, agg, effect_types) + effect = super(ResidualCorrectedStandardization, self).estimate_effect(outcome1, outcome2, agg, effect_types) return effect -class DoublyRobustIpFeature(DoublyRobust): - """ - A doubly-robust estimator of the effect of treatment. - This model adds the weighting (inverse probability weighting) as feature to the model. - """ +class PropensityFeatureStandardization(BaseDoublyRobust): + def __init__(self, outcome_model, weight_model, + outcome_covariates=None, weight_covariates=None, + feature_type="weight_vector"): + """ + A doubly-robust estimator of the effect of treatment. + This model adds the weighting (inverse probability weighting) + as additional feature to the outcome model. + + References: + * Bang and Robins, https://doi.org/10.1111/j.1541-0420.2005.00377.x + * Kang and Schafer, section 3.3, https://dx.doi.org/10.1214/07-STS227 + + Args: + outcome_model(IndividualOutcomeEstimator): A causal model that estimate on individuals level + weight_model (WeightEstimator | PropensityEstimator): A causal model for weighting individuals (e.g. IPW). + outcome_covariates (array): Covariates to use for outcome model. + If None - all covariates passed will be used. Either list of column names or boolean mask. + weight_covariates (array): Covariates to use for weight model. + If None - all covariates passed will be used. Either list of column names or boolean mask. + feature_type (str): the type of covariate to add. One of the following options: + * "weight_vector": uses a signed weight vector. Only defined for binary treatment. + For example, if `weight_model` is IPW then: 1/Pr[A=a_i|X] for each sample `i`. + As described in Bang and Robins (2005). + * "weight_matrix": uses the entire weight matrix. + For example, if `weight_model` is IPW then: 1/Pr[A_i=a|X_i=x], + for all treatment values `a` and for every sample `i`. + * "propensity_vector": uses the probabilities for being in treatment group: Pr[A=1|X]. + Better defined for binary treatment. + Equivalent to Scharfstein, Rotnitzky, and Robins (1999) that use its inverse. + * "logit_propensity_vector": uses logit transformation of the propensity to treat Pr[A=1|X]. + As described in Kang and Schafer (2007) + * "propensity_matrix": uses the probabilities for all treatment options, + Pr[A_i=a|X_i=x] for all treatment values `a` and samples `i`. + """ + super().__init__(outcome_model, weight_model, + outcome_covariates, weight_covariates) + self.feature_type = feature_type + + self._feature_functions = self._define_feature_functions() def estimate_individual_outcome(self, X, a, treatment_values=None, predict_proba=None): X_augmented = self._augment_outcome_model_data(X, a) @@ -276,12 +310,13 @@ def _augment_outcome_model_data(self, X, a): matrix (W | X). """ X_outcome, X_weight = self._prepare_data(X, a) - try: - weights_feature = self.weight_model.compute_weight_matrix(X_weight, a) - weights_feature = weights_feature.add_prefix("ipf_") - except NotImplementedError: - weights_feature = self.weight_model.compute_weights(X_weight, a) - weights_feature = weights_feature.rename("ipf") + feature_func = self._feature_functions.get(self.feature_type) + if feature_func is None: + raise ValueError( + f"feature type {self.feature_type} is not recognized." + f"Supported options are: {set(self._feature_functions.keys())}" + ) + weights_feature = feature_func(X_weight, a) # Let standardization deal with incorporating treatment assignment (a) into the data: X_augmented = pd.concat([weights_feature, X_outcome], join="outer", axis="columns") return X_augmented @@ -300,12 +335,72 @@ def fit(self, X, a, y, refit_weight_model=True, **kwargs): self.outcome_model.fit(X=X_augmented, y=y, a=a) return self - -class DoublyRobustJoffe(DoublyRobust): + def _define_feature_functions(self): + + def weight_vector(X, a): + w = self.weight_model.compute_weights(X, a) + w = w.rename("ipf") + return w + + def signed_weight_vector(X, a): + if a.nunique() != 2: + raise AssertionError( + f"`feature_type` 'weight_vector' can only be used with binary treatment." + f"Instead, treatment values are {set(a)}." + ) + w = weight_vector(X, a) + w[a == 0] *= -1 + return w + + def weight_matrix(X, a): + W = self.weight_model.compute_weight_matrix(X, a) + W = W.add_prefix("ipf_") + return W + + def masked_weight_matrix(X, a): + W = weight_matrix(X, a) + A = pd.get_dummies(a) + A = A.add_prefix("ipf_") # To match naming of `W` + W_masked = W * A + return W_masked + + def propensity_vector(X, a): + p = self.weight_model.compute_propensity(X, a) + p = p.rename("propensity") + return p + + def logit_propensity_vector(X, a, safe=True): + p = propensity_vector(X, a) + if safe: + epsilon = np.finfo(float).eps + p = np.clip(p, epsilon, 1 - epsilon) + return np.log(p / (1 - p)) + + def propensity_matrix(X, a): + P = self.weight_model.compute_propensity_matrix(X) + # P = P.iloc[:, 1:] # Drop first column + P = P.add_prefix("propensity_") + return P + + feature_functions = { + "weight_vector": weight_vector, + # "signed_weight_vector": signed_weight_vector, # Hernan & Robins Fine Point 13.2, but seems to be biased + "weight_matrix": weight_matrix, + # "masked_weight_matrix": masked_weight_matrix, # Seems to be biased + "propensity_vector": propensity_vector, + "logit_propensity_vector": logit_propensity_vector, + "propensity_matrix": propensity_matrix, + } + return feature_functions + + +class WeightedStandardization(BaseDoublyRobust): """ - A doubly-robust estimator of the effect of treatment. - This model uses the weights from the weight-model (e.g. inverse probability weighting) as individual weights for - fitting the outcome model. + This model uses the weights from the weight-model (e.g. inverse probability weighting) + as individual weights for fitting the outcome model. + + References: + * Kang and Schafer, section 3.2, https://dx.doi.org/10.1214/07-STS227 """ def estimate_individual_outcome(self, X, a, treatment_values=None, predict_proba=None): diff --git a/causallib/estimation/tmle.py b/causallib/estimation/tmle.py index 795dd2f8..d6788c4f 100644 --- a/causallib/estimation/tmle.py +++ b/causallib/estimation/tmle.py @@ -8,7 +8,7 @@ from sklearn.preprocessing import MinMaxScaler, OneHotEncoder from sklearn.utils.multiclass import type_of_target -from .doubly_robust import DoublyRobust as BaseDoublyRobust +from .doubly_robust import BaseDoublyRobust from causallib.estimation.base_estimator import IndividualOutcomeEstimator from causallib.estimation.base_weight import PropensityEstimator from causallib.utils.stat_utils import robust_lookup diff --git a/causallib/tests/test_doublyrobust.py b/causallib/tests/test_doublyrobust.py index 64062ecb..318b76f0 100644 --- a/causallib/tests/test_doublyrobust.py +++ b/causallib/tests/test_doublyrobust.py @@ -26,7 +26,9 @@ from sklearn.linear_model import LogisticRegression, LinearRegression from warnings import simplefilter, catch_warnings -from causallib.estimation import DoublyRobustVanilla, DoublyRobustIpFeature, DoublyRobustJoffe +from causallib.estimation import ( + ResidualCorrectedStandardization, PropensityFeatureStandardization, WeightedStandardization +) from causallib.estimation import IPW from causallib.estimation import Standardization, StratifiedStandardization @@ -124,7 +126,7 @@ def ensure_model_combinations_work(self, estimator_class): self.assertTrue(True) # Dummy assert, didn't crash with self.subTest("Check prediction"): ind_outcome = dr.estimate_individual_outcome(data["X"], data["a"]) - y = data["y"] if isinstance(dr, DoublyRobustVanilla) else None # Avoid warnings + y = data["y"] if isinstance(dr, ResidualCorrectedStandardization) else None # Avoid warnings pop_outcome = dr.estimate_population_outcome(data["X"], data["a"], y) dr.estimate_effect(ind_outcome[1], ind_outcome[0], agg="individual") dr.estimate_effect(pop_outcome[1], pop_outcome[0]) @@ -189,14 +191,14 @@ def ensure_many_models(self, clip_min=None, clip_max=None): self.assertTrue(True) # Fit did not crash -class TestDoublyRobustVanilla(TestDoublyRobustBase): +class TestResidualCorrectedStandardization(TestDoublyRobustBase): @classmethod def setUpClass(cls): TestDoublyRobustBase.setUpClass() # Avoids regularization of the model: ipw = IPW(LogisticRegression(C=1e6, solver='lbfgs'), use_stabilized=False) std = Standardization(LinearRegression(normalize=True)) - cls.estimator = DoublyRobustVanilla(std, ipw) + cls.estimator = ResidualCorrectedStandardization(std, ipw) def test_uninformative_tx_leads_to_std_like_results(self): self.ensure_uninformative_tx_leads_to_std_like_results(self.estimator) @@ -214,7 +216,7 @@ def test_weight_refitting_refits(self): self.ensure_weight_refitting_refits(self.estimator) def test_model_combinations_work(self): - self.ensure_model_combinations_work(DoublyRobustVanilla) + self.ensure_model_combinations_work(ResidualCorrectedStandardization) def test_pipeline_learner(self): self.ensure_pipeline_learner() @@ -223,14 +225,14 @@ def test_many_models(self): self.ensure_many_models() -class TestDoublyRobustJoffe(TestDoublyRobustBase): +class TestWeightedStandardization(TestDoublyRobustBase): @classmethod def setUpClass(cls): TestDoublyRobustBase.setUpClass() # Avoids regularization of the model: ipw = IPW(LogisticRegression(C=1e6, solver='lbfgs'), use_stabilized=False) std = Standardization(LinearRegression(normalize=True)) - cls.estimator = DoublyRobustJoffe(std, ipw) + cls.estimator = WeightedStandardization(std, ipw) def test_uninformative_tx_leads_to_std_like_results(self): self.ensure_uninformative_tx_leads_to_std_like_results(self.estimator) @@ -248,7 +250,7 @@ def test_weight_refitting_refits(self): self.ensure_weight_refitting_refits(self.estimator) def test_model_combinations_work(self): - self.ensure_model_combinations_work(DoublyRobustJoffe) + self.ensure_model_combinations_work(WeightedStandardization) def test_pipeline_learner(self): self.ensure_pipeline_learner() @@ -300,14 +302,14 @@ def test_many_models(self): model.fit(data["X"], data["a"], data["y"], refit_weight_model=False) -class TestDoublyRobustIPFeature(TestDoublyRobustBase): +class TestPropensityFeatureStandardization(TestDoublyRobustBase): @classmethod def setUpClass(cls): TestDoublyRobustBase.setUpClass() # Avoids regularization of the model: ipw = IPW(LogisticRegression(C=1e6, solver='lbfgs'), use_stabilized=False) std = Standardization(LinearRegression(normalize=True)) - cls.estimator = DoublyRobustIpFeature(std, ipw) + cls.estimator = PropensityFeatureStandardization(std, ipw) def fit_and_predict_all_learners(self, data, estimator): X, a, y = data["X"], data["a"], data["y"] @@ -327,16 +329,66 @@ def test_is_fitted(self): self.ensure_is_fitted(self.estimator) def test_data_is_separated_between_models(self): - self.ensure_data_is_separated_between_models(self.estimator, 2 + 1) # 2 ip-features + 1 treatment assignment + self.ensure_data_is_separated_between_models(self.estimator, 1 + 1) # 1 ip-feature + 1 treatment assignment def test_weight_refitting_refits(self): self.ensure_weight_refitting_refits(self.estimator) def test_model_combinations_work(self): - self.ensure_model_combinations_work(DoublyRobustIpFeature) + self.ensure_model_combinations_work(PropensityFeatureStandardization) def test_pipeline_learner(self): self.ensure_pipeline_learner() def test_many_models(self): self.ensure_many_models(clip_min=0.001, clip_max=1-0.001) + + def test_many_feature_types(self): + with self.subTest("Ensure all feature types are tested"): + feature_types = [ + "weight_vector", # "signed_weight_vector", + "weight_matrix", # "masked_weight_matrix", + "propensity_vector", "propensity_matrix", + "logit_propensity_vector", + ] + model_feature_types = set(self.estimator._feature_functions.keys()) + if set(feature_types) != model_feature_types: + raise AssertionError( + "Hey there, there's a mismatch between `PropensityFeatureStandardization._feature_types" + "and its corresponding tests. Did you add a new type without testing?" + ) + + use_tmle_data = True + if use_tmle_data: # Align the datasets to the same attributes + from causallib.tests.test_tmle import generate_data + data = generate_data(1100, 2, 0, seed=0) + data['y'] = data['y_cont'] + else: + data = self.create_uninformative_ox_dataset() + data['treatment_effect'] = data['beta'] + + for feature_type in feature_types: + with self.subTest(f"Testing {feature_type}"): + self.estimator.feature_type = feature_type + self.estimator.fit(data['X'], data['a'], data['y']) + + # Test estimation: + pop_outcomes = self.estimator.estimate_population_outcome(data['X'], data['a']) + effect = pop_outcomes[1] - pop_outcomes[0] + np.testing.assert_allclose( + data['treatment_effect'], effect, + atol=0.05 + ) + + # Test added covariates: + X_size = data['X'].shape[1] + added_covariates = 1 if "vector" in feature_type else 2 # Else it's a matrix + n_coefs = self.estimator.outcome_model.learner.coef_.size + self.assertEqual(n_coefs, X_size + added_covariates + 1) # 1 for treatment assignment + + # with self.subTest("Test signed_weight_vector takes only binary", skip=True): + # a = data['a'].copy() + # a.iloc[-a.shape[0] // 4:] += 1 + # self.estimator.feature_type = "signed_weight_vector" + # with self.assertRaises(AssertionError): + # self.estimator.fit(data['X'], a, data['y']) diff --git a/causallib/tests/test_plots.py b/causallib/tests/test_plots.py index d6a25881..a550fa61 100644 --- a/causallib/tests/test_plots.py +++ b/causallib/tests/test_plots.py @@ -16,7 +16,7 @@ from sklearn.linear_model import LogisticRegression, LinearRegression from causallib.evaluation import PropensityEvaluator, OutcomeEvaluator -from causallib.estimation import DoublyRobustVanilla, IPW, StratifiedStandardization +from causallib.estimation import ResidualCorrectedStandardization, IPW, StratifiedStandardization from causallib.datasets import load_nhefs import unittest import pandas as pd @@ -31,7 +31,7 @@ def setUpClass(self): self.data = load_nhefs() ipw = IPW(LogisticRegression(solver="liblinear"), clip_min=0.05, clip_max=0.95) std = StratifiedStandardization(LinearRegression()) - self.dr = DoublyRobustVanilla(std, ipw) + self.dr = ResidualCorrectedStandardization(std, ipw) self.dr.fit(self.data.X, self.data.a, self.data.y) self.prp_evaluator = PropensityEvaluator(self.dr.weight_model) self.out_evaluator = OutcomeEvaluator(self.dr.outcome_model) diff --git a/examples/doubly_robust.ipynb b/examples/doubly_robust.ipynb index 95c89ab3..5b22baf5 100644 --- a/examples/doubly_robust.ipynb +++ b/examples/doubly_robust.ipynb @@ -23,7 +23,7 @@ "from sklearn.linear_model import LogisticRegression, LinearRegression\n", "from causallib.datasets import load_nhefs\n", "from causallib.estimation import IPW, Standardization, StratifiedStandardization\n", - "from causallib.estimation import DoublyRobustVanilla, DoublyRobustIpFeature, DoublyRobustJoffe\n", + "from causallib.estimation import ResidualCorrectedStandardization, PropensityFeatureStandardization, WeightedStandardization\n", "from causallib.evaluation import PropensityEvaluator, OutcomeEvaluator" ] }, @@ -257,7 +257,7 @@ { "data": { "text/plain": [ - "DoublyRobustVanilla(outcome_covariates=None, outcome_model=StratifiedStandardization(learner=LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,\n", + "ResidualCorrectedStandardization(outcome_covariates=None, outcome_model=StratifiedStandardization(learner=LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,\n", " normalize=False)), predict_proba=False, weight_covariates=None,\n", " weight_model=IPW(clip_min=0.05, clip_max=0.95, use_stabilized=False,\n", " learner=LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,\n", @@ -274,7 +274,7 @@ "source": [ "ipw = IPW(LogisticRegression(solver=\"liblinear\"), clip_min=0.05, clip_max=0.95)\n", "std = StratifiedStandardization(LinearRegression())\n", - "dr = DoublyRobustVanilla(std, ipw)\n", + "dr = ResidualCorrectedStandardization(std, ipw)\n", "dr.fit(data.X, data.a, data.y)" ] }, @@ -357,7 +357,7 @@ { "data": { "text/plain": [ - "DoublyRobustIpFeature(outcome_covariates=None, outcome_model=Standardization(encode_treatment=False, predict_proba=False,\n", + "PropensityFeatureStandardization(outcome_covariates=None, outcome_model=Standardization(encode_treatment=False, predict_proba=False,\n", " learner=LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,\n", " normalize=False)), predict_proba=False, weight_covariates=None,\n", " weight_model=IPW(clip_min=None, clip_max=None, use_stabilized=False,\n", @@ -375,7 +375,7 @@ "source": [ "ipw = IPW(LogisticRegression(solver=\"liblinear\"))\n", "std = Standardization(LinearRegression())\n", - "dr = DoublyRobustIpFeature(std, ipw)\n", + "dr = PropensityFeatureStandardization(std, ipw)\n", "dr.fit(data.X, data.a, data.y)" ] }, @@ -501,7 +501,7 @@ { "data": { "text/plain": [ - "DoublyRobustJoffe(outcome_covariates=None, outcome_model=Standardization(encode_treatment=False, predict_proba=False,\n", + "WeightedStandardization(outcome_covariates=None, outcome_model=Standardization(encode_treatment=False, predict_proba=False,\n", " learner=LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,\n", " normalize=False)), predict_proba=False, weight_covariates=None,\n", " weight_model=IPW(clip_min=None, clip_max=None, use_stabilized=False,\n", @@ -519,7 +519,7 @@ "source": [ "ipw = IPW(LogisticRegression(solver=\"liblinear\"))\n", "std = Standardization(LinearRegression())\n", - "dr = DoublyRobustJoffe(std, ipw)\n", + "dr = WeightedStandardization(std, ipw)\n", "dr.fit(data.X, data.a, data.y)" ] }, @@ -674,8 +674,8 @@ " if not col.startswith(\"wt\") and not col.startswith(\"age\")]\n", "ipw = IPW(LogisticRegression(solver=\"liblinear\"))\n", "std = Standardization(LinearRegression())\n", - "dr = DoublyRobustIpFeature(std, ipw, \n", - " weight_covariates=weight_covariates)\n", + "dr = PropensityFeatureStandardization(std, ipw, \n", + " weight_covariates=weight_covariates)\n", "# By not specifying `outcome_covariates` the model will use all covariates\n", "dr.fit(data.X, data.a, data.y);" ] @@ -749,7 +749,7 @@ "source": [ "ipw = IPW(LogisticRegression(solver=\"liblinear\"), clip_min=0.05, clip_max=0.95)\n", "std = Standardization(LinearRegression(), encode_treatment=True)\n", - "dr = DoublyRobustVanilla(std, ipw)" + "dr = ResidualCorrectedStandardization(std, ipw)" ] }, { @@ -789,7 +789,7 @@ "source": [ "ipw = IPW(LogisticRegression(solver=\"liblinear\"))\n", "std = Standardization(LinearRegression())\n", - "dr = DoublyRobustIpFeature(std, ipw)\n", + "dr = PropensityFeatureStandardization(std, ipw)\n", "dr.fit(data.X, data.a, data.y);" ] }, diff --git a/examples/fast_food_employment_card_krueger.ipynb b/examples/fast_food_employment_card_krueger.ipynb index da00d07a..3fd3f9e2 100644 --- a/examples/fast_food_employment_card_krueger.ipynb +++ b/examples/fast_food_employment_card_krueger.ipynb @@ -448,9 +448,9 @@ "from sklearn.linear_model import LogisticRegression, LinearRegression\n", "from matplotlib import pyplot as plt\n", "from causallib.estimation import (\n", - " DoublyRobustIpFeature,\n", - " DoublyRobustJoffe,\n", - " DoublyRobustVanilla,\n", + " PropensityFeatureStandardization,\n", + " WeightedStandardization,\n", + " ResidualCorrectedStandardization,\n", " IPW,\n", " MarginalOutcomeEstimator,\n", " Standardization,\n", @@ -476,9 +476,9 @@ " IPW(learner=learner()),\n", " Standardization(learner=LinearRegression()),\n", " StratifiedStandardization(learner=LinearRegression()),\n", - " DoublyRobustIpFeature(makestd(), makeipw()),\n", - " DoublyRobustJoffe(makestd(), makeipw()),\n", - " DoublyRobustVanilla(makestd(), makeipw()),\n", + " PropensityFeatureStandardization(makestd(), makeipw()),\n", + " WeightedStandardization(makestd(), makeipw()),\n", + " ResidualCorrectedStandardization(makestd(), makeipw()),\n", "]\n", "\n", "\n", @@ -569,19 +569,19 @@ " 1.007530\n", " \n", " \n", - " DoublyRobustIpFeature\n", + " PropensityFeatureStandardization\n", " 16.686028\n", " 17.638321\n", " 0.952293\n", " \n", " \n", - " DoublyRobustJoffe\n", + " WeightedStandardization\n", " 16.625123\n", " 17.667617\n", " 1.042494\n", " \n", " \n", - " DoublyRobustVanilla\n", + " ResidualCorrectedStandardization\n", " 16.653759\n", " 17.673968\n", " 1.020210\n", @@ -591,16 +591,16 @@ "" ], "text/plain": [ - " 0 1 ATE\n", - "MarginalOutcomeEstimator 17.544118 17.324373 -0.219745\n", - "Matching 17.544118 18.647059 1.102941\n", - "PropensityMatching 17.422190 17.725504 0.303314\n", - "IPW 16.967157 18.135547 1.168390\n", - "Standardization 16.285505 17.631131 1.345626\n", - "StratifiedStandardization 16.664511 17.672040 1.007530\n", - "DoublyRobustIpFeature 16.686028 17.638321 0.952293\n", - "DoublyRobustJoffe 16.625123 17.667617 1.042494\n", - "DoublyRobustVanilla 16.653759 17.673968 1.020210" + " 0 1 ATE\n", + "MarginalOutcomeEstimator 17.544118 17.324373 -0.219745\n", + "Matching 17.544118 18.647059 1.102941\n", + "PropensityMatching 17.422190 17.725504 0.303314\n", + "IPW 16.967157 18.135547 1.168390\n", + "Standardization 16.285505 17.631131 1.345626\n", + "StratifiedStandardization 16.664511 17.672040 1.007530\n", + "PropensityFeatureStandardization 16.686028 17.638321 0.952293\n", + "WeightedStandardization 16.625123 17.667617 1.042494\n", + "ResidualCorrectedStandardization 16.653759 17.673968 1.020210" ] }, "execution_count": 8, @@ -991,19 +991,19 @@ " 1.766561\n", " \n", " \n", - " DoublyRobustIpFeature\n", + " PropensityFeatureStandardization\n", " 17.500168\n", " 18.502095\n", " 1.001927\n", " \n", " \n", - " DoublyRobustJoffe\n", + " WeightedStandardization\n", " 17.429642\n", " 19.192642\n", " 1.763000\n", " \n", " \n", - " DoublyRobustVanilla\n", + " ResidualCorrectedStandardization\n", " 17.429092\n", " 19.193340\n", " 1.764248\n", @@ -1013,16 +1013,16 @@ "" ], "text/plain": [ - " 0 1 ATE\n", - "MarginalOutcomeEstimator 17.544118 19.084559 1.540441\n", - "Matching 17.544118 19.084559 1.540441\n", - "PropensityMatching 17.645221 17.856618 0.211397\n", - "IPW 17.438962 19.157859 1.718897\n", - "Standardization 17.431120 19.197557 1.766437\n", - "StratifiedStandardization 17.431684 19.198245 1.766561\n", - "DoublyRobustIpFeature 17.500168 18.502095 1.001927\n", - "DoublyRobustJoffe 17.429642 19.192642 1.763000\n", - "DoublyRobustVanilla 17.429092 19.193340 1.764248" + " 0 1 ATE\n", + "MarginalOutcomeEstimator 17.544118 19.084559 1.540441\n", + "Matching 17.544118 19.084559 1.540441\n", + "PropensityMatching 17.645221 17.856618 0.211397\n", + "IPW 17.438962 19.157859 1.718897\n", + "Standardization 17.431120 19.197557 1.766437\n", + "StratifiedStandardization 17.431684 19.198245 1.766561\n", + "PropensityFeatureStandardization 17.500168 18.502095 1.001927\n", + "WeightedStandardization 17.429642 19.192642 1.763000\n", + "ResidualCorrectedStandardization 17.429092 19.193340 1.764248" ] }, "execution_count": 14,