This checklist tracks the implementation of the design matrix column naming refactor based on the plan in naming_refactoring.md
.
R/naming-utils.R
.zeropad()
:zeropad(i, n_total)
i
zero-padded to width ceiling(log10(n_total+1))
.zeropad(3, 15)
-> "03"
, zeropad(12, 150)
-> "012"
.@keywords internal
).sanitize()
:sanitize(x, allow_dot = TRUE)
make.names(x, unique = FALSE)
. If allow_dot = FALSE
, replaces .
with _
.@export
).fmrireg:::sanitize
) until merge, possibly via re-export if needed temporarily.basis_suffix()
:basis_suffix(j, nb)
paste0("_b", zeropad(j, nb))
.j
.@export
).make_unique_tags()
:make_unique_tags(tags)
make.unique(tags, sep = "#")
.@keywords internal
).make_term_tag()
:make_term_tag(hrfspec, existing_tags = character())
hrfspec$id
or paste(vapply(hrfspec$vars, rlang::as_label, ""), collapse = "_")
.tag <- sanitize(tag, allow_dot = FALSE)
.make_unique_tags(c(sanitize(existing_tags, FALSE), tag))[length(existing_tags) + 1L]
.@keywords internal
).level_token()
:level_token(var, lev)
paste0(sanitize(var, allow_dot = TRUE), ".", sanitize(lev, allow_dot = TRUE))
.@keywords internal
).continuous_token()
:continuous_token(colname)
sanitize(colname, allow_dot = TRUE)
.@keywords internal
).make_cond_tag()
:make_cond_tag(tokens)
paste(tokens, collapse = "_")
.@keywords internal
).add_basis()
:add_basis(cond_tags, nb)
nb == 1L
, return cond_tags
.as.vector(outer(cond_tags, basis_suffix(seq_len(nb), nb), paste0))
.@keywords internal
).make_column_names()
:make_column_names(term_tag, cond_tags, nb)
stopifnot(!grepl("__", term_tag))
.cond_tags <- add_basis(cond_tags, nb)
internally.paste0(term_tag, "_", cond_tags)
.@keywords internal
).is_valid_heading()
(Test Helper):is_valid_heading(x)
grepl("^[A-Za-z\\.][A-Za-z0-9\\._#]*$", x)
(allowing #
for unique tags).tests/testthat/helper-naming.R
(not exported).rlang
is imported if as_label
is used.export(sanitize, zeropad, basis_suffix)
. Tag internal functions.tests/testthat/test-naming-utils.R
.realise_event_terms()
event_model_helpers.R
where unique term names/tags are generated.uid
: Add logic to generate sequential UIDs (e.g., sprintf("t%02d", seq_along(term_list))
).uid
: Store the generated UID on both the hrfspec
(e.g., spec$uid <- uid
) and the resulting event_term
(attr(term, "uid") <- uid
).term_tag
: Replace existing unique naming logic with a call to make_term_tag(spec, sanitized_existing_tags)
(ensure existing_tags
are sanitized with allow_dot=FALSE
before passing).term_tag
: Store the result on both the hrfspec
(e.g., spec$term_tag <- term_tag
) and the resulting event_term
(attr(term, "term_tag") <- term_tag
).$varname
field from the term object.columns.*()
Familycolumns.Poly
:continuous_token(paste0("poly_", x$argname, "_", zeropad(1:x$degree, x$degree)))
. (Use degree
for padding width).nbasis
.columns.BSpline
(and similar basis functions like DCT):continuous_token(paste0("bs_", x$argname, "_", zeropad(1:x$degree, x$degree)))
(using appropriate prefix like bs
, dct
, and using degree
for padding width).nbasis
.columns.Scale
:continuous_token(paste0("z_", x$argname))
.columns.ScaleWithin
:continuous_token(paste0("z_", x$argname, "_by_", x$grpname))
.columns.RobustScale
:continuous_token(paste0("robz_", x$argname))
.columns.Standardized
:continuous_token(x$name)
.columns.Ident
:continuous_token(var)
per variable if multiple inputs (e.g., Ident(a, b)
-> c("a", "b")
after tokenization).nbasis.*
might need updating if degree
handling changes internal column counts.conditions.event_term()
conditions.event_term
in conditions.R
(or similar).val <- attr(x, "..conds")
first; return val
if found and options match. Store result in attr(x, "..conds") <- result
before returning.length(x$events) == 1
and ncol(columns(x$events[[1]])) == 1
), return columns(x$events[[1]])
directly (after potential basis expansion).lapply
over x$events
.is_categorical(ev)
(using existing helper), get levels(ev$value)
and generate tokens using level_token(ev$varname, levs)
.columns(ev)
to get basis tokens (already sanitized via continuous_token
).expand.grid
on the list of component tokens.apply
with make_cond_tag
to paste tokens with _
separator.base_cond_tags
.drop.empty
: Removed flawed drop.empty
logic.expand_basis
:expand_basis
is TRUE:nb <- nbasis(attr(x, "hrfspec")$hrf)
.final_cond_tags <- add_basis(base_cond_tags, nb)
.final_cond_tags <- base_cond_tags
.final_cond_tags
.expand_basis=FALSE
and expand_basis=TRUE
, including interactions, caching, and shortcuts.convolve.event_term()
convolve.event_term
in convolve.R
(or similar).term_tag <- attr(x, "term_tag") %||% stop("term_tag attribute not found on event_term")
.nb <- nbasis(hrf)
.colnames(design_matrix(...))
instead.cn <- make_column_names(term_tag, base_cnames, nb)
.colnames(cmat) <- cn
.if (getOption("fmrireg.debug", FALSE)) { stopifnot(all(is_valid_heading(cn))) }
(requires is_valid_heading
accessible).term_tag_condition_tag[_b##]
format.build_event_model_design_matrix()
build_event_model_design_matrix
function.colnames(dm_mat)
after cbind
-ing term matrices.col_indices
: Ensure attr(dm, "col_indices")
still correctly records column ranges based on the incoming (already correctly named) term matrices.attr(dm, "colnames_final") <- TRUE
after cbind
.if (getOption("fmrireg.debug", FALSE)) { old_names <- colnames(dm); ... later ...; stopifnot(identical(colnames(dm), old_names)) }
around potentially problematic downstream code sections within this function.convolve.event_term
.columns.Standardized
, columns.Scaled
etc.: Check if any contain duplicated logic for string manipulation that is now handled by the primary columns.*
methods and continuous_token
. Remove redundancy.levels.*
methods: Check for similar redundant string formatting logic and remove.translate_legacy_pattern()
Helper: translate_legacy_pattern(pattern)
in R/contrast.R
.:basis\\[(\d+)\\]
with _b\1
(or similar robust pattern).:
with _
(for interactions)..col_index()
:nbasis
to decide on expand_basis
.pattern <- translate_legacy_pattern(pattern)
at the start.conditions(..., expand_basis=TRUE)
is used when appropriate (likely already correct).paste(collapse=":")
:R/contrast.R
for paste(..., collapse = ":")
used for internal keys/rownames._
.gsub
:gsub(":", ".", ...)
call within contrast_weights.contrast_formula_spec
.contrast_weights.*
methods (e.g., pair_contrast_spec
), remove code like rep(weights, each = nbasis)
.R/contrast.R
, update regex examples in function docs (e.g., column_contrast
) to reflect new syntax (_b03
, Var.Level_Mod
).@section Column-name grammar...
to R/contrast.R
roxygen block.@export translate_legacy_pattern
to its roxygen block.tests/testthat/test-contrast-column.R
(or relevant files like test_event_model.R
).conditions()
output if used in contrast tests).colnames(design_matrix(model))
to a snapshot/reference.event_model(on ~ hrf(x, subset = x == 1) + hrf(x, subset = x == 2), ...)
.x_
This phase assumes the core naming refactor (Phases 0-7) is complete, stable, and a release cycle with deprecation warnings for legacy contrast patterns (from Phase 6.5) has passed.
translate_legacy_pattern()
: Delete the helper function entirely from R/contrast.R
.fmrireg:::translate_legacy_pattern
)..col_index()
: Delete the call to translate_legacy_pattern()
and any associated messaging. Ensure it consistently uses conditions()
with appropriate expand_basis
.integer(0)
on failure/no match, ensure it throws an explicit error if inputs are invalid (though grep
naturally returns integer(0)
for no matches, which is fine).expand_basis
Logic:expand_basis
based on HRF specs within .col_index
, contrast_weights.column_contrast_spec
, etc. Rely on explicit arguments or the shared condnames()
helper.contrast_weights.pair_contrast_spec
(or its helpers) is the only place checking nbasis(hrf)
directly for basis broadcasting logic.condnames()
Helper:condnames(term, expanded = TRUE/FALSE)
that reliably calls conditions(term, drop.empty = FALSE, expand_basis = expanded)
. Mark @keywords internal
.mask_to_weights()
Helper:mask_to_weights(names, A_mask, B_mask = NULL)
based on the minimal API example, handling +1/nA and -1/nB logic.abs(sum(weights)) < tol
(e.g., tol = 1e-8
) for A-B type weights (where B_mask
is not NULL and sum(B_mask) > 0
).contrast_weights.column_contrast_spec
:condnames(term, expanded = TRUE)
.grep
directly on these names for pattern_A
and pattern_B
.idx_A %in% idx_B
) and stop()
if overlap occurs.mask_to_weights()
helper to calculate final weights.contrast_weights.pair_contrast_spec
:condnames(term, expanded = FALSE)
.cells()
data frame.where
clause if present to filter cells_df
.A
and B
formulas on the (filtered) cells_df
.apply(filtered_cells_df, 1, make_cond_tag)
and %in%
.mask_to_weights()
to calculate base weights (vector named by base condition names).nb <- nbasis(...)
).nb > 1
:\n - Get expanded condition names using condnames(term, expanded = TRUE)
.\n - Create a zero vector/matrix for expanded names.\n - Map the calculated base weights to the corresponding expanded names efficiently (e.g., using match(sub(\\"_b\\\\\\\\d+$\\", \\"\\", expanded_names), names(base_weights))
to find indices and assign base_weights
).\n - Add a warning
if any non-zero base_weights
do not find a corresponding match in expanded_names
(signals potential internal mismatch).nb == 1
):\n - Use the base weights directly.\n - Structure and return the result list.tryCatch
blocks or similar logic (that previously returned defaults on error) with direct calls or stop()
, except around user-provided formula evaluation (eval_tidy
), where a tryCatch
converting errors to informative stop()
messages should be retained.contrast_weights.*
Methods: Check oneway_contrast
, interaction_contrast
, poly_contrast
etc. for simplification opportunities (removing redundant checks, using cells()
/conditions()
directly).oneway_contrast
might reuse mask_to_weights()
internally for generating each contrast column comparing a level to others.sum(weights) == 0
(within tolerance) for all A-B type contrasts (e.g., pair_contrast
, column_contrast
with A and B).?contrast
, ?column_contrast
, ?pair_contrast
, etc.term_tag_condition_tag[_b##]
grammar.?fmrireg_naming
help page exists and is accurate.vignettes/*.Rmd
files for examples using legacy regex patterns (e.g., :basis\\[
, Var[Level]
) and update them.This phase addresses specific naming issues and refines the logic after the main refactor (Phases 0-8) is stable.
[ ] Refine term_tag
Generation for Ident()
-only Terms:
hrf(Ident(var1, var2))
can lead to term_tag
like Ident_var1_var2
, and final columns Ident_var1_var2_var1
, Ident_var1_var2_var2
.var1
, var2
if id
is not provided, or customID_var1
, customID_var2
if hrf(Ident(var1,var2), id="customID")
.make_term_tag()
in R/naming-utils.R
.hrfspec$id
is NULL and the only variable in hrfspec$vars
is an Ident()
call and that Ident()
call itself contains multiple simple variable names:term_tag
.term_tag
could be an empty string ""
in this specific scenario, or based on the id
if provided.term_tag
becomes empty, make_column_names
would effectively produce condition_tag[_b##]
.hrfspec$id
is NULL, and the term is Ident(V1, V2, ...)
, the term_tag
could be derived from V1_V2
(names within Ident), rather than Ident_V1_V2
.hrfspec$id
is provided (e.g., hrf(Ident(RT1,RT2), id="myterm")
), the term_tag
should be based on myterm
(e.g. myterm
), resulting in myterm_RT1
, myterm_RT2
.columns.Ident
continues to return the bare variable names (e.g., "RT1"
, "RT2"
) as the condition_tag
components.Ident()
terms exist.Ident(var1,var2)
, Ident(var1,var2, id="myid")
, and multiple Ident()
terms to verify clear and non-redundant naming.[ ] Clarify and Test Naming for Combined Basis Functions:
Poly(RT,2)
) is combined with an HRF basis expansion (e.g., HRF_SPMG2
).term_tag_PolyName.Var_k_b##
structure is correctly and consistently generated.naming_refactoring.md
should handle this correctly.columns.Poly
, columns.BSpline
etc. to ensure they only produce the PolyName.Var_k
part (e.g., poly_RT_01
).convolve.event_term
and make_column_names
to confirm they correctly append the _b##
(from HRF) to the condition_tag
(which itself might be poly_RT_01
).event_model(onset ~ hrf(Poly(RT,2), basis="spmg2"), ...)
and event_model(onset ~ hrf(BSpline(Age,3), basis="gamma3"), ...)
to assert the expected column names (e.g., term_poly_RT_01_b01
, term_poly_RT_01_b02
, term_poly_RT_02_b01
, term_poly_RT_02_b02
).Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.