Binary spatial colocalization

In all previous notebooks, the input map Y was a continuous brain map — a z-score, a Cohen’s d, a correlation coefficient. But sometimes Y is binary: a network mask, an ROI, a lesion map, a thresholded cluster map.

For binary data, spatial colocalization reduces to point-biserial correlation, which is mathematically equivalent to Pearson correlation. Spearman rank correlation degrades with heavily tied data (most parcels = 0), making Pearson the correct choice.

Setting binary_y=True in NiSpace:

  • Prevents z-scoring of Y (which would distort binary values)

  • Sets colocalization_method="pearson" when no method is specified

  • Issues a warning if a ranked method is requested

In this notebook we use the Yeo 7 functional networks (Yeo et al. 2011) as binary Y maps and ask which cortical features (cell types, gradients, myelination) are enriched in each network.

[2]:
import tqdm.notebook
tqdm.notebook.tqdm = tqdm.tqdm

import numpy as np
import nibabel as nib
import matplotlib.pyplot as plt
import seaborn as sns
from nilearn.datasets import fetch_atlas_yeo_2011
from nispace.workflows import colocalization
from nispace.plotting import brainplot

Loading the Yeo 7 networks

The Yeo 2011 atlas defines 7 large-scale functional brain networks from resting-state fMRI. We use the thick_7 variant (7 networks, thickened boundaries for better MNI-space coverage). Each voxel is labelled 0 (background) or 1–7 (network ID). We extract 7 binary NIfTI images, one per network.

[3]:
yeo = fetch_atlas_yeo_2011(n_networks=7, thickness="thick")
thick7 = nib.load(yeo.maps)
data   = thick7.get_fdata().squeeze()

# Canonical Yeo 7 network names (Yeo et al. 2011)
yeo_names = {
    1: "Visual",
    2: "SomMot",
    3: "DorsAttn",
    4: "SalVentAttn",
    5: "Limbic",
    6: "Control",
    7: "Default",
}

# Extract one binary NIfTI per network
yeo_imgs   = [nib.Nifti1Image((data == i).astype(np.float32), thick7.affine)
              for i in yeo_names]
yeo_labels = list(yeo_names.values())

# save in dict for use with NiSpace
yeo_dict = dict(zip(yeo_labels, yeo_imgs))

print("Networks:", yeo_labels)
for name, img in yeo_dict.items():
    n_voxels = int(img.get_fdata().sum())
    print(f"  {name}: {n_voxels} voxels")
[fetch_atlas_yeo_2011] Dataset found in /Users/llotter/nilearn_data/yeo_2011
Networks: ['Visual', 'SomMot', 'DorsAttn', 'SalVentAttn', 'Limbic', 'Control', 'Default']
  Visual: 178225 voxels
  SomMot: 155895 voxels
  DorsAttn: 122714 voxels
  SalVentAttn: 109290 voxels
  Limbic: 92292 voxels
  Control: 155339 voxels
  Default: 240978 voxels
[4]:
# Visualize the networks as a sanity check
brainplot(yeo_imgs, title=list(yeo_names.values()), ncols=2)
WARNING | 20/07/26 18:34:06 | nispace.plotting: Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
[4]:
(<Figure size 1440x840 with 43 Axes>,
 [<Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >])
../_images/nb_introduction_intro14_binary_colocalization_4_2.png

Analysis A: functional network overlap (positive control)

As a positive control, we test colocalization with the rsn17 reference dataset — 17 resting-state networks that partially overlap with the Yeo 7 parcellation. We expect high within-network overlap (same network, different parcellation) and low across-network overlap.

This is a positive control: if we don’t recover the block structure (Visual ↔ Visual, Default ↔ Default, etc.), something is wrong with the pipeline or the data.

[5]:
nsp_a = colocalization(
    x="rsn17",
    y=yeo_dict,
    parcellation="Yan200",
    binary_y=True,          # Y is binary: prevents z-scoring, auto-selects pearson
    n_perm=1000,
    seed=42,
    n_proc=-1,
    plot=False,
    return_nispace_only=True
)
INFO | 20/07/26 18:34:24 | nispace.workflows: Loading integrated rsn17 dataset as X data.
INFO | 20/07/26 18:34:24 | nispace.datasets: Loading rsn17 maps.
INFO | 20/07/26 18:34:24 | nispace.datasets: Loading data parcellated with 'Yan200'
The NiSpace "RSN17" dataset contains resting-state network probability maps describing the 17 "Kong"
networks. The maps were derived from individual-level cortical parcellations of 1029 HCP subjects
into 17 functional networks, generated as described in Kong et al., 2021 and made available at
https://github.com/ThomasYeoLab/Kong2022_ArealMSHBM. The maps are only available in fsLR and
fsaverage spaces. Note that the networks follow the Kong et al. naming convention, which differs
from the 17 "Yeo networks". Please cite the original publication when using these maps.
  - Kong et al., 2021  https://doi.org/10.1093/cercor/bhab101
To ensure reproducibility, note the NiSpace version: 0.0.2b2.dev48+g51f21588b.d20260717 (commit: g51f21588b).

INFO | 20/07/26 18:34:24 | nispace.api: binary_y=True: Expecting Y input derived from binary maps (e.g., cluster or network maps); background_value=False for Y parcellation (unless explicitly overridden via background_value={'y': ...} in .fit()).
INFO | 20/07/26 18:34:24 | nispace.api: *** NiSpace.fit() - Data extraction and preparation. ***
INFO | 20/07/26 18:34:24 | nispace.core.parcellation: Building cortex Parcellation for 'Yan200' from library. DOI: 10.1016/j.neuroimage.2023.120010
INFO | 20/07/26 18:34:24 | nispace.core.parcellation: Available spaces: MNI152NLin2009cAsym, MNI152NLin6Asym, fsLR, fsaverage
INFO | 20/07/26 18:34:25 | nispace.core.parcellation: Parcellation 'Yan200': validation passed.
INFO | 20/07/26 18:34:25 | nispace.core.parcellation: Lazy-loading parcellation image for space 'MNI152NLin2009cAsym'.
INFO | 20/07/26 18:34:26 | nispace.core.parcellation: Parcellation 'Yan200': active space set to 'MNI152NLin2009cAsym'.
INFO | 20/07/26 18:34:26 | nispace.api: Checking input data for 'x' (should be, e.g., PET data):
INFO | 20/07/26 18:34:26 | nispace.io: Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
INFO | 20/07/26 18:34:26 | nispace.api: Got 'x' data for 17 x 200 parcels.
INFO | 20/07/26 18:34:26 | nispace.api: Checking input data for 'y' (should be, e.g., subject data):
INFO | 20/07/26 18:34:26 | nispace.io: Input type: dict, assuming (img_name, img) pairs for imaging data.
INFO | 20/07/26 18:34:26 | nispace.io: Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO | 20/07/26 18:34:26 | nispace.io: Parcellating imaging data.
Parcellating (-1 proc): 100%|████████████████████████████████████████████████████████████| 7/7 [00:00<00:00, 709.49it/s]
INFO | 20/07/26 18:34:44 | nispace.api: Got 'y' data for 7 x 200 parcels.
INFO | 20/07/26 18:34:44 | nispace.api: Z-standardizing 'X' data.
INFO | 20/07/26 18:34:44 | nispace.api: *** NiSpace.colocalize() - Estimating X & Y colocalizations. ***
INFO | 20/07/26 18:34:44 | nispace.api: Running 'pearson' colocalization.
Colocalizing (pearson, -1 proc): 100%|██████████████████████████████████████████████████| 7/7 [00:00<00:00, 7691.94it/s]
INFO | 20/07/26 18:34:47 | nispace.api: *** NiSpace.permute() - Estimate exact non-parametric p values. ***
INFO | 20/07/26 18:34:47 | nispace.api: Permutation of: X maps.
INFO | 20/07/26 18:34:47 | nispace.api: Using default null method 'moran' (parcellation null space: 'fsLR').
INFO | 20/07/26 18:34:47 | nispace.core.parcellation: Lazy-loading parcellation image for space 'fsLR'.
INFO | 20/07/26 18:34:47 | nispace.api: Loading observed colocalizations (method = 'pearson').
INFO | 20/07/26 18:34:47 | nispace.core.permute: Generating null maps (n = 1000, null_method = 'moran').
INFO | 20/07/26 18:34:47 | nispace.nulls: Null map generation: Assuming n = 17 data vector(s) for n = 200 parcels.
INFO | 20/07/26 18:34:47 | nispace.nulls: Using provided distance matrix/matrices.
Moran null maps (-1 proc): 100%|███████████████████████████████████████████████████████| 17/17 [00:00<00:00, 228.60it/s]
INFO | 20/07/26 18:34:47 | nispace.nulls: Null data generation finished.
INFO | 20/07/26 18:34:47 | nispace.core.permute: Z-standardizing null maps.

Null colocalizations (pearson, -1 proc): 100%|████████████████████████████████████| 1000/1000 [00:00<00:00, 7939.20it/s]
INFO | 20/07/26 18:34:47 | nispace.core.permute: Calculating exact p-values (tails = {'rho': 'two'}).
[6]:
# the straight-forward way to visualize this is a heatmap
fig, ax = plt.subplots(1, figsize=(8,2.5))
sns.heatmap(
    nsp_a.get_colocalizations(),
    center=0,
    square=True
)
[6]:
<Axes: xlabel='map'>
../_images/nb_introduction_intro14_binary_colocalization_7_1.png

Analysis B: cortical organization features

Now we ask a more substantive question: which structural and organizational features of the cortex are enriched in each Yeo 7 network?

We use the cortexfeatures reference dataset with the CortexOrganisation collection, which includes 11 maps spanning:

  • Morphology: cortical thickness, T1w/T2w myelination proxy

  • Gradients: sensorimotor-to-association (SA) axis, FC gradients 1–3 (Margulies et al. 2016)

  • Developmental/evolutionary expansion (Hill et al. 2010; Xu et al. 2020)

  • Species homology, gene expression PC1

Expected patterns:

  • Myelination (feature-t1t2): highest in SomMot (heavily myelinated sensorimotor cortex), lowest in Default (lightly myelinated association cortex)

  • FC gradient 1 (feature-fcgradient1): captures the unimodal-to-transmodal axis — aligns strongly with the Default network at one end and SomMot/Visual at the other

[7]:
nsp_b = colocalization(
    x="cortexfeatures",
    x_collection="CortexOrganisation",  # 11 maps: thickness, myelination, SA axis, FC gradients, expansion, homology
    y=yeo_dict,
    parcellation="Yan200",
    binary_y=True,
    n_perm=1000,
    seed=42,
    n_proc=-1,
    plot=False,
    return_nispace_only=True
)
INFO | 20/07/26 18:34:47 | nispace.workflows: Loading integrated cortexfeatures dataset as X data.
INFO | 20/07/26 18:34:47 | nispace.datasets: Loading cortexfeatures maps.
INFO | 20/07/26 18:34:47 | nispace.datasets: Loading integrated collection 'CortexOrganisation' for dataset 'cortexfeatures'.
INFO | 20/07/26 18:34:47 | nispace.datasets: Filtering maps by collection.
INFO | 20/07/26 18:34:47 | nispace.datasets: Loading data parcellated with 'Yan200'
INFO | 20/07/26 18:34:47 | nispace.datasets: Fetching map info for dataset 'cortexfeatures'.
The NiSpace "CortexFeatures" dataset consists of different features of cortical topology available
via neuromaps. Many of these maps were used in Shafiei et al., 2023. The maps are fetched directly
from neuromaps (space="fsLROriginal" or "fsaverageOriginal") or downloaded from the NiSpace-data
GitHub repo (space="fsLR" or "fsaverage"; recommended). Please cite neuromaps (https://neuromaps-
main.readthedocs.io/) and the original publication for each map when used.
  - Markello et al., 2022  https://doi.org/10.1038/s41592-022-01625-w
  - Shafiei et al., 2023  https://doi.org/10.1038/s41467-023-41689-6
To ensure reproducibility, note the NiSpace version: 0.0.2b2.dev48+g51f21588b.d20260717 (commit: g51f21588b).

  thickness        10.1016/j.neuroimage.2013.05.041  CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2013.05.041
  t1t2             10.1016/j.neuroimage.2013.05.041  CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2013.05.041
  saaxis           10.1016/j.neuron.2021.06.016      CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuron.2021.06.016
  geneexpr         10.7554/eLife.72129               CC BY-NC-SA 4.0 https://doi.org/10.7554/eLife.72129
  develexpansion   10.1073/pnas.1001229107           CC BY-NC-SA 4.0 https://doi.org/10.1073/pnas.1001229107
  evolexpansion    10.1073/pnas.1001229107           free            https://doi.org/10.1073/pnas.1001229107
  evolexpansion    10.1016/j.neuroimage.2020.117346  free            https://doi.org/10.1016/j.neuroimage.2020.117346
  specieshomology  10.1016/j.neuroimage.2020.117346  free            https://doi.org/10.1016/j.neuroimage.2020.117346
INFO | 20/07/26 18:34:47 | nispace.core.parcellation: Building cortex Parcellation for 'Yan200' from library. DOI: 10.1016/j.neuroimage.2023.120010
INFO | 20/07/26 18:34:47 | nispace.core.parcellation: Available spaces: MNI152NLin2009cAsym, MNI152NLin6Asym, fsLR, fsaverage
INFO | 20/07/26 18:34:48 | nispace.core.parcellation: Parcellation 'Yan200': validation passed.
INFO | 20/07/26 18:34:48 | nispace.core.parcellation: Lazy-loading parcellation image for space 'MNI152NLin2009cAsym'.
INFO | 20/07/26 18:34:49 | nispace.core.parcellation: Parcellation 'Yan200': active space set to 'MNI152NLin2009cAsym'.
INFO | 20/07/26 18:34:49 | nispace.io: Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
INFO | 20/07/26 18:34:49 | nispace.io: Input type: dict, assuming (img_name, img) pairs for imaging data.
INFO | 20/07/26 18:34:49 | nispace.io: Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO | 20/07/26 18:34:49 | nispace.io: Parcellating imaging data.
Parcellating (-1 proc): 100%|██████████████████████████████████████████████████████████| 7/7 [00:00<00:00, 11925.32it/s]
Colocalizing (pearson, -1 proc): 100%|██████████████████████████████████████████████████| 7/7 [00:00<00:00, 7675.85it/s]
INFO | 20/07/26 18:35:01 | nispace.core.parcellation: Lazy-loading parcellation image for space 'fsLR'.
INFO | 20/07/26 18:35:01 | nispace.core.permute: Generating null maps (n = 1000, null_method = 'moran').
INFO | 20/07/26 18:35:01 | nispace.nulls: Null map generation: Assuming n = 8 data vector(s) for n = 200 parcels.
INFO | 20/07/26 18:35:01 | nispace.nulls: Using provided distance matrix/matrices.

Moran null maps (-1 proc): 100%|████████████████████████████████████████████████████████| 8/8 [00:00<00:00, 6539.55it/s]
INFO | 20/07/26 18:35:01 | nispace.nulls: Null data generation finished.
INFO | 20/07/26 18:35:01 | nispace.core.permute: Z-standardizing null maps.

Null colocalizations (pearson, -1 proc): 100%|████████████████████████████████████| 1000/1000 [00:00<00:00, 7744.90it/s]
INFO | 20/07/26 18:35:02 | nispace.core.permute: Calculating exact p-values (tails = {'rho': 'two'}).

[8]:
# this time with significance annotation
data = nsp_b.get_colocalizations()
p = nsp_b.get_p_values()
pc = nsp_b.get_corrected_p_values()
annot = ((p < 0.05).astype(int) + (pc < 0.05).astype(int)).replace({0: "", 1: "☆", 2: "★"})

fig, ax = plt.subplots(1, figsize=(8,2.5))
sns.heatmap(
    data,
    annot=annot,
    fmt="s",
    center=0,
    square=True
)
ax.set_title("$☆: p < 0.05,\ ★: p_{Meff} < 0.05$", size=11)
[8]:
Text(0.5, 1.0, '$☆: p < 0.05,\\ ★: p_{Meff} < 0.05$')
../_images/nb_introduction_intro14_binary_colocalization_10_1.png

Summary

  • binary_y=True in NiSpace() or in any workflow function switches to binary-Y mode: no z-scoring, Pearson auto-selected.

  • Binary colocalization = point-biserial correlation; Spearman degrades with many tied zeros.

  • colocalization(x=..., y=binary_maps, binary_y=True) is the one-liner workflow.

  • The Yeo 7 example shows how to extract binary masks from an atlas NIfTI and test them against any NiSpace reference dataset.

For the NiMARE case (binary ALE cluster maps as Y, with a coordinate-sampling null), see the NiMARE integration notebook.