Skip to content

Commit 8f53864

Browse files
Add single-variable rake fallback to poststratify
1 parent 49484a9 commit 8f53864

3 files changed

Lines changed: 84 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@
4949
`fct_lump`). To enable transfer scoring, pass deterministic
5050
transformations at fit time (e.g. wrappers built around stored
5151
fit-time bin edges) or re-fit rake on the scoring data.
52+
- **`rake()` now auto-falls back to `poststratify()` for single-variable inputs.**
53+
- Calls that pass exactly one adjustment variable no longer fail with a
54+
variable-count assertion.
55+
- The function logs a warning and delegates to `poststratify()` with the
56+
same transformation/NA-handling/trimming options, so users can keep
57+
`method="rake"` in generic workflows without branching on variable count.
5258

5359
## Code Quality & Refactoring
5460

balance/weighting_methods/rake.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import pandas as pd
2121
from balance import adjustment as balance_adjustment, util as balance_util
2222
from balance.util import _safe_fillna_and_infer
23+
from balance.weighting_methods.poststratify import poststratify
2324

2425
logger: logging.Logger = logging.getLogger(__package__)
2526

@@ -261,12 +262,29 @@ def rake(
261262
sample_df = sample_df.loc[:, variables]
262263
target_df = target_df.loc[:, variables]
263264

264-
# TODO: When len(variables) == 1, fall back to poststratify instead of
265-
# raising, so users can call adjust(method="rake") without worrying about
266-
# the variable count.
265+
if len(variables) == 1:
266+
logger.warning(
267+
"rake() received a single variable (%s); falling back to "
268+
"poststratify() on that variable.",
269+
variables[0],
270+
)
271+
return poststratify(
272+
sample_df=sample_df,
273+
sample_weights=sample_weights,
274+
target_df=target_df,
275+
target_weights=target_weights,
276+
variables=variables,
277+
transformations=transformations,
278+
na_action=na_action,
279+
weight_trimming_mean_ratio=weight_trimming_mean_ratio,
280+
weight_trimming_percentile=weight_trimming_percentile,
281+
keep_sum_of_weights=keep_sum_of_weights,
282+
store_fit_metadata=store_fit_metadata,
283+
)
284+
267285
assert len(variables) > 1, (
268-
"Must weight on at least two variables for raking. "
269-
f"Currently have variables={variables} only"
286+
"Must weight on at least one variable. "
287+
"Received no common variables between sample and target."
270288
)
271289

272290
transformations_to_apply = transformations

tests/test_rake.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from balance import adjustment as balance_adjustment
2222
from balance.sample_class import Sample
2323
from balance.util import _assert_type
24+
from balance.weighting_methods.poststratify import poststratify
2425
from balance.weighting_methods.rake import (
2526
_find_lcm_of_array_lengths,
2627
_hare_niemeyer_allocation,
@@ -124,14 +125,21 @@ def test_rake_input_assertions(self) -> None:
124125
pd.Series((1,) * n_rows),
125126
)
126127

127-
# Must pass more than one variable
128-
self._assert_rake_raises_with_message(
129-
"Must weight on at least two variables",
130-
sample[["a"]],
131-
pd.Series((1,) * n_rows),
132-
target[["a"]],
133-
pd.Series((1,) * n_rows),
128+
# A single variable falls back to poststratify.
129+
with self.assertLogs("balance.weighting_methods", level="WARNING") as cm:
130+
single_var_result = rake(
131+
sample_df=sample[["a"]],
132+
sample_weights=pd.Series((1,) * n_rows),
133+
target_df=target[["a"]],
134+
target_weights=pd.Series((1,) * n_rows),
135+
)
136+
self.assertTrue(
137+
any(
138+
"falling back to poststratify" in message.lower()
139+
for message in cm.output
140+
)
134141
)
142+
self.assertIn("weight", single_var_result)
135143

136144
# Must pass weights for sample
137145
self._assert_rake_raises_with_message(
@@ -234,6 +242,46 @@ def test_rake_fails_when_all_na(self) -> None:
234242
transformations=None,
235243
)
236244

245+
def test_rake_single_variable_matches_poststratify(self) -> None:
246+
sample_df = pd.DataFrame(
247+
{"x": ["a", "a", "b", "b", "b"], "noise": [1, 2, 3, 4, 5]}
248+
)
249+
target_df = pd.DataFrame({"x": ["a", "a", "a", "b", "b", "b", "b"]})
250+
sample_w = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0])
251+
target_w = pd.Series([1.0] * len(target_df))
252+
253+
with self.assertLogs("balance.weighting_methods", level="WARNING"):
254+
rake_res = rake(
255+
sample_df=sample_df,
256+
sample_weights=sample_w,
257+
target_df=target_df,
258+
target_weights=target_w,
259+
variables=["x"],
260+
transformations=None,
261+
na_action="add_indicator",
262+
weight_trimming_mean_ratio=10.0,
263+
keep_sum_of_weights=True,
264+
store_fit_metadata=True,
265+
)
266+
267+
post_res = poststratify(
268+
sample_df=sample_df[["x"]],
269+
sample_weights=sample_w,
270+
target_df=target_df[["x"]],
271+
target_weights=target_w,
272+
variables=["x"],
273+
transformations=None,
274+
na_action="add_indicator",
275+
weight_trimming_mean_ratio=10.0,
276+
keep_sum_of_weights=True,
277+
store_fit_metadata=True,
278+
)
279+
280+
pd.testing.assert_series_equal(rake_res["weight"], post_res["weight"])
281+
self.assertEqual(rake_res["model"]["method"], "poststratify")
282+
self.assertTrue(rake_res["model"]["store_fit_metadata"])
283+
self.assertIn("cell_weight_ratio", rake_res["model"])
284+
237285
def test_rake_weights(self) -> None:
238286
"""
239287
Test basic rake weighting functionality with categorical data.

0 commit comments

Comments
 (0)