Skip to contents

Replaces user-defined sentinel values (default -99) in covariate columns with values that the NLME engine can consume, leaving the PML model code untouched. Within a reset block, masked rows are left as NA so the engine performs row-level propagation via fcovariate() / covariate() / interpolate(); only blocks (and subjects) where the engine has nothing to propagate receive an externally-computed fallback value.

Usage

imputeMissingCovariates(
  model,
  data = NULL,
  missingToken = c(-99),
  method = c("subject", "population"),
  continuousFn = stats::median,
  categoricalFn = imputeMode,
  centralValueWeighting = c("subject", "row"),
  stratifyBy = NULL,
  minStratumFraction = 0.1,
  keepOriginal = TRUE,
  logFile = NULL
)

Arguments

model

NlmePmlModel object. Must already have its covariate list populated (via addCovariate() for API-built models or textualmodel() for textual ones) and id mapped via colMapping().

data

Optional data.frame to impute. Defaults to model@inputData. Never mutated by reference; an internal deep copy is used throughout.

missingToken

Vector of sentinel values. Default c(-99). Supports numeric, NA, and character (e.g. c(-99, NA, ".")). Treated as a single missing-value predicate: a row is "missing" if its value matches any configured token. Tokens are not inferred: real NA values, ".", and blank cells are only imputed when you list them here. Leaving them undeclared is an error rather than a silent no-op, because the NLME engine reads all three as missing.

method

"subject" (default) uses the cascade described in Details; "population" skips subject-level lookups and fills every masked row from the population or stratum central value.

continuousFn

Function applied to a non-empty numeric vector returning a single value. Default median. Must return a length-1 finite numeric.

categoricalFn

Function applied to a non-empty vector (character, factor, or numeric for occasion / numeric-coded categorical) returning a single value. Default imputeMode() – the package's exported modal-value helper with deterministic tie-breaking by first appearance and factor-level preservation. Pass a custom function (e.g. function(x) sort(unique(x))[1]) to override. Validation is type-driven, not model-driven:

  • For factor columns, the returned value must already be a level of the input factor; factor levels are never extended implicitly. This is a technical R constraint (factor codes cannot encode an unknown label), not a model-semantic rule.

  • For character / numeric columns, the returned value is written into the data verbatim, even when it is not among the categories declared in model@covariateList[[name]]. NLME treats out-of-declaration values per the model's default category semantics; the function does not second-guess that behaviour. If you need strict membership, supply a categoricalFn that enforces it.

centralValueWeighting

How the population / stratum central value is pooled. "subject" (default) is two-stage: the aggregator is applied to each subject's values, then to the resulting vector of subject-level summaries, so every subject carries equal weight. "row" applies the aggregator once across all non-missing rows, so subjects with more records count more.

"subject" is the default because it matches how the NLME engine pools covariate values for mean() / median() centering (one summary per subject, then combined across subjects), and because row weighting otherwise tracks sampling density – a study-design artifact – rather than the population.

Under "subject" a custom continuousFn / categoricalFn is invoked once per contributing subject and once across the subject summaries; a trimmed mean, for instance, then trims subject means rather than raw rows. The setting does not affect the "propagated" donor value, which is by definition computed within a single subject.

stratifyBy

Optional character vector of categorical column names. Population fallback is then computed within each stratum.

minStratumFraction

Minimum acceptable stratum size as a fraction of total subjects; a warning is issued if any stratum is smaller. Default 0.1.

keepOriginal

If TRUE (default), the raw pre-imputation values of each modified data column are preserved as <dataCol>_orig. A pre-existing backup column triggers an error to prevent corrupting traceability with stale data.

logFile

Path to write the imputation log. Default NULL resolves to file.path(model@modelInfo@workingDir, "imputeMissingCovariates.log"), creating the working directory if it does not exist yet (it usually will not, since the model has not been run). If the directory cannot be created, the log falls back to tempdir() with a warning. An explicit logFile is never given this treatment: its parent directory must already exist, so a typo fails loudly instead of scattering directories.

The returned path uses forward slashes on every platform so it can be copied straight out of the console.

Value

A list with elements:

  • model – clone of input NlmePmlModel with @inputData replaced by the imputed data.frame.

  • data – imputed data.frame (same object as result$model@inputData).

  • summarydata.frame with one row per (covariate, column, imputation_source, stratum) combination. Columns: covariate, column, type, imputation_source, stratum, imputed_value, n_imputed, n_subjects_affected. imputed_value is NA for "engine" (no value written; the engine fills at run time), a truncated list of distinct per-subject donors for "propagated", and the pooled central value for "stratum" / "population".

  • logFile – absolute path of the written log file.

Details

The function returns a modified clone of the input NlmePmlModel (with @inputData replaced by the imputed data.frame), along with the imputed data, a traceability summary, and a log file. All other model slots (@columnMapping, @covariateList, @statements, @hasResetInfo, @resetInfo, etc.) are preserved verbatim so custom mappings like WT -> BodyWeight survive the round-trip.

Imputation cascade (method = "subject", default). The NLME engine propagates covariate values within a reset block but cannot carry them across blocks, so the cascade classifies each (subject, reset block):

  1. Block has at least one non-missing value – masked rows are left as NA (source "engine"). The engine fills them via fcovariate() / covariate() / interpolate().

  2. Block is fully missing but the subject has non-missing values in other blocks – the masked rows are filled with the subject aggregate computed by continuousFn / categoricalFn over the subject's non-missing values across all blocks (source "propagated").

  3. Subject is fully missing across every block – the masked rows are filled with the population (or stratum) central value (source "population" or "stratum").

method = "population" skips subject-level lookups and replaces every masked row with the population (or stratum) central value.

Note the two methods differ in their effect on covariate centering. method = "subject" leaves masked rows as NA, and the engine excludes missing rows when it computes mean() / median() centering values, so centering is unaffected. method = "population" writes a value into every masked row, and those values do take part in centering.

Examples

if (FALSE) { # \dontrun{
  model <- pkmodel(...)
  model <- addCovariate(model, "WT", type = "Continuous")
  model <- colMapping(model, c(WT = "BodyWeight"))

  result <- imputeMissingCovariates(model)
  model <- result$model
  fit <- fitmodel(model)
  print(result$summary)
} # }