Changelog
Source:NEWS.md
fmrireg (development version)
Dependencies
-
tidyrmoved from Imports to Suggests (it is still used by vignettes).design_plot()now reshapes the design matrix with an internal helper that returns the same long tibbletidyr::pivot_longer()produced, which trims the install footprint, notably under webR.
Bug fixes
fmri_template(contrasts = )now reaches the fit (#265).build_model()attaches each contrast specification to the event term it resolves against, so it is computed exactly likehrf(..., contrasts = ): under every estimation scope (including the defaultrunwise_metawith a noise model, which used to fail with “Post-hoc contrasts are not available …”), incoef(fit, type = "contrasts"), and in the mapswrite_results()/reduce_write_results()write (which used to get betas only). A template contrast given as a weight vector is computed post hoc and added to the fit’s contrast table.reduce_contrasts()reads that table for every contrast and reportsseandstat; the"template_contrasts"attribute is gone. Post hoc,contrast(~ A - B)now resolves against the term whose levels it names; it used to fail with “does not resolve against any event term”.from_bids()pairs files by exact BIDS entities (#266). Subject and task labels are compared exactly (subjects = "1"no longer picks upsub-10,task = "faces"no longer picks uptask-facesloc). A run’s series, events and confounds must agree onsub,ses,taskandrun; sessions are separate blocks, ordered by session then run (they used to be merged or mis-paired by run number). Two series for one run (for exampleres-2next to the default resolution) are an error naming the newresargument;sessionsselects sessions. The TR comes from the series’ own sidecar, the raw sidecar or an inheritedtask-*_bold.json, never from other JSON underderivatives/(output sidecars of an earlierwrite_results()used to break a second run). Each binding’smeta$runslists the session, run, scan, events file and functional brain mask of every run.Joint estimation no longer warns “Design matrix is rank deficient” when a condition (or trial) is absent from one run while the joint design is full rank (#267). The warning came from the per-run fits that only estimate the AR model or robust weights; the joint design’s rank is still checked and reported.
runwise_metafits still warn for the deficient run, whose estimate is blank.fmri_latent_lm()gains acontrolargument (#263). It used to swallowcontrol =in...and always fit joint estimation with one AR model shared across components.control = NULLkeeps that default; a control such asfmri_lm_control(estimation = estimation_spec("runwise_meta"), noise = noise_spec("ar1", voxelwise = TRUE))now fits a separate noise model per component, identical tofmri_lm()on the score matrix. Such fits have no voxel-space covariance, sostandard_error()/stats()withrecon = TRUErefuse them. Any other argument in...is now an error rather than ignored, anautocorthat contradictscontrolis an error, anddurationsdefaults to 0 likefmri_lm().group_data(format = "nifti")reads variance maps (list(beta = , var = )) and t-only maps (list(t = , df = )) through fmrigds, like beta/se maps, and returns a gds-backed object; it used to fall back to the legacygroup_data_niftireader for these two inputs. Fits are unchanged (57 configurations pinned against the legacy reader). On t-only gds data,fmri_meta(combine = )with the defaultweights = "ivw"now warns and combines with equal weights, as the legacy reader did; it used to stop. fmrireg now requires fmrigds (>= 0.1.0.9002). The legacy reader is reachable only from the deprecatedgroup_data_from_nifti().fmri_lm_control()now defaultsestimationtoestimation_spec("runwise_meta"), the same defaultfmri_lm()uses. Before, an omittedestimationresolved to the joint scope, so passing a control object only to setrobust,noise,weightsor another section silently switchedfmri_lm()from runwise meta-estimation to a single joint fit and changed the estimates. The requested (or default) scope is now honoured; passestimation = estimation_spec("joint")to fit runs jointly. Two documented exceptions remain for an omittedestimation: HAC, sandwich, and Satterthwaite inference, which exist only for a joint fit, select the joint scope with a message saying so; and registered engines keep their joint default. Thefmri_lm()andfmri_lm_control()help pages describe both (#247).The
coef_image()help page now documentsobjectas anfmri_lmorfmri_metaobject and lists each class’sstatisticvocabulary ("tstat"/"prob"forfmri_lm,"z"/"p"forfmri_meta). Theestimate_betas()page now has a single description and a single return value that match thefmri_framemethod, and no longer lists the unsupported"r1"and"pls"methods (#248).The
$methods for lazy data chunks and pre-projected designs (S3method("$", data_chunk),S3method("$", fmri_preproject)) are now generated by roxygen. Their@exportS3Method base::`$`tags never matched, so the nextdevtools::document()run would have dropped the hand-written NAMESPACE lines;chunk$dataandproj$Pinvcalled from outside the package would then have returned the storedNULLinstead of reading the chunk or computing the pseudo-inverse. A new test dispatches both methods from outside the namespace (#251).fmri_meta()on group data fromgroup_data()(fmrigds-backed) now runs through fmrigds: fmrireg registers the reducer"fmrireg:meta_reg"(its FE / DerSimonian-Laird / Paule-Mandel meta-regression with Huber and equal/custom weights) at load, and fit-timecontrasts =usefmrigds::coef_contrast()instead of a second fit. Results are unchanged. Data with several subject-level contrasts used to be analysed for the first contrast only, without a warning; every contrast is now fitted andfmri_meta()returns anfmri_meta_listoffmri_metaobjects named by contrast (single-contrast data still give onefmri_meta).fmri_ttest(engine = "meta")stops with a message on such data.spatial_fdr()is now a wrapper overfmrigds::spatial_fdr_sabha(), the fmrigds port of the same procedure (identical results); fmrireg’s C++ implementation was removed. The fmrigds post-hoc"fmrireg:fdr:spatial"is kept as an alias of fmrigds’s"fdr:sabha".fmrireg now requires fmrigds (>= 0.1.0.9001) (
coef_contrast(),spatial_fdr_sabha(),"ttest:welch"); thea_09vignette no longer calls the unexportedfmrigds::plan().-
fmri_ttest()has one input path: gds data, legacygroup_dataand data frames are normalised once, and the classic and Welch engines are fmrigds’s"ols:voxelwise"and"ttest:welch"reducers on every input (the separate gds method is gone). Classic results are unchanged. Consequences:- gds data with several subject-level contrasts used to be analysed for the first contrast only by the classic and Welch engines; they now stop with a message naming the contrasts, as the meta engine does.
- On gds data,
paired,mu0,voxelwise_cov(classic engine) and contrasts with several coefficients now work; they used to stop. - The Welch engine reports the group coefficient’s standard error (it was
NA) and drops non-finite subjects per feature (they used to make the whole featureNaN). It now requiresformula = ~ 1 + group; before, a formula withoutgroupsilently ran the classic engine under the name “welch”. -
voxelwise_covwith a groupcontrastfailed with “subscript out of bounds”; it now returns the signed group coefficient. -
engine = "auto"records the engine that ran ("classic"or"meta") instead of"auto";paired = TRUErecords"classic".signis always recorded. - A rank-deficient design now stops with a message; the classic engine used to return pseudo-inverse estimates.
- The meta engine on file-backed data (gds,
group_data_*) stops whenmu0orvoxelwise_covis given;group_data_*inputs used to ignore both silently.
fmri_meta()accepts agdsbuilt directly with fmrigds (as_gds(),gds()); it used to have no method for it.ols_t_cpp()andwelch_t_cpp()are deprecated and will be removed in the next minor release; usefmri_ols_fit()/fmri_ttest().fmri_ols_fit()without a voxelwise covariate now uses"ols:voxelwise"(same results).fmrireg no longer overwrites fmrigds’s own reducers. Every group
fmri_meta()/fmri_ttest()call used to re-registermeta:fe,meta:fe_reg,meta:re_reg(switching it from DerSimonian-Laird to Paule-Mandel) and thefdr:spatialposthoc with fmrireg’s versions, sofmrigds::reduce()gave different results once fmrireg had run, andreduce(method = "meta:fe")failed. fmrireg’s spatial-FDR posthoc is now registered once, at load, as"fmrireg:fdr:spatial".Contrast standard errors and t statistics no longer depend on the scale of the data. Voxelwise beta statistics set t = 0 below an absolute standard-error threshold and the voxel-status check called voxels “constant” below an absolute variance, so data in small units (Y x 1e-8) gave different t values or none. All paths that form t from a per-voxel covariance now share one rule: a standard error of exactly zero gives t = 0, p = 1 (two paths returned NA).
With the reference backend (
compute_spec(backend = "reference")), joint AR fits estimated phi separately in each voxel chunk from at most 64 voxels, so results depended onvoxel_chunks. Phi is now estimated once per run, and the reference backend matches the matrix backend.The
rrr_glsengine now resolvesnoise_spec(censor = "auto"/"none")and a censor column carried by the dataset, like the other fitting paths; it passed the raw setting to AR estimation and ignored the dataset’s censor column.standard_error(<fmri_latent_lm>, recon = TRUE)works for AR fits; it failed because the AR path did not store what the reconstruction needs.When AR estimation fails, fmrireg now warns (class
fmrireg_ar_estimation_failed) before fitting without whitening; it used to fall back silently.print()on anfmri_lmfit uses the cli layout that reports the estimation scope, residual df, formula, engine and contrasts. An older duplicate method had been masking it, and the cli version wrote to stderr.print()on anfmri_modelshows the event terms, event count and baseline instead of empty sections.write_results()withformat = c("h5", "nifti")computes each map once and writes each JSON sidecar once; it computed statistic volumes up to twice per format and wrote sidecars twice to the same path. File names and contents are unchanged.Contrast formulas and named contrasts for group fits now error on unknown coefficient names or formulas they cannot parse;
parse_contrast_formula()returned all-zero weights for anything but~ a - b.group_data(format = "fmrilm")(and a list of fits withformat = "auto") failed with “invalid ‘file’ argument”; it now stops at once and names the working route (write_results(), thengroup_data(..., format = "nifti")).fit_contrasts()on anfmri_lmfit readresult$sigma, which partially matchedsigma2whensigmawas absent; it now stops instead of returning wrong standard errors.tidy()had the same problem withdf.An internal helper that checked for required packages only checked the first name it was given.
spatial_fdr()counted features with a missing p-value in each group’s Storey estimate of pi0 (as if they were belowtau), lowering pi0, inflating the other tests’ weights and making q-values anti-conservative in groups with missing values. Group sizes (m_g) now count finite p-values only.
Documentation
- The model-comparison section of the linear-model vignette now scores AIC with the AR(1) likelihood of the fits it compares. It used the independent-errors formula, which rewards flexible bases for absorbing serially correlated noise, so the selected model depended on the B-spline basis in the installed fmrihrf. The canonical (generating) HRF now wins in every voxel, with fmrihrf both before and after its B-spline fix (#250).
Internal
- The meta-analysis C++ code has one entry point,
meta_fit_cov_cpp(), which takes an optional voxelwise covariate; the three other copies of its per-feature loop (behindfmri_meta_fit(),fmri_meta_fit_contrasts()andmeta_fit_vcov_cpp()) are gone (about 300 lines of C++). Those functions keep their names, arguments and outputs, and give identical results;fmri_meta_fit_contrasts()now computes its contrasts from the packed covariance. - Removed about 1,150 lines of code that no exported function, S3 method or
.onLoadcould reach, including the unwired bootstrap and sandwich helpers,fast_rlm_run(),multiresponse_rlm(),ar_whiten_inplace()and an unused C++ kernel.create_fmri_model()had three definitions, of which the last file loaded won; it now has one, unchanged in behaviour, with matching documentation. Fitted results do not change. Seedata-raw/simplification-2026-10-plan.mdfor the remaining plan. - fmrireg no longer depends on robustbase, progress, roptim, crayon or RcppParallel, and seven unused Suggests are gone.
.onLoadno longer callsRcppParallel::setThreadOptions(), which changed RcppParallel’s thread pool for other packages and nothing in fmrireg.options(fmrireg.num_threads)and, when the option is unset, theFMRIREG_NUM_THREADSenvironment variable set the thread count of fmrireg’s OpenMP kernels. - One parallel backend. Every R-level parallel loop (voxel chunks with
compute_spec(parallel_chunks = TRUE), voxelwise AR withparallel_voxels, trial-wiseestimate_betas(method = "mixed")and the"future"backend ofrun_jobs()) now goes through future.apply under the activefuture::plan(); furrr is no longer used. C++ kernels keep their OpenMP threads (fmrireg.num_threads).estimate_betas(method = "mixed", progress = TRUE)no longer requires progressr; without a parallel plan it shows a cli progress bar. - plotly and bslib moved from Imports to Suggests: only
design_plot()uses them, and it already needed shiny from Suggests. Together with furrr, RcppParallel and the packages removed earlier, installing fmrireg pulls in 89 non-base packages instead of 123. - Phase 2 of the simplification removed about 2,000 more lines: one AR-order helper, one per-run design builder, one result packager for engines and the plugin API, a single
write_results()output plan, the legacy AR compatibility layer (folded into the fmriAR adapter), duplicatedfmri_lm()front-ends, and dead branches in the integrated solver. - The unused example files
inst/extdata/{images_study,auditory},design_aug.txtandconfig.R(about 2 MB) are no longer installed.face_design.txtandvideo_design.txtremain. -
NEWS.mdandREADME.mdare included in the source package again, soutils::news(package = "fmrireg")works.
Continuous integration
- Hosted workflows resolve dependencies again. The pinned
bbuchsbaum/fmridatasetRemote conflicted with the unpinnedbbuchsbaum/fmridatasetRemote of the pinned fmristore once fmridataset’s default branch moved, and the pkgdown workflow’s extragithub::bbuchsbaum/delarrconflicted with the delarr commit that fmridataset pins. fmridataset is now tracked at its default branch, as fmristore requires, and the extra delarr reference is gone (#249).
fmrireg 0.3.0
Breaking: design generics now come from fmridesign (fmridesign >= 0.6.1.9000)
fmrireg no longer defines its own copies of longnames(), shortnames(), construct() and correlation_map(), and no longer registers methods for classes that fmridesign owns (event_model, event_term, convolved_term, feature_term, baseline_model, …). It re-exports fmridesign’s generics, so fmrireg::longnames and fmridesign::longnames are the same function and a method registered by either package is reached from both. .onLoad no longer reaches into fmridesign’s namespace. Coefficient and design-matrix column names do not change. What does change:
-
blockids(<event_model>)now returns one run id per event, not one per scan. fmrireg used to override fmridesign’s method with per-scan ids whenever it was loaded, so the result depended on whether fmrireg was attached. Code that indexes scans with it must change:blockids(em) # per event (fmridesign's method) blockids(em$sampling_frame) # per scan: what blockids(em) used to return blocklens(em) # scans per run (unchanged)fmridesign prints a one-time message the first time
blockids()is called on anevent_modelin a session. -
longnames()uses fmridesign’s.format instead of#.design before now hrf(cond)cond#Acond.Ahrf(rt)rt#rtrthrf(cond, rt)cond#Acond.A_rthrf(cond, attn)cond#A:attn#xcond.A_attn.xtrialwise().trial_factor...#01.trial_factor....01A long name is the design-matrix column name without its
<term>_prefix (and without the_bNNbasis suffix unlessexpand_basis = TRUE): columncond_cond.Ahas long namecond.A. See?fmridesign::longnames.shortnames()is unchanged for factor terms, but parametric modulators now keep the modulator (hrf(cond, rt)givesA:rt, formerlyA), and the result for anevent_modelis an unnamed vector (formerly named). Empty interaction cells are still left out of
longnames()andshortnames()(the default,drop.empty = TRUE), so names line up with columns.drop.empty = FALSElists the full factor grid.conditions()always returns the full grid, including empty cells.correlation_map(<baseline_model>)is fmridesign’s method. It keeps fmrireg’s former behaviour:within_run = TRUEby default (run intercepts dropped, columns centred within runs, run-specific columns correlated on their own run), with identical cells and values.label_valuesis accepted as an alias forannotate, and cells are labelled by default at 12 or fewer columns, as before. The drawing (VIF diagonal, outlined high correlations) follows fmridesign’s style. Arguments that are notgeom_tile()arguments now raise an error instead of being ignored.design_matrix(<convolved_term>, blockid = )keeps a one-column result as a matrix.
Other fixes in this change
-
standard_error(<fmri_latent_lm>)(includingrecon = TRUE) and the internalpull_stat_revised()name their columns with the design-matrix column names, matchingcoef()andstats(recon = TRUE). For simple designs the names change from the condition name to the column name (for examplea.1becomesa_a.1). Multi-basis HRFs and designs with an empty interaction cell, where the number of conditions differs from the number of columns, used to fail with an error and now work. -
glm_ols()andglm_lss(), which accept an HRF basis by name ("HRF_SPMG1"etc.), now resolve it withgetExportedValue()and accept anyHRF_*object that fmrihrf exports. The names"HRF_AFNI","HRF_GAM","HRF_IL"and"HRF_DD"were listed as valid but never existed in fmrihrf; they now give “Unknown HRF basis name” instead of an “object not found” error. - The fmrigds reducer check calls
fmrigds::get_reducer()directly instead of looking it up in fmrigds’s namespace.
Breaking: corrected SPMG kernel (fmrihrf >= 0.4.0)
- fmrireg now builds against fmrihrf 0.4.0, which corrects the SPM canonical HRF used by
HRF_SPMG1,HRF_SPMG2, andHRF_SPMG3. The kernel now has the SPM double-gamma shape (about 9% undershoot rather than 0.6%), and its raw peak is about ten times lower (0.175 rather than 1.75). For the same data, betas and standard errors for the canonical and temporal-derivative columns are therefore about ten times larger; t and F statistics change only through the corrected shape. The thirdHRF_SPMG3column is now a dispersion derivative, not a second time derivative, so its coefficients are not comparable across versions at all. Becausehrf()defaults to an unnormalized SPMG1 basis, this affects default models. Refit existing analyses rather than comparing raw coefficients across versions. - The bundled
fmri_benchmark_datasetswere regenerated with the corrected kernel (same seeds), and the data-raw generator was updated for thefmri_framedataset API. - NIfTI maps written by
write_results()are now explicitly FLOAT32 (NIfTI datatype 16), as documented. This was already the neuroim2 default for in-memory volumes; it now also holds for volumes whose source file was stored as DOUBLE.
Breaking change: coef() on fmri_lm fits is now voxels x terms
-
coef.fmri_lm()now returns one orientation for every argument combination: one row per voxel, one column per coefficient, with term names on the column margin (#227). Previously the default call (type = "betas",include_baseline = FALSE) returned the transpose, terms x voxels with term names as row names, whileinclude_baseline = TRUEandtype = "contrasts"were already voxels x terms.coef()now also agrees withstats(),standard_error()andp_values(), socoef(fit) / standard_error(fit)lines up element-wise.Update code that indexed the old default by row:
coef(fit)[term, ]becomescoef(fit)[, term],rownames(coef(fit))becomescolnames(coef(fit))(orcoef_names(fit)), and at(coef(fit))added to reach voxels x terms must be dropped. When the number of voxels equals the number of event coefficients the old and new results have the samedim(), so such code does not error: check it by hand. The orientation flip has no deprecation period, since an orientation cannot warn only the callers who depend on it.coef()now has its own help page,?coef.fmri_lm. -
The old orientation caused two silent transpositions, both now fixed:
-
fit_contrasts()on anfmri_lmfit guessed the orientation ofcoef()from its dimensions. When the voxel count equalled the number of event coefficients, it computed the contrast from the transposed betas. - The “known vs recovered” table in the Package Overview vignette (two voxels, two conditions) showed each condition’s estimates across voxels next to each voxel’s true values.
-
reduce_betas()and latent-space reconstruction incoef(<fmri_latent_lm>, recon = TRUE)also compensated for the old orientation. They now use the single one, and their output is unchanged.
Runwise fits: correct baseline columns and near-exact fits
Multi-run runwise fits pooled betas by run-local column position. Each run’s design has the event columns plus only that run’s baseline columns, so the stored betas were too narrow (6 of 10 columns for two runs with default drift). They mixed different runs’ drift and intercept terms, and
coef(fit, include_baseline = TRUE)gave them the wrong global names, for example run 1’s intercept labelledbase_bs1_block_2. Betas are now pooled by global design column. A run-specific baseline column keeps its own run’s estimate. A column shared by all runs (event regressors, a global intercept) is pooled by inverse-variance weighting. The stored betas now have one column per design-matrix column. Event-column estimates are unchanged.Runwise pooling returned
NaNfor every coefficient when a run’s standard error was exactly zero. With inverse-variance weighting, a near-exact fit made 0/0. Pooling now takes the zero-variance limit.The memory-lean residual-sum-of-squares computation (
y'y - b'X'y) cancelled to zero for near-exact fits, which reportedse = 0. Voxels whose RSS falls below the precision ofy'yare now recomputed from explicit residuals.The internal demo fit used in examples and tests now has 3 voxels instead of 2, so its voxels x terms results are not square.
stats(fit, "estimates") renamed to stats(fit, "betas")
stats(fit, type = "estimates")returned t-statistics, and the name invited reading them as estimates (#217). The parameter family is now called"betas", matchingcoef(type = "betas"), and that is the default forstats(),standard_error()andp_values()onfmri_lmandfmri_latent_lmfits.stats(fit, type = "estimates")still works but raises a deprecation warning (classfmrireg_deprecated_estimates_type) that points tocoef(); setoptions(fmrireg.suppress_deprecation = TRUE)to silence it.standard_error()andp_values()accept"estimates"as a silent synonym, because their function names already say what they return. Thetypedocumentation ofstats()now says it returns t-statistics and links tocoef().The coefficient view of
autoplot()/plot()forfmri_lmis namedtype = "betas"to match.type = "estimates"stays a silent synonym, because the plot is labelled and cannot be mistaken for the estimates themselves. The plots now read coefficient values fromcoef()instead of rebuilding them as t x SE, which gave 0 where the SE was numerically zero.Duplicate definitions of
coef(),stats()andstandard_error()forfmri_lminR/fmrilm.RandR/fmri_lm_methods.Rhave been merged into one. Which copy ran used to depend on file collation order.
Plotting
- New
autoplot()/plot()methods forfmri_lmfits, with five views selected bytype:-
"betas"/"contrasts": t-statistics across voxels as a sina plot with p < .001 reference lines and the share of voxels beyond each tail; withvoxel =, a coefficient plot with confidence intervals, one panel per event term. -
"hrf": estimated responses with pointwise confidence bands from the coefficient covariance (one voxel) or a t-based interval of the mean (several voxels). Peak latency is labelled only when the basis has more than one function. -
"timecourse": observed and fitted signal for one voxel, onset lanes per condition, and residuals on the same scale, with the partial R-squared of the event regressors. -
"residuals": residual autocorrelation at lags 1-10, by default the median and 10th-90th percentile over up to 200 voxels. The white-noise range is centred on the (negative) autocorrelation that least-squares residuals have under the design and uses the exact null covariance, and the subtitle counts voxels failing a portmanteau test. For AR fits the prewhitened (GLS) residuals are shown alongside, which checks that the noise model removed the serial correlation.
-
- New
theme_fmrireg(), used by every fmrireg plot. Setoptions(fmrireg.plot_theme = <theme>)to substitute your own. - All plots share one colour-blind-safe condition palette and readable regressor labels (
condition_condition.facesis shown asfaces), built from term metadata. -
design_map()draws the run boundary at the correct scan (it was placed using per-event block ids), keeps columns in model order grouped by term, scales each column to its maximum absolute value by default (scale_columns = TRUE), draws run intercepts in grey, and reports the rank and condition number of the design. Extra arguments now go togeom_raster(), sogeom_tile()arguments such ascolour =are ignored with a warning. -
correlation_map()now defaults tohalf_matrix = TRUEand a newwithin_run = TRUE, which centres columns within runs and drops run intercepts before correlating, blanks pairs of columns from different runs, and correlates pairs involving a run-specific column over that run’s scans. The diagonal is left blank, constant columns are shown in grey, event regressors’ VIFs are given in the caption, andabsolute_limits = FALSEnow spans plus or minus the largest absolute correlation.within_run = FALSE, half_matrix = FALSEis closest to the previous behaviour. -
plot.fmri_model()is now implemented in fmrireg (it no longer delegates to fmridesign or needs cowplot): small multiples of every regressor on one time axis, with nuisance regressors included, run-specific columns drawn only in their run, and no lines joined across runs. -
autoplot()for regressors marks event onsets, draws single-event responses under the summed curve when they overlap, extends the time axis to cover event durations, and names SPMG2/SPMG3 basis functions. -
cowplotis no longer a suggested package.
Datasets are now fmri_frame objects
fmrireghas moved from the removed legacy dataset API offmridataset(matrix_dataset(),fmri_mem_dataset(),fmri_dataset(),latent_dataset(),get_data_matrix(),get_mask(),data_chunks(),read_fmri_config(), …) to its canonical frame API. Every function that takes adataset(fmri_lm(),fmri_rlm(),fmri_model(),create_fmri_model(),estimate_betas(),estimate_hrf(),fmri_latent_lm(),glm_ols(),glm_lss(),extract_nuisance_timeseries(),build_model(), and the engine plugin API) now requires afmridataset::fmri_frame(or a lazyfmri_viewof one). The old dataset classes are no longer accepted, andfmridataset (>= 0.10.0.9000)is required.-
Four thin constructors build a frame from the inputs
fmriregusers typically hold; each stores the run structure asrun_id/TRobservation columns, an optional logicalcensorcolumn, and the event table as a keyedfmridataset::event_table():-
matrix_frame(datamat, TR, run_length, event_table, censor)for a time-by-feature matrix (anindex_space; replacesmatrix_dataset()). -
neurovec_frame(scans, mask, TR, ...)for in-memoryNeuroVecruns (avolume_spaceon the mask; replacesfmri_mem_dataset()). -
nifti_frame(scans, mask, TR, ...)for NIfTI files, lazily read throughfmridataset::nifti_array_source()(replacesfmri_dataset()). -
latent_frame(x, TR, run_length, ...)for afmristore::LatentNeuroVec: component scores become the assay and the loadings a synthesis-onlybasis_space(replaceslatent_dataset()). For fMRIPrep derivatives usefmridataset::read_bids_bold()directly.
-
Reading a frame back uses the
fmridatasetAPI instead of list fields:fmridataset::as_sampling_frame(x)replacesx$sampling_frame,fmridataset::temporal_schema(x)exposes run lengths, TR, and censoring,fmridataset::collect_assay(x)replacesget_data_matrix(), the event table isfmridataset::event_data(x$tables$events), and the spatial reference is recovered fromfmridataset::space(x). Censoring forar_options = list(censor = "auto")is read from the frame’scensorcolumn.estimate_betas()has a singlefmri_framemethod. Frames on avolume_spacereturnNeuroVecbetas as the volumetric method did; frames on anindex_spaceorbasis_spacereturn coefficient matrices as the matrix and latent methods did. The experimentalprewhitenargument of the latent method, which never ran, has been dropped.dataset_spec()/instantiate()/realize_dataset()name the new constructors: file-backed bindings produce"nifti_frame"specs and inline matrices produce"matrix_frame"specs; the constructor allowlist ismatrix_frame,nifti_frame,neurovec_frame, andlatent_frame.simulate_fmri_matrix()$time_seriesis now anfmri_frame.Engine capability
forbid_by_cluster_dataset_classesis matched against the dataset’s class or its feature-space class ("basis_space"marks latent frames).read_fmri_config()is gone with the legacy API; nothing infmriregused it outside commented-out tests, so it was not ported.Fixed the
unused variable 'P'compiler warning inols_t_cpp()(src/ols_t.cpp).Internally, chunkwise and runwise fitting iterate over lazy frame views (
x[rows, ],x[, cols]+collect_assay()) instead ofdata_chunks()/exec_strategy(), with the same chunk partition, so fitted numbers are unchanged.
HRF Estimation
-
estimate_hrf()is now a vectorized, condition-level smooth FIR estimator. It constructs an explicit event-aligned spline basis, removes baseline and fixed nuisance designs once, fits all voxels with one penalized multiresponse solve, and can choose a shared smoothing strength by scale-normalized GCV. The newfmri_hrf_estimateresult preserves curve and voxel labels and provides standard errors, confidence intervals,tidy(),predict(),coef(), andas.matrix()methods. This replaces a voxel-by-voxel GAM path that treated convolved design values as if they were post-stimulus time and failed before prediction.
Statistical Corrections
Explicit volume weights.
weights_spec(values = )now enables weighting without requiring a construction method, and joint IID OLS applies the same weights to its shared design and every response chunk. Zero-weight volumes are excluded from residual degrees of freedom. Reference backends and joint AR/robust combinations that cannot apply requested volume weights now fail instead of fitting an unweighted model.-
AR degrees of freedom.
fmri_lm()no longer deflates the residual degrees of freedom when an AR structure is used. The previous adjustment multipliedn - pby1 / (1 + 2 * sum(1 - k/n)), which is the variance inflation one would obtain if every autocorrelation equalled 1. It never depended on the fitted AR coefficients — atn = 200,p = 12, AR(1) it returned 62.9 whether the true autocorrelation was 0.05 or 0.9 — and it was applied on top of prewhitening, double-counting a correction already made.This changes reported AR p-values. They become less conservative; degrees of freedom roughly triple for AR(1). The previous behaviour cost statistical power rather than inflating false positives, so results that were significant before remain significant, but p-values and any power-sensitive analyses will differ from earlier versions.
calculate_effective_df()gains a workingmethod = "satterthwaite"(previously a verbatim duplicate ofmethod = "simple") that computestr(RV)^2 / tr(RVRV)from a supplied design and post-whitening covariance, reducing exactly ton - pwhen the errors are uncorrelated. Residual correlation surviving the filter is what legitimately costs degrees of freedom; AR order by itself does not. -
Time-sketched GLM inference (
engine = "latent_sketch"). Reported statistics now describe the estimator that is actually returned.-
Sketch-and-solve standard errors are now honest.
"srht","gaussian"and"countsketch"fit the model tomsketched rows, so their coefficients vary aboutsqrt(T / m)times more than full-data OLS, but they reported OLS-sized standard errors withT - pdegrees of freedom. Under the null the type-I rate at nominal 0.05 was 0.42 (SRHT), 0.45 (Gaussian) and 0.45 (CountSketch) atm = 40,T = 200, and 0.16-0.23 atm = 120. The fits now report the conditional-on-sketch covariance(Xs'Xs)^-1 Xs'SS'Xs (Xs'Xs)^-1, an unbiased residual variance||r_s||^2 / tr(P SS')and Satterthwaite residual degrees of freedom (aboutm - p), which restores type-I rates of 0.047-0.051 for every sketch-and-solve method, with and without AR prewhitening (OLS on the same data: 0.052). Standard errors, t-statistics, p-values anddf.residualfrom these methods change: standard errors grow by aboutsqrt(T / m). -
Fields are on the data scale. The SRHT used an unnormalised Hadamard transform, so for
"srht"and"ihs"sigma2,result$sigma,resvarandrsswere aboutTtimes too large andcov.unscaledaboutTtimes too small (the two cancelled in standard errors). All sketches are now normalised (E||Sr||^2 = ||r||^2), andsigma2is on the scale of the noise variance for every method. For sketch-and-solve fitscov.unscaledis the conditional covariance above; with landmarks,sigma2remains the interpolated variancesum_l w_l^2 sigma2_l. -
IHS is OLS-exact.
"ihs"now returns the exact(X'X)^-1, the exact full-data residual variance andT - pdegrees of freedom; previously its covariance came from one random sketch (scaling the whole SE map by a random factor of 0.98-1.56 atm = 40) and its variance from a second, independent sketch. By default it iterates until no coefficient moves by more thantol = 1e-3OLS standard errors, up toiters = 100iterations (median 11, max 16 atm = 8pover 200 sketch seeds, leaving at most 6e-4 standard errors of error; the former fixed 3 iterations left a median of 0.5 and a maximum of 1.9).time_sketch$tol = 0runs exactlyitersiterations, and a fit that stops atitersbefore reachingtolwarns. Its standard errors, t-statistics and p-values now match OLS. - Fits carry
$sketch(method,m, residual df, IHS iterations and convergence). Sketch-and-solve now requiresm > pand errors otherwise. -
lowrank_control()documents all four methods and theitersandtolcontrols, validatestime_sketch$method, and no longer carries aniters = 0Ldefault that was invalid for"ihs".
-
Sketch-and-solve standard errors are now honest.
Bug Fixes
Multi-run fits with
baseline_model(intercept = "global")no longer duplicate the global intercept.term_matrices()anddesign_matrix(<fmri_model>, blockid =)now build the whole design without a run filter when every run is selected, so the design holds oneconstant_globalcolumn instead of one per run,baseline_term_indicesstays within the design, and joint fits no longer alias the duplicate columns toNA. Selections of a subset of runs are unchanged. Runs are identified from the sampling frame, so a run without events still counts when every run is requested, andterm_matrices()now errors if its baseline term matrices do not span exactly the baseline design.Parcel-pooled AR (
noise_spec(pooling = "parcel"),by_cluster) inengine = "latent_sketch"summed the sketched Gram matrices of all parcels and solved each parcel’s cross-products against that sum, shrinking every coefficient by about the number of parcels (with 10 parcels the reported residual variance was 104-106 times too large and no null test ever rejected). Each parcel is now solved with its own whitened design and covariance, andmethod = "ihs", which previously ran plain SRHT here, is now honoured. Because the coefficient covariance differs between parcels, post-hocfit_contrasts()on such fits errors; declare contrasts in the model instead. Parcel labels containingNAnow error instead of leaving voxels without a cluster.engine = "latent_sketch"is now rank safe. Its solver inverted the sketched Gram matrix withchol()plus a silent ridge fallback, andchol()often succeeds on a numerically singular matrix. With one aliased nuisance column (T = 200,m = 40),"countsketch"reported a meansigma2of 221 against a true 1 and event standard errors of 38-166 against about 3.6."srht"and"gaussian"gave aliased coefficients small finite standard errors, and"ihs"reported aliased variances of about 2e13. Estimability is now judged on the full (whitened, per parcel forby_cluster) design with the pivoted-QR rule that exact fits use. The sketch is solved on the estimable columns only, through a QR of the sketched design. As in exact fits, aliased coefficients and their standard errors areNA,cov.unscaledandcovariance_by_clustercarry thealiasedattribute, and contrasts that load on an aliased column areNAwith a warning naming it. The estimable coefficients, standard errors,sigma2and Satterthwaite df equal those of the same sketch applied to the reduced design. If the sketched design restricted to the estimable columns is rank deficient,mis too small for the design and the fit stops with an error namingtime_sketch$m. The ridge fallback is gone."ihs"likewise no longer falls back to a pseudo-inverse of a singular sketched Hessian (withmbelow the number of columns it returned coefficients 10-800 OLS standard errors off while reporting the exact OLS covariance); it requiresmat least the number of estimable columns and errors otherwise. Forby_clusterfits the non-estimable-contrast warning is issued once, not once per cluster.-
Reporting for
engine = "latent_sketch"fits:-
write_results()metadata andresult$df$methodnow recordDegreesOfFreedomMethod = "satterthwaite"for sketch-and-solve fits."ihs"and exact fits keep"residual". -
by_clusterfits now report covariance scope"cluster"(with the per-cluster covariances) instead of"summary". -
result$rssis now the residual sum of squares of the fitted (sketched) rows. It wassigma2 * rdf, which is not a residual sum of squares becauserdfis a Satterthwaite df. Its expectation issigma2 * kappa, wherekappa = tr(P SS')is stored inresult$sketch$kappa. For"ihs",rssis unchanged (the full-data RSS). For landmark fits it is interpolated from the landmarks, likesigma2.
-
The single-voxel HRF plot (
autoplot(fit, type = "hrf", voxel = v)) of aby_clustersketch fit drew no confidence band and always marked the curve as significant, because it read the sharedcov.unscaled, which such fits do not have. It now uses the covariance of the voxel’s cluster.time_sketchlists without anmelement (for examplelist(method = "ihs")) failed with “m <= Tlen is not TRUE”, becausesk$mpartially matchedsk$method. The defaultm = min(8p, T)now applies.The
"ihs"time sketch inengine = "latent_sketch"now performs an actual iterative Hessian sketch. Each iteration previously used a sketched gradient as well as a sketched Hessian, so it re-solved an independent sketched problem and never converged to the least-squares solution; more iterations did not help. The gradient now uses the full data, and a step halving keeps the residual sum of squares non-increasing, soiterscontrols accuracy as documented. Iterations start from the sketch-and-solve solution, so the baseline is fitted from the first step.iters < 1and non-finite inputs are now errors.The
fmridatasetrequirement is now(>= 0.11.0.9000). The pre-frame API was removed upstream without a version change, so both sides of the break reported0.10.0.9000and the previous constraint could not tell them apart: a stale install resolved cleanly and then failed at lazy loading withobject 'as.matrix_dataset' is not exported by 'namespace:fmridataset'. fmridataset bumped to0.11.0.9000for this purpose (bbuchsbaum/fmridataset#92), so the mismatch is now caught at dependency resolution with a message that names the real problem.The test suite has been ported to the frame API. The frame migration and a parallel test-coverage branch were developed against the same base and merged independently, so the merged tree combined frame-only package code with ~50 test files still building legacy
matrix_dataset(),fmri_mem_dataset(), andlatent_dataset()fixtures.R CMD checkfailed on every platform with 106 test failures that neither branch saw on its own. Fixtures now usematrix_frame(),neurovec_frame(), andlatent_frame(); no assertion was relaxed to accommodate the port.chunkwise_lm()dispatches onfmri_framefrom outside the package.chunkwise_lm.fmri_frame()was never registered as an S3 method, so dispatch from outside the namespace failed with “no applicable method” even though internal calls resolved..rrr_extract_response_matrix()validates its input again. A non-frame argument fell through tocollect_assay()and failed insidefmridatasetwith a message naming neither the argument nor the caller.extract_censor_from_dataset()no longer errors on a non-frame dataset. It now reports “no censoring” instead, which matters because censoring is consulted on every fit under the default change below.A censor column carried by a dataset is now used by default.
fmri_lm()previously consulted it only whenar_options = list(censor = "auto")was passed as well, so amatrix_frame(censor = )(and, before it,fmri_dataset(censor = )) was silently discarded: fits on a censored frame were bit-identical to fits on an uncensored one, with no warning. An unsetcensornow resolves against the dataset, which is what"auto"asked for explicitly; passcensor = "none"to ignore a censor column deliberately. This changes results for anyone who set censoring on a dataset and did not opt in, since those fits were not censored at all. An explicitcensorvector inar_optionsstill overrides the dataset.fmri_lm()now warns when censoring cannot affect the fit. Censoring feeds AR estimation and whitening only and never drops volumes from the regression, so under the default iid noise model it does nothing at all. That was previously silent, and indistinguishable from censoring having been applied.Shared AR estimation now pools voxel residual autocovariances by default instead of fitting the cross-voxel mean residual series. The former targets a typical voxel covariance; the latter suppresses voxel-specific noise and targets a different, often more autocorrelated coherent component. The old behavior remains available explicitly as
noise_spec(..., shared_estimator = "mean_series"). Fixed-order fits now use fmriAR’s pooled AR autocovariance path as well; the previous ARMA(p, 0) shortcut accidentally collapsed both choices to the mean series.shared_estimator = "mean_series"now retains the matching OLS design-bias correction. OLS projection is linear across response columns, so averaging residuals after projection is exactly the same as projecting the mean series.The OLS residual-bias correction now uses a separate covariance-tail lag budget rather than stopping at the fitted AR order. Projection mixes tail autocovariances into lags 0:p, so a p-lag correction left avoidable bias.
Global AR estimation over separately fitted runs now supplies the exact block-diagonal residual-forming design. Row-binding the per-run designs incorrectly described a single coefficient vector shared across runs.
Design-corrected AR coefficients are estimated once from initial OLS residuals and held fixed for the GLS solve. Later GLS residuals have a different residual-forming operator and no longer reuse the OLS
design=correction. The low-rank engine now follows this contract too, and the low-rank and RRR whitening paths reset at run boundaries.GDS exports now release unreachable HDF5 handles before atomic finalization, allowing Windows to move and immediately reopen the completed file.
write_results(strategy = "by_stat")no longer scatters contrast maps onto the wrong voxels..compute_statistical_volumes()calledas.logical()on the mask, which dropsdimfor plain arrays (the type.fmri_dataset_mask_space()always supplies), sowhich(..., arr.ind = TRUE)returned linear indices instead of 3D coordinates.Oversized residual-bootstrap blocks now remain one contiguous temporal block instead of silently becoming interleaved odd/even samples.
Mixed-model solver failures now warn and return
NAcoefficients rather than plausible zeros; malformed response dimensions fail before solver dispatch.robust_psinow has an effect. It was previously unreachable:robustdefaulted toFALSErather thanNULL, sorobust_options$typewas always claimed beforerobust_psiwas consulted.robust’s default is nowNULL, which also makes “unspecified” distinguishable from “explicitly off”.An explicit
robust = FALSEis no longer silently overridden byrobust_options = list(type = "huber"); the combination is now an error.Supplying
cfgalongside other configuration arguments is now an error instead of silently discarding them. Previouslycor_struct = "ar2"with acfgnaming"iid"ran IID without warning.AR shorthands (
cor_struct,cor_iter,cor_global,ar1_exact_first,ar_p,ar_voxelwise) that disagree with the correspondingar_optionsentry now error rather than being silently dropped. Shorthands that agree are still accepted.enginenow warns about the execution arguments it ignores (strategy,nchunks,use_fast_path,progress,parallel_voxels,parallel_chunks), which were previously accepted and silently discarded.compute_sandwich_variance()computed its meat matrix asX' diag(e^4) Xinstead ofX' diag(e^2) X, giving standard errors about 3.5x too large. Both sandwich helpers are now checked againstsandwich::vcovHC().
Deprecated
-
lowrank_control(time_sketch = list(method = "ihs"))is deprecated. Inengine = "latent_sketch"the design is narrow (p of about 10-30) and the response wide (1e4-1e5 columns), so least squares costs oneX'Zpass, and every iterative-Hessian-sketch iteration repeats that pass to form the exact gradient: IHS could never beat one exact solve (9.35 s against 0.12-0.23 s for OLS)."ihs"now computes the exact OLS fit through the solver of the exact joint path, so coefficients, standard errors, contrasts, rank handling (aliased columnsNA) and theT - rankresidual df are those of exact OLS; without AR it is identical tofmri_lm(..., strategy = "chunkwise"). It emits a once-per-session message pointing to"countsketch"/"gaussian"for speed or exact OLS for accuracy. This supersedes the IHS iteration andtolentries under Statistical Corrections and Bug Fixes. The controlstime_sketch$itersandtime_sketch$tolare accepted and ignored for one release,mis ignored for"ihs"(it no longer has to reach the number of estimable columns), andresult$sketchno longer carriesitersorconverged. The internal IHS kernel (cpp_ihs_latent(),ihs_latent_solve()) is removed.
Performance
- The SRHT sketched solve in
engine = "latent_sketch"is 17-83x faster (Tfrom 200 to 1000, 1e4-5e4 response columns, 2 threads). Its Walsh-Hadamard transform walked the column-major data row by row with a temporary vector per butterfly; it now transforms each response column in place in a contiguous buffer and, when built with OpenMP, runs in parallel over columns (options(fmrireg.num_threads)). Output is bit-identical to the previous kernel. The sketched solve now forms coefficients and residuals with two BLAS products through the explicitQfactor instead ofqr.coef()/qr.resid(), which applied Householder reflections one response column at a time, and the sketch Gram matrixSS'is computed once per fit instead of once per cluster. AtT = 400,p = 20,m = 8pand 5e4 response columns the SRHT solve fell from 3.5 s to 0.07 s, and the Gaussian and CountSketch solves from about 0.3 s to 0.04 s (exact OLS: 0.05 s). SRHT still trails CountSketch and Gaussian at realistic sizes, so?lowrank_controlnow recommends those two for speed. - Voxelwise AR fitting no longer recomputes the run-level design projection for every voxel.
.fast_preproject()performs ann x nsolve and was being called once per voxel on a design that does not vary by voxel; hoisting it gives roughly a 1.7x speedup on the voxelwise AR path (4.95s to 2.98s for 2000 voxels at 300 timepoints).
Documentation
-
ar_voxelwisewas documented as overridingar_options$voxelwise. It never did — the options list took precedence — and disagreement is now an error. -
use_fast_pathwas documented as defaulting toFALSEin the runwise and chunkwise fitters; it defaults toTRUE. - Corrected claims that sandwich variance estimation is applied automatically. It is not used by any
fmri_lm()fitting path; the robust path reports a model-based weighted-least-squares variance. The accompanying degrees-of- freedom formula was also wrong and has been rewritten.
Changes
-
fmriARis now declared with a minimum version (>= 0.3.3) and listed inRemotes:; it previously had neither.
New Features
Reexported
feature()(from fmridesign) andfeature_regressor()(from fmrihrf) so mixed event formulas such asonset ~ hrf(condition) + feature(rms, dt = 0.1)work afterlibrary(fmrireg). Feature terms are sampled series, not trials; helpers that assumehrfspectrial structure skip or unwrap them instead of erroring (fitted_hrf(),shortnames(),design_plot(),preflight(), pair-contrast resolution).write_results()now exports statistical maps as NIfTI volumes viaformat = "nifti"(orformat = c("h5", "nifti")), reusing the same BIDS entity and filename machinery as the HDF5 backend. This removes the need to hand-roll loops overcoef_image()+neuroim2::write_vol().Added
coef_images(), a plural companion tocoef_image()that returns a named list ofNeuroVols for every coefficient of a giventype("estimates","contrasts", or"F") andstatistic.
Bug Fixes
-
write_results()now labels F-contrast maps asfstat/fpvalinstead of reusing thetstat/pvallabels. F statistics follow an F distribution (not Student’s t), so mixing them under atstatlabel produced mislabeled maps and, understrategy = "by_stat", could collide t- and F-contrast outputs in a single file. t- and F-contrasts are now written to separate, correctly labeled files.
fmrireg 0.1.2
Changes
- Moved PDF report generation (
report.fmri_lm) to the standalonefmrireportpackage. Usefmrireport::report()for report rendering.
Bug Fixes
- Fixed low-rank/sketch residual degrees of freedom to use original timepoints (
T - p) instead of sketch rows. - Replaced placeholder
fit_contrasts.fmri_lm()outputs with computed standard errors, test statistics, and p-values. - Consolidated duplicate meta-analysis pathways to shared implementations and removed inconsistent behavior across runwise/chunkwise pooling.
- Fixed F-contrast meta pooling to combine evidence via p-values rather than averaging F statistics.
- Fixed thread-safety issue in C++ meta kernels by deferring Paule-Mandel non-convergence warnings until after OpenMP regions.
- Added bounds validation and safer inverse fallback in sketch kernels to prevent unsafe indexing and hard failures on ill-conditioned data.
- Improved AR pipeline consistency for whitened covariance/fitted/residual handling and unified AR effective-df calculations.
- Added regression coverage for
fit_contrasts.fmri_lm()to prevent placeholder-stat regressions.
fmrireg 0.1.1
Bug Fixes
- Fixed
glm_lss()tests that incorrectly expected specific Cholesky decomposition errors. - Fixed convolution test with non-strictly-increasing onsets within blocks.
- Fixed testthat API usage (
expect_lt/expect_gtno longer use deprecatedinfoargument). - Fixed
latent_datasetAPI usage: now usesget_latent_scores()instead of deprecatedget_data(). - Changed
glm_lss()use_cppparameter default fromTRUEtoFALSE(C++ implementation retired; fmrilss package now used). - Added
fmrireg.suppress_deprecationoption check to all deprecated functions for cleaner test output. - Added
tests/testthat/setup.Rto suppress expected deprecation warnings during testing. - Suppressed expected kmeans convergence warnings in landmark SRHT tests.
fmrireg 0.1.0
Breaking Changes
-
Design Matrix Column Naming: The naming scheme for columns in design matrices generated by
event_model()has been completely revised for consistency and clarity.- All column names now strictly follow the format:
term_tag+_+condition_tag+ [_b##basis suffix]. -
term_tag: Automatically generated from variable names (e.g.,var1_var2) or user-providedid=inhrf(), sanitized (dots become underscores), and made unique with#suffix if needed (e.g.,cond,cond#1). -
condition_tag: Represents factor levels (e.g.,Factor.Level), continuous basis columns (e.g.,poly_RT_01,z_RT), or interactions joined by_(e.g.,Factor.Level_poly_RT_01). -
_b##: Optional suffix added only when the HRF has multiple basis functions (e.g.,_b01,_b02). - The previous
styleargument ("compact","qualified","uid") indesign_matrix()is removed. Only the single canonical format is produced. - Scripts or analyses that relied on matching previous column name formats (e.g., using
Var[Level],Var:Level,:basis[]) will need to be updated to use the newterm_tag_Condition.Tag_b##format.
- All column names now strictly follow the format:
Major Changes
-
Regressor System Refactoring:
- Introduced a new internal S3 class
Regfor representing regressors. - The main
regressor()function now usesReginternally but maintains backward compatibility (returns classc("regressor", "Reg", "list")). - Deprecated
single_trial_regressor()andnull_regressor()in favour of usingregressor()directly. - Unified regressor evaluation under the
evaluate.RegS3 method, supporting different calculation methods (“fft”, “conv”, “loop”, “Rconv”). - Evaluation methods now consistently use the refactored
evaluate.HRFfor HRF sampling. - Refactored C++ evaluation code into a single wrapper (
evaluate_regressor_cpp). - Removed redundant internal helper functions (
fastevalreg,fastevalreg2,conform_len,dots). - Implemented
autoplot.Reg(ggplot2) andprint.Reg(cli) methods, deprecating olderplot.regressorandprint.regressor. - Improved input validation and recycling for
regressor()arguments usingvctrs. - Added optional sparse matrix output to
evaluate.Reg. - Added memoization for HRF sampling within evaluation.
- Introduced a new internal S3 class