# combine\_adjacency for TFR eeg data

**URL:** <https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635>\
**Category:** Support & Discussions\
**Tags:** time-frequency, eeg\
**Created:** [April 12, 2024, 6:59pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635 "2024-04-12T18:59:31Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![apoorva6262](https://avatars.discourse-cdn.com/v4/letter/a/c6cbf5/32.png) [@apoorva6262](https://mne.discourse.group/u/apoorva6262)\
**Post date:** [April 12, 2024, 6:59pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/1 "2024-04-12T18:59:31Z")

</div>

I am working on eeg TFR data. I have two groups (1& 2) and I used morlet wavelets to get the TFR data. Now I need to perform a cluster permutation test to identify significant clusters for 2 ROIs (c1 +c3 and c2+c4 channels) between group 1 and 2.

The` tfr_group_1.data` and `tfr_group_2.data` have shape: [10, 6,40,4609] (that is 10 observations, 6 channels, 40 freqs, 4609 time points).

To conduct cluster permutation test , I need the adjacency matrix. How do I get that from the TFR data ?

Do I do it in this order ?

1. Use mne.channels.find\_ch\_adjacency(_info_ , _ch\_type_ ) , ch\_type=eeg .  
`ch_adjacency = mne.channels.find_ch_adjacency(raw.info, ch_type='eeg')`  
Do you create a fake raw object here to account for 4 channels only (given I am interested in 2 ROIs ?
2. `full_adj = combine_adjacency(1, 1, ch_adjacency)` (freqs, times, chan) shape  
Here ‘1’ for position 1, means that you are looking at 1 freq point above and below the freq of interest ?
3. How do you account for ROI of interest? Do I get the mean of the TFR for the ROIs ?

---

<div class="post-metadata">

**Author:** ![apoorva6262](https://avatars.discourse-cdn.com/v4/letter/a/c6cbf5/32.png) [@apoorva6262](https://mne.discourse.group/u/apoorva6262)\
**Post date:** [April 12, 2024, 7:00pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/2 "2024-04-12T19:00:38Z")

</div>

@drammock@larsoner

---

<div class="post-metadata">

**Author:** ![drammock](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/drammock/32/4_2.png) [@drammock](https://mne.discourse.group/u/drammock)\
**Post date:** [April 12, 2024, 7:58pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/3 "2024-04-12T19:58:58Z")

</div>

> [@apoorva6262](#):
>
> The` tfr_group_1.data` and `tfr_group_2.data` have shape: [10, 6,40,4609] (that is 10 observations, 6 channels, 40 freqs, 4609 time points).

Just confirming: “observation” here means participant, right? I’m a bit confused by `tfr_group_1.data` because the `.data` part implies that this is a TFR object, which shouldn’t have data from multiple subjects in it. Maybe it’s an EpochsTFR? So 10 epochs per participant? Or is the `.data` part wrong, and this is actually a numpy array?

> [@apoorva6262](#):
>
> Do you create a fake raw object here to account for 4 channels only (given I am interested in 2 ROIs ?

I don’t think it’s a good idea to restrict to 4 ROI channels before clustering. Rather, I’d suggest finding clusters using _all channels you have_ and then afterwards looking to see if your ROIs happen to form a significant cluster at the time(s) and frequenc(ies) where you expect to see an effect. So if you’re starting out with e.g. 32 channels, use all 32.

> [@apoorva6262](#):
>
> ch\_adjacency = mne.channels.find\_ch\_adjacency(raw.info, ch\_type=‘eeg’)

That is basically right, but the function returns 2 things (adjacency matrix and list of channel names). So should be more like

```python
adj_mat, ch_names = mne.channels.find_ch_adjacency(raw.info, ch_type='eeg')

```

> [@apoorva6262](#):
>
> full\_adj = combine\_adjacency(1, 1, ch\_adjacency)

That is correct, as long as you remember that you need to transpose the data array so that the order of dimensions is `observations (AKA "subjects" in your case I think), frequency, time, channel`.

> [@apoorva6262](#):
>
> How do you account for ROI of interest?

As mentioned above, I would cluster on _all channels_ and then check to see if I find a significant effect on the channels I care about. It’s effectively what you’re doing for frequency and time too (you didn’t mention cropping to just the times/frequencies of interest right? you can treat channels the same way).

---

<div class="post-metadata">

**Author:** ![apoorva6262](https://avatars.discourse-cdn.com/v4/letter/a/c6cbf5/32.png) [@apoorva6262](https://mne.discourse.group/u/apoorva6262)\
**Post date:** [April 12, 2024, 8:47pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/4 "2024-04-12T20:47:36Z")

</div>

1. Sorry for the confusion , I meant to say they are two array lists. Group 1 and 2 have 10 subjects each . The transposed dimensions are [subjects, freq, time,chan] and are put into one list `X` . This list has two arrays , for group 1 and 2.

2. Ok I understand why all channels are to be used. But what if we have unequal number of channels across subjects/groups , as some subjects had different ‘bad’ channels that were excluded? Just take the common channels across subjects ? so run a loop to check for common channels ?

3. For `full_adj = combine_adjacency(1, 1, ch_adjacency)` , the `ch_adjacency` is the `adj_mat` right ?

4. And this is how you use the final cluster function for` list X`? and for stat\_fun I want to use the stats.ttest\_ind from scipy stats library

```python
def t_stat_fun(x, y, equal_var=True):
      t_statistic, _ = stats.ttest_ind(x, y, equal_var=equal_var)
      return t_statistic

```

`w, clusters, clusters_pv, h0 = mne.stats.permutation_cluster_test(, threshold=5, n_permutations=5000, stat_fun = t_stat_fun, adjacency=adj_matrix)`

---

<div class="post-metadata">

**Author:** ![drammock](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/drammock/32/4_2.png) [@drammock](https://mne.discourse.group/u/drammock)\
**Post date:** [April 12, 2024, 9:25pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/5 "2024-04-12T21:25:52Z")

</div>

> [@apoorva6262](#):
>
> But what if we have unequal number of channels across subjects/groups , as some subjects had different ‘bad’ channels that were excluded? Just take the common channels across subjects ? so run a loop to check for common channels ?

Normally I might say “a good use case for `interpolate_bads`” but I think that might be a false-positive risk (i.e., interpolated channels are kind of guaranteed to somewhat resemble their neighbors, making cluster-formation more likely). So excluding the “common bads” from all subjs might be the better choice? Depends on how many channels you have and how many you end up excluding. Definitely do `plot_adjacency` once you’ve excluded all bad chans and see how many “holes in the lattice” you end up with — it might be untenable in which case you would probably _have to_ interpolate, and just be cautious about how you interpret significant clusters that contain interpolated channels.

> [@apoorva6262](#):
>
> For `full_adj = combine_adjacency(1, 1, ch_adjacency)` , the `ch_adjacency` is the `adj_mat` right ?

yes

> [@apoorva6262](#):
>
> And this is how you use the final cluster function…

stat\_fun won’t work as written, because of what `ttest_ind` returns (not a tuple of length 2). if you call its output `result` then you would return `result.statistic`. Read the “Returns” section of [scipy.stats.ttest\_ind — SciPy v1.13.0 Manual](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html). You may or may not also need to specify `keepdims`, so if you try it and it fails, try again with `keepdims=True`

The sample code for `permutation_cluster_test` looks almost right; the first argument `X` is missing, and I’m not sure why you’re calling the first return value `w` (it will be t-values)

---

<div class="post-metadata">

**Author:** ![apoorva6262](https://avatars.discourse-cdn.com/v4/letter/a/c6cbf5/32.png) [@apoorva6262](https://mne.discourse.group/u/apoorva6262)\
**Post date:** [April 23, 2024, 12:26am UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/6 "2024-04-23T00:26:44Z")

</div>

Thanks @drammock

1. I looked at the adjacency plot, there are not too many common channels. I am thinking of just identifying clusters for these channels only (than interpolating)

 ![Screen Shot 2024-04-22 at 5.18.24 PM](https://global.discourse-cdn.com/free1/uploads/mne/original/2X/f/f257b419e366714be5356ab4ca74bc53b17fae39.jpeg)

1. I chose wilcoxon test instead and ran this for two groups and I am getting an error :

```python
adj_matrix = mne.channels.find_ch_adjacency(raw_drop.info, ch_type='eeg')[0]
full_adj_matrix = mne.stats.combine_adjacency(1, 1, adj_matrix)

def stat_fun_wilcox(X,Y):
    result = wilcoxon(X,Y)
    return result.statistic

w,clusters, clusters_pv, h0 = mne.stats.permutation_cluster_test(combined_tfrs, threshold=None,n_permutations=1024, 
                                                                  stat_fun=stat_fun_wilcox, adjacency=full_adj_matrix)

```

```python
ValueError: adjacency (len 19) must be of the correct size, i.e. be equal to or evenly divide the number of tests (3064985).

If adjacency was computed for a source space, try using the fwd["src"] or inv["src"] as some original source space vertices can be excluded during forward computation

```

full\_adj\_matrix.shape is (19,19) (basically 19 channels) , does this have to be a different dimension ?  
combined\_tfrs[0].shape is (15 subjects, 35 freqs , 4609 times, 19 chan)

---

<div class="post-metadata">

**Author:** ![drammock](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/drammock/32/4_2.png) [@drammock](https://mne.discourse.group/u/drammock)\
**Post date:** [April 23, 2024, 3:52pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/7 "2024-04-23T15:52:44Z")

</div>

adjacency plot doesn’t look right. Here’s what is expected (these are the EEG channels in the MNE sample data):

 ![fig](https://global.discourse-cdn.com/free1/uploads/mne/original/2X/0/0567814a68190936eb90e6bbf2e8754f7c8010f6.png)

notice how in the plot in the tutorial the connecting lines are all fairly “local”. You may need to remove some connections. See the warning in the notes section here: [mne.channels.find\_ch\_adjacency — MNE 1.7.0 documentation](https://mne.tools/stable/generated/mne.channels.find_ch_adjacency.html#mne.channels.find_ch_adjacency)

---

<div class="post-metadata">

**Author:** ![apoorva6262](https://avatars.discourse-cdn.com/v4/letter/a/c6cbf5/32.png) [@apoorva6262](https://mne.discourse.group/u/apoorva6262)\
**Post date:** [April 23, 2024, 5:56pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/8 "2024-04-23T17:56:56Z")

</div>

The plot is right, I have taken the common channels across subjects and groups. The frontal , temporal and occipital channels are noisy for most subjects and have been flagged as ‘bad’. That is why you see such few channels

---

<div class="post-metadata">

**Author:** ![drammock](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/drammock/32/4_2.png) [@drammock](https://mne.discourse.group/u/drammock)\
**Post date:** [April 23, 2024, 6:18pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/9 "2024-04-23T18:18:02Z")

</div>

it’s not the number of channels i’m concerned about. It’s the lines connecting them. E.g., the channel that is farthest right-frontal looks like it might connect to the one that is furthest right-temporal, and it is clearly connected to a left-parietal-ish one. Those are not plausibly “adjacent”.

---

<div class="post-metadata">

**Author:** ![apoorva6262](https://avatars.discourse-cdn.com/v4/letter/a/c6cbf5/32.png) [@apoorva6262](https://mne.discourse.group/u/apoorva6262)\
**Post date:** [April 23, 2024, 8:25pm UTC](https://mne.discourse.group/t/combine-adjacency-for-tfr-eeg-data/8635/10 "2024-04-23T20:25:19Z")

</div>

![Screen Shot 2024-04-23 at 1.23.28 PM](https://global.discourse-cdn.com/free1/uploads/mne/original/2X/7/73c2a846529dba6c180eaea40abf674a2521ead7.jpeg)

Thanks, resolved! but when I run the cluster perm test I still get the error:

```python
ValueError: adjacency (len 19) must be of the correct size, i.e. be equal to or evenly divide the number of tests (3064985).

If adjacency was computed for a source space, try using the fwd["src"] or inv["src"] as some original source space vertices can be excluded during forward computation

```
