Skip to content

Commit 9b3a3a2

Browse files
Add files via upload
1 parent 5dca154 commit 9b3a3a2

1 file changed

Lines changed: 297 additions & 0 deletions

File tree

scripts/neighborhood_analysis.R

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Cleanup
2+
rm(list = ls())
3+
4+
library(Seurat)
5+
library(ggplot2)
6+
library(paletteer)
7+
library(data.table)
8+
library(dbscan)
9+
library(pheatmap)
10+
library(tidyr)
11+
12+
# Set working directory
13+
wd <- ""
14+
setwd(wd)
15+
rm(wd)
16+
17+
# Make folders for output
18+
plot_dir <- "neighbourhood_plots"
19+
dir.create(path = plot_dir, showWarnings = F)
20+
21+
data_dir <- "neighbourhood_data"
22+
dir.create(path = data_dir, showWarnings = F)
23+
24+
25+
# Load the Seurat objects with the clustering
26+
message("Loading data")
27+
mc_matched_hm <- readRDS("objects/clustered/re_matched_hm_clustered_2.RDS") # Molecular Cartography
28+
vz_matched_hm <- readRDS("objects/clustered/vz_matched_hm_clustered_2.RDS") # Merscope
29+
xen_matched_hm <- readRDS("objects/clustered/xen_matched_hm_clustered_2.RDS") # Xenium
30+
31+
# Convert coluns with clusters to numeric and set the resolution
32+
mc_matched_hm@meta.data[grep("SCT", colnames(mc_matched_hm@meta.data))] <- lapply(mc_matched_hm@meta.data[grep("SCT", colnames(mc_matched_hm@meta.data))], as.numeric)
33+
vz_matched_hm@meta.data[grep("SCT", colnames(vz_matched_hm@meta.data))] <- lapply(vz_matched_hm@meta.data[grep("SCT", colnames(vz_matched_hm@meta.data))], as.numeric)
34+
xen_matched_hm@meta.data[grep("SCT", colnames(xen_matched_hm@meta.data))] <- lapply(xen_matched_hm@meta.data[grep("SCT", colnames(xen_matched_hm@meta.data))], as.numeric)
35+
36+
mc_resolution <- "SCT_snn_res.0.55"
37+
vz_resolution <- "SCT_snn_res.0.55"
38+
xen_resolution <- "SCT_snn_res.0.55"
39+
40+
# Manually annotated cells types:
41+
# See: https://www.nature.com/articles/s41467-023-44117-x/figures/7
42+
message("Annotating clusters")
43+
44+
45+
# Molecular Cartography
46+
mc_cell_types <- c(
47+
"Early CGNP-like", "Differentiated neuronal-like", "Early CGNP-like, proliferating",
48+
"Stromal / Meningeal", "Late CGNP-like", "Stromal / Meningeal (2)",
49+
"Monocytes", "Migrating CGNP-like" , "Vascular / Endothelial",
50+
"Migrating CGNP-like (2)", "Other Immune", "TULP1+ cells",
51+
"Stromal / Meningeal (3)", "Astrocyte / Astrocytic-like")
52+
mc_matched_hm@meta.data$cell_types <- mc_cell_types[mc_matched_hm@meta.data[[mc_resolution]]]
53+
mc_resolution <- "cell_types"
54+
55+
# Merscope
56+
vz_cell_types <- c(
57+
"Differentiated neuronal-like", "Early CGNP-like", "Early CGNP-like, proliferating",
58+
"Stromal / Meningeal", "Migrating CGNP-like", "Astrocyte / Astrocytic-like",
59+
"Differentiated neuronal-like (2)", "Monocytes", "Differentiated neuronal-like (3)",
60+
"Vascular / Endothelial", "Differentiated neuronal-like (4)", "Other Immune",
61+
"Other Immune (2)", "Other Immune (3)", "Early CGNP-like (2)")
62+
63+
vz_matched_hm@meta.data$cell_types <- vz_cell_types[vz_matched_hm@meta.data[[vz_resolution]]]
64+
vz_resolution <- "cell_types"
65+
66+
# Xenium
67+
xen_cell_types <- c(
68+
"Differentiated neuronal-like", "Differentiated neuronal-like (2)", "Other Immune",
69+
"Other Immune (2)", "B Cells", "Oligodendrocyte-like",
70+
"Differentiated neuronal-like (3)", "Late CGNP-like", "Early CGNP-like",
71+
"Vascular / Endothelial", "Stromal / Meningeal", "Monocytes",
72+
"Astrocyte / Astrocytic-like", "Differentiated neuronal-like (4)",
73+
"Early CGNP-like, proliferating")
74+
75+
xen_matched_hm@meta.data$cell_types <- xen_cell_types[xen_matched_hm@meta.data[[xen_resolution]]]
76+
xen_resolution <- "cell_types"
77+
78+
# Simplify the heatmaps by merging / excluding certain cell populations
79+
# Heatmaps with all cell types are also generated
80+
slimmed_cell_types = c("Astrocyte / Astrocytic-like" = "Astrocyte / Astrocytic-like",
81+
"Differentiated neuronal-like" = "Differentiated neuronal-like",
82+
"Differentiated neuronal-like (2)" = "Differentiated neuronal-like",
83+
"Differentiated neuronal-like (3)" = "Differentiated neuronal-like",
84+
"Differentiated neuronal-like (4)" = "Differentiated neuronal-like",
85+
"Oligodendrocyte-like" = "NA",
86+
"Early CGNP-like" = "CGNP-like",
87+
"Early CGNP-like (2)" = "CGNP-like",
88+
"Early CGNP-like, proliferating" = "Early CGNP-like, proliferating",
89+
"Late CGNP-like" = "CGNP-like",
90+
"Migrating CGNP-like" = "CGNP-like",
91+
"Migrating CGNP-like (2)" = "CGNP-like",
92+
"TULP1+ cells" = "NA",
93+
"Stromal / Meningeal" = "Stromal / Meningeal",
94+
"Stromal / Meningeal (2)" = "Stromal / Meningeal",
95+
"Stromal / Meningeal (3)" = "Stromal / Meningeal",
96+
"Vascular / Endothelial" = "Vascular / Endothelial",
97+
"Monocytes" = "Monocytes",
98+
"Other Immune" = "Other Immune",
99+
"Other Immune (2)" = "Other Immune",
100+
"Other Immune (3)" = "Other Immune",
101+
"Other Immune (4)" = "Other Immune",
102+
"B Cells" = "Other Immune")
103+
104+
mc_matched_hm@meta.data$cell_types_slim <- slimmed_cell_types[mc_matched_hm@meta.data$cell_types]
105+
vz_matched_hm@meta.data$cell_types_slim <- slimmed_cell_types[vz_matched_hm@meta.data$cell_types]
106+
xen_matched_hm@meta.data$cell_types_slim <- slimmed_cell_types[xen_matched_hm@meta.data$cell_types]
107+
108+
# Alphabetically sorted list for plotting
109+
genes <- sort(rownames(xen_matched_hm@assays$MultiTech@meta.features))
110+
111+
# Functions for neighbourhood and permutation tests
112+
##############################
113+
get_neighbours_all <- function(obj, sample_names, fovs, K = 10, n_perm = 5000, cores = 32)
114+
{
115+
116+
cell_list <-lapply(1:length(fovs), function(n){
117+
# Get the K nearest neighbours
118+
knn <- kNN(obj@images[[fovs[[n]]]]$centroids@coords, k = K, sort = FALSE)
119+
120+
# Assign cell types
121+
obj_cell_types <- obj@meta.data[obj@meta.data$orig.ident == sample_names[[n]],]$cell_types
122+
us_obj_cell_types <- sort(unique(obj_cell_types))
123+
nbs <- vapply(X = knn$id, function(x){obj_cell_types[[x]]}, character(1))
124+
dim(nbs) <- dim(knn$id)
125+
cbind(data.table(cell_type = obj_cell_types), as.data.table(nbs))
126+
})
127+
cells <- rbindlist(cell_list, use.names = TRUE, fill = FALSE, idcol = FALSE)
128+
129+
# Make the interactions simmetrical and count
130+
cells <- melt(data = cells, id.vars = "cell_type", value.name = "cell_type2", value.factor = FALSE)
131+
cells[, variable := NULL]
132+
counts <- copy(cells)
133+
counts <- as.data.table(complete(data = counts, cell_type, cell_type2))
134+
counts[, interaction := fifelse(cell_type < cell_type2, paste(cell_type, cell_type2, sep = ";"), paste(cell_type2, cell_type, sep = ";"))]
135+
counts <- unique(counts[, .N, by = interaction])
136+
counts[, c("cell_type1", "cell_type2") := tstrsplit(x = interaction, split = ";")]
137+
counts[, interaction := NULL]
138+
139+
# Make permutations by shuffling the interacting cell types
140+
permutations <- parallel::mclapply(1:n_perm, function(n){
141+
pcells <- copy(cells)
142+
pcells$cell_type <- sample(x = pcells$cell_type, size = length(pcells$cell_type), replace = FALSE)
143+
pcells$cell_type2 <- sample(x = pcells$cell_type2, size = length(pcells$cell_type2), replace = FALSE)
144+
pcells <- as.data.table(complete(data = pcells, cell_type, cell_type2))
145+
pcells[, interaction := fifelse(cell_type < cell_type2, paste(cell_type, cell_type2, sep = ";"), paste(cell_type2, cell_type, sep = ";"))]
146+
pcells <- unique(pcells[, .N, by = interaction])
147+
pcells[, c("cell_type1", "cell_type2") := tstrsplit(x = interaction, split = ";")]
148+
pcells[, interaction := NULL]
149+
return(pcells)
150+
}, mc.cores = cores, mc.allow.recursive = TRUE)
151+
permutations <- rbindlist(permutations, idcol = "permutation")
152+
153+
return(list(cells = cells, counts = counts, permutations = permutations))
154+
155+
}
156+
157+
get_pvals_all <- function(permutations, counts, n_perm) {
158+
pvals <- copy(permutations)
159+
pvals[, difference := counts$N - N, by = permutation]
160+
pvals[, zeta := mean(difference) / sd(difference), by = c("cell_type1", "cell_type2")] # µ(a - X) = a - µ(X) and sd(a - X) = sd(X)
161+
pvals[, greater := (sum(difference < 0) + 1) / (n_perm + 1), by = c("cell_type1", "cell_type2")] # https://pubmed.ncbi.nlm.nih.gov/21044043/
162+
pvals[, lower := (sum(difference > 0) + 1) / (n_perm + 1), by = c("cell_type1", "cell_type2")]
163+
pvals[, greater_bh := p.adjust(greater, method = "BH")]
164+
pvals[, lower_bh := p.adjust(lower, method = "BH")]
165+
pvals[, pvalue := fifelse(lower < greater, lower, greater)]
166+
pvals[, pvalue_bh := fifelse(lower_bh < greater_bh, lower_bh, greater_bh)]
167+
pvals[, score := fifelse(lower < greater, -1 + lower, 1 - greater)]
168+
pvals[, score := fifelse(pvalue > 0.05, NA, score)]
169+
pvals[, score_bh := fifelse(lower_bh < greater_bh, -1 + lower_bh, 1 - greater_bh)]
170+
pvals[, score_bh := fifelse(pvalue_bh > 0.05, NA, score_bh)]
171+
pvals[, zeta := fifelse(pvalue_bh > 0.05, NA, zeta)]
172+
pvals <- unique(pvals[, .(cell_type1, cell_type2, zeta, greater, lower, pvalue, score, greater_bh, lower_bh, pvalue_bh, score_bh)])
173+
return(pvals)
174+
}
175+
176+
# Heatmaps drawing functions with p-value / z-score
177+
##################################################
178+
nn_heatmapper <- function(pm, title = NULL){
179+
ggplot(data = pm[order(cell_type1, cell_type2)], aes(x=cell_type1, y=cell_type2, fill=score_bh)) +
180+
geom_tile() + theme_minimal() + coord_fixed() + labs(x = NULL, y = NULL, title = title) +
181+
scale_fill_gradient2(name = NULL, low = "blue", high = "red", mid = "white",
182+
midpoint = 0.0, limit = c(-1,1), space = "Lab") +
183+
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1))
184+
}
185+
186+
nn_heatmapper_z <- function(pm, title = NULL){
187+
ggplot(data = pm[order(cell_type1, cell_type2)], aes(x=cell_type1, y=cell_type2, fill=zeta)) +
188+
geom_tile() + theme_minimal() + coord_fixed() + labs(x = NULL, y = NULL, title = title) +
189+
scale_fill_gradient2(name = NULL, low = "blue", high = "red", mid = "white",
190+
midpoint = 0.0, space = "Lab") +
191+
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1))
192+
}
193+
####################################################
194+
195+
###################################################################
196+
n_perm = 10000 # Permutations
197+
K = 10 # Number of nearest neighbours
198+
199+
message("Subsetting objects")
200+
xen_nn_obj <- subset(xen_matched_hm, subset = cell_types_slim != "NA")
201+
mc_nn_obj <- subset(mc_matched_hm, subset = cell_types_slim != "NA")
202+
vz_nn_obj <- subset(vz_matched_hm, subset = cell_types_slim != "NA")
203+
204+
xen_nn_obj@meta.data$cell_types <- slimmed_cell_types[xen_nn_obj@meta.data$cell_types]
205+
mc_nn_obj@meta.data$cell_types <- slimmed_cell_types[mc_nn_obj@meta.data$cell_types]
206+
vz_nn_obj@meta.data$cell_types <- slimmed_cell_types[vz_nn_obj@meta.data$cell_types]
207+
208+
message("Neighbours Xenium all clusters")
209+
xen_neighbours_all <- get_neighbours_all(xen_matched_hm, c("mb266.Xenium", "mb295.Xenium"), c("mb266.Xenium", "mb295.Xenium"), K = K, n_perm = n_perm)
210+
message("Pvalues Xenium all clusters")
211+
xen_pvals_all <- get_pvals_all(xen_neighbours_all$permutations, xen_neighbours_all$counts, n_perm = n_perm)
212+
message("Saving data")
213+
fwrite(x = xen_pvals_all, file = file.path(data_dir, "/xen_nn_10-10000_all_pvals.csv"))
214+
message("Making heatmaps")
215+
xen_nn_heatmap_all_p <- nn_heatmapper(xen_pvals_all, "Xenium")
216+
xen_nn_heatmap_all_z <- nn_heatmapper_z(xen_pvals_all, "Xenium")
217+
message("Saving heatmaps")
218+
ggsave(plot = xen_nn_heatmap_all_p, filename = file.path(plot_dir, "/xen_nn_10-10000_all_p_bh_heatmap.pdf"), width = 20, height = 20)
219+
ggsave(plot = xen_nn_heatmap_all_z, filename = file.path(plot_dir, "/xen_nn_10-10000_all_z_bh_heatmap.pdf"), width = 20, height = 20)
220+
rm(xen_neighbours_all)
221+
gc()
222+
223+
message("Neighbours Xenium selected clusters")
224+
xen_neighbours_slim <- get_neighbours_all(xen_nn_obj, c("mb266.Xenium", "mb295.Xenium"), c("mb266.Xenium", "mb295.Xenium"), K = K, n_perm = n_perm)
225+
message("Pvalues Xenium selected clusters")
226+
xen_pvals_slim <- get_pvals_all(xen_neighbours_slim$permutations, xen_neighbours_slim$counts, n_perm = n_perm)
227+
message("Saving data")
228+
fwrite(x = xen_pvals_slim, file = file.path(data_dir, "/xen_nn_10-10000_slim_pvals.csv"))
229+
message("Making heatmaps")
230+
xen_nn_heatmap_slim_p <- nn_heatmapper(xen_pvals_slim, "Xenium")
231+
xen_nn_heatmap_slim_z <- nn_heatmapper_z(xen_pvals_slim, "Xenium")
232+
message("Saving heatmaps")
233+
ggsave(plot = xen_nn_heatmap_slim_p, filename = file.path(plot_dir, "/xen_nn_10-10000_slim_p_bh_heatmap.pdf"), width = 20, height = 20)
234+
ggsave(plot = xen_nn_heatmap_slim_z, filename = file.path(plot_dir, "/xen_nn_10-10000_slim_z_bh_heatmap.pdf"), width = 20, height = 20)
235+
rm(xen_neighbours_slim)
236+
gc()
237+
238+
message("Neighbours MC all clusters")
239+
mc_neighbours_all <- get_neighbours_all(mc_matched_hm, c("mb266.w5a1.MC", "mb295.w6a1.MC", "mb295.w6a2.MC"), c("mb266.w5a1.Resolve", "mb295.w6a1.Resolve", "mb295.w6a2.Resolve"), K = K, n_perm = n_perm)
240+
message("Pvalues MC all clusters")
241+
mc_pvals_all <- get_pvals_all(mc_neighbours_all$permutations, mc_neighbours_all$counts, n_perm = n_perm)
242+
message("Saving data")
243+
fwrite(x = mc_pvals_all, file = file.path(data_dir, "/mc_nn_10-10000_all_pvals.csv"))
244+
message("Making heatmaps")
245+
mc_nn_heatmap_all_p <- nn_heatmapper(mc_pvals_all, "Molecular Cartography")
246+
mc_nn_heatmap_all_z <- nn_heatmapper_z(mc_pvals_all, "Molecular Cartography")
247+
message("Saving heatmaps")
248+
ggsave(plot = mc_nn_heatmap_all_p, filename = file.path(plot_dir, "/mc_nn_10-10000_all_p_bh_heatmap.pdf"), width = 20, height = 20)
249+
ggsave(plot = mc_nn_heatmap_all_z, filename = file.path(plot_dir, "/mc_nn_10-10000_all_z_bh_heatmap.pdf"), width = 20, height = 20)
250+
rm(mc_neighbours_all)
251+
gc()
252+
253+
message("Neighbours MC selected clusters")
254+
mc_neighbours_slim <- get_neighbours_all(mc_nn_obj, c("mb266.w5a1.MC", "mb295.w6a1.MC", "mb295.w6a2.MC"), c("mb266.w5a1.Resolve", "mb295.w6a1.Resolve", "mb295.w6a2.Resolve"), K =K, n_perm = n_perm)
255+
message("Pvalues MC selected clusters")
256+
mc_pvals_slim <- get_pvals_all(mc_neighbours_slim$permutations, mc_neighbours_slim$counts, n_perm = n_perm)
257+
message("Saving data")
258+
fwrite(x = mc_pvals_slim, file = file.path(data_dir, "/mc_nn_10-10000_slim_pvals.csv"))
259+
message("Making heatmaps")
260+
mc_nn_heatmap_slim_p <- nn_heatmapper(mc_pvals_slim, "Molecular Cartography")
261+
mc_nn_heatmap_slim_z <- nn_heatmapper_z(mc_pvals_slim, "Molecular Cartography")
262+
message("Saving heatmaps")
263+
ggsave(plot = mc_nn_heatmap_slim_p, filename = file.path(plot_dir, "/mc_nn_10-10000_slim_p_bh_heatmap.pdf"), width = 20, height = 20)
264+
ggsave(plot = mc_nn_heatmap_slim_z, filename = file.path(plot_dir, "/mc_nn_10-10000_slim_z_bh_heatmap.pdf"), width = 20, height = 20)
265+
rm(mc_neighbours_slim)
266+
gc()
267+
268+
message("Neighbours Merscope all clusters")
269+
vz_neighbours_all <- get_neighbours_all(vz_matched_hm, c("mb266.region1.MERFISH", "mb295.region0.MERFISH"), c("mb266.region1.Vizgen", "mb295.region0.Vizgen"), K = K, n_perm = n_perm)
270+
message("Pvalues Merscope all clusters")
271+
vz_pvals_all <- get_pvals_all(vz_neighbours_all$permutations, vz_neighbours_all$counts, n_perm = n_perm)
272+
message("Saving data")
273+
fwrite(x = vz_pvals_all, file = file.path(data_dir, "/vz_nn_100-10000_all_pvals.csv"))
274+
message("Making heatmaps")
275+
vz_nn_heatmap_all_p <- nn_heatmapper(vz_pvals_all, "Merscope")
276+
vz_nn_heatmap_all_z <- nn_heatmapper_z(vz_pvals_all, "Merscope")
277+
message("Saving heatmaps")
278+
ggsave(plot = vz_nn_heatmap_all_p, filename = file.path(plot_dir, "/vz_nn_10-10000_all_p_bh_heatmap.pdf"), width = 20, height = 20)
279+
ggsave(plot = vz_nn_heatmap_all_z, filename = file.path(plot_dir, "/vz_nn_10-10000_all_z_bh_heatmap.pdf"), width = 20, height = 20)
280+
rm(vz_neighbours_all)
281+
gc()
282+
283+
message("Neighbours Merscope selected clusters")
284+
vz_neighbours_slim <- get_neighbours_all(vz_nn_obj, c("mb266.region1.MERFISH", "mb295.region0.MERFISH"), c("mb266.region1.Vizgen", "mb295.region0.Vizgen"), K = K, n_perm = n_perm)
285+
message("Pvalues Merscope selected clusters")
286+
vz_pvals_slim <- get_pvals_all(vz_neighbours_slim$permutations, vz_neighbours_slim$counts, n_perm = n_perm)
287+
message("Saving data")
288+
fwrite(x = vz_pvals_slim, file = file.path(data_dir, "/vz_nn_10-10000_slim_pvals.csv"))
289+
message("Making heatmaps")
290+
vz_nn_heatmap_slim_p <- nn_heatmapper(vz_pvals_slim, "Merscope")
291+
vz_nn_heatmap_slim_z <- nn_heatmapper_z(vz_pvals_slim, "Merscope")
292+
message("Saving heatmaps")
293+
ggsave(plot = vz_nn_heatmap_slim_p, filename = file.path(plot_dir, "/vz_nn_10-10000_slim_p_bh_heatmap.pdf"), width = 20, height = 20)
294+
ggsave(plot = vz_nn_heatmap_slim_z, filename = file.path(plot_dir, "/vz_nn_10-10000_slim_z_bh_heatmap.pdf"), width = 20, height = 20)
295+
rm(vz_neighbours_slim)
296+
gc()
297+

0 commit comments

Comments
 (0)