Differential expression (pseudobulk DE) within microglia stratified by plaque proximity
Published
March 8, 2026
Rationale
Instead of treating distance as a continuous variable, we dichotomize microglia into proximal (< 50 um from a plaque) and distal (>= 50 um) groups, then run pseudobulk differential expression (DESeq2) comparing the two.
By aggregating counts per biological replicate (sample/brain) before testing, we respect the experimental design (pseudobulk). Each mouse is the true unit of replication (n = 6 brains, 3 per treatment).
Distribution of microglia-to-plaque distances by treatment group. Distances are capped at 500 um for visualization.
Ridge density plots of microglia-to-plaque distances revealed a treatment-dependent shift for CAA but not parenchymal plaques: in aducanumab-treated brains, microglia were distributed further from CAA plaques compared to IgG controls. In contrast, microglia-to-parenchymal plaque distance distributions were nearly identical across treatment groups, with a sharp peak within 50 µm reflecting dense plaque-associated microglia clustering.
Pseudobulk DE: Adu vs IgG in plaque-proximal microglia
Within microglia that sit < 50 um from a plaque, we test which genes differ between aducanumab-treated (Adu, n = 3) and IgG-treated (n = 3) brains. Pseudobulk counts are aggregated per brain, then DESeq2 tests Adu vs IgG. This is run separately for CAA-proximal and parenchymal-proximal microglia.
Code
# Aggregate single-cell counts into pseudobulk profiles via sparse indicator matrixbuild_pseudobulk <-function(slide_list, group_col) {# Extract raw count matrices from each slide all_counts <-map(slide_list, \(obj) {LayerData(obj, assay =DefaultAssay(obj), layer ="counts") })# Build metadata with grouping column all_meta <-map(slide_list, \(obj) { obj@meta.data |>rownames_to_column("cell") |>mutate(pb_group = .data[[group_col]]) })# Keep only genes shared across slides, then column-bind shared_genes <-Reduce(intersect, map(all_counts, rownames)) counts <-do.call(cbind, map(all_counts, \(m) m[shared_genes, ])) meta <-bind_rows(all_meta)# Sparse indicator matrix: cells x groups, multiplied to sum counts per group groups <-unique(meta$pb_group) indicator <- Matrix::sparseMatrix(i =match(meta$cell, colnames(counts)),j =match(meta$pb_group, groups),x =1,dims =c(ncol(counts), length(groups)),dimnames =list(colnames(counts), groups) )list(pb_counts =as.matrix(counts %*% indicator),meta = meta,groups = groups )}
# Subset to proximal (or distal) cells, pseudobulk by sample, test Adu vs IgGrun_treatment_de <-function( slide_list, proximity_col, proximity_value, plot_title) {# Keep only cells matching the requested proximity group slide_list_sub <-map(slide_list, \(obj) {subset(obj, cells =colnames(obj)[obj[[proximity_col, drop =TRUE]] == proximity_value]) })# Pseudobulk aggregation: one profile per sample (brain) pb <-build_pseudobulk(slide_list_sub, "sample_ID")# Map sample_ID to treatment group treatment_map <-bind_rows(map(slide_list, \(obj) { obj@meta.data |>distinct(sample_ID, Treatment.Group) }) ) |>distinct() col_data <-data.frame(treatment =factor( treatment_map$Treatment.Group[match(pb$groups, treatment_map$sample_ID)],levels =c("IgG", "Adu") ),row.names = pb$groups )run_deseq2(pb$pb_counts, col_data,design_formula =~ treatment,contrast =c("treatment", "Adu", "IgG"),plot_title = plot_title,plot_subtitle ="Adu vs IgG")}
The Xenium panel is small (~480 genes in total) and not all genes are expressed in microglia. After subsetting to proximal cells and pseudobulk aggregation across only 6 samples (3 Adu + 3 IgG), many genes have near-zero total counts and are dropped by the rowSums >= 10 filter. The table below traces the number of genes at each step.
Given the limited biological replication (n = 3 per group) and the restricted gene panel, no genes reached significance after BH correction. We report genes with nominal p < 0.01 as exploratory candidates requiring validation.
Code
# Step 1: genes in the Xenium panel (shared across slides)panel_genes <-intersect(rownames(LayerData(slide1_mg, layer ="counts")),rownames(LayerData(slide2_mg, layer ="counts")))# Step 2: genes detected in at least 1 microglia cell (any proximity)all_counts <-cbind(LayerData(slide1_mg, layer ="counts")[panel_genes, ],LayerData(slide2_mg, layer ="counts")[panel_genes, ])detected_in_mg <-sum(Matrix::rowSums(all_counts >0) >0)# Step 3–4: genes surviving pseudobulk count filter per proximity groupcount_after_pb <-function(slide_list, proximity_col, proximity_value) { sub <-map(slide_list, \(obj) {subset(obj, cells =colnames(obj)[obj[[proximity_col, drop =TRUE]] == proximity_value]) }) pb <-build_pseudobulk(sub, "sample_ID") n_cells <-sum(map_int(sub, ncol)) n_genes_before <-nrow(pb$pb_counts) n_genes_after <-sum(rowSums(pb$pb_counts) >=10)tibble(n_cells = n_cells, genes_in_pb = n_genes_before, genes_after_filter = n_genes_after)}caa_stats <-count_after_pb(list(slide1_mg, slide2_mg), "prox_caa", "proximal")paren_stats <-count_after_pb(list(slide1_mg, slide2_mg), "prox_paren", "proximal")tibble(step =c("Xenium panel (shared across slides)","Detected in >=1 microglia cell","CAA-proximal: cells in pseudobulk","CAA-proximal: genes in pseudobulk","CAA-proximal: genes after >=10 counts filter","Paren-proximal: cells in pseudobulk","Paren-proximal: genes in pseudobulk","Paren-proximal: genes after >=10 counts filter" ),count =c(length(panel_genes), detected_in_mg, caa_stats$n_cells, caa_stats$genes_in_pb, caa_stats$genes_after_filter, paren_stats$n_cells, paren_stats$genes_in_pb, paren_stats$genes_after_filter )) |>kbl(caption ="Gene filtering funnel — from Xenium panel to testable genes in DESeq2") |>kable_styling("striped", full_width =FALSE)
Gene filtering funnel — from Xenium panel to testable genes in DESeq2
Among nominally significant genes (p < 0.01, uncorrected; no genes passed BH correction at n = 3 vs 3), aducanumab-treated microglia near parenchymal plaques show exploratory signals of chemokine and inflammasome priming (Ccl12, Ifitm3, Nlrp3) and selective metabolic remodeling (Hk3↑, Pfkm↓, Ldhb↓), without engagement of the canonical Itgax⁺ DAM transcriptional program. These patterns are exploratory and require validation in larger cohorts.