
Build an Intercept-Free, Full-Rank Covariate Design Matrix from a Formula
Source:R/helper_model_matrix.R
create_model_matrix_from_features.RdExpands formula against data (via model.matrix) into
a purely numeric covariate design matrix suitable for the package's own fast_*
GLM/survival/ordinal fitting routines, which manage their own intercept and treatment
columns separately rather than relying on the formula/model-matrix machinery for them.
This is the standard covariate-matrix builder used throughout EDI's inference classes
(e.g. Inference$private$X) whenever adjustment covariates need to go from a
user-facing formula/data-frame representation to a numeric matrix the C++ backends can
consume.
Value
A numeric matrix with nrow(data) rows and one column per retained,
full-rank expanded covariate term (no intercept column). Has zero columns if
data has zero columns.
Details
What it does. (1) If data has zero columns, returns a numeric
nrow(data) x 0 matrix immediately (no covariates to expand). (2) Otherwise calls
model.matrix(formula, data = data), which performs standard
formula expansion: factor variables are dummy-coded against their reference level (the
first level of levels, or the level ordering already present in
data), interactions (a:b, a*b) are expanded to product columns, and
any model.matrix contrasts option in effect at call time applies. (3) If the
first resulting column is named "(Intercept)" (i.e. the formula was not given
- 1 / + 0), that column is dropped — this function always returns a
covariate-only matrix with no intercept column, since EDI's design and inference classes
add their own intercept/treatment columns at a fixed position. (4) The result is passed
through drop_linearly_dependent_cols, which detects the numeric rank of
the matrix (via matrix_rank_cpp() at tolerance 1e-7) and, if the matrix is
rank-deficient, greedily retains a full-rank subset of columns using the pivot order
from qr(M, tol = 1e-7) (dropping the same tolerance's worth of
redundant/aliased columns, e.g. from collinear dummy expansions or an over-specified
interaction structure); this rank-reduction step is silent — no warning is issued when
columns are dropped, and the dropped columns' identity/names are not returned to the
caller, only the reduced matrix.
Input conventions. data is expected to already be free of missing
values at call time (imputation, when configured, happens upstream in the design/
inference class before this function is called); this function does not impute or warn
about NAs, and model.matrix will drop incomplete rows or error, per its
own na.action default, if NAs remain. Column order and names in the
returned matrix follow model.matrix's expansion order (all factor/interaction
columns for a term before the next term), possibly reduced by the rank-deficiency step;
callers relying on stable column identity (e.g. warm-starting coefficients across calls)
should not assume the set or order of columns is invariant if data's factor
levels or rank change between calls.
Failure semantics. If drop_linearly_dependent_cols detects the matrix is
non-numeric or contains non-finite values, it returns the matrix unchanged (rank
reduction is skipped rather than erroring); a downstream fitting routine operating on a
rank-deficient or non-finite design matrix may then fail to converge or report
non-finite coefficients/standard errors, which is where such problems will actually
surface to the user.
See also
model.matrix, which performs the formula expansion this
function wraps; drop_linearly_dependent_cols (internal, same file) for the
rank-deficiency cleanup step. Analogous Python API:
patsy/
statsmodels formula API
for formula-based design matrix construction.