Searchlight Analysis
Bradley Buchsbaum
2026-04-14
Source:vignettes/Searchlight_Analysis.Rmd
Searchlight_Analysis.RmdSearchlight Analysis
Simulated data
We first create a small volumetric dataset for demonstration. The
call below generates a 4D image with 6×6×6 spatial voxels and 80
observations along the time dimension. Observations are grouped into 4
blocks (20 trials each), and the response factor y has two
levels. The return value contains both the data
(mvpa_dataset) and the design
(mvpa_design).
dataset <- gen_sample_dataset(D=c(6,6,6), nobs = 80, blocks=4, nlevels=2)
print(dataset)
#> $dataset
#>
#> MVPA Dataset
#>
#> - Training Data
#> - Dimensions: 6 x 6 x 6 x 80 observations
#> - Type: DenseNeuroVec
#> - Test Data
#> - None
#> - Mask Information
#> - Areas: TRUE : 216
#> - Active voxels/vertices: 216
#>
#>
#> $design
#>
#> MVPA Design
#>
#> - Training Data
#> - Observations: 80
#> - Response Type: Factor
#> - Levels: a, b
#> - Class Distribution: a: 40, b: 40
#> - Test Data
#> - None
#> - Structure
#> - Blocking: Present
#> - Number of Blocks: 4
#> - Mean Block Size: 20 (SD: 0 )
#> - Split Groups: NoneCross‑validation
Because trials are organized into runs (blocks), we use a blocked
cross‑validation scheme: train on k−1 blocks and test on
the held‑out block. This leave‑one‑group‑out strategy respects temporal
correlations within runs and yields a more realistic estimate of
generalization.
block <- dataset$design$block_var
crossval <- blocked_cross_validation(block)
crossval
#>
#> Blocked Cross-Validation
#>
#> - Dataset Information
#> - Observations: 80
#> - Number of Folds: 4
#> - Block Information
#> - Total Blocks: 4
#> - Mean Block Size: 20 (SD: 0 )
#> - Block Sizes: 1: 20, 2: 20, 3: 20, 4: 20Model specification
We use the shrinkage discriminant analysis model
(sda_notune), which estimates its shrinkage parameter from
the training folds. See the sda
package for details.
sda_model <- load_model("sda_notune")
model <- mvpa_model(model=sda_model, dataset=dataset$dataset, design=dataset$design, crossval=crossval)
model
#> mvpa_model object.
#> model: sda_notune
#> model type: classification
#>
#> Blocked Cross-Validation
#>
#> - Dataset Information
#> - Observations: 80
#> - Number of Folds: 4
#> - Block Information
#> - Total Blocks: 4
#> - Mean Block Size: 20 (SD: 0 )
#> - Block Sizes: 1: 20, 2: 20, 3: 20, 4: 20
#>
#>
#> MVPA Dataset
#>
#> - Training Data
#> - Dimensions: 6 x 6 x 6 x 80 observations
#> - Type: DenseNeuroVec
#> - Test Data
#> - None
#> - Mask Information
#> - Areas: TRUE : 216
#> - Active voxels/vertices: 216
#>
#>
#> MVPA Design
#>
#> - Training Data
#> - Observations: 80
#> - Response Type: Factor
#> - Levels: a, b
#> - Class Distribution: a: 40, b: 40
#> - Test Data
#> - None
#> - Structure
#> - Blocking: Present
#> - Number of Blocks: 4
#> - Mean Block Size: 20 (SD: 0 )
#> - Split Groups: NoneStandard searchlight
run_searchlight() returns image volumes with performance
metrics at each sphere center. For two‑class problems we report
cross‑validated accuracy and AUC (centered at 0 by subtracting 0.5). The
radius is in millimeters; method = "standard"
evaluates one model per sphere centered on each voxel.
result <- run_searchlight(model, radius=4, method="standard")
resultRandomized searchlight
The randomized variant samples non‑overlapping spheres and propagates
each sphere’s score to all voxels it covers. Repeating this for
niter exhaustive passes yields, at each voxel, the average
performance across the spheres that included it. This can improve
spatial localization and often reduces computation, since the number of
models scales with nvoxels / radius × niter rather than the
total number of voxels.
result <- run_searchlight(model, radius=4, method="randomized", niter=8)
resultUsing different classifiers
Any classifier in the rMVPA registry can be used in a searchlight.
You can also register your own with register_mvpa_model().
Two robust options that ship with rMVPA are hdrda and
pca_lda:
# High‑Dimensional Regularized Discriminant Analysis
hdrda <- load_model("hdrda")
model_hdrda <- mvpa_model(model = hdrda, dataset = dataset$dataset, design = dataset$design,
crossval = crossval)
res_hdrda <- run_searchlight(model_hdrda, radius = 4, method = "randomized", niter = 2)
res_hdrda
# PCA + LDA pipeline
pca_lda <- load_model("pca_lda")
model_pca_lda <- mvpa_model(model = pca_lda, dataset = dataset$dataset, design = dataset$design,
crossval = crossval)
res_pca_lda <- run_searchlight(model_pca_lda, radius = 4, method = "randomized", niter = 2)
res_pca_ldaDomain‑adaptive cross‑decoding (REMAP‑RRR)
REMAP‑RRR models memory as perception plus a low‑rank correction in a
jointly whitened space. It learns a residual map Δ on the prototype
residuals (Y_w − X_w) ≈ X_w Δ, builds predicted memory templates Ŷ_w =
X_w + λ X_w Δ (with λ tuned on training items), and classifies held‑out
memory trials by correlation to Ŷ_w. Provide an external test set in
your dataset/design (same pattern as localizer→WM), and optionally a
link_by column shared by train/test designs to pair items
across domains.
# Synthetic source→target setup
toy <- gen_sample_dataset(D = c(6,6,6), nobs = 120, nlevels = 4, blocks = 3, external_test = TRUE)
regionMask <- neuroim2::NeuroVol(sample(1:5, size = length(toy$dataset$mask), replace = TRUE),
neuroim2::space(toy$dataset$mask))
mspec <- remap_rrr_model(
dataset = toy$dataset,
design = toy$design,
link_by = NULL, # defaults to class-wise pairing
rank = 0, # identity fallback (no rrpack required)
leave_one_key_out = TRUE
)
remap_res <- run_regional(mspec, regionMask)
names(remap_res$vol_results) # includes remap_improv, delta_frob_mean, lambda_meanSet rank = "auto" or a positive integer to enable
reduced‑rank mapping via rrpack.
See also
-
vignette("Regional_Analysis")– the ROI-based counterpart to searchlight analysis -
vignette("CrossValidation")– cross-validation strategies for fMRI -
vignette("CustomAnalyses")– plug in your own analysis functions