Skip to content

spatial_interaction

Short Description

sm.pl.spatial_interaction: This function provides a sophisticated approach to visualizing spatial interactions between cell types or phenotypes through heatmaps. It adeptly highlights the frequency and significance of co-occurrence patterns, where the intensity of colors reflects the scaled abundance of interactions. Non-significant results are clearly marked with blank regions, offering a clear demarcation of areas where interactions do not meet the specified threshold of significance. This visualization tool is invaluable for uncovering intricate spatial relationships and potential signaling networks within complex tissue environments.

Function

spatial_interaction(adata, spatial_interaction='spatial_interaction', summarize_plot=True, p_val=0.05, row_cluster=False, col_cluster=False, cmap='vlag', nonsig_color='grey', subset_phenotype=None, subset_neighbour_phenotype=None, binary_view=False, return_data=False, fileName='spatial_interaction.pdf', saveDir=None, **kwargs)

Parameters:

Name Type Description Default
adata AnnData

The annotated data matrix with spatial interaction calculations.

required
spatial_interaction str

Key in adata.uns where spatial interaction data is stored, typically the output of sm.tl.spatial_interaction.

'spatial_interaction'
summarize_plot bool

If True, summarizes cell-cell interactions across all images or samples to provide an aggregated view.

True
p_val float

Threshold for significance of interactions. Interactions with a P-value above this threshold are considered non-significant.

0.05
row_cluster, col_cluster (bool

If True, performs hierarchical clustering on rows or columns in the heatmap to group similar patterns of interaction.

required
cmap str

Colormap for the heatmap visualization. Default is 'vlag'.

'vlag'
nonsig_color str

Color used to represent non-significant interactions in the heatmap.

'grey'
subset_phenotype, subset_neighbour_phenotype (list

Subsets of phenotypes or neighboring phenotypes to include in the analysis and visualization.

required
binary_view bool

If True, visualizes interactions in a binary manner, highlighting presence or absence of significant interactions without intensity gradation.

False
return_data bool

If True, returns the DataFrame used for plotting instead of the plot itself.

False
fileName str

Name of the file to save the plot. Relevant only if saveDir is not None.

'spatial_interaction.pdf'
saveDir str

Directory to save the generated plot. If None, the plot is not saved.

None
**kwargs

Additional keyword arguments for seaborn's clustermap function, such as linecolor and linewidths.

{}

Returns:

Type Description

pandas.DataFrame (dataframe): Only if return_data is True. The DataFrame containing the data used for plotting.

Example
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Basic visualization of spatial interactions with default settings
sm.pl.spatial_interaction(adata)

# Detailed heatmap of spatial interactions, excluding non-significant interactions
sm.pl.spatial_interaction(adata, summarize_plot=False, p_val=0.01, cmap='coolwarm', nonsig_color='lightgrey',
                    binary_view=True, row_cluster=True, col_cluster=True)

# Visualizing specific phenotypes interactions, with custom colormap and binary view
sm.pl.spatial_interaction(adata, subset_phenotype=['T cells', 'B cells'], subset_neighbour_phenotype=['Macrophages'],
                    cmap='seismic', binary_view=True, row_cluster=True, col_cluster=False,
                    figsize=(10, 8), dendrogram_ratio=(.1, .2), cbar_pos=(0, .2, .03, .4))
Source code in scimap/plotting/spatial_interaction.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def spatial_interaction(
    adata,
    spatial_interaction='spatial_interaction',
    summarize_plot=True,
    p_val=0.05,
    row_cluster=False,
    col_cluster=False,
    cmap='vlag',
    nonsig_color='grey',
    subset_phenotype=None,
    subset_neighbour_phenotype=None,
    binary_view=False,
    return_data=False,
    fileName='spatial_interaction.pdf',
    saveDir=None,
    **kwargs,
):
    """
    Parameters:
            adata (anndata.AnnData):
                The annotated data matrix with spatial interaction calculations.

            spatial_interaction (str, optional):
                Key in `adata.uns` where spatial interaction data is stored, typically the output of `sm.tl.spatial_interaction`.

            summarize_plot (bool, optional):
                If True, summarizes cell-cell interactions across all images or samples to provide an aggregated view.

            p_val (float, optional):
                Threshold for significance of interactions. Interactions with a P-value above this threshold are considered non-significant.

            row_cluster, col_cluster (bool, optional):
                If True, performs hierarchical clustering on rows or columns in the heatmap to group similar patterns of interaction.

            cmap (str, optional):
                Colormap for the heatmap visualization. Default is 'vlag'.

            nonsig_color (str, optional):
                Color used to represent non-significant interactions in the heatmap.

            subset_phenotype, subset_neighbour_phenotype (list, optional):
                Subsets of phenotypes or neighboring phenotypes to include in the analysis and visualization.

            binary_view (bool, optional):
                If True, visualizes interactions in a binary manner, highlighting presence or absence of significant interactions without intensity gradation.

            return_data (bool, optional):
                If True, returns the DataFrame used for plotting instead of the plot itself.

            fileName (str, optional):
                Name of the file to save the plot. Relevant only if `saveDir` is not None.

            saveDir (str, optional):
                Directory to save the generated plot. If None, the plot is not saved.

            **kwargs:
                Additional keyword arguments for seaborn's clustermap function, such as `linecolor` and `linewidths`.

    Returns:
            pandas.DataFrame (dataframe):
                Only if `return_data` is True. The DataFrame containing the data used for plotting.

    Example:
        ```python

        # Basic visualization of spatial interactions with default settings
        sm.pl.spatial_interaction(adata)

        # Detailed heatmap of spatial interactions, excluding non-significant interactions
        sm.pl.spatial_interaction(adata, summarize_plot=False, p_val=0.01, cmap='coolwarm', nonsig_color='lightgrey',
                            binary_view=True, row_cluster=True, col_cluster=True)

        # Visualizing specific phenotypes interactions, with custom colormap and binary view
        sm.pl.spatial_interaction(adata, subset_phenotype=['T cells', 'B cells'], subset_neighbour_phenotype=['Macrophages'],
                            cmap='seismic', binary_view=True, row_cluster=True, col_cluster=False,
                            figsize=(10, 8), dendrogram_ratio=(.1, .2), cbar_pos=(0, .2, .03, .4))
        ```
    """

    # set color for heatmap
    # cmap_updated = copy.copy(matplotlib.cm.get_cmap(cmap))
    # cmap_updated = matplotlib.cm.get_cmap(cmap)
    cmap_updated = matplotlib.colormaps[cmap]
    cmap_updated.set_bad(color=nonsig_color)

    # Copy the interaction results from anndata object
    try:
        interaction_map = adata.uns[spatial_interaction].copy()
    except KeyError:
        raise ValueError(
            'spatial_interaction not found- Please run sm.tl.spatial_interaction first'
        )

    # subset the data if user requests
    if subset_phenotype is not None:
        if isinstance(subset_phenotype, str):
            subset_phenotype = [subset_phenotype]
        # subset the phenotype
        interaction_map = interaction_map[
            interaction_map['phenotype'].isin(subset_phenotype)
        ]

    if subset_neighbour_phenotype is not None:
        if isinstance(subset_neighbour_phenotype, str):
            subset_neighbour_phenotype = [subset_neighbour_phenotype]
        # subset the phenotype
        interaction_map = interaction_map[
            interaction_map['neighbour_phenotype'].isin(subset_neighbour_phenotype)
        ]

    # Seperate Interaction intensity from P-value
    p_value = interaction_map.filter(regex='pvalue_')
    p_val_df = pd.concat(
        [interaction_map[['phenotype', 'neighbour_phenotype']], p_value],
        axis=1,
        join='outer',
    )
    p_val_df = p_val_df.set_index(['phenotype', 'neighbour_phenotype'])
    interaction_map = interaction_map[
        interaction_map.columns.difference(p_value.columns)
    ]
    interaction_map = interaction_map.set_index(['phenotype', 'neighbour_phenotype'])

    # Binarize the values if user requests
    if binary_view == True:
        interaction_map[interaction_map > 0] = 1
        interaction_map[interaction_map <= 0] = -1

    if summarize_plot == True:
        # convert first two columns to multi-index column
        # interaction_map = interaction_map.set_index(['phenotype','neighbour_phenotype'])
        # p_val_df = p_val_df.set_index(['phenotype','neighbour_phenotype'])

        # If multiple images are present, take the average of interactions
        interaction_map['mean'] = interaction_map.mean(axis=1).values
        interaction_map = interaction_map[['mean']]  # keep only the mean column
        interaction_map = interaction_map['mean'].unstack()
        # Do the same for P-values
        p_val_df['mean'] = p_val_df.mean(axis=1).values
        p_val_df = p_val_df[['mean']]  # keep only the mean column
        # set the P-value threshold
        p_val_df.loc[p_val_df[p_val_df['mean'] > p_val].index, 'mean'] = np.NaN
        p_val_df = p_val_df['mean'].unstack()

        # change to the order passed in subset
        if subset_phenotype is not None:
            interaction_map = interaction_map.reindex(subset_phenotype)
            p_val_df = p_val_df.reindex(subset_phenotype)
        if subset_neighbour_phenotype is not None:
            interaction_map = interaction_map.reindex(
                columns=subset_neighbour_phenotype
            )
            p_val_df = p_val_df.reindex(columns=subset_neighbour_phenotype)

        # Plotting heatmap
        mask = p_val_df.isnull()  # identify the NAN's for masking
        im = interaction_map.fillna(
            0
        )  # replace nan's with 0 so that clustering will work
        # heatmap
        plot = sns.clustermap(
            im,
            cmap=cmap_updated,
            row_cluster=row_cluster,
            col_cluster=col_cluster,
            mask=mask,
            **kwargs,
        )

    else:
        if len(interaction_map.columns) < 2:
            raise ValueError(
                'Data for only a single image is available please set summarize_plot=True and try again'
            )
        # convert first two columns to multi-index column
        # interaction_map = interaction_map.set_index(['phenotype','neighbour_phenotype'])
        # p_val_df = p_val_df.set_index(['phenotype','neighbour_phenotype'])

        # P value threshold
        p_val_df = p_val_df.apply(lambda x: np.where(x > p_val, np.nan, x))

        # Remove rows that are all nan
        idx = p_val_df.index[p_val_df.isnull().all(1)]  # Find all nan rows
        interaction_map = interaction_map.loc[
            interaction_map.index.difference(idx)
        ]  # clean intensity data
        p_val_df = p_val_df.loc[p_val_df.index.difference(idx)]  # clean p-value data

        # order the plot as needed
        if subset_phenotype or subset_neighbour_phenotype is not None:
            interaction_map.reset_index(inplace=True)
            p_val_df.reset_index(inplace=True)
            if subset_phenotype is not None:
                interaction_map['phenotype'] = (
                    interaction_map['phenotype'].astype('str').astype('category')
                )
                interaction_map['phenotype'] = interaction_map[
                    'phenotype'
                ].cat.reorder_categories(subset_phenotype)
                interaction_map = interaction_map.sort_values('phenotype')
                # Do same for Pval
                p_val_df['phenotype'] = (
                    p_val_df['phenotype'].astype('str').astype('category')
                )
                p_val_df['phenotype'] = p_val_df['phenotype'].cat.reorder_categories(
                    subset_phenotype
                )
                p_val_df = p_val_df.sort_values('phenotype')
            if subset_neighbour_phenotype is not None:
                interaction_map['neighbour_phenotype'] = (
                    interaction_map['neighbour_phenotype']
                    .astype('str')
                    .astype('category')
                )
                interaction_map['neighbour_phenotype'] = interaction_map[
                    'neighbour_phenotype'
                ].cat.reorder_categories(subset_neighbour_phenotype)
                interaction_map = interaction_map.sort_values('neighbour_phenotype')
                # Do same for Pval
                p_val_df['neighbour_phenotype'] = (
                    p_val_df['neighbour_phenotype'].astype('str').astype('category')
                )
                p_val_df['neighbour_phenotype'] = p_val_df[
                    'neighbour_phenotype'
                ].cat.reorder_categories(subset_neighbour_phenotype)
                p_val_df = p_val_df.sort_values('neighbour_phenotype')
            if subset_phenotype and subset_neighbour_phenotype is not None:
                interaction_map = interaction_map.sort_values(
                    ['phenotype', 'neighbour_phenotype']
                )
                p_val_df = p_val_df.sort_values(['phenotype', 'neighbour_phenotype'])

            # convert the data back into multi-index
            interaction_map = interaction_map.set_index(
                ['phenotype', 'neighbour_phenotype']
            )
            p_val_df = p_val_df.set_index(['phenotype', 'neighbour_phenotype'])

        # Plotting heatmap
        mask = p_val_df.isnull()  # identify the NAN's for masking
        im = interaction_map.fillna(
            0
        )  # replace nan's with 0 so that clustering will work
        mask.columns = im.columns

        # covert the first two columns into index
        # Plot
        plot = sns.clustermap(
            im,
            cmap=cmap_updated,
            row_cluster=row_cluster,
            col_cluster=col_cluster,
            mask=mask,
            **kwargs,
        )

    # Saving the figure if saveDir and fileName are provided
    if saveDir:
        if not os.path.exists(saveDir):
            os.makedirs(saveDir)
        full_path = os.path.join(saveDir, fileName)
        plot.savefig(full_path, dpi=300)
        plt.close()
        print(f"Saved plot to {full_path}")
    else:
        plt.show()

    if return_data is True:
        # perpare data for export
        map_data = interaction_map.copy()
        p_val_data = mask.copy()
        map_data.reset_index(inplace=True)
        p_val_data.reset_index(inplace=True)
        # remove the first two colums
        map_data = map_data.drop(['phenotype', 'neighbour_phenotype'], axis=1)
        p_val_data = p_val_data.drop(['phenotype', 'neighbour_phenotype'], axis=1)
        p_val_data.columns = map_data.columns
        # remove the mased values
        final_Data = map_data.where(~p_val_data, other=np.nan)
        final_Data.index = interaction_map.index
        return final_Data