From 2549a13bd8eb6d19ba95a0e9e9c8d1cac5854639 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Mon, 24 Feb 2020 17:50:09 -0500 Subject: [PATCH 1/2] Fixed inference results when `cate_feature_names` is not defined --- econml/inference.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/econml/inference.py b/econml/inference.py index 01e20e65c..026aa196a 100644 --- a/econml/inference.py +++ b/econml/inference.py @@ -237,7 +237,7 @@ def coef__inference(self): if coef.size == 0: # X is None raise AttributeError("X is None, please call intercept_inference to learn the constant!") - if callable(self._est.cate_feature_names): + if hasattr(self._est, 'cate_feature_names') and callable(self._est.cate_feature_names): def fname_transformer(x): return self._est.cate_feature_names(x) else: @@ -426,7 +426,7 @@ def coef__inference(self, T): coef_stderr = self.fitted_models_final[ind].coef_stderr_ if coef.size == 0: # X is None raise AttributeError("X is None, please call intercept_inference to learn the constant!") - if callable(self._est.cate_feature_names): + if hasattr(self._est, 'cate_feature_names') and callable(self._est.cate_feature_names): def fname_transformer(x): return self._est.cate_feature_names(x) else: @@ -692,7 +692,7 @@ def summary_frame(self, alpha=0.1, value=0, decimals=3, feat_name=None): if self.d_y == 1: res.index = res.index.droplevel(1) if self.inf_type == 'coefficient': - if feat_name and self.fname_transformer: + if feat_name is not None and self.fname_transformer: ind = self.fname_transformer(feat_name) else: ct = res.shape[0] // self.d_y From ddf9289955726de1cbc3c7bbccf9ccbde6b78af0 Mon Sep 17 00:00:00 2001 From: Miruna Oprescu Date: Tue, 25 Feb 2020 14:26:08 -0500 Subject: [PATCH 2/2] Added test for the new fixes --- econml/tests/test_inference.py | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 econml/tests/test_inference.py diff --git a/econml/tests/test_inference.py b/econml/tests/test_inference.py new file mode 100644 index 000000000..4426d6f75 --- /dev/null +++ b/econml/tests/test_inference.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import numpy as np +import unittest +from sklearn.base import clone +from sklearn.preprocessing import PolynomialFeatures +from econml.dml import LinearDMLCateEstimator + + +class TestInference(unittest.TestCase): + + @classmethod + def setUpClass(cls): + np.random.seed(123) + # DGP constants + cls.n = 1000 + cls.d_w = 30 + cls.d_x = 5 + # Generate data + cls.X = np.random.uniform(0, 1, size=(cls.n, cls.d_x)) + cls.W = np.random.normal(0, 1, size=(cls.n, cls.d_w)) + cls.T = np.random.normal(0, 1, size=(cls.n, )) + cls.Y = np.random.normal(0, 1, size=(cls.n, )) + + def test_inference_results(self): + """Tests the inference results summary.""" + # Test inference results when `cate_feature_names` doesn not exist + cate_est = LinearDMLCateEstimator( + featurizer=PolynomialFeatures(degree=1, + include_bias=False) + ) + wrapped_est = self._NoFeatNamesEst(cate_est) + wrapped_est.fit( + TestInference.Y, + TestInference.T, + TestInference.X, + TestInference.W, + inference='statsmodels' + ) + summary_results = wrapped_est.summary() + coef_rows = np.asarray(summary_results.tables[0].data)[1:, 0] + np.testing.assert_array_equal(coef_rows, ['X{}'.format(i) for i in range(TestInference.d_x)]) + + class _NoFeatNamesEst: + def __init__(self, cate_est): + self.cate_est = clone(cate_est, safe=False) + + def __getattr__(self, name): + if name != 'cate_feature_names': + return getattr(self.cate_est, name) + else: + return self.__getattribute__(name)