Skip to content

Add reflect option for a sign-invariant basis (#117) - #118

Open
soodoku wants to merge 1 commit into
tlverse:masterfrom
finite-sample:reflect-basis
Open

soodoku wants to merge 1 commit into
tlverse:masterfrom
finite-sample:reflect-basis

Conversation

@soodoku

@soodoku soodoku commented Oct 17, 2026

Copy link
Copy Markdown

Closes #117.

Every HAL basis function switches on as a covariate increases, so negating a covariate changes the penalty and the fit (example in #117). This adds an opt-in reflect argument to fit_hal() and enumerate_basis().

What changes

  • enumerate_basis(..., reflect = TRUE) builds the basis on cbind(x, -x). basis_of_degree() and enumerate_edge_basis() get an internal reflected_from argument, which drops tensor products that pair a covariate with its own reflection.
  • fit_hal(..., reflect = TRUE) makes the design matrix from cbind(X, -X), names the reflected columns -x1, -x2, ... (so summary() reads, e.g., I(-x1 >= -0.5)), and stores reflect in the fit. predict() augments new_data the same way.
  • reflect = TRUE with formula stops with an error.
  • The default, reflect = FALSE, is unchanged.

The quantizer and the edge basis work column by column, so negating a covariate only swaps the two copies of it: the reflected basis is the same set of functions under any sign coding, with no change to quantizer().

Tests (tests/testthat/test-reflect.R)

  • Fits along a fixed lambda path, zero and first order, are unchanged when two of three covariates are negated (max difference < 1e-3 at thresh = 1e-12; observed 2e-5 to 3e-5).
  • The same comparison with the default basis changes by more than 1e-2, so the test can fail.
  • No basis function pairs a covariate with its reflection, and every covariate enters in both directions.
  • predict() on new data and summary() work; formula with reflect = TRUE errors.

Checks

  • devtools::test(): 52 passing, 0 failures.
  • devtools::check(build_args = "--no-build-vignettes"): 0 errors, 4 warnings, 1 note, identical to master on the same machine (vignettes not built, Rcpp build version, hal in tests, a lost brace in formula_helpers.Rd).
  • styler leaves the changed lines alone. devtools::document() regenerated only the four affected Rd files; I left RoxygenNote at 7.3.1.

Targets master, since devel is contained in it; happy to retarget.

🤖 Generated with Claude Code

With reflect = TRUE, fit_hal and enumerate_basis build the basis on
cbind(X, -X), without tensor products that pair a covariate with its own
reflection, and predict augments new_data the same way. The basis is then
the same set of functions when any covariate is negated, so the fit does
not depend on the sign coding of the covariates. Default unchanged.

Closes tlverse#117.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fits depend on the sign coding of the covariates: proposal for an opt-in reflected basis

1 participant