Skip to contents

Feature-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.

conn_example <- build_connectivity_example()

knitr::kable(round(conn_example$cross_raw, 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

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    36

This 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.

stripe_example <- build_striping_example()

knitr::kable(round(stripe_example$raw, 2))
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.

knitr::kable(round(stripe_example$double_centered, 2))
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.

knitr::kable(round(stripe_example$residualized, 2))
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").