Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# EchoGO NEWS

## EchoGO development

### Changed
- RRvGO outputs are now split into three semantically honest buckets:
- `rrvgo_true_consensus_with_bg` for strict cross-method consensus
- `rrvgo_conservative_bg_supported` for GOseq or with-background-supported fallback terms
- `rrvgo_exploratory_all_significant` for all significant GO terms

### Documentation
- Clarified that `with_bg` network outputs are the conservative background-supported layer, not strict cross-method consensus.
- Added report interpretation notes explaining that legacy evaluation filenames still use the older `true_consensus` wording.

## EchoGO 0.1.2 (2026-01-14)

### Added
Expand Down
2 changes: 1 addition & 1 deletion R/network.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#' @description
#' Builds two network modes from the consensus table:
#' \itemize{
#' \item \strong{with_bg (True Consensus)} = GOseq (with BG) + g:Profiler (with BG) + Consensus (with BG)
#' \item \strong{with_bg (Conservative background-supported)} = GOseq (with BG) + g:Profiler (with BG) + Consensus (with BG)
#' \item \strong{with_bg_and_nobg (Exploratory)} = all of the above \emph{plus} the no-background sources
#' }
#' Then constructs per-ontology (BP, MF, CC) GO-term overlap networks.
Expand Down
25 changes: 25 additions & 0 deletions R/report_render.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@
return(NA_character_)
}
`%||%` <- function(a, b) if (!is.null(a)) a else b
ensure_pandoc <- function() {
if (isTRUE(rmarkdown::pandoc_available("1.12.3"))) return(TRUE)

candidates <- unique(c(
Sys.getenv("RSTUDIO_PANDOC", unset = ""),
file.path(Sys.getenv("ProgramFiles", unset = ""), "Pandoc"),
file.path(Sys.getenv("ProgramFiles", unset = ""), "RStudio", "resources", "app", "bin", "quarto", "bin", "tools"),
file.path(Sys.getenv("LOCALAPPDATA", unset = ""), "Pandoc"),
file.path(Sys.getenv("LOCALAPPDATA", unset = ""), "Programs", "Pandoc")
))
candidates <- candidates[nzchar(candidates)]

for (cand in candidates) {
exe <- if (grepl("pandoc\\.exe$", cand, ignore.case = TRUE)) cand else file.path(cand, "pandoc.exe")
if (!file.exists(exe)) next
Sys.setenv(RSTUDIO_PANDOC = dirname(exe))
if (isTRUE(rmarkdown::pandoc_available("1.12.3"))) return(TRUE)
}
FALSE
}

if (!ensure_pandoc()) {
warning("Pandoc not found; skipping report generation. Install Pandoc or RStudio, or set RSTUDIO_PANDOC.")
return(NA_character_)
}

# --- Base (root) & Report dir
base_dir <- normalizePath(outdir, winslash = "/", mustWork = FALSE)
Expand Down
46 changes: 46 additions & 0 deletions R/rrvgo_modes.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.echogo_truthy <- function(x) {
if (is.logical(x)) return(!is.na(x) & x)
if (is.numeric(x)) return(!is.na(x) & x != 0)
if (is.character(x)) return(tolower(trimws(x)) %in% c("true", "1", "yes", "y"))
rep(FALSE, length(x))
}

.echogo_norm_go_ontology <- function(x) {
dplyr::case_when(
x %in% c("GO:BP", "BP") ~ "BP",
x %in% c("GO:MF", "MF") ~ "MF",
x %in% c("GO:CC", "CC") ~ "CC",
TRUE ~ as.character(x)
)
}

.echogo_rrvgo_mode_tables <- function(consensus_df) {
if (!is.data.frame(consensus_df) || !nrow(consensus_df)) {
empty <- consensus_df[0, , drop = FALSE]
return(list(
true_consensus_with_bg = empty,
conservative_bg_supported = empty,
exploratory_all_significant = empty
))
}

df <- consensus_df %>%
dplyr::mutate(
ontology = .echogo_norm_go_ontology(.data$ontology),
significant_in_any = .echogo_truthy(.data$significant_in_any),
origin = as.character(.data$origin)
) %>%
dplyr::filter(.data$ontology %in% c("BP", "MF", "CC"), .data$significant_in_any)

list(
true_consensus_with_bg = df %>%
dplyr::filter(.data$origin == "GO terms - Consensus (with BG)"),
conservative_bg_supported = df %>%
dplyr::filter(.data$origin %in% c(
"GO terms - GOseq only",
"GO terms - g:Profiler only (with BG)",
"GO terms - Consensus (with BG)"
)),
exploratory_all_significant = df
)
}
8 changes: 4 additions & 4 deletions R/rrvgowrappers.R
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#' Run RRvGO Semantic Clustering on Consensus Terms (multi-OrgDb, option-aware)
#'
#' Applies RRvGO-based semantic similarity reduction to GO terms from either the strict
#' consensus set or exploratory enrichment set. Produces annotated cluster tables, bubble plots,
#' Applies RRvGO-based semantic similarity reduction to GO terms from strict,
#' conservative background-supported, or exploratory enrichment sets. Produces annotated cluster tables, bubble plots,
#' heatmaps, scatter plots, treemaps, and wordclouds per ontology.
#'
#' @param df_input A consensus enrichment data frame filtered for one mode (true consensus or exploratory).
#' @param label A label to use for the output subfolder (e.g. "true_consensus_with_bg").
#' @param df_input A consensus enrichment data frame filtered for one mode.
#' @param label A label to use for the output subfolder (e.g. "true_consensus_with_bg" or "conservative_bg_supported").
#' @param output_base Directory where output will be saved (default: "similarity_based_consensus").
#' Tip: pass a canonical path like file.path(outdir, "rrvgo") from the pipeline.
#' @param ontologies Vector of GO ontologies to process (default: c("BP", "MF", "CC")).
Expand Down
45 changes: 15 additions & 30 deletions R/run_echogo_pipeline.R
Original file line number Diff line number Diff line change
Expand Up @@ -234,39 +234,24 @@ run_echogo_pipeline <- function(
if (verbose) message("🧠 Running semantic clustering (RRVGO)...")
rr_fun <- if (exists("run_rrvgo_consensus_analysis")) run_rrvgo_consensus_analysis else NULL
if (!is.null(rr_fun)) {
true_consensus_df <- consensus_df %>%
dplyr::filter(
significant_in_any == TRUE,
ontology %in% c("BP", "MF", "CC"),
in_goseq == TRUE |
origin %in% c("GO terms - g:Profiler only (with BG)", "GO terms - Consensus (with BG)")
)

rr_formals <- names(formals(rr_fun))
rr_call <- list(
df_input = true_consensus_df,
label = "true_consensus_with_bg"
rr_modes <- .echogo_rrvgo_mode_tables(consensus_df)
rr_specs <- list(
list(df_input = rr_modes$true_consensus_with_bg, label = "true_consensus_with_bg"),
list(df_input = rr_modes$conservative_bg_supported, label = "conservative_bg_supported"),
list(df_input = rr_modes$exploratory_all_significant, label = "exploratory_all_significant")
)
if ("orgdb" %in% rr_formals) rr_call$orgdb <- orgdb
if ("output_base" %in% rr_formals) rr_call$output_base <- dirs$rrvgo
if ("outdir" %in% rr_formals) rr_call$outdir <- file.path(dirs$rrvgo, "true_consensus_with_bg")
do.call(rr_fun, rr_call)

extra_terms <- setdiff(
subset(consensus_df, significant_in_any)$term_id,
subset(consensus_df, origin %in% c("GO terms - GOseq only",
"GO terms - g:Profiler only (with BG)",
"GO terms - Consensus (with BG)"))$term_id
)
if (length(extra_terms) > 0) {
rr2_call <- list(
df_input = subset(consensus_df, significant_in_any),
label = "exploratory_all_significant"
)
if ("orgdb" %in% rr_formals) rr2_call$orgdb <- orgdb
if ("output_base" %in% rr_formals) rr2_call$output_base <- dirs$rrvgo
if ("outdir" %in% rr_formals) rr2_call$outdir <- file.path(dirs$rrvgo, "exploratory_all_significant")
do.call(rr_fun, rr2_call)
for (rr_spec in rr_specs) {
if (!nrow(rr_spec$df_input)) {
if (verbose) message(" · RRvGO ", rr_spec$label, ": no rows after filtering; skipping.")
next
}
rr_call <- rr_spec
if ("orgdb" %in% rr_formals) rr_call$orgdb <- orgdb
if ("output_base" %in% rr_formals) rr_call$output_base <- dirs$rrvgo
if ("outdir" %in% rr_formals) rr_call$outdir <- file.path(dirs$rrvgo, rr_spec$label)
do.call(rr_fun, rr_call)
}
if (legacy_on) .mirror_tree(dirs$rrvgo, file.path(outdir, "Similarity_based_consensus"))
} else {
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ EchoGO::echogo_quickstart(run_demo = TRUE)

This confirms that consensus scoring, RRvGO, networks, and the HTML report all run successfully.

RRvGO now separates three interpretation layers when data permit: strict `true_consensus_with_bg`, fallback `conservative_bg_supported`, and broad `exploratory_all_significant`.

For HTML report rendering, EchoGO needs Pandoc. If RStudio is installed, EchoGO will automatically use the bundled Pandoc when it can find it.

------------------------------------------------------------------------

## 🏁 Quickstart
Expand Down
9 changes: 7 additions & 2 deletions doc/EchoGO_interpretation.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ Gene-overlap GO term networks:
- `with_bg_and_nobg/` → exploratory networks including no-background terms\
Static PDF/SVG + interactive HTML versions.

Interpretation note: `with_bg/` is the conservative background-supported network layer. It can include GOseq-only and g:Profiler-with-background terms even when strict cross-method consensus is empty.

### **report/**

The automatically generated HTML report when `make_report = TRUE`.
Expand Down Expand Up @@ -450,14 +452,17 @@ In the demo snapshot, you should find:

```{r rrvgo_check}
rr_true <- file.path(out, "rrvgo", "rrvgo_true_consensus_with_bg")
rr_cons <- file.path(out, "rrvgo", "rrvgo_conservative_bg_supported")
rr_all <- file.path(out, "rrvgo", "rrvgo_exploratory_all_significant")

data.frame(
mode = c("True_Consensus_with_BG", "Exploratory_all"),
exists = c(dir.exists(rr_true), dir.exists(rr_all))
mode = c("True_Consensus_with_BG", "Conservative_BG_Supported", "Exploratory_all"),
exists = c(dir.exists(rr_true), dir.exists(rr_cons), dir.exists(rr_all))
)
```

Modern EchoGO runs may also include `rrvgo_conservative_bg_supported/`, a fallback semantic-clustering view that keeps GOseq or with-background-supported biology visible when the strict true-consensus set is empty.

If present, these folders typically contain:

- Reduced term tables (e.g., representative terms per cluster).
Expand Down
4 changes: 4 additions & 0 deletions doc/EchoGO_workflow.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ Semantic similarity reduction (per ontology BP/MF/CC):
- `rrvgo_true_consensus_with_bg/` – strict consensus clusters\
- `rrvgo_exploratory_all_significant/` – all significant terms clustered

Current EchoGO builds may also generate `rrvgo_conservative_bg_supported/` for GOseq or with-background-supported fallback clusters when the strict true-consensus set is empty.

Interpretation note: `rrvgo_true_consensus_with_bg/` should be read as strict cross-method consensus only.

These folders typically contain:

- Treemaps, bubble plots, heatmaps, wordclouds\
Expand Down
77 changes: 70 additions & 7 deletions inst/reports/echogo_report.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ suppressPackageStartupMessages({

`%||%` <- function(a, b) if (!is.null(a)) a else b
npath <- function(x) if (is.null(x)) NULL else normalizePath(x, winslash = "/", mustWork = FALSE)
truthy_flag <- function(x) {
if (is.logical(x)) return(!is.na(x) & x)
if (is.numeric(x)) return(!is.na(x) & x != 0)
if (is.character(x)) return(tolower(trimws(x)) %in% c("true", "1", "yes", "y"))
rep(FALSE, length(x))
}

# ---- HTML escaping (no knitr::escape_html dependency) ----
esc_html <- function(x){
Expand Down Expand Up @@ -496,6 +502,8 @@ if (is.na(sup_csv)) {
}
}

# Retain the legacy Unicode-labelled block for reference, but do not execute it.
if (FALSE) {
# Prefer the explicit "NONE significant" placeholder if it exists; otherwise show the ontology plots.
if (!is.na(pdf_none)) {
cat(embed_pdf_toggle("GOseq: no significant terms (FDR ≤ 0.05)", pdf_none))
Expand All @@ -504,6 +512,16 @@ if (!is.na(pdf_none)) {
cat(embed_pdf_toggle("Top 50 Enriched GO Terms — Cellular Component (GOseq, all depths)", pdf_cc))
cat(embed_pdf_toggle("Top 50 Enriched GO Terms — Molecular Function (GOseq, all depths)", pdf_mf))
}
}

# Prefer the explicit "NONE significant" placeholder if it exists; otherwise show the ontology plots.
if (!is.na(pdf_none)) {
cat(embed_pdf_toggle("GOseq: no significant terms (FDR <= 0.05)", pdf_none))
} else {
cat(embed_pdf_toggle("Top 50 Enriched GO Terms - Biological Process (GOseq, all depths)", pdf_bp))
cat(embed_pdf_toggle("Top 50 Enriched GO Terms - Cellular Component (GOseq, all depths)", pdf_cc))
cat(embed_pdf_toggle("Top 50 Enriched GO Terms - Molecular Function (GOseq, all depths)", pdf_mf))
}
```

```{r, results='asis'}
Expand Down Expand Up @@ -729,6 +747,7 @@ section_dir_tree(
```{r rrvgo-header, results='asis', eval = "rrvgo" %in% sections}
cat("## 4) RRvGO — Semantic similarity / redundancy reduction\n\n")
cat("**How to read these panels:** RRvGO clusters semantically similar GO terms to reduce redundancy. In the treemap, larger tiles represent more representative or higher-scoring terms. These views condense long lists into coherent functional themes in both True Consensus and Exploratory modes.\n\n")
cat("This report separates three RRvGO views when available: `True Consensus` for strict cross-method consensus, `Conservative BG-Supported` for GOseq or with-background-supported terms, and `Exploratory` for the full significant GO landscape.\n\n")

stopifnot(exists("idx"))

Expand Down Expand Up @@ -771,17 +790,22 @@ pick_rrvgo_rows <- function(mode_rx, ont) {

# Modes & ontologies
onts <- c("BP","CC","MF")
modes_named <- c("true_consensus_with_bg" = "True Consensus",
"exploratory_all_significant" = "Exploratory")
modes_named <- c(
"true_consensus_with_bg" = "True Consensus",
"conservative_bg_supported" = "Conservative BG-Supported",
"exploratory_all_significant" = "Exploratory"
)

# Gather rows
rows <- list()
for (ont in onts) {
r1 <- pick_rrvgo_rows("true_consensus_with_bg", ont)
if (nrow(r1)) r1$mode <- modes_named[["true_consensus_with_bg"]]
r2 <- pick_rrvgo_rows("exploratory_all_significant", ont)
if (nrow(r2)) r2$mode <- modes_named[["exploratory_all_significant"]]
rows <- c(rows, list(r1, r2))
r2 <- pick_rrvgo_rows("conservative_bg_supported", ont)
if (nrow(r2)) r2$mode <- modes_named[["conservative_bg_supported"]]
r3 <- pick_rrvgo_rows("exploratory_all_significant", ont)
if (nrow(r3)) r3$mode <- modes_named[["exploratory_all_significant"]]
rows <- c(rows, list(r1, r2, r3))
}
rr_tbl <- dplyr::bind_rows(rows)

Expand All @@ -792,7 +816,7 @@ if (!nrow(rr_tbl)) {
rr_tbl <- rr_tbl |>
dplyr::distinct(OrgDb, mode, ontology, rel_path, full_path, .keep_all = TRUE) |>
dplyr::arrange(OrgDb,
factor(mode, levels = c("True Consensus","Exploratory")),
factor(mode, levels = c("True Consensus","Conservative BG-Supported","Exploratory")),
factor(ontology, levels = onts)) |>
dplyr::mutate(exists = exists_file(.data$full_path))

Expand All @@ -807,8 +831,13 @@ if (!nrow(rr_tbl)) {
# ---- Embed by OrgDb × Mode × Ontology (ABS path) ----
for (odb in unique(rr_tbl$OrgDb)) {
cat(sprintf("\n### %s\n\n", odb))
for (m in c("True Consensus","Exploratory")) {
for (m in c("True Consensus","Conservative BG-Supported","Exploratory")) {
cat(sprintf("#### %s\n\n", m))
mode_rows <- rr_tbl[rr_tbl$OrgDb == odb & rr_tbl$mode == m & rr_tbl$exists, ]
if (!nrow(mode_rows)) {
cat("<p><em>No treemaps were generated for this mode.</em></p>\n\n")
next
}
for (ont in onts) {
sub <- rr_tbl[rr_tbl$OrgDb == odb & rr_tbl$mode == m & rr_tbl$ontology == ont & rr_tbl$exists, ]
if (nrow(sub)) {
Expand All @@ -830,6 +859,7 @@ section_dir_tree(
roots = { rts <- c("similarity_based_consensus","Similarity_based_consensus","rrvgo"); rts[!duplicated(tolower(rts))] },
notes = list(
"rrvgo_true_consensus_with_bg/OrgDb=<pkg>/" = "True Consensus RRvGO outputs per OrgDb.",
"rrvgo_conservative_bg_supported/OrgDb=<pkg>/" = "Conservative background-supported RRvGO outputs per OrgDb.",
"rrvgo_exploratory_all_significant/OrgDb=<pkg>/" = "Exploratory RRvGO outputs per OrgDb.",
"…/rrvgo_<ONT>_treemap.pdf" = "Redundancy-reduced treemaps (ONT ∈ {BP, CC, MF}).",
"…/rrvgo_<ONT>_scatterplot.pdf" = "Semantic scatterplots.",
Expand All @@ -855,6 +885,8 @@ stopifnot(exists("idx"))
# Summary CSVs (unchanged logic, now via idx)
sum_bg <- get_one("^(Network_analysis|network_analysis|networks)/(summary_with_bg\\.csv|with_bg/network_summary_.+\\.csv)$")
sum_ex <- get_one("^(Network_analysis|network_analysis|networks)/(summary_with_bg_and_nobg\\.csv|with_bg_and_nobg/network_summary_.+\\.csv)$")
sum_bg_tbl <- if (!is.na(sum_bg)) readr::read_csv(sum_bg, show_col_types = FALSE) else NULL
sum_ex_tbl <- if (!is.na(sum_ex)) readr::read_csv(sum_ex, show_col_types = FALSE) else NULL

if (!is.na(sum_bg)) print(
knitr::kable(
Expand All @@ -871,6 +903,21 @@ if (!is.na(sum_ex)) print(
)
)

if (!is.null(sum_bg_tbl) && !is.null(sum_ex_tbl)) {
same_network_summary <- dplyr::full_join(
sum_bg_tbl %>% dplyr::select(.data$ontology, total_terms_bg = .data$total_terms, total_edges_bg = .data$total_edges),
sum_ex_tbl %>% dplyr::select(.data$ontology, total_terms_ex = .data$total_terms, total_edges_ex = .data$total_edges),
by = "ontology"
) %>%
dplyr::filter(!is.na(.data$total_terms_bg), !is.na(.data$total_terms_ex))

if (nrow(same_network_summary) &&
all(same_network_summary$total_terms_bg == same_network_summary$total_terms_ex) &&
all(same_network_summary$total_edges_bg == same_network_summary$total_edges_ex)) {
cat("<p><em>Network note: the with-background and exploratory summaries are identical here because the no-background-only terms did not add extra graph edges after the network gene-count and overlap filters were applied.</em></p>\n\n")
}
}

# Interactive widgets from the index (absolute paths via idx$full_path)
grab_net <- function(mode_dir, ont) {
rx <- sprintf("(^|/)(Network_analysis|network_analysis|networks)/%s/.+%s.+filtered\\.html$", mode_dir, ont)
Expand Down Expand Up @@ -921,6 +968,22 @@ if ("evaluation" %in% sections) cat("## 6) Consensus Evaluation — EQI, rarefac

cat("**How to read these panels:** EQI and fold-enrichment distributions summarize overall term quality and effect sizes; cumulative and rarefaction curves track how many unique terms appear as tools or species are added. A plateau in True Consensus with continued growth in Exploratory indicates robust, non-redundant signal; the network-complexity table quantifies structural differences between modes.\n\n")

strict_consensus_term_count <- NA_integer_
if (exists("cons_df") && is.data.frame(cons_df) && nrow(cons_df) &&
all(c("origin", "significant_in_any", "ontology") %in% names(cons_df))) {
strict_consensus_term_count <- sum(
truthy_flag(cons_df$significant_in_any) &
cons_df$origin == "GO terms - Consensus (with BG)" &
cons_df$ontology %in% c("BP", "MF", "CC"),
na.rm = TRUE
)
}

cat("**Evaluation note:** EchoGO's evaluation PDF filenames still retain the legacy `true_consensus` label from earlier releases. Read those files as the package's with-background evaluation view, not as proof that strict cross-method consensus terms exist in the current panel.\n\n")
if (!is.na(strict_consensus_term_count) && strict_consensus_term_count == 0L) {
cat("**Panel note:** This run has 0 strict with-background consensus GO terms, so any evaluation files carrying `true_consensus` are legacy-named outputs rather than strict-consensus evidence.\n\n")
}

# --- Tiny guard (defined here in case it's not already available) ---
if (!exists("safe_embed_pdf")) {
safe_embed_pdf <- function(title, path){
Expand Down
2 changes: 1 addition & 1 deletion man/run_all_networks.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading