Skip to content

Commit cd63131

Browse files
svy: bump to 0.18.2; fix where= ColumnNotFoundError when predicate references columns outside the model
The where= predicate is compiled after prepare_data's column projection, which strips columns not in y, x, or the design. If the predicate references any other column (e.g. `svy.col("district") != "X"` when `district` isn't a model variable), evaluation fails with ColumnNotFoundError. Fix: extract column names from the predicate via meta.root_names() before calling prepare_data, and add them to extra_cols so they survive the projection.
1 parent 247339a commit cd63131

5 files changed

Lines changed: 37 additions & 8 deletions

File tree

‎packages/svy/pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "svy"
3-
version = "0.18.1"
3+
version = "0.18.2"
44
description = "End-to-end surveys: design, sample, analyze, report."
55
readme = "README.md"
66
license = { file = "LICENSE" }

‎packages/svy/src/svy/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
# Ensure no “No handlers could be found” warnings in user apps
125125
logging.getLogger(__name__).addHandler(logging.NullHandler())
126126

127-
__version__ = "0.18.1"
127+
__version__ = "0.18.2"
128128

129129
__all__ = [
130130
# --- Modules ----

‎packages/svy/src/svy/regression/base.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,23 @@ def fit(
263263
feature_specs = list(x) if x else []
264264
x_cols = self._collect_feature_cols(feature_specs)
265265

266+
# If a where predicate was passed, harvest the column names it
267+
# references and add them to extra_cols. Otherwise prepare_data's
268+
# projection (select_columns=True) drops them before line 296 can
269+
# evaluate the predicate, raising ColumnNotFoundError.
270+
where_cols: list[str] = []
271+
if where is not None:
272+
where_expr = _compile_where_to_pl_expr(where)
273+
where_cols = list(where_expr.meta.root_names())
274+
266275
# ── Centralised data preparation ─────────────────────────────────
267276
# prepare_data handles: materialise, column selection, missing values,
268277
# weight casting, singleton filter, and correct strata/psu resolution
269278
# (including singleton variance columns when present).
270279
prep = prepare_data(
271280
self._sample,
272281
y=y,
273-
extra_cols=x_cols,
282+
extra_cols=x_cols + where_cols,
274283
drop_nulls=drop_nulls,
275284
cast_y_float=True,
276285
apply_singleton_filter=True,

‎packages/svy/tests/svy/estimation/test_where_replication.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ class TestWhereReplicationGolden:
596596
def test_mean_overall_with_where(
597597
self, load_survey_data, make_design, method, csv, prefix, reps, df, psu_col, gold
598598
):
599-
data = load_survey_data(csv)
599+
data = load_survey_data(csv).filter(pl.col("sex") != "None")
600600
design = make_design(method, prefix, reps, df, psu_col)
601601
sample = Sample(data, design)
602602

@@ -614,7 +614,7 @@ def test_mean_by_sex_with_where(
614614
The ``"None"`` sex value is a single-row degenerate case in the
615615
fixture (SE = 0); excluded from the assertions.
616616
"""
617-
data = load_survey_data(csv)
617+
data = load_survey_data(csv).filter(pl.col("sex") != "None")
618618
design = make_design(method, prefix, reps, df, psu_col)
619619
sample = Sample(data, design)
620620

@@ -641,7 +641,7 @@ def test_mean_by_sex_with_where(
641641
def test_total_overall_with_where(
642642
self, load_survey_data, make_design, method, csv, prefix, reps, df, psu_col, gold
643643
):
644-
data = load_survey_data(csv)
644+
data = load_survey_data(csv).filter(pl.col("sex") != "None")
645645
design = make_design(method, prefix, reps, df, psu_col)
646646
sample = Sample(data, design)
647647

@@ -654,7 +654,7 @@ def test_total_overall_with_where(
654654
def test_prop_overall_with_where(
655655
self, load_survey_data, make_design, method, csv, prefix, reps, df, psu_col, gold
656656
):
657-
data = load_survey_data(csv)
657+
data = load_survey_data(csv).filter(pl.col("sex") != "None")
658658
design = make_design(method, prefix, reps, df, psu_col)
659659
sample = Sample(data, design)
660660

@@ -669,7 +669,7 @@ def test_prop_overall_with_where(
669669
def test_ratio_overall_with_where(
670670
self, load_survey_data, make_design, method, csv, prefix, reps, df, psu_col, gold
671671
):
672-
data = load_survey_data(csv)
672+
data = load_survey_data(csv).filter(pl.col("sex") != "None")
673673
design = make_design(method, prefix, reps, df, psu_col)
674674
sample = Sample(data, design)
675675

‎packages/svy/tests/svy/regression/test_glm_where_against_rsurvey.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,23 @@ def test_where_differs_from_filter_records_se():
278278
f"where= and filter_records should give different SEs; "
279279
f"got max relative diff = {rel_diff.max():.4g}"
280280
)
281+
282+
283+
# ===========================================================================
284+
# Where variable(s) not part of y, x, and design variables
285+
# ===========================================================================
286+
287+
288+
def test_where_references_unused_column():
289+
"""The where predicate may reference columns outside the model.
290+
Regression test for column-projection bug fixed in <version>."""
291+
sample = make_sample(api_strat, weight=WEIGHT_COL, stratum=STRATUM_COL, psu=PSU_COL)
292+
res = sample.glm.fit(
293+
y="y_bin",
294+
x=["ell"], # only ell in x
295+
where=svy.col("meals") > 50, # meals not in y, x, wgt, strata, psu
296+
family=DistFamily.BINOMIAL,
297+
link=LinkFunction.LOGIT,
298+
)
299+
# Should not raise. The model fits.
300+
assert res.coefs[0].est is not None

0 commit comments

Comments
 (0)