Step 1. Load
Libraries
library(readxl)
library(clusterProfiler)
library(org.Hs.eg.db)
library(ReactomePA)
library(enrichplot)
library(msigdbr)
library(openxlsx)
library(stringr)
library(purrr)
library(tibble)
library(tidyr)
library(ggplot2)
library(dplyr) # load LAST so dplyr verbs win the masking conflict
Step 2. Load Top100
Marker Table
markers <- read_excel("../Supplementary_Table_S6.xlsx") %>%
rename_with(tolower) %>%
mutate(cluster = as.character(cluster))
# Fix known outdated/renamed gene symbols
symbol_updates <- c("QARS" = "QARS1", "CARS" = "CARS1", "WARS" = "WARS1")
markers <- markers %>%
mutate(gene = ifelse(gene %in% names(symbol_updates), symbol_updates[gene], gene)) %>%
filter(gene != "46083.0") # remove spreadsheet artifact
clusters_list <- as.character(sort(as.numeric(unique(markers$cluster))))
cat("Clusters found (numeric order):", paste(clusters_list, collapse = ", "), "\n")
Clusters found (numeric order): 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
print(colnames(markers))
[1] "p_val" "avg_log2fc" "pct.1" "pct.2" "p_val_adj" "cluster" "gene"
Step 3. Build
Background Universe and Per-Cluster Gene Lists
# Background = all genes that appear anywhere in the marker table (all clusters combined)
background_genes <- unique(markers$gene)
cat("Background universe size:", length(background_genes), "genes\n")
Background universe size: 1095 genes
# Map SYMBOL -> ENTREZID (needed for enrichKEGG and ReactomePA)
gene_map <- bitr(background_genes, fromType = "SYMBOL", toType = "ENTREZID",
OrgDb = org.Hs.eg.db, drop = TRUE)
background_entrez <- unique(gene_map$ENTREZID)
get_cluster_genes <- function(cluster_id, marker_df, top_n = 100) {
marker_df %>%
filter(cluster == cluster_id) %>%
distinct(gene, avg_log2fc) %>%
arrange(desc(avg_log2fc)) %>%
slice_head(n = top_n) %>%
pull(gene)
}
cluster_gene_lists <- setNames(
lapply(clusters_list, get_cluster_genes, marker_df = markers),
clusters_list
)
sapply(cluster_gene_lists, length)
0 1 2 3 4 5 6 7 8 9 10 11 12 13
100 100 100 100 100 100 100 100 100 100 100 99 100 100
Step 4. Load Hallmark
Gene Sets (for enricher())
hallmark_sets <- msigdbr(species = "Homo sapiens", collection = "H") %>%
distinct(gs_name, gene_symbol)
Step 5. Run ORA for
Each Cluster (GO:BP, KEGG, Reactome,
Hallmark)
all_results <- list()
for (cl in clusters_list) {
message("Running ORA for cluster ", cl)
genes_symbol <- cluster_gene_lists[[cl]]
genes_entrez <- gene_map$ENTREZID[gene_map$SYMBOL %in% genes_symbol]
res_list <- list()
# GO:BP
res_list$GO_BP <- tryCatch(
enrichGO(gene = genes_symbol, universe = background_genes,
OrgDb = org.Hs.eg.db, keyType = "SYMBOL",
ont = "BP", pAdjustMethod = "BH",
pvalueCutoff = 1, qvalueCutoff = 1),
error = function(e) { message(" GO:BP failed: ", e$message); NULL })
# KEGG
res_list$KEGG <- tryCatch(
enrichKEGG(gene = genes_entrez, universe = background_entrez,
organism = "hsa", pAdjustMethod = "BH",
pvalueCutoff = 1, qvalueCutoff = 1),
error = function(e) { message(" KEGG failed: ", e$message); NULL })
# Reactome
res_list$Reactome <- tryCatch(
enrichPathway(gene = genes_entrez, universe = background_entrez,
organism = "human", pAdjustMethod = "BH",
pvalueCutoff = 1, qvalueCutoff = 1, readable = TRUE),
error = function(e) { message(" Reactome failed: ", e$message); NULL })
# Hallmark (via generic enricher)
res_list$Hallmark <- tryCatch(
enricher(gene = genes_symbol, universe = background_genes,
TERM2GENE = hallmark_sets, pAdjustMethod = "BH",
pvalueCutoff = 1, qvalueCutoff = 1),
error = function(e) { message(" Hallmark failed: ", e$message); NULL })
all_results[[cl]] <- res_list
}
Step 6. Export All
Enrichment Tables to Excel
wb <- createWorkbook()
for (cl in names(all_results)) {
res <- all_results[[cl]]
if (is.null(res)) next
for (ont in names(res)) {
obj <- res[[ont]]
if (is.null(obj) || nrow(as.data.frame(obj)) == 0) next
sheet_name <- substr(paste0("C", cl, "_", ont), 1, 31)
addWorksheet(wb, sheet_name)
writeData(wb, sheet_name, as.data.frame(obj))
}
}
saveWorkbook(wb, "Cluster_ORA_Results_Top100.xlsx", overwrite = TRUE)
Step 7. Combine Top
Terms per Cluster into One Long Table
build_long_ora <- function(all_results) {
purrr::map_dfr(names(all_results), function(cl) {
res <- all_results[[cl]]
purrr::map_dfr(names(res), function(db) {
obj <- res[[db]]
if (is.null(obj)) return(NULL)
df <- as.data.frame(obj)
if (nrow(df) == 0) return(NULL)
df <- as.data.frame(df) # strip any lingering S4/tibble subclass
df$cluster <- cl
df$database <- db
dplyr::select(df, cluster, database, ID, Description, GeneRatio, BgRatio,
pvalue, p.adjust, qvalue, geneID, Count)
})
})
}
all_long_ora <- build_long_ora(all_results)
write.csv(all_long_ora, "Cluster_ORA_Results_long.csv", row.names = FALSE)
cat("Total enriched terms across all clusters/databases:", nrow(all_long_ora), "\n")
Total enriched terms across all clusters/databases: 20335
Step 8. Check How Many
Significant Terms per Cluster
all_long_ora %>%
group_by(cluster) %>%
summarise(n_sig_padj05 = sum(p.adjust < 0.05, na.rm = TRUE),
n_sig_padj25 = sum(p.adjust < 0.25, na.rm = TRUE),
n_total_terms = n(),
min_padj = suppressWarnings(min(p.adjust, na.rm = TRUE))) %>%
arrange(as.numeric(as.character(cluster)))
Step 9. Parse GeneRatio
to Numeric (needed for dotplot x-axis)
parse_ratio <- function(x) {
parts <- str_split(x, "/", simplify = TRUE)
as.numeric(parts[, 1]) / as.numeric(parts[, 2])
}
all_long_ora <- all_long_ora %>%
mutate(GeneRatioNum = parse_ratio(GeneRatio))
Step 10. Dotplot per
Cluster (GeneRatio x, Count size, p.adjust color)
plot_cluster_ora_dotplot <- function(cluster_id, long_df, n_top = 10, sig_cutoff = 0.05) {
df <- long_df %>%
filter(cluster == cluster_id, p.adjust < sig_cutoff) %>%
arrange(p.adjust) %>%
slice_head(n = n_top)
used_cutoff <- sig_cutoff
# Fallback if nothing passes 0.05
if (nrow(df) == 0) {
df <- long_df %>%
filter(cluster == cluster_id, p.adjust < 0.25) %>%
arrange(p.adjust) %>%
slice_head(n = n_top)
used_cutoff <- 0.25
}
if (nrow(df) == 0) {
cat("**No enriched terms reached p.adjust < 0.25 for cluster", cluster_id, "**\n\n")
return(invisible(NULL))
}
df <- df %>%
mutate(Description = str_wrap(Description, width = 40),
label = paste0(Description, " [", database, "]"))
p <- ggplot(df, aes(x = GeneRatioNum, y = reorder(label, GeneRatioNum),
size = Count, color = p.adjust)) +
geom_point() +
scale_color_gradient(low = "#B2182B", high = "#4393C3", name = "p.adjust") +
scale_size_continuous(name = "Gene count", range = c(3, 9)) +
labs(title = paste0("Cluster ", cluster_id, " - Top100 ORA (p.adjust<", used_cutoff, ")"),
x = "Gene Ratio", y = NULL) +
theme_minimal(base_size = 11) +
theme(axis.text.y = element_text(size = 9))
print(p)
ggsave(filename = paste0("cluster", cluster_id, "_top100_ORA_dotplot.png"),
plot = p, width = 9, height = 6, dpi = 300)
cat("\n\n")
}
for (cl in clusters_list) {
cat("\n## Cluster", cl, "- ORA Dotplot\n\n")
plot_cluster_ora_dotplot(cl, all_long_ora)
}
Cluster 0 - ORA
Dotplot
Cluster 1 - ORA
Dotplot
Cluster 2 - ORA
Dotplot
No enriched terms reached p.adjust < 0.25 for cluster 2
Cluster 3 - ORA
Dotplot
Cluster 4 - ORA
Dotplot
Cluster 5 - ORA
Dotplot
Cluster 6 - ORA
Dotplot
Cluster 7 - ORA
Dotplot
Cluster 8 - ORA
Dotplot
Cluster 9 - ORA
Dotplot
No enriched terms reached p.adjust < 0.25 for cluster 9
Cluster 10 - ORA
Dotplot
No enriched terms reached p.adjust < 0.25 for cluster 10
Cluster 11 - ORA
Dotplot
Cluster 12 - ORA
Dotplot
Step 11. Table of Top
Pathways per Cluster (Printed Individually)
for (cl in clusters_list) {
cat("\n### Cluster", cl, "- Top Pathways (p.adjust < 0.05, fallback 0.25)\n\n")
df <- all_long_ora %>%
dplyr::filter(cluster == cl, p.adjust < 0.05) %>%
dplyr::arrange(p.adjust)
if (nrow(df) == 0) {
df <- all_long_ora %>%
dplyr::filter(cluster == cl, p.adjust < 0.25) %>%
dplyr::arrange(p.adjust)
}
if (nrow(df) == 0) {
cat("No enriched pathways found for this cluster.\n\n")
next
}
df_show <- df %>%
dplyr::slice_head(n = 10) %>%
dplyr::select(database, Description, GeneRatio, p.adjust, Count, geneID) %>%
dplyr::mutate(p.adjust = formatC(p.adjust, format = "e", digits = 2))
print(knitr::kable(df_show, caption = paste0("Cluster ", cl, " top pathways")))
cat("\n\n")
}
Cluster 0 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 0 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| hsa05152 |
KEGG |
Tuberculosis |
6/42 |
3.91e-02 |
6 |
6772/3117/9902/3123/972/3119 |
| hsa05416 |
KEGG |
Viral myocarditis |
5/42 |
3.91e-02 |
5 |
3117/1756/3123/958/3119 |
Cluster 1 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 1 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| hsa04650 |
KEGG |
Natural killer cell mediated cytotoxicity |
7/48 |
5.00e-02 |
7 |
3805/3821/3802/22914/3811/3804/5551 |
Cluster 2 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
No enriched pathways found for this cluster.
Cluster 3 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 3 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| R-HSA-74160 |
Reactome |
Gene expression (Transcription) |
12/48 |
9.02e-02 |
12 |
TCF7/TXNIP/LEF1/MAML2/BTG1/DYRK2/PHF1/FOXO1/SOCS3/ATM/ZNF101/SESN3 |
| R-HSA-212436 |
Reactome |
Generic Transcription Pathway |
11/48 |
9.02e-02 |
11 |
TCF7/TXNIP/LEF1/MAML2/BTG1/DYRK2/FOXO1/SOCS3/ATM/ZNF101/SESN3 |
| R-HSA-73857 |
Reactome |
RNA Polymerase II Transcription |
11/48 |
9.02e-02 |
11 |
TCF7/TXNIP/LEF1/MAML2/BTG1/DYRK2/FOXO1/SOCS3/ATM/ZNF101/SESN3 |
| GO:0030217 |
GO_BP |
T cell differentiation |
12/77 |
2.19e-01 |
12 |
TCF7/LEF1/IL6ST/TGFBR2/CD27/LY9/FOXP1/CD28/IL7R/SOCS3/ITPKB/ZFP36L1 |
| GO:0030098 |
GO_BP |
lymphocyte differentiation |
13/77 |
2.19e-01 |
13 |
TCF7/LEF1/IL6ST/TGFBR2/CD27/LY9/FOXP1/CD28/IL7R/SOCS3/ATM/ITPKB/ZFP36L1 |
| GO:0019222 |
GO_BP |
regulation of metabolic process |
46/77 |
2.19e-01 |
46 |
TMIGD2/ADTRP/BEX4/TCF7/LDLRAP1/ITGA6/NCF1/TXK/LBH/TXNIP/PLAC8/FHIT/BEX2/LEF1/IL6ST/UTY/TGFBR2/ABLIM1/SCML4/APBB1/APBA2/TRIM22/SATB1/PLCL1/LY9/MAML2/SNX9/BTG1/DYRK2/PNRC1/SFMBT2/BEX3/FOXP1/CD28/PHF1/FOXO1/TSHZ2/IL7R/ATM/SBNO2/ZNF101/IL16/ZFP36L1/CREBRF/SESN3/HLA-E |
| GO:0035264 |
GO_BP |
multicellular organism growth |
6/77 |
2.19e-01 |
6 |
PLAC8/APBA2/SELENOM/PLEKHA1/ATM/ZFP36L1 |
Cluster 4 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 4 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| HALLMARK_HEME_METABOLISM |
Hallmark |
HALLMARK_HEME_METABOLISM |
5/38 |
9.75e-02 |
5 |
CLIC2/BLVRB/OSBP2/MPP1/GDE1 |
Cluster 5 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 5 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| R-HSA-1266738 |
Reactome |
Developmental Biology |
20/63 |
4.91e-02 |
20 |
NELL2/MAML2/FOXP1/ARHGEF28/ZNF521/SEMA4A/KRT1/DSC1/IL12RB2/CEBPD/BMP4/PKP2/KITLG/EPHA1/PTK2/EPHB1/CSF1/RUNX1/PRKCA/LRIG1 |
Cluster 6 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 6 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| R-HSA-112316 |
Reactome |
Neuronal System |
8/50 |
2.45e-01 |
8 |
GRIA4/GNB4/HOMER2/TUBB6/EPB41L2/MAOA/NEFL/NLGN1 |
Cluster 7 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 7 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| GO:0007059 |
GO_BP |
chromosome segregation |
41/92 |
1.08e-32 |
41 |
PSRC1/SGO2/NDC80/ASPM/KIF14/NEK2/DLGAP5/NUSAP1/TOP2A/GPSM2/CENPE/ECT2/KIFC1/CDCA2/HJURP/CENPF/CDK1/KIF23/MKI67/CCNB2/CDCA8/KIF4A/BUB1/RACGAP1/SGO1/AURKA/KNL1/FAM83D/BIRC5/UBE2C/NUF2/KIF15/PLK1/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF18B |
| GO:1903047 |
GO_BP |
mitotic cell cycle process |
49/92 |
4.90e-29 |
49 |
PSRC1/CDKN2C/NDC80/KIF14/KIF20A/NEK2/DLGAP5/NUSAP1/GPSM2/CENPE/CENPA/ECT2/CDC25C/CCNA2/KIFC1/CDCA2/CENPF/STMN1/CDK1/KIF23/TK1/MKI67/CCNB2/CCNF/CDCA8/KIF4A/RRM2/BUB1/RACGAP1/AURKA/CDKN3/KNL1/BIRC5/UBE2C/NUF2/CIT/KIF15/PLK1/CDKN2A/CEP55/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF20B/KIF18B |
| GO:0098813 |
GO_BP |
nuclear chromosome segregation |
36/92 |
2.56e-28 |
36 |
PSRC1/NDC80/ASPM/KIF14/NEK2/DLGAP5/NUSAP1/TOP2A/CENPE/ECT2/KIFC1/CENPF/CDK1/KIF23/CCNB2/CDCA8/KIF4A/BUB1/RACGAP1/SGO1/AURKA/KNL1/FAM83D/BIRC5/UBE2C/NUF2/KIF15/PLK1/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF18B |
| GO:0051301 |
GO_BP |
cell division |
46/92 |
2.56e-28 |
46 |
PSRC1/SGO2/NDC80/ASPM/KIF14/KIF20A/NEK2/NUSAP1/TOP2A/GPSM2/CENPE/CENPA/ECT2/CDC25C/CCNA2/KIFC1/CDCA2/CENPF/STMN1/CDK1/KIF23/CDCA3/CCNB2/CCNF/CDCA8/KIF4A/BUB1/RACGAP1/SGO1/AURKA/KNL1/FAM83D/BIRC5/UBE2C/NUF2/CIT/PLK1/CDKN2A/CEP55/INCENP/TPX2/KIF2C/SMC4/SPC24/KIF20B/KIF18B |
| GO:0022402 |
GO_BP |
cell cycle process |
55/92 |
2.56e-28 |
55 |
PSRC1/CDKN2C/SGO2/NDC80/ASPM/KIF14/KIF20A/NEK2/DLGAP5/NUSAP1/TOP2A/GPSM2/CENPE/CENPA/ECT2/CDC25C/CCNA2/KIFC1/CDCA2/HJURP/CENPF/STMN1/CDK1/KIF23/TK1/MKI67/CCNB2/CCNF/CDCA8/KIF4A/RRM2/BUB1/RACGAP1/SGO1/AURKA/CDKN3/KNL1/FAM83D/BIRC5/UBE2C/NUF2/CIT/KIF15/PLK1/CDKN2A/CEP55/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF20B/KIF18B |
| GO:0000278 |
GO_BP |
mitotic cell cycle |
50/92 |
4.16e-28 |
50 |
PSRC1/CDKN2C/NDC80/KIF14/KIF20A/NEK2/DLGAP5/NUSAP1/GPSM2/CENPE/CENPA/ECT2/CDC25C/CCNA2/KIFC1/CDCA2/CENPF/STMN1/TUBA4A/CDK1/KIF23/TK1/MKI67/CCNB2/CCNF/CDCA8/KIF4A/RRM2/BUB1/RACGAP1/AURKA/CDKN3/KNL1/BIRC5/UBE2C/NUF2/CIT/KIF15/PLK1/CDKN2A/CEP55/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF20B/KIF18B |
| GO:0000280 |
GO_BP |
nuclear division |
38/92 |
1.87e-27 |
38 |
PSRC1/NDC80/ASPM/KIF14/NEK2/DLGAP5/NUSAP1/TOP2A/CENPE/CDC25C/KIFC1/CDCA2/CENPF/CDK1/KIF23/MKI67/CCNB2/CDCA8/KIF4A/BUB1/RACGAP1/SGO1/AURKA/KNL1/BIRC5/UBE2C/NUF2/KIF15/PLK1/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF20B/KIF18B |
| HALLMARK_G2M_CHECKPOINT |
Hallmark |
HALLMARK_G2M_CHECKPOINT |
33/58 |
2.87e-27 |
33 |
CDKN2C/NDC80/NEK2/NUSAP1/TOP2A/CENPE/CENPA/CCNA2/CENPF/STMN1/HMMR/CDK1/KIF23/MKI67/CCNB2/CCNF/KIF4A/KPNA2/BUB1/RACGAP1/AURKA/CDKN3/KNL1/BIRC5/UBE2C/KIF15/PLK1/INCENP/TPX2/KIF2C/KIF22/SMC4/KIF20B |
| GO:0007049 |
GO_BP |
cell cycle |
58/92 |
7.80e-27 |
58 |
PSRC1/CDKN2C/SGO2/NDC80/ASPM/KIF14/KIF20A/NEK2/HPGD/DLGAP5/NUSAP1/TOP2A/GPSM2/CENPE/CENPA/ECT2/CDC25C/CCNA2/KIFC1/CDCA2/HJURP/CENPF/STMN1/TUBA4A/CDK1/KIF23/TK1/MKI67/CCNB2/CCNF/CDCA8/KIF4A/RRM2/BUB1/RACGAP1/SGO1/AURKA/CDKN3/KNL1/FAM83D/BIRC5/UBE2C/NUF2/PRR11/CIT/KIF15/PLK1/CDKN2A/CEP55/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF20B/KIF18B |
| GO:0048285 |
GO_BP |
organelle fission |
38/92 |
3.65e-26 |
38 |
PSRC1/NDC80/ASPM/KIF14/NEK2/DLGAP5/NUSAP1/TOP2A/CENPE/CDC25C/KIFC1/CDCA2/CENPF/CDK1/KIF23/MKI67/CCNB2/CDCA8/KIF4A/BUB1/RACGAP1/SGO1/AURKA/KNL1/BIRC5/UBE2C/NUF2/KIF15/PLK1/INCENP/TPX2/KIF2C/KIF22/KIF18A/SMC4/SPC24/KIF20B/KIF18B |
Cluster 8 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 8 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| GO:0034220 |
GO_BP |
monoatomic ion transmembrane transport |
13/84 |
1.25e-01 |
13 |
ATP7B/TMEM163/KCNQ2/KCND2/ATP12A/CACNA1D/CHRNA6/ORAI3/GRIA4/NCS1/KCNMA1/SLC26A4/CACNA2D1 |
| R-HSA-9709957 |
Reactome |
Sensory Perception |
5/50 |
1.34e-01 |
5 |
CACNA1D/LRP2/LRP12/KCNMA1/RDH10 |
| GO:0006811 |
GO_BP |
monoatomic ion transport |
15/84 |
1.40e-01 |
15 |
LRP2/ATP7B/TMEM163/KCNQ2/KCND2/ATP12A/CACNA1D/CHRNA6/ORAI3/GRIA4/NCS1/STC2/KCNMA1/SLC26A4/CACNA2D1 |
Cluster 9 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
No enriched pathways found for this cluster.
Cluster 10 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
No enriched pathways found for this cluster.
Cluster 11 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 11 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| HALLMARK_EPITHELIAL_MESENCHYMAL_TRANSITION |
Hallmark |
HALLMARK_EPITHELIAL_MESENCHYMAL_TRANSITION |
11/60 |
5.68e-03 |
11 |
FUCA1/SAT1/IL32/TIMP1/FN1/SPOCK1/LGALS1/IGFBP3/BASP1/ID2/ITGB1 |
| GO:0009416 |
GO_BP |
response to light stimulus |
8/93 |
7.82e-03 |
8 |
CDKN1A/RGS9/NMU/TIMP1/BHLHE40/MDM2/ID2/ITGB1 |
| R-HSA-1474228 |
Reactome |
Degradation of the extracellular matrix |
6/72 |
2.67e-02 |
6 |
FN1/MMP25/ADAM8/TIMP1/CTSD/FURIN |
| GO:0019058 |
GO_BP |
viral life cycle |
12/93 |
5.00e-02 |
12 |
APOBEC3G/SLAMF1/IL32/LGALS1/FURIN/GPR15/APOBEC3C/ITGB7/IFITM2/GBP2/ITGB1/IFITM3 |
Cluster 12 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 12 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| HALLMARK_TNFA_SIGNALING_VIA_NFKB |
Hallmark |
HALLMARK_TNFA_SIGNALING_VIA_NFKB |
19/49 |
3.02e-06 |
19 |
SERPINE1/CSF2/DUSP5/TNFSF9/DUSP4/RNF19B/PHLDA1/IER3/SDC4/PPP1R15A/ATF3/DUSP1/SQSTM1/TNF/TNFAIP8/CFLAR/TRIB1/PTGER4/STAT5A |
| GO:0008625 |
GO_BP |
extrinsic apoptotic signaling pathway via death domain
receptors |
8/86 |
2.28e-02 |
8 |
SERPINE1/LGALS3/ATF3/BAG3/TNF/DAPK1/CFLAR/TNFSF10 |
| GO:2001237 |
GO_BP |
negative regulation of extrinsic apoptotic signaling
pathway |
8/86 |
2.28e-02 |
8 |
SERPINE1/CSF2/HSPA1B/HSPA1A/LGALS3/LMNA/TNF/CFLAR |
| GO:0097191 |
GO_BP |
extrinsic apoptotic signaling pathway |
12/86 |
2.28e-02 |
12 |
SERPINE1/CSF2/HSPA1B/HSPA1A/LGALS3/ATF3/LMNA/BAG3/TNF/DAPK1/CFLAR/TNFSF10 |
| GO:0042981 |
GO_BP |
regulation of apoptotic process |
30/86 |
2.28e-02 |
30 |
SERPINE1/CSF2/CCL3/HSPA1B/CD40/HSPA1A/TNFSF9/PHLDA1/RGCC/IER3/LGALS3/GADD45G/PTGIS/SERPINB9/CTLA4/MEF2C/ATF3/CYP1B1/LMNA/DUSP1/SQSTM1/BAG3/TNF/DAPK1/TNFAIP8/CFLAR/TNFSF10/ARHGAP10/ZEB2/STAT5A |
| GO:2001236 |
GO_BP |
regulation of extrinsic apoptotic signaling
pathway |
10/86 |
2.41e-02 |
10 |
SERPINE1/CSF2/HSPA1B/HSPA1A/LGALS3/ATF3/LMNA/TNF/CFLAR/TNFSF10 |
| GO:0043067 |
GO_BP |
regulation of programmed cell death |
30/86 |
2.60e-02 |
30 |
SERPINE1/CSF2/CCL3/HSPA1B/CD40/HSPA1A/TNFSF9/PHLDA1/RGCC/IER3/LGALS3/GADD45G/PTGIS/SERPINB9/CTLA4/MEF2C/ATF3/CYP1B1/LMNA/DUSP1/SQSTM1/BAG3/TNF/DAPK1/TNFAIP8/CFLAR/TNFSF10/ARHGAP10/ZEB2/STAT5A |
| GO:2001234 |
GO_BP |
negative regulation of apoptotic signaling pathway |
9/86 |
2.60e-02 |
9 |
SERPINE1/CSF2/HSPA1B/HSPA1A/IER3/LGALS3/LMNA/TNF/CFLAR |
| GO:0043065 |
GO_BP |
positive regulation of apoptotic process |
17/86 |
3.23e-02 |
17 |
CCL3/CD40/PHLDA1/RGCC/GADD45G/PTGIS/CTLA4/MEF2C/ATF3/CYP1B1/DUSP1/SQSTM1/TNF/DAPK1/TNFAIP8/CFLAR/TNFSF10 |
| GO:0006915 |
GO_BP |
apoptotic process |
33/86 |
3.23e-02 |
33 |
SERPINE1/CSF2/GZMB/CCL3/HSPA1B/CD40/HSPA1A/TNFSF9/PHLDA1/RGCC/IER3/LGALS3/GADD45G/PTGIS/SERPINB9/PPP1R15A/CTLA4/MEF2C/RYR2/ATF3/CYP1B1/LMNA/DUSP1/SQSTM1/BAG3/TNF/DAPK1/TNFAIP8/CFLAR/TNFSF10/ARHGAP10/ZEB2/STAT5A |
Cluster 13 - Top
Pathways (p.adjust < 0.05, fallback 0.25)
Cluster 13 top pathways
|
database |
Description |
GeneRatio |
p.adjust |
Count |
geneID |
| HALLMARK_INTERFERON_ALPHA_RESPONSE |
Hallmark |
HALLMARK_INTERFERON_ALPHA_RESPONSE |
18/55 |
7.80e-11 |
18 |
IFIT2/OASL/IFIT3/CXCL10/IFI44/DHX58/IFIH1/ISG15/DDX60/PARP14/HERC6/EPSTI1/USP18/ISG20/OAS1/WARS1/RTP4/TENT5A |
| HALLMARK_INTERFERON_GAMMA_RESPONSE |
Hallmark |
HALLMARK_INTERFERON_GAMMA_RESPONSE |
23/55 |
5.03e-10 |
23 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/IFI44/DHX58/IFIH1/ISG15/CCL5/PELI1/DDX60/PARP14/HERC6/EPSTI1/OAS2/USP18/ISG20/WARS1/RTP4/TNFAIP3/APOL6/MT2A |
| GO:0051607 |
GO_BP |
defense response to virus |
19/88 |
5.08e-08 |
19 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/DHX58/PMAIP1/IFIH1/ISG15/ZC3HAV1/DDX60/AIM2/OAS2/USP18/ISG20/OAS1/RTP4/TNFAIP3 |
| GO:0009607 |
GO_BP |
response to biotic stimulus |
40/88 |
5.35e-08 |
40 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/NCF2/ARG2/CCL3/IFI44/DHX58/PMAIP1/IFIH1/IL1A/ISG15/PLCG2/CCL5/PELI1/ZC3HAV1/DDX60/PARP14/AIM2/HERC6/OAS2/USP18/PRKCE/ISG20/RGS1/OAS1/CCR7/SPIRE1/RTP4/TENT5A/NFKBIZ/KYNU/LTA/ABI3/TNFAIP3/RNF19B/IL4I1 |
| GO:0009615 |
GO_BP |
response to virus |
21/88 |
8.87e-08 |
21 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/IFI44/DHX58/PMAIP1/IFIH1/ISG15/CCL5/ZC3HAV1/DDX60/AIM2/OAS2/USP18/ISG20/OAS1/RTP4/TNFAIP3 |
| GO:0045087 |
GO_BP |
innate immune response |
29/88 |
1.55e-07 |
29 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/NCF2/ARG2/CCL3/DHX58/IFIH1/ISG15/PLCG2/CCL5/PELI1/ZC3HAV1/DDX60/PARP14/AIM2/OAS2/USP18/PRKCE/ISG20/OAS1/SPIRE1/NFKBIZ/KYNU/TNFAIP3/RNF19B |
| GO:0043207 |
GO_BP |
response to external biotic stimulus |
38/88 |
1.75e-07 |
38 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/NCF2/ARG2/CCL3/IFI44/DHX58/PMAIP1/IFIH1/IL1A/ISG15/PLCG2/CCL5/PELI1/ZC3HAV1/DDX60/PARP14/AIM2/HERC6/OAS2/USP18/PRKCE/ISG20/RGS1/OAS1/CCR7/SPIRE1/RTP4/TENT5A/NFKBIZ/KYNU/LTA/TNFAIP3/RNF19B |
| GO:0051707 |
GO_BP |
response to other organism |
38/88 |
1.75e-07 |
38 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/NCF2/ARG2/CCL3/IFI44/DHX58/PMAIP1/IFIH1/IL1A/ISG15/PLCG2/CCL5/PELI1/ZC3HAV1/DDX60/PARP14/AIM2/HERC6/OAS2/USP18/PRKCE/ISG20/RGS1/OAS1/CCR7/SPIRE1/RTP4/TENT5A/NFKBIZ/KYNU/LTA/TNFAIP3/RNF19B |
| GO:0140546 |
GO_BP |
defense response to symbiont |
29/88 |
5.99e-07 |
29 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/NCF2/ARG2/CCL3/DHX58/IFIH1/ISG15/PLCG2/CCL5/PELI1/ZC3HAV1/DDX60/PARP14/AIM2/OAS2/USP18/PRKCE/ISG20/OAS1/SPIRE1/NFKBIZ/KYNU/TNFAIP3/RNF19B |
| GO:0098542 |
GO_BP |
defense response to other organism |
29/88 |
6.77e-07 |
29 |
IFIT2/OASL/IFIT3/CXCL10/IFIT1/HERC5/NCF2/ARG2/CCL3/DHX58/IFIH1/ISG15/PLCG2/CCL5/PELI1/ZC3HAV1/DDX60/PARP14/AIM2/OAS2/USP18/PRKCE/ISG20/OAS1/SPIRE1/NFKBIZ/KYNU/TNFAIP3/RNF19B |
NA
Step 12. Cross-Check:
Proposed Name vs Top Enriched Terms
proposed_names <- c(
"0" = "MHC-II high aberrant state", "1" = "NK-like cytotoxic", "2" = "Th2-like",
"3" = "Naive/CD4 reference", "4" = "Migratory/Adhesion state", "5" = "Stem-like",
"6" = "Th2-like (Activated)", "7" = "Cycling (G2/M)", "8" = "Metabolically reprogrammed",
"9" = "GZMA-cytotoxic", "10" = "Central memory/CD4 reference", "11" = "Pro-inflammatory",
"12" = "GZMB-high inflammatory", "13" = "IFN stimulated"
)
validation_table <- all_long_ora %>%
dplyr::filter(p.adjust < 0.05) %>%
dplyr::group_by(cluster) %>%
dplyr::arrange(p.adjust) %>%
dplyr::slice_head(n = 3) %>%
dplyr::summarise(Top3_Terms = paste0(Description, " [", database, "] (p.adj=",
formatC(p.adjust, format = "e", digits = 2), ")",
collapse = "; "),
.groups = "drop") %>%
dplyr::mutate(Proposed_Name = proposed_names[as.character(cluster)]) %>%
dplyr::select(cluster, Proposed_Name, Top3_Terms)
knitr::kable(validation_table, caption = "Proposed name vs. top ORA-enriched terms (top100 markers, all databases)")
Proposed name vs. top ORA-enriched terms (top100 markers, all
databases)
| cluster |
Proposed_Name |
Top3_Terms |
| 0 |
MHC-II high aberrant state |
Tuberculosis [KEGG] (p.adj=3.91e-02); Viral myocarditis
[KEGG] (p.adj=3.91e-02) |
| 1 |
NK-like cytotoxic |
Natural killer cell mediated cytotoxicity [KEGG]
(p.adj=5.00e-02) |
| 11 |
Pro-inflammatory |
HALLMARK_EPITHELIAL_MESENCHYMAL_TRANSITION [Hallmark]
(p.adj=5.68e-03); response to light stimulus [GO_BP] (p.adj=7.82e-03);
Degradation of the extracellular matrix [Reactome] (p.adj=2.67e-02) |
| 12 |
GZMB-high inflammatory |
HALLMARK_TNFA_SIGNALING_VIA_NFKB [Hallmark]
(p.adj=3.02e-06); extrinsic apoptotic signaling pathway via death domain
receptors [GO_BP] (p.adj=2.28e-02); negative regulation of extrinsic
apoptotic signaling pathway [GO_BP] (p.adj=2.28e-02) |
| 13 |
IFN stimulated |
HALLMARK_INTERFERON_ALPHA_RESPONSE [Hallmark]
(p.adj=7.80e-11); HALLMARK_INTERFERON_GAMMA_RESPONSE [Hallmark]
(p.adj=5.03e-10); defense response to virus [GO_BP]
(p.adj=5.08e-08) |
| 5 |
Stem-like |
Developmental Biology [Reactome] (p.adj=4.91e-02) |
| 7 |
Cycling (G2/M) |
chromosome segregation [GO_BP] (p.adj=1.08e-32);
mitotic cell cycle process [GO_BP] (p.adj=4.90e-29); nuclear chromosome
segregation [GO_BP] (p.adj=2.56e-28) |
Step 13. Notes
- This script uses over-representation analysis (ORA)
– the standard method used in single-cell RNA-seq workflows to
validate/name clusters from marker gene lists.
- Background universe = all genes appearing anywhere in the marker
table across all clusters.
- Four databases tested per cluster: GO:BP (
enrichGO), KEGG
(enrichKEGG), Reactome (enrichPathway),
Hallmark (enricher with msigdbr Hallmark sets).
- Dotplots follow standard
clusterProfiler::dotplot()
convention: x-axis = GeneRatio, size = gene count, color =
p.adjust.
- Step 11 adds a readable results table per cluster (not just a plot),
listing top pathway name, database, GeneRatio, p.adjust, gene count, and
the actual overlapping gene IDs.
- If a cluster shows “No enriched pathways found,” this is a genuine
result – the top100 genes don’t strongly match any curated pathway/GO
term, common for transitional or poorly characterized states.
LS0tCnRpdGxlOiAiT3Zlci1SZXByZXNlbnRhdGlvbiBBbmFseXNpcyAoT1JBKSBvbiBUb3AxMDAgTWFya2VyIEdlbmVzIHBlciBDbHVzdGVyIgpzdWJ0aXRsZTogIkdPOkJQICsgS0VHRyArIFJlYWN0b21lICsgSGFsbG1hcmssIHdpdGggcGVyLWNsdXN0ZXIgZG90cGxvdHMiCmF1dGhvcjogIk5hc2lyIE1haG1vb2QgQWJiYXNpIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICB0aGVtZTogam91cm5hbAotLS0KCgoKIyBTdGVwIDEuIExvYWQgTGlicmFyaWVzCgpgYGB7ciBsaWJyYXJpZXN9CmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGNsdXN0ZXJQcm9maWxlcikKbGlicmFyeShvcmcuSHMuZWcuZGIpCmxpYnJhcnkoUmVhY3RvbWVQQSkKbGlicmFyeShlbnJpY2hwbG90KQpsaWJyYXJ5KG1zaWdkYnIpCmxpYnJhcnkob3Blbnhsc3gpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShwdXJycikKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikgICAjIGxvYWQgTEFTVCBzbyBkcGx5ciB2ZXJicyB3aW4gdGhlIG1hc2tpbmcgY29uZmxpY3QKYGBgCgojIFN0ZXAgMi4gTG9hZCBUb3AxMDAgTWFya2VyIFRhYmxlCgpgYGB7ciBsb2FkLWRhdGF9Cm1hcmtlcnMgPC0gcmVhZF9leGNlbCgiLi4vU3VwcGxlbWVudGFyeV9UYWJsZV9TNi54bHN4IikgJT4lCiAgcmVuYW1lX3dpdGgodG9sb3dlcikgJT4lCiAgbXV0YXRlKGNsdXN0ZXIgPSBhcy5jaGFyYWN0ZXIoY2x1c3RlcikpCgojIEZpeCBrbm93biBvdXRkYXRlZC9yZW5hbWVkIGdlbmUgc3ltYm9scwpzeW1ib2xfdXBkYXRlcyA8LSBjKCJRQVJTIiA9ICJRQVJTMSIsICJDQVJTIiA9ICJDQVJTMSIsICJXQVJTIiA9ICJXQVJTMSIpCm1hcmtlcnMgPC0gbWFya2VycyAlPiUKICBtdXRhdGUoZ2VuZSA9IGlmZWxzZShnZW5lICVpbiUgbmFtZXMoc3ltYm9sX3VwZGF0ZXMpLCBzeW1ib2xfdXBkYXRlc1tnZW5lXSwgZ2VuZSkpICU+JQogIGZpbHRlcihnZW5lICE9ICI0NjA4My4wIikgICAjIHJlbW92ZSBzcHJlYWRzaGVldCBhcnRpZmFjdAoKY2x1c3RlcnNfbGlzdCA8LSBhcy5jaGFyYWN0ZXIoc29ydChhcy5udW1lcmljKHVuaXF1ZShtYXJrZXJzJGNsdXN0ZXIpKSkpCmNhdCgiQ2x1c3RlcnMgZm91bmQgKG51bWVyaWMgb3JkZXIpOiIsIHBhc3RlKGNsdXN0ZXJzX2xpc3QsIGNvbGxhcHNlID0gIiwgIiksICJcbiIpCgpwcmludChjb2xuYW1lcyhtYXJrZXJzKSkKYGBgCgojIFN0ZXAgMy4gQnVpbGQgQmFja2dyb3VuZCBVbml2ZXJzZSBhbmQgUGVyLUNsdXN0ZXIgR2VuZSBMaXN0cwoKYGBge3IgYmFja2dyb3VuZC11bml2ZXJzZX0KIyBCYWNrZ3JvdW5kID0gYWxsIGdlbmVzIHRoYXQgYXBwZWFyIGFueXdoZXJlIGluIHRoZSBtYXJrZXIgdGFibGUgKGFsbCBjbHVzdGVycyBjb21iaW5lZCkKYmFja2dyb3VuZF9nZW5lcyA8LSB1bmlxdWUobWFya2VycyRnZW5lKQpjYXQoIkJhY2tncm91bmQgdW5pdmVyc2Ugc2l6ZToiLCBsZW5ndGgoYmFja2dyb3VuZF9nZW5lcyksICJnZW5lc1xuIikKCiMgTWFwIFNZTUJPTCAtPiBFTlRSRVpJRCAobmVlZGVkIGZvciBlbnJpY2hLRUdHIGFuZCBSZWFjdG9tZVBBKQpnZW5lX21hcCA8LSBiaXRyKGJhY2tncm91bmRfZ2VuZXMsIGZyb21UeXBlID0gIlNZTUJPTCIsIHRvVHlwZSA9ICJFTlRSRVpJRCIsCiAgICAgICAgICAgICAgICAgIE9yZ0RiID0gb3JnLkhzLmVnLmRiLCBkcm9wID0gVFJVRSkKCmJhY2tncm91bmRfZW50cmV6IDwtIHVuaXF1ZShnZW5lX21hcCRFTlRSRVpJRCkKCmdldF9jbHVzdGVyX2dlbmVzIDwtIGZ1bmN0aW9uKGNsdXN0ZXJfaWQsIG1hcmtlcl9kZiwgdG9wX24gPSAxMDApIHsKICBtYXJrZXJfZGYgJT4lCiAgICBmaWx0ZXIoY2x1c3RlciA9PSBjbHVzdGVyX2lkKSAlPiUKICAgIGRpc3RpbmN0KGdlbmUsIGF2Z19sb2cyZmMpICU+JQogICAgYXJyYW5nZShkZXNjKGF2Z19sb2cyZmMpKSAlPiUKICAgIHNsaWNlX2hlYWQobiA9IHRvcF9uKSAlPiUKICAgIHB1bGwoZ2VuZSkKfQoKY2x1c3Rlcl9nZW5lX2xpc3RzIDwtIHNldE5hbWVzKAogIGxhcHBseShjbHVzdGVyc19saXN0LCBnZXRfY2x1c3Rlcl9nZW5lcywgbWFya2VyX2RmID0gbWFya2VycyksCiAgY2x1c3RlcnNfbGlzdAopCgpzYXBwbHkoY2x1c3Rlcl9nZW5lX2xpc3RzLCBsZW5ndGgpCmBgYAoKIyBTdGVwIDQuIExvYWQgSGFsbG1hcmsgR2VuZSBTZXRzIChmb3IgZW5yaWNoZXIoKSkKCmBgYHtyIGhhbGxtYXJrLXNldHN9CmhhbGxtYXJrX3NldHMgPC0gbXNpZ2RicihzcGVjaWVzID0gIkhvbW8gc2FwaWVucyIsIGNvbGxlY3Rpb24gPSAiSCIpICU+JQogIGRpc3RpbmN0KGdzX25hbWUsIGdlbmVfc3ltYm9sKQpgYGAKCiMgU3RlcCA1LiBSdW4gT1JBIGZvciBFYWNoIENsdXN0ZXIgKEdPOkJQLCBLRUdHLCBSZWFjdG9tZSwgSGFsbG1hcmspCgpgYGB7ciBydW4tb3JhfQphbGxfcmVzdWx0cyA8LSBsaXN0KCkKCmZvciAoY2wgaW4gY2x1c3RlcnNfbGlzdCkgewogIG1lc3NhZ2UoIlJ1bm5pbmcgT1JBIGZvciBjbHVzdGVyICIsIGNsKQoKICBnZW5lc19zeW1ib2wgPC0gY2x1c3Rlcl9nZW5lX2xpc3RzW1tjbF1dCiAgZ2VuZXNfZW50cmV6IDwtIGdlbmVfbWFwJEVOVFJFWklEW2dlbmVfbWFwJFNZTUJPTCAlaW4lIGdlbmVzX3N5bWJvbF0KCiAgcmVzX2xpc3QgPC0gbGlzdCgpCgogICMgR086QlAKICByZXNfbGlzdCRHT19CUCA8LSB0cnlDYXRjaCgKICAgIGVucmljaEdPKGdlbmUgPSBnZW5lc19zeW1ib2wsIHVuaXZlcnNlID0gYmFja2dyb3VuZF9nZW5lcywKICAgICAgICAgICAgIE9yZ0RiID0gb3JnLkhzLmVnLmRiLCBrZXlUeXBlID0gIlNZTUJPTCIsCiAgICAgICAgICAgICBvbnQgPSAiQlAiLCBwQWRqdXN0TWV0aG9kID0gIkJIIiwKICAgICAgICAgICAgIHB2YWx1ZUN1dG9mZiA9IDEsIHF2YWx1ZUN1dG9mZiA9IDEpLAogICAgZXJyb3IgPSBmdW5jdGlvbihlKSB7IG1lc3NhZ2UoIiAgR086QlAgZmFpbGVkOiAiLCBlJG1lc3NhZ2UpOyBOVUxMIH0pCgogICMgS0VHRwogIHJlc19saXN0JEtFR0cgPC0gdHJ5Q2F0Y2goCiAgICBlbnJpY2hLRUdHKGdlbmUgPSBnZW5lc19lbnRyZXosIHVuaXZlcnNlID0gYmFja2dyb3VuZF9lbnRyZXosCiAgICAgICAgICAgICAgIG9yZ2FuaXNtID0gImhzYSIsIHBBZGp1c3RNZXRob2QgPSAiQkgiLAogICAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgPSAxLCBxdmFsdWVDdXRvZmYgPSAxKSwKICAgIGVycm9yID0gZnVuY3Rpb24oZSkgeyBtZXNzYWdlKCIgIEtFR0cgZmFpbGVkOiAiLCBlJG1lc3NhZ2UpOyBOVUxMIH0pCgogICMgUmVhY3RvbWUKICByZXNfbGlzdCRSZWFjdG9tZSA8LSB0cnlDYXRjaCgKICAgIGVucmljaFBhdGh3YXkoZ2VuZSA9IGdlbmVzX2VudHJleiwgdW5pdmVyc2UgPSBiYWNrZ3JvdW5kX2VudHJleiwKICAgICAgICAgICAgICAgICAgIG9yZ2FuaXNtID0gImh1bWFuIiwgcEFkanVzdE1ldGhvZCA9ICJCSCIsCiAgICAgICAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgPSAxLCBxdmFsdWVDdXRvZmYgPSAxLCByZWFkYWJsZSA9IFRSVUUpLAogICAgZXJyb3IgPSBmdW5jdGlvbihlKSB7IG1lc3NhZ2UoIiAgUmVhY3RvbWUgZmFpbGVkOiAiLCBlJG1lc3NhZ2UpOyBOVUxMIH0pCgogICMgSGFsbG1hcmsgKHZpYSBnZW5lcmljIGVucmljaGVyKQogIHJlc19saXN0JEhhbGxtYXJrIDwtIHRyeUNhdGNoKAogICAgZW5yaWNoZXIoZ2VuZSA9IGdlbmVzX3N5bWJvbCwgdW5pdmVyc2UgPSBiYWNrZ3JvdW5kX2dlbmVzLAogICAgICAgICAgICAgVEVSTTJHRU5FID0gaGFsbG1hcmtfc2V0cywgcEFkanVzdE1ldGhvZCA9ICJCSCIsCiAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgPSAxLCBxdmFsdWVDdXRvZmYgPSAxKSwKICAgIGVycm9yID0gZnVuY3Rpb24oZSkgeyBtZXNzYWdlKCIgIEhhbGxtYXJrIGZhaWxlZDogIiwgZSRtZXNzYWdlKTsgTlVMTCB9KQoKICBhbGxfcmVzdWx0c1tbY2xdXSA8LSByZXNfbGlzdAp9CmBgYAoKIyBTdGVwIDYuIEV4cG9ydCBBbGwgRW5yaWNobWVudCBUYWJsZXMgdG8gRXhjZWwKCmBgYHtyIGV4cG9ydC1leGNlbH0Kd2IgPC0gY3JlYXRlV29ya2Jvb2soKQoKZm9yIChjbCBpbiBuYW1lcyhhbGxfcmVzdWx0cykpIHsKICByZXMgPC0gYWxsX3Jlc3VsdHNbW2NsXV0KICBpZiAoaXMubnVsbChyZXMpKSBuZXh0CgogIGZvciAob250IGluIG5hbWVzKHJlcykpIHsKICAgIG9iaiA8LSByZXNbW29udF1dCiAgICBpZiAoaXMubnVsbChvYmopIHx8IG5yb3coYXMuZGF0YS5mcmFtZShvYmopKSA9PSAwKSBuZXh0CgogICAgc2hlZXRfbmFtZSA8LSBzdWJzdHIocGFzdGUwKCJDIiwgY2wsICJfIiwgb250KSwgMSwgMzEpCiAgICBhZGRXb3Jrc2hlZXQod2IsIHNoZWV0X25hbWUpCiAgICB3cml0ZURhdGEod2IsIHNoZWV0X25hbWUsIGFzLmRhdGEuZnJhbWUob2JqKSkKICB9Cn0KCnNhdmVXb3JrYm9vayh3YiwgIkNsdXN0ZXJfT1JBX1Jlc3VsdHNfVG9wMTAwLnhsc3giLCBvdmVyd3JpdGUgPSBUUlVFKQpgYGAKCiMgU3RlcCA3LiBDb21iaW5lIFRvcCBUZXJtcyBwZXIgQ2x1c3RlciBpbnRvIE9uZSBMb25nIFRhYmxlCgpgYGB7ciBjb21iaW5lLWxvbmd9CmJ1aWxkX2xvbmdfb3JhIDwtIGZ1bmN0aW9uKGFsbF9yZXN1bHRzKSB7CiAgcHVycnI6Om1hcF9kZnIobmFtZXMoYWxsX3Jlc3VsdHMpLCBmdW5jdGlvbihjbCkgewogICAgcmVzIDwtIGFsbF9yZXN1bHRzW1tjbF1dCiAgICBwdXJycjo6bWFwX2RmcihuYW1lcyhyZXMpLCBmdW5jdGlvbihkYikgewogICAgICBvYmogPC0gcmVzW1tkYl1dCiAgICAgIGlmIChpcy5udWxsKG9iaikpIHJldHVybihOVUxMKQogICAgICBkZiA8LSBhcy5kYXRhLmZyYW1lKG9iaikKICAgICAgaWYgKG5yb3coZGYpID09IDApIHJldHVybihOVUxMKQogICAgICBkZiA8LSBhcy5kYXRhLmZyYW1lKGRmKSAgICMgc3RyaXAgYW55IGxpbmdlcmluZyBTNC90aWJibGUgc3ViY2xhc3MKICAgICAgZGYkY2x1c3RlciA8LSBjbAogICAgICBkZiRkYXRhYmFzZSA8LSBkYgogICAgICBkcGx5cjo6c2VsZWN0KGRmLCBjbHVzdGVyLCBkYXRhYmFzZSwgSUQsIERlc2NyaXB0aW9uLCBHZW5lUmF0aW8sIEJnUmF0aW8sCiAgICAgICAgICAgICAgICAgICAgIHB2YWx1ZSwgcC5hZGp1c3QsIHF2YWx1ZSwgZ2VuZUlELCBDb3VudCkKICAgIH0pCiAgfSkKfQoKYWxsX2xvbmdfb3JhIDwtIGJ1aWxkX2xvbmdfb3JhKGFsbF9yZXN1bHRzKQp3cml0ZS5jc3YoYWxsX2xvbmdfb3JhLCAiQ2x1c3Rlcl9PUkFfUmVzdWx0c19sb25nLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQoKY2F0KCJUb3RhbCBlbnJpY2hlZCB0ZXJtcyBhY3Jvc3MgYWxsIGNsdXN0ZXJzL2RhdGFiYXNlczoiLCBucm93KGFsbF9sb25nX29yYSksICJcbiIpCmBgYAoKIyBTdGVwIDguIENoZWNrIEhvdyBNYW55IFNpZ25pZmljYW50IFRlcm1zIHBlciBDbHVzdGVyCgpgYGB7ciBjaGVjay1zaWduaWZpY2FuY2V9CmFsbF9sb25nX29yYSAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUKICBzdW1tYXJpc2Uobl9zaWdfcGFkajA1ID0gc3VtKHAuYWRqdXN0IDwgMC4wNSwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgbl9zaWdfcGFkajI1ID0gc3VtKHAuYWRqdXN0IDwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgbl90b3RhbF90ZXJtcyA9IG4oKSwKICAgICAgICAgICAgbWluX3BhZGogPSBzdXBwcmVzc1dhcm5pbmdzKG1pbihwLmFkanVzdCwgbmEucm0gPSBUUlVFKSkpICU+JQogIGFycmFuZ2UoYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoY2x1c3RlcikpKQpgYGAKCiMgU3RlcCA5LiBQYXJzZSBHZW5lUmF0aW8gdG8gTnVtZXJpYyAobmVlZGVkIGZvciBkb3RwbG90IHgtYXhpcykKCmBgYHtyIHBhcnNlLWdlbmVyYXRpb30KcGFyc2VfcmF0aW8gPC0gZnVuY3Rpb24oeCkgewogIHBhcnRzIDwtIHN0cl9zcGxpdCh4LCAiLyIsIHNpbXBsaWZ5ID0gVFJVRSkKICBhcy5udW1lcmljKHBhcnRzWywgMV0pIC8gYXMubnVtZXJpYyhwYXJ0c1ssIDJdKQp9CgphbGxfbG9uZ19vcmEgPC0gYWxsX2xvbmdfb3JhICU+JQogIG11dGF0ZShHZW5lUmF0aW9OdW0gPSBwYXJzZV9yYXRpbyhHZW5lUmF0aW8pKQpgYGAKCiMgU3RlcCAxMC4gRG90cGxvdCBwZXIgQ2x1c3RlciAoR2VuZVJhdGlvIHgsIENvdW50IHNpemUsIHAuYWRqdXN0IGNvbG9yKQoKYGBge3IgZG90cGxvdC1wZXItY2x1c3RlciwgcmVzdWx0cz0nYXNpcycsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTZ9CnBsb3RfY2x1c3Rlcl9vcmFfZG90cGxvdCA8LSBmdW5jdGlvbihjbHVzdGVyX2lkLCBsb25nX2RmLCBuX3RvcCA9IDEwLCBzaWdfY3V0b2ZmID0gMC4wNSkgewoKICBkZiA8LSBsb25nX2RmICU+JQogICAgZmlsdGVyKGNsdXN0ZXIgPT0gY2x1c3Rlcl9pZCwgcC5hZGp1c3QgPCBzaWdfY3V0b2ZmKSAlPiUKICAgIGFycmFuZ2UocC5hZGp1c3QpICU+JQogICAgc2xpY2VfaGVhZChuID0gbl90b3ApCgogIHVzZWRfY3V0b2ZmIDwtIHNpZ19jdXRvZmYKCiAgIyBGYWxsYmFjayBpZiBub3RoaW5nIHBhc3NlcyAwLjA1CiAgaWYgKG5yb3coZGYpID09IDApIHsKICAgIGRmIDwtIGxvbmdfZGYgJT4lCiAgICAgIGZpbHRlcihjbHVzdGVyID09IGNsdXN0ZXJfaWQsIHAuYWRqdXN0IDwgMC4yNSkgJT4lCiAgICAgIGFycmFuZ2UocC5hZGp1c3QpICU+JQogICAgICBzbGljZV9oZWFkKG4gPSBuX3RvcCkKICAgIHVzZWRfY3V0b2ZmIDwtIDAuMjUKICB9CgogIGlmIChucm93KGRmKSA9PSAwKSB7CiAgICBjYXQoIioqTm8gZW5yaWNoZWQgdGVybXMgcmVhY2hlZCBwLmFkanVzdCA8IDAuMjUgZm9yIGNsdXN0ZXIiLCBjbHVzdGVyX2lkLCAiKipcblxuIikKICAgIHJldHVybihpbnZpc2libGUoTlVMTCkpCiAgfQoKICBkZiA8LSBkZiAlPiUKICAgIG11dGF0ZShEZXNjcmlwdGlvbiA9IHN0cl93cmFwKERlc2NyaXB0aW9uLCB3aWR0aCA9IDQwKSwKICAgICAgICAgICBsYWJlbCA9IHBhc3RlMChEZXNjcmlwdGlvbiwgIiBbIiwgZGF0YWJhc2UsICJdIikpCgogIHAgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IEdlbmVSYXRpb051bSwgeSA9IHJlb3JkZXIobGFiZWwsIEdlbmVSYXRpb051bSksCiAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IENvdW50LCBjb2xvciA9IHAuYWRqdXN0KSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICIjQjIxODJCIiwgaGlnaCA9ICIjNDM5M0MzIiwgbmFtZSA9ICJwLmFkanVzdCIpICsKICAgIHNjYWxlX3NpemVfY29udGludW91cyhuYW1lID0gIkdlbmUgY291bnQiLCByYW5nZSA9IGMoMywgOSkpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZTAoIkNsdXN0ZXIgIiwgY2x1c3Rlcl9pZCwgIiAtIFRvcDEwMCBPUkEgKHAuYWRqdXN0PCIsIHVzZWRfY3V0b2ZmLCAiKSIpLAogICAgICAgICB4ID0gIkdlbmUgUmF0aW8iLCB5ID0gTlVMTCkgKwogICAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDkpKQoKICBwcmludChwKQogIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMCgiY2x1c3RlciIsIGNsdXN0ZXJfaWQsICJfdG9wMTAwX09SQV9kb3RwbG90LnBuZyIpLAogICAgICAgICBwbG90ID0gcCwgd2lkdGggPSA5LCBoZWlnaHQgPSA2LCBkcGkgPSAzMDApCiAgY2F0KCJcblxuIikKfQoKZm9yIChjbCBpbiBjbHVzdGVyc19saXN0KSB7CiAgY2F0KCJcbiMjIENsdXN0ZXIiLCBjbCwgIi0gT1JBIERvdHBsb3RcblxuIikKICBwbG90X2NsdXN0ZXJfb3JhX2RvdHBsb3QoY2wsIGFsbF9sb25nX29yYSkKfQpgYGAKCiMgU3RlcCAxMS4gVGFibGUgb2YgVG9wIFBhdGh3YXlzIHBlciBDbHVzdGVyIChQcmludGVkIEluZGl2aWR1YWxseSkKCmBgYHtyIHBhdGh3YXlzLXRhYmxlLXBlci1jbHVzdGVyLCByZXN1bHRzPSdhc2lzJ30KZm9yIChjbCBpbiBjbHVzdGVyc19saXN0KSB7CiAgY2F0KCJcbiMjIyBDbHVzdGVyIiwgY2wsICItIFRvcCBQYXRod2F5cyAocC5hZGp1c3QgPCAwLjA1LCBmYWxsYmFjayAwLjI1KVxuXG4iKQoKICBkZiA8LSBhbGxfbG9uZ19vcmEgJT4lCiAgICBkcGx5cjo6ZmlsdGVyKGNsdXN0ZXIgPT0gY2wsIHAuYWRqdXN0IDwgMC4wNSkgJT4lCiAgICBkcGx5cjo6YXJyYW5nZShwLmFkanVzdCkKCiAgaWYgKG5yb3coZGYpID09IDApIHsKICAgIGRmIDwtIGFsbF9sb25nX29yYSAlPiUKICAgICAgZHBseXI6OmZpbHRlcihjbHVzdGVyID09IGNsLCBwLmFkanVzdCA8IDAuMjUpICU+JQogICAgICBkcGx5cjo6YXJyYW5nZShwLmFkanVzdCkKICB9CgogIGlmIChucm93KGRmKSA9PSAwKSB7CiAgICBjYXQoIk5vIGVucmljaGVkIHBhdGh3YXlzIGZvdW5kIGZvciB0aGlzIGNsdXN0ZXIuXG5cbiIpCiAgICBuZXh0CiAgfQoKICBkZl9zaG93IDwtIGRmICU+JQogICAgZHBseXI6OnNsaWNlX2hlYWQobiA9IDEwKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoZGF0YWJhc2UsIERlc2NyaXB0aW9uLCBHZW5lUmF0aW8sIHAuYWRqdXN0LCBDb3VudCwgZ2VuZUlEKSAlPiUKICAgIGRwbHlyOjptdXRhdGUocC5hZGp1c3QgPSBmb3JtYXRDKHAuYWRqdXN0LCBmb3JtYXQgPSAiZSIsIGRpZ2l0cyA9IDIpKQoKICBwcmludChrbml0cjo6a2FibGUoZGZfc2hvdywgY2FwdGlvbiA9IHBhc3RlMCgiQ2x1c3RlciAiLCBjbCwgIiB0b3AgcGF0aHdheXMiKSkpCiAgY2F0KCJcblxuIikKfQoKYGBgCgojIFN0ZXAgMTIuIENyb3NzLUNoZWNrOiBQcm9wb3NlZCBOYW1lIHZzIFRvcCBFbnJpY2hlZCBUZXJtcwoKYGBge3IgYW5ub3RhdGlvbi1jaGVja30KcHJvcG9zZWRfbmFtZXMgPC0gYygKICAiMCIgPSAiTUhDLUlJIGhpZ2ggYWJlcnJhbnQgc3RhdGUiLCAiMSIgPSAiTkstbGlrZSBjeXRvdG94aWMiLCAiMiIgPSAiVGgyLWxpa2UiLAogICIzIiA9ICJOYWl2ZS9DRDQgcmVmZXJlbmNlIiwgIjQiID0gIk1pZ3JhdG9yeS9BZGhlc2lvbiBzdGF0ZSIsICI1IiA9ICJTdGVtLWxpa2UiLAogICI2IiA9ICJUaDItbGlrZSAoQWN0aXZhdGVkKSIsICI3IiA9ICJDeWNsaW5nIChHMi9NKSIsICI4IiA9ICJNZXRhYm9saWNhbGx5IHJlcHJvZ3JhbW1lZCIsCiAgIjkiID0gIkdaTUEtY3l0b3RveGljIiwgIjEwIiA9ICJDZW50cmFsIG1lbW9yeS9DRDQgcmVmZXJlbmNlIiwgIjExIiA9ICJQcm8taW5mbGFtbWF0b3J5IiwKICAiMTIiID0gIkdaTUItaGlnaCBpbmZsYW1tYXRvcnkiLCAiMTMiID0gIklGTiBzdGltdWxhdGVkIgopCgp2YWxpZGF0aW9uX3RhYmxlIDwtIGFsbF9sb25nX29yYSAlPiUKICBkcGx5cjo6ZmlsdGVyKHAuYWRqdXN0IDwgMC4wNSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGRwbHlyOjphcnJhbmdlKHAuYWRqdXN0KSAlPiUKICBkcGx5cjo6c2xpY2VfaGVhZChuID0gMykgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShUb3AzX1Rlcm1zID0gcGFzdGUwKERlc2NyaXB0aW9uLCAiIFsiLCBkYXRhYmFzZSwgIl0gKHAuYWRqPSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXRDKHAuYWRqdXN0LCBmb3JtYXQgPSAiZSIsIGRpZ2l0cyA9IDIpLCAiKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICI7ICIpLAogICAgICAgICAgICAgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgZHBseXI6Om11dGF0ZShQcm9wb3NlZF9OYW1lID0gcHJvcG9zZWRfbmFtZXNbYXMuY2hhcmFjdGVyKGNsdXN0ZXIpXSkgJT4lCiAgZHBseXI6OnNlbGVjdChjbHVzdGVyLCBQcm9wb3NlZF9OYW1lLCBUb3AzX1Rlcm1zKQoKa25pdHI6OmthYmxlKHZhbGlkYXRpb25fdGFibGUsIGNhcHRpb24gPSAiUHJvcG9zZWQgbmFtZSB2cy4gdG9wIE9SQS1lbnJpY2hlZCB0ZXJtcyAodG9wMTAwIG1hcmtlcnMsIGFsbCBkYXRhYmFzZXMpIikKYGBgCgojIFN0ZXAgMTMuIE5vdGVzCgotIFRoaXMgc2NyaXB0IHVzZXMgKipvdmVyLXJlcHJlc2VudGF0aW9uIGFuYWx5c2lzIChPUkEpKiogLS0gdGhlIHN0YW5kYXJkIG1ldGhvZCB1c2VkIGluCiAgc2luZ2xlLWNlbGwgUk5BLXNlcSB3b3JrZmxvd3MgdG8gdmFsaWRhdGUvbmFtZSBjbHVzdGVycyBmcm9tIG1hcmtlciBnZW5lIGxpc3RzLgotIEJhY2tncm91bmQgdW5pdmVyc2UgPSBhbGwgZ2VuZXMgYXBwZWFyaW5nIGFueXdoZXJlIGluIHRoZSBtYXJrZXIgdGFibGUgYWNyb3NzIGFsbCBjbHVzdGVycy4KLSBGb3VyIGRhdGFiYXNlcyB0ZXN0ZWQgcGVyIGNsdXN0ZXI6IEdPOkJQIChgZW5yaWNoR09gKSwgS0VHRyAoYGVucmljaEtFR0dgKSwgUmVhY3RvbWUKICAoYGVucmljaFBhdGh3YXlgKSwgSGFsbG1hcmsgKGBlbnJpY2hlcmAgd2l0aCBtc2lnZGJyIEhhbGxtYXJrIHNldHMpLgotIERvdHBsb3RzIGZvbGxvdyBzdGFuZGFyZCBgY2x1c3RlclByb2ZpbGVyOjpkb3RwbG90KClgIGNvbnZlbnRpb246IHgtYXhpcyA9IEdlbmVSYXRpbywKICBzaXplID0gZ2VuZSBjb3VudCwgY29sb3IgPSBwLmFkanVzdC4KLSBTdGVwIDExIGFkZHMgYSByZWFkYWJsZSByZXN1bHRzIHRhYmxlIHBlciBjbHVzdGVyIChub3QganVzdCBhIHBsb3QpLCBsaXN0aW5nIHRvcCBwYXRod2F5CiAgbmFtZSwgZGF0YWJhc2UsIEdlbmVSYXRpbywgcC5hZGp1c3QsIGdlbmUgY291bnQsIGFuZCB0aGUgYWN0dWFsIG92ZXJsYXBwaW5nIGdlbmUgSURzLgotIElmIGEgY2x1c3RlciBzaG93cyAiTm8gZW5yaWNoZWQgcGF0aHdheXMgZm91bmQsIiB0aGlzIGlzIGEgZ2VudWluZSByZXN1bHQgLS0gdGhlIHRvcDEwMAogIGdlbmVzIGRvbid0IHN0cm9uZ2x5IG1hdGNoIGFueSBjdXJhdGVkIHBhdGh3YXkvR08gdGVybSwgY29tbW9uIGZvciB0cmFuc2l0aW9uYWwgb3IKICBwb29ybHkgY2hhcmFjdGVyaXplZCBzdGF0ZXMuCgo=