Skip to content

Commit 1907ad6

Browse files
committed
Ensure Jacobian calculations for polynomial features work when T=0
Signed-off-by: Keith Battocchi <kebatt@microsoft.com>
1 parent d4a1fa7 commit 1907ad6

File tree

3 files changed

+13
-6
lines changed

3 files changed

+13
-6
lines changed

econml/iv/sieve/_tsls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def transform(self, X):
151151
for i in range(X.shape[1]):
152152
p = powers.copy()
153153
c = powers[:, i]
154-
p[:, i] -= 1
154+
p[p[:, i] > 0, i] -= 1
155155
M = np.float_power(X[:, np.newaxis, :], p[np.newaxis, :, :])
156156
result[:, i, :] = c[np.newaxis, :] * np.prod(M, axis=-1)
157157
return result

econml/tests/test_treatment_featurization.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,20 +486,21 @@ def test_featurization(self):
486486
def test_jac(self):
487487
def func_transform(x):
488488
x = x.reshape(-1, 1)
489-
return np.hstack([x, x**2])
489+
return np.hstack([np.ones_like(x), x, x**2])
490490

491491
def calc_expected_jacobian(T):
492-
jac = DPolynomialFeatures(degree=2, include_bias=False).fit_transform(T)
492+
jac = DPolynomialFeatures(degree=2, include_bias=True).fit_transform(T)
493493
return jac
494494

495495
treatment_featurizers = [
496-
PolynomialFeatures(degree=2, include_bias=False),
496+
PolynomialFeatures(degree=2, include_bias=True),
497497
FunctionTransformer(func=func_transform)
498498
]
499499

500500
n = 10000
501501
d_t = 1
502502
T = np.random.normal(size=(n, d_t))
503+
T[0, 0] = 0 # hardcode one value of exactly zero to test that we don't generate nan
503504

504505
for treatment_featurizer in treatment_featurizers:
505506
# fit a dummy estimator first so the featurizer can be fit to the treatment

econml/utilities.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,9 +1460,15 @@ def jac(self, X, epsilon=0.001):
14601460
powers = self.featurizer.powers_
14611461
result = np.zeros(X.shape + (self.featurizer.n_output_features_,))
14621462
for i in range(X.shape[1]):
1463-
p = powers.copy()
1463+
# d/dx_i of x_1^p_1 * x_2^p_2 * ... x_i^p_i * ... x_n^p_n
1464+
# = p_i * x_1^p_1 * x_2^p_2 * ... x_i^(p_i-1) * ... x_n^p_n
1465+
# store the coefficient in c, and the updated powers in p
14641466
c = powers[:, i]
1465-
p[:, i] -= 1
1467+
p = powers.copy()
1468+
# decrement p[:, i], but only if it was more than 0 already
1469+
# (if it is 0 then c=0 so we'll correctly get 0 for a result regardless of the updated entries in p,
1470+
# but float_power would return nan if X has a 0 and the exponent is -1)
1471+
p[p[:, i] > 0, i] -= 1
14661472
M = np.float_power(X[:, np.newaxis, :], p[np.newaxis, :, :])
14671473
result[:, i, :] = c[np.newaxis, :] * np.prod(M, axis=-1)
14681474
return result

0 commit comments

Comments
 (0)