Follow-ups from the review of #901 (native Polars support for meta-learners), which is being merged now that the one merge blocker (_fit_bootstrap_clone crashing on a native X) is resolved. Everything below is non-blocking polish plus a test-coverage gap — safe to batch into a single small PR.
1. S-learner predict rebuilds the augmented matrices inside the per-group loop
causalml/inference/meta/slearner.py (BaseSRegressor.predict ~L138-139; BaseSClassifier.predict likewise) builds X_new_c = prepend_column(0.0, X) / X_new_t = prepend_column(1.0, X) inside for group in self.t_groups:. These depend only on X and are identical every iteration → 2·N full-matrix copies instead of 2. Hoist above the loop (restores the prior "build once" optimization).
2. X-learner fit target re-filters instead of reusing the numpy slice
causalml/inference/meta/xlearner.py:162 (and :592 in BaseXClassifier) fits the outcome model with filter_mask(y_filt, w == 1), a redundant second filter that yields a native pandas/polars Series, while y_filt_np = to_numpy(y_filt) is already computed one line below. Use y_filt_np[w == 1] — consistent with the rest of the method and avoids relying on polars-Series→numpy coercion at the sklearn boundary.
3. Test: use shared constants
tests/test_polars_support.py:33 hardcodes RANDOM_STATE = 42; per repo convention use RANDOM_SEED from tests/const.py.
4. Test gap: store_bootstraps → return_ci with a DataFrame X
The _fit_bootstrap_clone fix (the merge blocker) is CI-invisible — no test drives fit(store_bootstraps=True) → predict(return_ci=True) with a pandas or polars DataFrame X, which is the exact path that was crashing. Add one, plus a DR-classifier polars case (still untested), to lock the fixed path.
Context: full review thread on #901 (2026-07-01 round).
Follow-ups from the review of #901 (native Polars support for meta-learners), which is being merged now that the one merge blocker (
_fit_bootstrap_clonecrashing on a nativeX) is resolved. Everything below is non-blocking polish plus a test-coverage gap — safe to batch into a single small PR.1. S-learner
predictrebuilds the augmented matrices inside the per-group loopcausalml/inference/meta/slearner.py(BaseSRegressor.predict~L138-139;BaseSClassifier.predictlikewise) buildsX_new_c = prepend_column(0.0, X)/X_new_t = prepend_column(1.0, X)insidefor group in self.t_groups:. These depend only onXand are identical every iteration → 2·N full-matrix copies instead of 2. Hoist above the loop (restores the prior "build once" optimization).2. X-learner fit target re-filters instead of reusing the numpy slice
causalml/inference/meta/xlearner.py:162(and:592inBaseXClassifier) fits the outcome model withfilter_mask(y_filt, w == 1), a redundant second filter that yields a native pandas/polars Series, whiley_filt_np = to_numpy(y_filt)is already computed one line below. Usey_filt_np[w == 1]— consistent with the rest of the method and avoids relying on polars-Series→numpy coercion at the sklearn boundary.3. Test: use shared constants
tests/test_polars_support.py:33hardcodesRANDOM_STATE = 42; per repo convention useRANDOM_SEEDfromtests/const.py.4. Test gap:
store_bootstraps→return_ciwith a DataFrame XThe
_fit_bootstrap_clonefix (the merge blocker) is CI-invisible — no test drivesfit(store_bootstraps=True)→predict(return_ci=True)with a pandas or polars DataFrameX, which is the exact path that was crashing. Add one, plus a DR-classifier polars case (still untested), to lock the fixed path.Context: full review thread on #901 (2026-07-01 round).