Author

Matthew Zatzman

Published

August 22, 2026

Reproduces Figures 4C-D, S7C and S9C from the LuCA atlas deposit.

Code
library(tidyverse)
library(here)
library(ggbeeswarm)
library(ggthemes)
library(ggh4x)
library(glue)
library(patchwork)
library(Seurat)
devtools::load_all()
# stat_cor() pastes the p value in as a double, so its notation follows `scipen`. The
# published panels were rendered at R's default of 0 -- Fig 4C reads "p = 2e-05". A
# session that picked up a personal ~/.Rprofile setting scipen > 0 renders "p = 0.00002"
# instead, silently diverging from the paper.
options(scipen = 0)

markers <- load_markers()

symnum_0.1 <- list(cutpoints = c(0, 1e-04, 0.001, 0.01, 0.05, 0.1, Inf), symbols = c(
  "p<0.0001",
  "p<0.001", "p<0.01", "p<0.05", "p<0.1", "ns"
))

Load Data

Code
luca <- readRDS(here("paper_data", "luca_extended.rds"))

luca <- luca[, luca$ann_coarse == "Epithelial cell"]

scores <- read.table(file = here("paper_data", "luca_ucell_signatures.txt.gz"), header = T, sep = "\t")
rownames(scores) <- scores$cell_id
scores$cell_id <- NULL
luca <- AddMetaData(luca, scores)

Check Markers

Code
p <- DotPlotter(luca, features = markers$epi_markers_final, group.by = "ann_fine", rotate_y_strip = T)
p

Compute mean scores and plot

Code
sample_df <- luca@meta.data %>%
  add_count(sample, name = "n_epi") %>%
  add_count(sample, ann_fine, name = "n_fine") %>%
  mutate(prop_fine = n_epi / n_fine) %>%
  select(study, dataset, donor_id, sample, origin, platform, n_epi, ends_with("_mutation")) %>%
  distinct()

mean_scores <- luca@meta.data %>%
  group_by(sample) %>%
  add_count(name = "n_samp") %>%
  filter(n_samp >= 100) %>%
  group_by(study, disease, dataset, donor_id, sample, origin, platform, ann_fine, tumor_stage, uicc_stage) %>%
  add_count(name = "n_fine") %>%
  filter(n_fine >= 20) %>%
  summarize(across(colnames(scores), ~ mean(.x, na.rm = T))) %>%
  ungroup() %>%
  left_join(select(sample_df, sample, ends_with("_mutation")))

mean_scores_long <- mean_scores %>%
  pivot_longer(cols = colnames(scores), names_to = "Pathway", values_to = "mean_exp")

Figure 4C-D – nuclear MAPK versus LUAD identity

Code
# Six `normal_adjacent` samples also carry a cancer-cell call; excluding them is what makes
# this the 103 tumor samples (91 patients) the published panel reports.
df <- mean_scores %>%
  filter(ann_fine == "Cancer cells" & disease %in% c("lung adenocarcinoma") & origin != "normal_adjacent")

stopifnot(nrow(df) == 103)

p1 <- df %>%
  ggplot(aes(x = REACTOME_MAPK_TARGETS_NUCLEAR_EVENTS_MEDIATED_BY_MAP_KINASES, y = LUAD_GIRARD, color = tumor_stage)) +
  geom_point() +
  geom_smooth(method = "lm", show.legend = F, se = F) +
  ggpubr::stat_cor(show.legend = F, size = 2.75, label.x.npc = 0.45, label.y.npc = 0.075) +
  facet_grid(. ~ glue("Single Cell LuCA Atlas\nLUAD samples")) +
  see::scale_color_bluebrown_d(reverse = T, breaks = c("early", "advanced"), labels = c("Early", "Advanced")) +
  theme_few() +
  labs(x = "Reactome Nuclear MAPK", y = "LUAD Signature", color = "Tumor Stage")


df2 <- mean_scores %>%
  filter(ann_fine %in% c("Alveolar cell type 1", "Alveolar cell type 2") & origin %in% c("normal"))

# 29 AT1 and 40 AT2 per-sample profiles, from 45 normal samples.
stopifnot(nrow(df2) == 69)

p2 <- df2 %>%
  ggplot(aes(x = REACTOME_MAPK_TARGETS_NUCLEAR_EVENTS_MEDIATED_BY_MAP_KINASES, y = LUAD_GIRARD, color = ann_fine)) +
  geom_point() +
  geom_smooth(method = "lm", show.legend = F, se = F) +
  ggpubr::stat_cor(show.legend = F, size = 2.75, label.x.npc = 0.4, label.y.npc = 0.9) +
  facet_grid(. ~ glue("Single Cell LuCA Atlas\nNormal samples")) +
  scale_color_manual(values = c("#E64B35FF", "#4DBBD5FF"), breaks = c("Alveolar cell type 1", "Alveolar cell type 2"), labels = c("AT1", "AT2")) +
  theme_few() +
  labs(x = "Reactome Nuclear MAPK", y = "LUAD Signature", color = "Cell Type")

pcomb <- p1 | p2
ggsave(pcomb, file = here("plots", "luca_atlas_mapk_cor_plus_norm.pdf"), width = 8, height = 3)
pcomb

Figure S9C – Biocarta and KEGG MAPK versus LUAD identity

The same tumor and normal comparisons as 4C-D, repeated for the two other MAPK gene sets.

Code
p1 <- df %>%
  ggplot(aes(x = BIOCARTA_MAPK_PATHWAY, y = LUAD_GIRARD, color = tumor_stage)) +
  geom_point() +
  geom_smooth(method = "lm", show.legend = F, se = F) +
  ggpubr::stat_cor(show.legend = F, size = 2.75, label.x.npc = 0.45, label.y.npc = 0.075) +
  facet_grid(. ~ glue("Single Cell LuCA Atlas\nLUAD samples")) +
  see::scale_color_bluebrown_d(reverse = T, breaks = c("early", "advanced"), labels = c("Early", "Advanced")) +
  theme_few() +
  labs(x = "MAPK (Biocarta)", y = "LUAD Signature", color = "Tumor Stage")

p2 <- df2 %>%
  ggplot(aes(x = BIOCARTA_MAPK_PATHWAY, y = LUAD_GIRARD, color = ann_fine)) +
  geom_point() +
  geom_smooth(method = "lm", show.legend = F, se = F) +
  ggpubr::stat_cor(show.legend = F, size = 2.75, label.x.npc = 0.4, label.y.npc = 0.8) +
  facet_grid(. ~ glue("Single Cell LuCA Atlas\nNormal samples")) +
  scale_color_manual(values = c("#E64B35FF", "#4DBBD5FF"), breaks = c("Alveolar cell type 1", "Alveolar cell type 2"), labels = c("AT1", "AT2")) +
  theme_few() +
  labs(x = "MAPK (Biocarta)", y = "LUAD Signature", color = "Cell Type")

p3 <- df %>%
  ggplot(aes(x = KEGG_MAPK_SIGNALING_PATHWAY, y = LUAD_GIRARD, color = tumor_stage)) +
  geom_point() +
  geom_smooth(method = "lm", show.legend = F, se = F) +
  ggpubr::stat_cor(show.legend = F, size = 2.75, label.x.npc = 0.45, label.y.npc = 0.075) +
  facet_grid(. ~ glue("Single Cell LuCA Atlas\nLUAD samples")) +
  see::scale_color_bluebrown_d(reverse = T, breaks = c("early", "advanced"), labels = c("Early", "Advanced")) +
  theme_few() +
  labs(x = "MAPK (KEGG)", y = "LUAD Signature", color = "Tumor Stage")

p4 <- df2 %>%
  ggplot(aes(x = KEGG_MAPK_SIGNALING_PATHWAY, y = LUAD_GIRARD, color = ann_fine)) +
  geom_point() +
  geom_smooth(method = "lm", show.legend = F, se = F) +
  ggpubr::stat_cor(show.legend = F, size = 2.75, label.x.npc = 0.4, label.y.npc = 0.8) +
  facet_grid(. ~ glue("Single Cell LuCA Atlas\nNormal samples")) +
  scale_color_manual(values = c("#E64B35FF", "#4DBBD5FF"), breaks = c("Alveolar cell type 1", "Alveolar cell type 2"), labels = c("AT1", "AT2")) +
  theme_few() +
  labs(x = "MAPK (KEGG)", y = "LUAD Signature", color = "Cell Type")

# Tumors on the top row, normal alveolar cells on the bottom, so the two gene sets line up
# column-wise; the two rows key on different variables and so collect their guides apart.
pcomb1 <- (p1 | p3) + plot_layout(guides = "collect")
pcomb2 <- (p2 | p4) + plot_layout(guides = "collect")

pcomb <- pcomb1 / pcomb2
ggsave(pcomb, file = here("plots", "luca_atlas_mapk_cor_plus_norm_biocarta_kegg.pdf"), width = 7, height = 6)
pcomb

Figure S7C – signature scores by stage in untreated LUAD

Per-sample treatment status and stage for the atlas, curated from the contributing studies’ own supplementary tables. Maynard_Bivona_2020 sampled its patients longitudinally, so it joins on sample to keep the timepoints apart; every other study is annotated at the patient level and joins on donor_id.

Code
tx_f <- here("paper_data", "luca_treatment_annotation.csv")
if (!file.exists(tx_f)) {
  cli::cli_abort(c("{.path {tx_f}} not found.",
                   "i" = "Figure S7C needs the curated per-study LuCA treatment annotation."))
}

luca_treatment <- read.csv(tx_f)
stopifnot(!any(duplicated(luca_treatment$obs_join_key)))

df_tx <- df %>%
  mutate(obs_join_key = ifelse(study == "Maynard_Bivona_2020",
                               as.character(sample), as.character(donor_id))) %>%
  left_join(
    dplyr::select(luca_treatment,
                  obs_join_key,
                  tx_treatment_status = treatment_status,
                  tx_resolved_stage = resolved_stage),
    by = "obs_join_key"
  ) %>%
  mutate(
    # Every LUAD cancer-cell sample is annotated, so this only guards the panel: an
    # unannotated sample is not naive, and a named state keeps it out where an NA would not.
    tx_treatment_status = replace_na(tx_treatment_status, "not_annotated"),
    tx_resolved_stage = ifelse(tx_resolved_stage %in% c("IIIa", "IIIb", "IIIc"), "III", tx_resolved_stage)
  )

naive_df <- df_tx %>%
  filter(tx_treatment_status == "naive")

# Published panel: 93 treatment-naive LUAD samples from 81 patients.
stopifnot(nrow(naive_df) == 93)
Code
mapk_sigs <- c(
  "LUAD_GIRARD" = "LUAD signature",
  "KEGG_MAPK_SIGNALING_PATHWAY" = "MAPK (KEGG)",
  "BIOCARTA_MAPK_PATHWAY" = "MAPK (Biocarta)",
  "REACTOME_MAPK_TARGETS_NUCLEAR_EVENTS_MEDIATED_BY_MAP_KINASES" = "Nuclear MAPK (Reactome)"
)

mapk_pls <- map(names(mapk_sigs), .f = function(sig) {
  naive_df %>%
    ggplot(aes(x = tx_resolved_stage, y = .data[[sig]], fill = tx_resolved_stage)) +
    geom_boxplot(outliers = F) +
    geom_quasirandom(pch = 21, show.legend = F, alpha = 0.5) +
    scale_fill_viridis_d() +
    theme_few() +
    ggpubr::geom_pwc(label = "p.adj.signif", symnum.args = symnum_0.1, hide.ns = T) +
    labs(x = "Tumor Stage", y = mapk_sigs[[sig]], fill = "Tumor Stage")
})

pcomb <- wrap_plots(mapk_pls, nrow = 1, guides = "collect", axes = "collect_x") +
  plot_annotation(title = glue("LuCA Atlas Untreated LUAD cases"),
                  theme = theme(plot.title = element_text(hjust = 0.5))) &
  # Headroom for the significance brackets, which sit above the highest point.
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.075))) &
  NoLegend()
ggsave(pcomb, file = here("plots", "luca_atlas_mapk_sigs_by_stage_untreated.pdf"), width = 7, height = 3.5)
pcomb