Skip to content

Commit cafd26f

Browse files
fix(ipw): preserve treatment design_info instead of overwriting with covariates (#842)
Fixes #835. `InversePropensityWeighting._build_design_matrices` assigned `self._t_design_info` twice in a row — first to the treatment matrix's `design_info`, then immediately overwriting it with the covariate matrix's `design_info`. The attribute name suggests treatment metadata but only the covariate side was being kept. Store the covariate side under `self._x_design_info` to match the existing convention used in `instrumental_variable.py`, `interrupted_time_series.py`, `regression_discontinuity.py`, and `prepostnegd.py`. The treatment design now lives under `self._t_design_info` as the name implies. Adds a `TestDesignInfoAttributes` regression suite that asserts the two attributes are distinct and hold the correct sides of the formula. The class would have failed on master because `_t_design_info` would have held the covariate columns and `_x_design_info` would not have existed at all. Assisted-by: Claude Code Co-authored-by: Benjamin T. Vincent <inferencelab@gmail.com>
1 parent 293c8ca commit cafd26f

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

causalpy/experiments/inverse_propensity_weighting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def _build_design_matrices(self) -> None:
106106
"""
107107
t, X = dmatrices(self.formula, self.data)
108108
self._t_design_info = t.design_info
109-
self._t_design_info = X.design_info
109+
self._x_design_info = X.design_info
110110
self.labels = X.design_info.column_names
111111
self.t, self.X = np.asarray(t), np.asarray(X)
112112
self.y = self.data[self.outcome_variable]

causalpy/tests/test_ipw_get_ate.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,24 @@ def test_get_ate_with_unknown_method_falls_back_to_doubly_robust(self, ipw_resul
220220
assert np.isclose(ate_list[0], ate, equal_nan=True)
221221
assert np.isclose(ate_list[1], trt, equal_nan=True)
222222
assert np.isclose(ate_list[2], ntrt, equal_nan=True)
223+
224+
225+
class TestDesignInfoAttributes:
226+
"""Regression for #835: ``_t_design_info`` must hold the treatment design
227+
(left-hand side of the formula) and ``_x_design_info`` must hold the
228+
covariate design (right-hand side). The previous implementation assigned
229+
both to ``_t_design_info``, silently overwriting the treatment side."""
230+
231+
def test_t_design_info_is_treatment_side(self, ipw_result):
232+
"""The treatment side of ``trt ~ 1 + age + race`` is the lone column ``trt``."""
233+
assert ipw_result._t_design_info.column_names == ["trt"]
234+
235+
def test_x_design_info_is_covariate_side(self, ipw_result):
236+
"""The covariate side mirrors the right-hand side of the formula."""
237+
assert hasattr(ipw_result, "_x_design_info")
238+
assert "age" in ipw_result._x_design_info.column_names
239+
assert "race" in ipw_result._x_design_info.column_names
240+
241+
def test_t_and_x_design_info_are_distinct(self, ipw_result):
242+
"""The two attributes must point to different design objects."""
243+
assert ipw_result._t_design_info is not ipw_result._x_design_info

0 commit comments

Comments
 (0)