Feature-RSA Connectivity: ROI-to-ROI Generalization and Offset Control
Source:vignettes/Feature_RSA_Connectivity.Rmd
Feature_RSA_Connectivity.RmdFeature-RSA normally stops at one ROI at a time: does the learned geometry explain held-out data in that ROI? Sometimes the next question is larger. If ROI 3 learns a useful geometry, does that geometry show up in ROI 7 as well? If one target ROI is easy for every source ROI, how do you separate that global effect from pair-specific transfer?
feature_rsa_connectivity() and
feature_rsa_cross_connectivity() answer those questions at
the level of representational geometry rather than voxel weights. That
matters because ROI sizes can differ and you still get a clean ROI x ROI
summary.
What does the cross-ROI matrix look like?
The quick example below fits feature-RSA in four synthetic parcels, stores the predicted and observed RDM vectors for each ROI, and then builds the asymmetric ROI_i -> ROI_j matrix.
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 0 | 0.03 | -0.02 | -0.05 |
| 0 | 0.07 | -0.03 | -0.02 |
| 0 | 0.02 | 0.02 | -0.05 |
| 0 | 0.03 | -0.01 | -0.01 |
Rows are source ROIs: the predicted geometry learned in that ROI. Columns are target ROIs: the observed geometry in held-out data from that ROI. The diagonal is the familiar within-ROI feature-RSA fit. The off-diagonal entries are cross-ROI generalization scores.
How do you extract the per-ROI geometry?
The only extra requirement is return_rdm_vectors = TRUE
when you fit feature_rsa_model(). That stores compact
lower-triangle RDM vectors for each ROI, and
feature_rsa_rdm_vectors() pulls them into a tibble.
head(conn_example$vecs[, c("roinum", "n_obs")])
#> # A tibble: 4 × 2
#> roinum n_obs
#> <int> <int>
#> 1 1 36
#> 2 2 36
#> 3 3 36
#> 4 4 36This is the bridge between ordinary per-ROI feature-RSA and the ROI-to-ROI summaries. You still fit ROI models in the usual way. The difference is that you keep the predicted and observed geometry vectors around for a second-stage comparison.
When do you want the symmetric matrix?
feature_rsa_connectivity() asks whether the predicted
geometries themselves are similar across ROIs. It correlates predicted
RDM vectors with predicted RDM vectors, so the result is symmetric.
predicted_connectivity <- feature_rsa_connectivity(
conn_example$vecs,
method = "spearman"
)
knitr::kable(round(predicted_connectivity, 2))| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 1.00 | 0.86 | 0.69 | 0.80 |
| 0.86 | 1.00 | 0.68 | 0.84 |
| 0.69 | 0.68 | 1.00 | 0.62 |
| 0.80 | 0.84 | 0.62 | 1.00 |
Use this matrix when you want a network summary of learned representational geometry itself. It is useful for asking which ROIs sit near each other in model-predicted representational space.
When do you want the asymmetric matrix?
feature_rsa_cross_connectivity() answers the transfer
question directly: does the geometry predicted from ROI i match the
held-out observed geometry in ROI j?
cross_connectivity <- feature_rsa_cross_connectivity(
conn_example$vecs,
method = "spearman"
)
knitr::kable(round(cross_connectivity, 2))| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 0 | 0.03 | -0.02 | -0.05 |
| 0 | 0.07 | -0.03 | -0.02 |
| 0 | 0.02 | 0.02 | -0.05 |
| 0 | 0.03 | -0.01 | -0.01 |
This matrix is the right one when you care about directional
cross-ROI generalization. In general ROI_i -> ROI_j and
ROI_j -> ROI_i need not agree, because the rows come
from predicted geometry and the columns come from observed geometry.
What creates stripes in the ROI x ROI matrix?
Striping usually means one of two things:
- some source ROIs are globally strong predictors of many targets
- some target ROIs are globally easy to predict because they share a large common geometry with many sources
The synthetic example below creates that second pattern on purpose by making some ROIs carry more of a shared representational backbone than others.
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 1.00 | 0.92 | 0.91 | 0.21 |
| 0.90 | 0.99 | 0.74 | 0.21 |
| 0.85 | 0.73 | 1.00 | -0.05 |
| 0.29 | 0.30 | 0.08 | 0.99 |
In the raw matrix, ROIs 1 and 2 are easy targets across the board, while ROI 4 is globally harder. That is the kind of vertical stripe pattern you often see when a common geometry dominates the matrix.
How do the adjustment options differ?
adjust = "double_center" removes additive row and column
main effects from the ROI x ROI matrix itself. It is the clean default
when you want pair-specific transfer after discounting globally strong
sources and globally easy targets.
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 0.11 | 0.06 | 0.10 | -0.26 |
| 0.06 | 0.17 | -0.02 | -0.21 |
| 0.09 | -0.01 | 0.31 | -0.40 |
| -0.26 | -0.22 | -0.39 | 0.87 |
knitr::kable(stripe_example$offsets, digits = 2)| roinum | source_offset | target_offset |
|---|---|---|
| 1 | 0.13 | 0.13 |
| 2 | 0.08 | 0.11 |
| 3 | 0.00 | 0.05 |
| 4 | -0.21 | -0.29 |
The source_offset and target_offset columns
quantify the striping directly. Positive source offsets mark ROIs that
predict many targets well. Positive target offsets mark ROIs that are
easy to predict from many sources.
adjust = "residualize_mean" takes a different approach.
It removes the grand-mean RDM component before the cross-correlation is
computed, which is better when the stripe pattern reflects one dominant
shared geometry present in nearly every ROI.
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 0.98 | 0.09 | 0.39 | -0.70 |
| 0.13 | 0.99 | -0.34 | -0.29 |
| 0.38 | -0.26 | 1.00 | -0.70 |
| -0.78 | -0.40 | -0.74 | 1.00 |
Which version should you report?
- Report the raw asymmetric matrix when you want the full descriptive picture, including globally strong sources and globally easy targets.
- Use
adjust = "double_center"for visualizations or analyses that are meant to emphasize pair-specific cross-ROI generalization. - Use
adjust = "residualize_mean"when you think a single common geometry is driving much of the matrix and you want to see what remains after removing it.
These adjustments answer different questions, so it is often worth saving both the raw matrix and one adjusted matrix rather than choosing only one.
Next steps
If your transfer problem is across cognitive states rather than
across ROIs, see vignette("Feature_RSA_Domain_Adaptation").
If you want the broader decision map for within-ROI fits, cross-state
transfer, and ROI-to-ROI generalization, see
vignette("Feature_RSA_Advanced_Workflows").