Changelog
Source:NEWS.md
fmridesign (development version)
New features
- Added
check_estimability()for full-design rank, exact condition numbers, named weak directions, and contrast-specific estimability and variance factors. Event models include run intercepts by default and report event coverage per run; an explicit baseline/nuisance design can also be supplied.check_collinearity()now documents the limitations of pairwise checks (#32).
CRAN compliance
Design-column convolution no longer reaches into fmrihrf’s unexported
evaluate_regressor_cpp(). The shared-HRF fast path now evaluates each live column with the publicfmrihrf::regressor()/fmrihrf::evaluate()API. Results are identical to the previous path on every design built by the test suite; building a design matrix is roughly 1.3-2x slower (tens of milliseconds on a 4-run, 1200-scan design).boxcar_hrf_gen()andweighted_hrf_gen()callfmrihrf::hrf_boxcar()andfmrihrf::hrf_weighted()directly instead of looking them up in fmrihrf’s namespace.fmridesign no longer registers its own
print()method for fmrihrf’ssampling_frameclass, which overwrote fmrihrf’s method on load (“Registered S3 method overwritten by ‘fmridesign’”). Sampling frames now print with fmrihrf’s method.plot(<sampling_frame>)is unchanged and is documented under?plot.sampling_frame.onsets(),durations(),blockids()andnbasis()are now true re-exports of fmrihrf’s generics. fmridesign previously defined its own generics with the same names, so dispatch was split: for examplefmrihrf::onsets(<event_term>)found no method andfmridesign::nbasis(HRF_SPMG3)failed, and which one a bare call reached depended on attach order. Attaching fmridesign no longer masks these four names from fmrihrf.fmridesign now supplies every name and accessor method for its own classes, so downstream packages can use plain dispatch on the exported generics instead of defining duplicates or reaching into fmridesign’s namespace. New methods:
longnames()andshortnames()forevent_model,convolved_term,feature_termand bare events (event_seq);columns()andcells()forevent_model;conditions(),event_table(),nbasis()anddesign_matrix()forconvolved_term; andconditions()forbaseline_model. They previously lived in fmrireg.longnames()uses fmridesign’s canonicalvariable.levelform (condition.A), which is the design-matrix column name without its<term>_prefix (condition_condition.A);?longnamesdocuments the relationship. Design-matrix column names are unchanged.longnames(),shortnames()andcondition_map()now honourdrop.empty = TRUE(the default): an interaction cell with no events is left out, as it is from the design matrix, so the names line up one-to-one with the columns.drop.empty = FALSElists the full grid of factor levels, which is whatconditions()always returns (documented).condition_map(<event_model>)finds each condition’s column by its exact name,<term tag>_<canonical>, instead of by position. It previously returnedcolumn_name = NAfor every row of a term with an empty cell or a multi-basis HRF.blockids(<event_model>)is documented (?blockids.event_model): it returns one run id per event. Per-scan ids come fromblockids(x$sampling_frame);blocklens(x)gives scans per run. Because fmrireg previously overrode this method with per-scan ids, the first call in a session prints a one-time message. The message will be removed in the next release.correlation_map(<baseline_model>)gainswithin_run = TRUE: run intercepts are dropped, columns are centred within each run, and run-specific columns are correlated on their own run, with pairs from different runs left out. This reproduces fmrireg’s former method exactly;within_run = FALSEgives the previous fmridesign behaviour (runs concatenated). Cells are labelled by default when there are at most 12 columns, as in fmrireg’s method (event models keep the 20-column default). Both methods acceptlabel_valuesas an alias forannotate, and theevent_modelmethod acceptswithin_runtoo (defaultFALSE).correlation_map()now errors on arguments thatgeom_tile()does not accept, instead of passing them on to be dropped with a warning. The deprecatedsizeis still accepted.
Bug fixes
-
contrast_set()now combines individual specifications with nested contrast sets, including those returned byone_against_all_contrast(). It preserves leaf names and order, and reports invalid entries by argument path (#33). -
hrf(..., summate = FALSE)is honoured again for sustained events. The shared-HRF fast path ignoredsummateand always produced thesummate = TRUEdesign. - An event with a negative onset in the first run now fails with fmrihrf’s “
onsetsmust be non-negative” error, as the legacy path andfmrihrf::regressor()always did; the fast path had silently accepted it. The out-of-frame warning is still raised first.
fmridesign 0.6.1
Plotting overhaul
- All plotting methods share one visual system: the new exported
theme_fmridesign()andfmridesign_palette(), a colour-vision-checked categorical palette, one diverging palette centred on zero, readable column labels (e.g.face × high,drift 3,tx) and one way of marking runs. -
plot(<event_model>)draws one row per condition with event onset and duration marks. Rows of a term share an amplitude scale (y_scale). Basis sets get one row per basis function, and trialwise or long-basis designs switch to a heatmap with a per-trial overlap column. Newstyle,show_events,y_scale,time_range,titleandsubtitlearguments. -
design_map()is an SPM-style image grouped by term, with raw column ranges and a near-flat (dead) column check. Run boundaries are now placed correctly; they were previously computed from per-event block ids. -
correlation_map()shows the lower triangle with each column’s variance inflation factor (run means removed) on the diagonal and flags aliased columns. -
plot_contrasts()shows every design column, printed weights, the sum of weights and each contrast’s standard error per unit noise given the design. -
plot(<baseline_model>)shows every time-varying term (motion regressors were previously dropped), splitting translations and rotations into separate lanes. User-supplied nuisance column names are kept for display. -
plot(<sampling_frame>)no longer draws an empty panel; it adds"lane"and"grid"styles and anevents =overlay for checking coverage.
Bug fixes
-
User-visible correction: multi-basis column names change. For
hrf()terms with more than one basis function ("spmg2","spmg3", FIR, B-spline, tent, customnbasis > 1) and two or more conditions, design columns were filled condition-major (A_b01, A_b02, B_b01, ...) but named basis-major (A_b01, B_b01, A_b02, ...), so every column except the first and last was mislabelled. For example,task_task.scene_b01held face’s temporal derivative. The data layout is unchanged; column names,conditions(term, expand_basis = TRUE), and every name-based contrast (pair_contrast(),oneway_contrast(),column_contrast(),unit_contrast(),contrast(),interaction_contrast()) now follow the documented condition-majorterm_condition_b##layout, matchingdesign_colmap(). Contrast weights and coefficient labels from earlier versions for such terms were attached to the wrong columns and should be recomputed. Code that indexed multi-basis columns by position assuming basis-major order must be updated (#23). -
covariate()now expands matrix/data-frame arguments into one non-convolved regressor per column. Named inputs preserve sanitized column names, unnamed matrices usef01,f02, … suffixes, and final names follow the standard<term_tag>_<condition_tag>grammar (cov_xby default, ormotion_xwithid = "motion"). Covariate condition accessors and per-column metadata now expose the individual regressor identities instead of a concatenated multi-variable term name (#19). -
column_contrast()patterns now match the design-matrix column names, as documented (term_tag_condition_tag[_b##], e.g."^cond_cond\\.A$"or"^cond_cond\\.A_b01$"). Previously they were matched only against term-level condition names (cond.A), so documented patterns selected nothing. Term-level patterns still work: they are tried only when a pattern matches no design-matrix column, and select the same columns. A pattern that selects different columns in the two namespaces is now an error, and a pattern that matches nothing warns with the available column names (#24). -
design_matrix(<baseline_term>, blockid = )now returns each active column once. Withintercept = "global", requesting several runs used to return one duplicateconstant_globalcolumn per run (soblockid = 1:3gave three identical columns rather than one); the result now always equals the requested rows and non-zero columns of the full term matrix, in the term’s column order. Abasis = "constant", intercept = "global"drift term likewise returned zero columns for any run but the first. Terms without block structure now error onblockidinstead of returning an empty matrix. -
Fcontrasts(<event_model>)no longer returns an all-zero matrix with an “unmatched row names” warning for multi-basis terms. The term-level contrast is expanded tokronecker(C, diag(nbasis)), testing the condition effect jointly in every basis function (#25). -
Fcontrasts()for multi-factor terms (e.g.hrf(task, load)) assigned row names in the wrong order, so the matrix labelledtasktestedloadand vice versa. Rows now followconditions()order. -
interaction_contrast()now names its rows with the term’s canonical condition tags (e.g.task.face_load.low) and expands them across basis functions. It previously used raw cell labels (face_low) that matched no design column, producing all-zero weights with a warning. -
unit_contrast()and formula contrasts (contrast(~ face - obj)), and differences of contrasts built from them, now expand their weights across the basis functions of a multi-basis term. They previously returned zero-row weights with an “unmatched row names” warning. -
unit_contrast()now applies a logical selector inA: previouslyunit_contrast(~ cond == "A")ignoredAand averaged over every cell (weights 0.5/0.5 for two levels); it now selects level A only (weight 1). A bare factor (~ cond) still averages over all cells. -
Fcontrasts(<event_model>)skips terms with no categorical variable (e.g.hrf(rt), covariates) instead of failing for the whole model; a model with only such terms returns an empty list. -
condition_basis_list()now works for bareevent_termobjects without aterm_tag, which previously returned an empty list. -
baseline_model(nuisance_list = ...)now keeps the user’s nuisance column names (#28). Columns are namednuis_<name>_block_<run>(e.g.nuis_trans_x_block_1), matching the drift columns (base_poly1_block_1); names are sanitised to syntactic tokens and made unique within a run, and unnamed columns fall back to their original column index (nuis_2_block_1), which is preserved whennuisance_check = "drop"removes columns. This replaces the previousnuis#<run>_<col>names, so code that matched those names must be updated. The original names are kept in the nuisance term’ssource_colnamesfield. -
design_colmap(<baseline_model>)now reports nuisance columns with role"nuisance"(they were reported as"intercept"), takes theirrunfrom the block structure (it was parsed from the column index, so a 3-run model with 6 regressors per run reported runs 1 to 6), and labels them with the user’s column names inbasis_label.
fmridesign 0.6.0
New features
-
baseline_model()now checksnuisance_listinputs during construction for zero-variance columns, duplicate or near-duplicate columns, non-finite values, nuisance rank deficiency, and columns aliased with baseline terms. - Added
nuisance_check = c("warn", "error", "drop", "none")to control whether nuisance problems warn, stop, are dropped with an audit warning, or are skipped. - Added
check_nuisance()andclean_nuisance()helpers for inspecting and repairing block-wise nuisance regressors before model construction.
Performance
- Sped up
event_model()design-matrix construction by replacing the per-termtibble::tibble()/dplyr::bind_rows()calls used to assemble column metadata with a lightweight, validatedtibbleconstructor. This removes the metadata-building hotspot (~15% faster end-to-end on a representative multi-term, multi-run model) with byte-identical design matrices, column names,col_indices/term_spans, and metadata values. -
convolve.event_term()now skips columns that are all-zero within a block instead of building and evaluating an emptyfmrihrfregressor for each. For block-diagonal-ish designs (trialwise/LSS single-trial models, or factor levels present only in some runs) this is a large speedup (~2.2x faster on a representative 360-column trialwise model) while producing bit-identical output. Designs where every column is populated in every block are unaffected (a fast-exit keeps the original path), and blocks containingNA/NaNfall back to the previous full-column path so filtering semantics are unchanged. - Convolution hot path now shares one fine-grid HRF matrix across all columns and blocks and calls
fmrihrf’s C++ evaluator directly, skipping per-columnRegconstruction /prep_reg_inputsoverhead. Combined with a single global output matrix (no per-block zero-alloc +rbind) and deferred tibble materialization inbuild_event_model_design_matrix(), this is ~2–3.5× faster end-to-end on trialwise/LSS and multi-term workloads while remaining bit-identical tofmrihrf::evaluate(regressor(...)). Per-onsethrf_funlists and NA-misaligned designs keep the previous path. - Added
bench/cross-library design-matrix benchmarks against nilearn (the FitLins first-level design-matrix hot path). Run withbash bench/run_compare.sh; seebench/RESULTS.mdandbench/OPTIMIZATION_NOTES.md.
Bug fixes
-
contrast_weights()now removes rows for factor levels excluded by anhrf(..., subset = )term from the returned term-localweights, keeping them consistent with the reconciled full-designoffset_weights(#17). -
convolve_design()now extracts each condition column withdmat[[i]], so it produces correct regressors for basedata.frameinputs (its documented example). The previousdmat[, i][[1]]collapsed a data frame column to its first element; the tibble-based internal call path was unaffected. -
contrast_weights()andFcontrasts()forevent_modelobjects now name interaction-term contrasts with the same term tags used by design-matrixcol_indices, preventing downstream consumers from dropping crossed-term contrasts because of:/_key mismatches (#9). -
event_model()now warns when continuous parametric modulators are all-zero or have zero variance, catching degenerate design columns before model fitting while preserving the existing design-matrix shape (#8). - Fixed list-based
event_model()specifications sohrf(..., subset = )expressions can use base operators and helper functions from the calling environment, matching formula-interface subset behavior. - Fixed event-model column metadata construction for factor and multi-basis HRF terms that expand to multiple design columns.
- Event terms whose subsets select zero events now retain their canonical condition-column names and metadata on the resulting all-zero design matrix, rather than falling back to generic
col_1,col_2, … names. - Suppressed exact, known false-positive metadata warnings produced when decorated HRFs are reconstructed by
fmrihrf0.3.0, while continuing to surface unrelated warnings.