Skip to contents

design_fit() fits a design schema to analysis data. It does not fit a statistical model. It evaluates the formula and role expressions from a design_spec() object, resolves ., learns categorical levels, applies the missing-value policy, and builds the training design matrix.

The fitted schema is the object to reuse when training data and validation or prediction data must have the same predictor columns and factor-level encoding.

Usage

design_fit(spec, data, row_index = NULL)

Arguments

spec

(design_spec)
Unfitted design specification returned by design_spec().

data

(data.frame)
Analysis data used to learn the design schema. Objects that inherit from data.frame, including tibbles, are accepted.

row_index

(integer: NULL)
Optional row identity vector for data. If NULL, seq_len(nrow(data)) is used. Duplicate values are allowed. Values must be non-missing whole numbers and must have length nrow(data).

Value

A list of class "design_fit". Important elements include:

  • spec: the original design_spec() object.

  • formula, predictor_terms, and response_terms: fitted formula objects.

  • predictor_schema: learned predictor roles and categorical levels.

  • row_index: retained row identities after applying na_action.

  • dropped: degenerate categorical variables and terms dropped during fitting.

  • x: the retained training design matrix.

  • y: the retained evaluated response, or NULL for one-sided designs.

  • weights, offset, and case_id: retained role values, or NULL when the role was not specified.

The object also stores the role formulas, role source variables, encoding, intercept, na_action, novel_levels, response_name, and response_levels so the same schema can be applied to new data.

Details

Workflow

A typical modeling workflow starts with design_spec() to declare the design. design_fit() then learns the concrete schema from one analysis dataset. Use design_data() to extract training inputs from the fitted design. Use design_newdata() to encode validation or prediction data with the same predictor terms, categorical levels, column order, and role handling.

Retained rows

Retained rows are computed once from the evaluated predictor, response, weight, and offset expressions before degenerate categorical predictors are dropped. The na_action value stored in spec controls whether rows with missing predictor or response values are kept, removed, or rejected. If no rows remain after applying na_action, design_fit() errors.

Weights and offsets are stricter than predictors and responses. On every retained row, weights must be non-missing, finite, and non-negative. On every retained row, offsets must be non-missing and finite. Case IDs are metadata and do not participate in na_action.

Degenerate categorical predictors

After retained rows are fixed, categorical predictor columns with fewer than two observed retained levels are detected. Formula terms that reference any such categorical predictor are dropped in one pass. Interaction terms that reference a degenerate categorical predictor are dropped along with the main effect. Degenerate-term dropping does not add rows back and does not remove additional rows.

Constant numeric predictors are retained. They may produce rank-deficient predictor matrices. Downstream model functions are responsible for handling that rank deficiency.

Character predictors are treated as categorical predictors. Factor predictors store the observed retained levels in the fitted predictor schema. Declared but unobserved factor levels are not kept as design-matrix columns.

Response levels

For a bare-symbol factor or ordered-factor response, response_levels stores the declared response levels from the analysis data. These levels are needed for ordinal-count output in design_data() and design_newdata().

For one-sided specifications, transformed responses, and non-factor responses, response_levels = NULL.

Row index

row_index is the row identity vector for the supplied data. row_index = NULL means seq_len(nrow(data)). When supplied, row_index must be a whole-number vector with length(row_index) == nrow(data). It may contain duplicates, which is required for bootstrap analysis data. After NA filtering, returned row indices are row_index[retained_positions].

Examples

#----------------------------------------------------------------------------
# Fit a design schema and use it for weighted linear regression
#----------------------------------------------------------------------------
library(bkmodel)

set.seed(1L)
analysis <- data.frame(
  patient_id = sprintf("P%02d", 1:12),
  outcome = rnorm(12),
  age = round(rnorm(12, mean = 60, sd = 8)),
  treatment = factor(rep(c("control", "active"), 6)),
  stage = factor(rep(c("I", "II", "III"), each = 4)),
  exposure = runif(12, min = 0.5, max = 2),
  inverse_prob_weight = runif(12, min = 0.8, max = 1.2),
  chart_score = rnorm(12)
)

spec <- design_spec(
  outcome ~ .,
  weights = ~ inverse_prob_weight,
  offset = ~ log(exposure),
  case_id = ~ patient_id,
  exclude = ~ chart_score,
  encoding = "dummy",
  intercept = TRUE,
  na_action = "na.omit",
  novel_levels = "fail"
)

fit <- design_fit(spec, analysis)
colnames(fit$x)
#> [1] "(Intercept)"      "age"              "treatmentcontrol" "stageII"         
#> [5] "stageIII"        
fit$row_index
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12

inputs <- design_data(fit)

model <- stats::lm.wfit(
  x = inputs$x,
  y = inputs$y,
  w = inputs$weights,
  offset = inputs$offset
)
model$coefficients
#>      (Intercept)              age treatmentcontrol          stageII 
#>      0.156073030     -0.009539819     -0.017562419      0.928695544 
#>         stageIII 
#>      0.717146802 

#----------------------------------------------------------------------------
# Encode validation data with the fitted training schema
#----------------------------------------------------------------------------
validation <- data.frame(
  patient_id = sprintf("V%02d", 1:3),
  age = c(55, 67, 61),
  treatment = factor(c("active", "control", "active")),
  stage = factor(c("II", "I", "III")),
  exposure = c(1.1, 0.9, 1.5)
)

validation_inputs <- design_newdata(
  fit,
  validation,
  outcome = "ignore",
  weights = "ignore"
)

linear_predictor <- drop(validation_inputs$x %*% model$coefficients)
linear_predictor <- linear_predictor + validation_inputs$offset
linear_predictor
#>          1          2          3 
#>  0.6553887 -0.6060177  0.6967560