DESCRIPTION now declares the Quarto command line tool in
SystemRequirements, which the new vignette needs to build.
New Overview vignette, vignette("hvtiRimputation"). It walks one small
synthetic example from impute_mean() through the record, the row-level
attrition columns, indicators, provenance and the inputs the package
refuses, then shows the impute_multiple() prototype. On the pkgdown site,
articles put the table of contents on the left and use the full window
width, through pkgdown/extra.css. quarto and knitr join Suggests.
impute_multiple() is now reproducible by default (breaking). seed
is required, either as an argument or once per session through
options(hvtiRimputation.seed = ); with neither, the call fails. The old
default, seed = NA, left the draws to whatever state R's RNG happened to
be in. The mice call now runs under withr::with_seed() with the RNG kind
pinned to R's defaults, so the same seed gives the same imputations after
any set.seed() or RNGkind() change, and the caller's own random stream
is restored afterwards. A call that already passed a seed gets the same
values as before under R's default RNG kind. withr is a new import.R/ and fails on a call outside withr::with_seed() to
any function in a defined audited set: every stats random generator
(found from the installed stats exports, so new ones are covered),
sample(), jitter(), simulate(), mice, randomForestSRC and
varPro. It also fails on a randomForestSRC call without a negative
seed =, and it self-tests on planted violations. A function that draws
from the RNG internally without being in that set is outside the audit.hvtiR::install() installs the members together, and several
already required 4.4.0, so on an older R the install failed whatever this
package declared.impute_multiple() prototype. Multiple imputation by chained
equations (mice), returning m completed datasets stacked long plus the
same shape of record impute_mean() returns. Method defaults to mice's
own per-column dispatch (pmm/logreg/polyreg/polr) rather than one
string forced across every column; a named-vector method argument
overrides specific columns. A new class, hvti_imputation_multi, keeps
impute_mean()'s own contract untouched. imputed_data() is now a real S3
generic; its hvti_imputation_multi method requires an imputation
argument with no default, on purpose -- see
dev/specs/2026-09-23-impute-multiple-design.md for why. Pooling (Rubin's
rules) remains out of scope, per the design note and the README.First release. Single mean imputation, and the record that goes with it.
The impute_mean() help page no longer tells readers to call the designed
but unimplemented impute_multiple() function.
Coverage uploads without a Codecov token are now best-effort, so a successful package test run is not marked failed solely because tokenless upload is unavailable.
impute_mean() ports PROC STANDARD ... REPLACE -- the form with no
MEAN=/STD=, which the imputsub macro wraps. Missing values are filled
with the variable's mean over its non-missing values; observed values are
never touched.
The record is a logical matrix parallel to the data, one column per
imputed variable, TRUE where a value was filled. Read it with
imputed_matrix(). A count cannot answer was this patient's value
imputed?, so the matrix is the primary artifact and summary() is a view of
it rather than a replacement for it.
imputed_any(), complete_case_pass() and analysis_pass() are the
row-level columns the attrition record consumes. complete_case_pass() is
read from the input, before anything is filled -- evaluated on the
completed data every row over vars would pass, which is the opposite of
what the column means. analysis_pass() is the same question after
imputing.
kept_by_imputation() counts the rows in an analysis only because a
covariate was filled in -- the number that catches a port which imputed the
wrong variable list yet still reached the right row count.
⚠️ It is analysis_pass & !complete_case_pass, not
imputed_any & !complete_case_pass. The latter overcounts: it is TRUE
for a row that had a value filled but is still incomplete because a column
outside vars is missing, and such a row never enters the analysis at all.
The two agree only when imputation completes every row, which is the case
the design was written against -- so the wrong formula gave the right answer
there and the error was invisible.
imputation_indicators() generates missingness indicators from the
record, with a prefix the caller chooses. The SAS ms_* spelling is
available (prefix = "ms_") and is deliberately not the default: a
convention adopted for parity becomes permanent.
imputation_provenance() records the method, m, the fill values, the
package version and the seed. An imputed dataset that does not say how it was
imputed cannot be reproduced.
vars is required. PROC STANDARD with no VAR statement processes
every numeric variable in the dataset; that default is not inherited. An
imputation that silently chose its own variable list is the failure the
record exists to catch.NA while reporting it as imputed
would be a lie in the record.mean(c(Inf, -Inf), na.rm = TRUE) is
NaN; filling with it leaves the cell missing while the record marks it
imputed. An infinite mean is refused in the other direction, as not a
plausible measurement. The completed column is then asserted to hold no
missing values, so the invariant is proved rather than assumed.is.numeric().
bit64::integer64 packs a 64-bit integer into a double's bits, so taking
its mean into an ordinary double reinterprets the bit pattern -- a column of
1 and 3 completes as 0, with the provenance recording 9.88e-324 rather
than 2. Wrong data and wrong provenance, no error. The guard keys on the
class attribute rather than a list of known classes, so it also catches
whatever the next one is called. Convert with as.numeric() yourself, so
the conversion is a choice you made.data are an error. data[["age"]] returns
the first age, so naming it fills one column, leaves the other missing,
and emits a single record column that cannot say which. An audit cannot read
that.impute_multiple() (PROC MI, mult_imput) is designed and not built.
Pooling by Rubin's rules is deferred: multiple imputation is only multiple
imputation if the results are pooled, and that is separate work with its own
verification. BY-group means are unmeasured in the corpus and unimplemented.
PROC STANDARD MEAN=0 STD=1 REPLACE, which standardises and fills, is a
different operation and is not what impute_mean() does.