Skip to content

Responses separate from ModelSpec; wildcard(exclude=) takes a response - #3

Merged
jonathan-taylor merged 1 commit into
mainfrom
responses-separate
Oct 26, 2026
Merged

jonathan-taylor merged 1 commit into
mainfrom
responses-separate

Conversation

@jonathan-taylor

Copy link
Copy Markdown
Contributor

Follows #2, which was merged before this commit was pushed. As in scikit-learn, the design and the response are built separately: a ModelSpec builds X, and a response object builds y from the same data.

Responses separate from ModelSpec. ModelSpec loses its response= parameter and build_response; neither has been released, so nothing breaks. Surv, Binomial and Multinomial are fit and built on their own with fit, transform or fit_transform. A dataframe of another backend, such as polars, gives a response in that backend (for polars, a categorical comes back as an Enum).

wildcard(exclude=...) takes a response and leaves out its columns, so the response is named once and cannot leak into its own design:

response = Surv('time', 'status')
y = response.fit_transform(D)
X = MS([wildcard(exclude=response)]).fit_transform(D)

MS([wildcard(exclude='y')]) is the design of R's y ~ .. A bare wildcard is every column.

build_submodel expands a wildcard as at fit.

Missing values, following scikit-learn: transformers keep every row, fit on the observed values, and are missing where their input is. This fixes a bug where a single NaN made every value of a poly, ns or bs feature NaN (and ns could return uninitialized values). The docs show how to choose the rows R's na.omit keeps.

The conversion of dataframes of other backends moves to modelmatrix/_native.py, so response.py can use it.

Tests: 265 passed and 5 strict xfails. New tests cover missing values in poly, ns and bs; responses returned in polars and pyarrow; and exclude= given a response.

Docs: the responses page is rewritten for this design and executes locally. Sales ~ . still gives R's lm coefficients, and dropping incomplete rows of Hitters leaves R's 263.

🤖 Generated with Claude Code

As in scikit-learn, the design and the response are built separately:
ModelSpec no longer has a `response` parameter or `build_response`.

- Surv, Binomial and Multinomial are fit and built on their own:
  `fit`, `transform`, `fit_transform`, in the backend of the data
  (e.g. polars, with an Enum for a categorical).
- `wildcard(exclude=...)` also takes a Response, leaving out its
  columns: `ModelSpec([wildcard(exclude=Surv('time', 'status'))])`.
- build_submodel expands a wildcard as at fit.
- Poly, BSpline and NaturalSpline are fit on the observed values and
  are missing only in the rows where the variable is missing, as
  scikit-learn's transformers: a single NaN made the whole feature
  NaN (and, for ns, uninitialized values could leak through).
- The conversion of dataframes of other backends moves to _native.py.
- Docs: the responses page rewritten for this, and a section on
  missing values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonathan-taylor
jonathan-taylor merged commit 22ced84 into main Oct 26, 2026
2 checks passed
@jonathan-taylor
jonathan-taylor deleted the responses-separate branch October 26, 2026 22:24
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.

1 participant