Skip to content

Commit a84cba3

Browse files
ashoksmoremeta-codesync[bot]
authored andcommitted
(#504) Add regression test for CBPS rank-deficient SVD handling (#507)
Summary: - Add a focused regression test for rank-deficient CBPS design matrices (duplicate covariate column). - Asserts CBPS completes and near-zero singular values are filtered before optimization. - Remove the comment `TODO: add unittest` in `cbps.py`. Closes #504 Pull Request resolved: #507 Test Plan: - [x] `pytest tests/test_cbps.py::Testcbps::test_cbps_rank_deficient_svd_filters_near_zero_singular_values -ra` - [x] `pytest tests/test_cbps.py -ra` Differential Revision: D107679003 Pulled By: talgalili fbshipit-source-id: 99e1b7b58f272e23b2831dd40c19ad91ae7ded16
1 parent 743400e commit a84cba3

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 0.22.0 (Unreleased)
2+
3+
## Tests
4+
5+
- Add regression test for CBPS rank-deficient SVD handling when covariate
6+
columns are collinear (near-zero singular values filtered during SVD
7+
preprocessing).
8+
19
# 0.21.0 (2026-06-02)
210

311
## New Features

balance/weighting_methods/cbps.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,6 @@ def cbps( # noqa
606606
U, s, Vh = scipy.linalg.svd(X_matrix, full_matrices=False)
607607

608608
# remove near-zero singular values to address the rank-deficiency of X_matrix
609-
# TODO: add unittest
610609
singular_value_threshold = 1e-10
611610
U = U[:, s > singular_value_threshold]
612611
Vh = Vh[s > singular_value_threshold, :]

tests/test_cbps.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,44 @@ def test__reverse_svd_and_centralization(self) -> None:
376376
np.around(np.matmul(U, beta), 7),
377377
)
378378

379+
def test_cbps_rank_deficient_svd_filters_near_zero_singular_values(self) -> None:
380+
"""Regression test for rank-deficient design matrices in CBPS."""
381+
sample_df = pd.DataFrame({"a": [0, 1, 0, 1, 0, 1], "b": [0, 1, 0, 1, 0, 1]})
382+
target_df = pd.DataFrame({"a": [0, 1, 0, 1], "b": [0, 1, 0, 1]})
383+
sample_weights = pd.Series([1.0] * len(sample_df))
384+
target_weights = pd.Series([1.0] * len(target_df))
385+
386+
result = balance_cbps.cbps(
387+
sample_df,
388+
sample_weights,
389+
target_df,
390+
target_weights,
391+
variables=["a", "b"],
392+
transformations=None,
393+
store_fit_metadata=True,
394+
)
395+
396+
self.assertIn("weight", result)
397+
weights = result["weight"]
398+
self.assertIsInstance(weights, pd.Series)
399+
self.assertEqual(len(weights), len(sample_df))
400+
self.assertTrue(np.all(np.isfinite(weights)))
401+
402+
model = result["model"]
403+
self.assertIsInstance(model, dict)
404+
svd_s = model["svd_s"]
405+
n_design_columns = len(model["X_matrix_columns"])
406+
407+
self.assertLess(
408+
len(svd_s),
409+
n_design_columns,
410+
msg="Near-zero singular values should be removed for rank-deficient X",
411+
)
412+
self.assertTrue(
413+
np.all(svd_s > TOLERANCE),
414+
msg="Retained singular values should exceed the CBPS filtering threshold",
415+
)
416+
379417
def test_cbps_consistency_with_default_arguments(self) -> None:
380418
"""Test CBPS function consistency with default arguments on complex data.
381419

0 commit comments

Comments
 (0)