Skip to content

Commit 5ab8dc1

Browse files
committed
more tests
1 parent f394ac8 commit 5ab8dc1

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

tests/test_formula_parse.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,86 @@ class TestMultipleEstimationExpansion:
168168
"Y2 ~ X2 | f2",
169169
],
170170
),
171+
# Multiple dep vars with sw in covariates and csw in fixed effects
172+
(
173+
"sw(Y1, Y2) ~ sw(X1, X2) | csw(f1, f2)",
174+
[
175+
"Y1 ~ X1 | f1",
176+
"Y1 ~ X1 | f1 + f2",
177+
"Y1 ~ X2 | f1",
178+
"Y1 ~ X2 | f1 + f2",
179+
"Y2 ~ X1 | f1",
180+
"Y2 ~ X1 | f1 + f2",
181+
"Y2 ~ X2 | f1",
182+
"Y2 ~ X2 | f1 + f2",
183+
],
184+
),
185+
# sw0 in covariates + sw in fixed effects
186+
(
187+
"Y ~ sw0(X1, X2) | sw(f1, f2)",
188+
[
189+
"Y ~ 1 | f1",
190+
"Y ~ 1 | f2",
191+
"Y ~ X1 | f1",
192+
"Y ~ X1 | f2",
193+
"Y ~ X2 | f1",
194+
"Y ~ X2 | f2",
195+
],
196+
),
197+
# csw in covariates + csw0 in fixed effects
198+
(
199+
"Y ~ csw(X1, X2) | csw0(f1, f2)",
200+
[
201+
"Y ~ X1 | 1",
202+
"Y ~ X1 | f1",
203+
"Y ~ X1 | f1 + f2",
204+
"Y ~ X1 + X2 | 1",
205+
"Y ~ X1 + X2 | f1",
206+
"Y ~ X1 + X2 | f1 + f2",
207+
],
208+
),
209+
# sw(dep vars) + csw(covariates) + sw(fixed effects)
210+
(
211+
"sw(Y1, Y2) ~ csw(X1, X2) | sw(f1, f2)",
212+
[
213+
"Y1 ~ X1 | f1",
214+
"Y1 ~ X1 | f2",
215+
"Y1 ~ X1 + X2 | f1",
216+
"Y1 ~ X1 + X2 | f2",
217+
"Y2 ~ X1 | f1",
218+
"Y2 ~ X1 | f2",
219+
"Y2 ~ X1 + X2 | f1",
220+
"Y2 ~ X1 + X2 | f2",
221+
],
222+
),
223+
# mvsw in covariates + sw in fixed effects
224+
(
225+
"Y ~ mvsw(X1, X2) | sw(f1, f2)",
226+
[
227+
"Y ~ 1 | f1",
228+
"Y ~ 1 | f2",
229+
"Y ~ X1 | f1",
230+
"Y ~ X1 | f2",
231+
"Y ~ X2 | f1",
232+
"Y ~ X2 | f2",
233+
"Y ~ X1 + X2 | f1",
234+
"Y ~ X1 + X2 | f2",
235+
],
236+
),
237+
# mvsw in covariates + csw in fixed effects
238+
(
239+
"Y ~ mvsw(X1, X2) | csw(f1, f2)",
240+
[
241+
"Y ~ 1 | f1",
242+
"Y ~ 1 | f1 + f2",
243+
"Y ~ X1 | f1",
244+
"Y ~ X1 | f1 + f2",
245+
"Y ~ X2 | f1",
246+
"Y ~ X2 | f1 + f2",
247+
"Y ~ X1 + X2 | f1",
248+
"Y ~ X1 + X2 | f1 + f2",
249+
],
250+
),
171251
],
172252
)
173253
def test_expand_all_multiple_estimation(self, formula, expected):
@@ -269,6 +349,63 @@ def test_parse_sw_in_fe_and_independent(self):
269349
result = Formula.parse("Y ~ sw(X1, X2) | sw(f1, f2)")
270350
assert len(result) == 4 # 2 x 2
271351

352+
def test_parse_multiple_dep_vars_with_sw_and_csw(self):
353+
"""Y1 + Y2 (preprocessed to sw) + sw in covars + csw in FE."""
354+
result = Formula.parse("Y1 + Y2 ~ sw(X1, X2) | csw(f1, f2)")
355+
assert len(result) == 8 # 2 dep * 2 covars * 2 FE
356+
second_stages = [f.second_stage for f in result]
357+
fixed_effects = [f.fixed_effects for f in result]
358+
assert second_stages == [
359+
"Y1 ~ X1", "Y1 ~ X1", "Y1 ~ X2", "Y1 ~ X2",
360+
"Y2 ~ X1", "Y2 ~ X1", "Y2 ~ X2", "Y2 ~ X2",
361+
]
362+
assert fixed_effects == [
363+
"f1", "f1 + f2", "f1", "f1 + f2",
364+
"f1", "f1 + f2", "f1", "f1 + f2",
365+
]
366+
367+
def test_parse_csw0_in_fe_maps_to_none(self):
368+
"""csw0 in FE produces a '1' zero-step which maps to fixed_effects=None."""
369+
result = Formula.parse("Y ~ X1 | csw0(f1, f2)")
370+
assert len(result) == 3
371+
assert result[0].fixed_effects is None # zero step: "1" -> None
372+
assert result[1].fixed_effects == "f1"
373+
assert result[2].fixed_effects == "f1 + f2"
374+
375+
def test_parse_sw0_in_fe_maps_to_none(self):
376+
"""sw0 in FE produces a '1' zero-step which maps to fixed_effects=None."""
377+
result = Formula.parse("Y ~ X1 | sw0(f1, f2)")
378+
assert len(result) == 3
379+
assert result[0].fixed_effects is None # zero step: "1" -> None
380+
assert result[1].fixed_effects == "f1"
381+
assert result[2].fixed_effects == "f2"
382+
383+
def test_parse_mvsw_covars_with_csw_fe(self):
384+
"""mvsw in covariates combined with csw in fixed effects."""
385+
result = Formula.parse("Y ~ mvsw(X1, X2) | csw(f1, f2)")
386+
assert len(result) == 8 # 4 mvsw * 2 csw
387+
second_stages = [f.second_stage for f in result]
388+
fixed_effects = [f.fixed_effects for f in result]
389+
assert second_stages == [
390+
"Y ~ 1", "Y ~ 1",
391+
"Y ~ X1", "Y ~ X1",
392+
"Y ~ X2", "Y ~ X2",
393+
"Y ~ X1 + X2", "Y ~ X1 + X2",
394+
]
395+
assert fixed_effects == [
396+
"f1", "f1 + f2",
397+
"f1", "f1 + f2",
398+
"f1", "f1 + f2",
399+
"f1", "f1 + f2",
400+
]
401+
402+
def test_parse_to_dict_csw0_fe_groups(self):
403+
"""parse_to_dict should group csw0 FE correctly, with None for zero-step."""
404+
result = Formula.parse_to_dict("Y ~ X1 | csw0(f1, f2)")
405+
assert None in result # zero step
406+
assert "f1" in result
407+
assert "f1 + f2" in result
408+
272409

273410
class TestValidation:
274411
"""Tests for formula validation / error handling."""

0 commit comments

Comments
 (0)