From 28e090d605e41be83c10ad2a9e7463393b264592 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:14:30 +0200 Subject: [PATCH 01/92] update description file --- DESCRIPTION | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index b44fb89..120316a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,9 +29,15 @@ License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 +Imports: + methods, + AnnotationDbi, + GO.db Suggests: knitr, rmarkdown, + clusterProfiler, + gprofiler2, org.Hs.eg.db, testthat (>= 3.0.0) VignetteBuilder: knitr From 18696db37aad1a2c04c8f277238c2bfe0d1c5f48 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:15:51 +0200 Subject: [PATCH 02/92] update the main function EMMA_run --- R/EMMA_run.R | 94 ++++++++++++++++++++++------------------------------ 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 05a978c..60e997f 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -2,19 +2,30 @@ #' #' This function executes functional enrichment analysis using existing tools #' and captures the associated parameters and provenance information for the -#' analysis during runtime. +#' analysis when available during runtime. +#' #' @param expr A function call that performs functional enrichment analysis. #' The call is captured and executed by EMMA to record analysis parameters and #' provenance information #' @param envir An environment in which to evaluate `expr` +#' @param session Logical, indicating whether to store sessionInfo or not. +#' It defaults to (`TRUE`) saving the session +#' @param args_form A character string indicating whether to store the evaluated +#' or the unevaluated arguments. It default to store the evaluated arguments #' #' @returns Functional enrichment analysis results in the native format #' returned by the original `expr` #' @export #' #' @examples -#' EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP")) -EMMA_run <- function(expr, envir = parent.frame()) { +#' data("de_res_IFNg_vs_naive", package = "EMMA") +#' data("universe", package = "EMMA") +#' EMMA_run(clusterProfiler::enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP")) +EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, + args_form = c("evaluated", "unevaluated")) { + args_form <- match.arg(args_form) # capture call call <- substitute(expr) @@ -23,46 +34,25 @@ EMMA_run <- function(expr, envir = parent.frame()) { if (!is.call(call)) { stop("`expr` must be a function call, e.g. enrichGO(...)!") } - - # capture function name - function_name <- paste(deparse(call[[1]]), collapse = "") + # capture call information + info_call <- .EMMA_capture_call_info(call = call) + function_name <- info_call$function_name + package_name <- info_call$package_name + package_version <- info_call$package_version + # capture args (unevaluated) arg_list <- as.list(call)[-1] - arg_names <- names(arg_list) - # now we'll put some warning on important args if - # the user did not define them (like the fdr correction ...) - - checks <- list( - list(params = c("pAdjustMethod", "correction_method"), - label = "multiple-testing correction method"), - list(params = c("universe", "background"), - label = "background gene set") - ) - - - for (check in checks) { - if (!any(check$params %in% arg_names)) { - warning( - sprintf( - "No %s was specified for %s(). -Consider using the corresponding parameter for your call.", - check$label, - function_name - ), - call. = FALSE - ) - } - } - - # maybe warn when the organism and the gene names dont match? like u have - # mouse data but u defined organism as human + # some good practice warning, i.e. when multiple testing correction is skipped + # or bg geneset not set + .EMMA_warnings(arg_names = arg_names, + function_name = function_name) #capture analysis time start_time <- Sys.time() - message("Running Enrichment Analysis with ", function_name, " ...") # maybe we can print the list of arg used also in the msg? + message("Running Enrichment Analysis with ", function_name, " ...") # capture the value of the arguments args <- lapply(arg_list, eval, envir = envir) @@ -70,30 +60,24 @@ Consider using the corresponding parameter for your call.", # get the function fun <- eval(call[[1]], envir = envir) - # run the analysis + ##### run the analysis #### results <- do.call(fun, args) - # capture pkg used - enrich_function <- match.fun(function_name) - pkg <- environmentName(environment(enrich_function)) - - - # store everything - EMMA_record <- list( - call = call, - package = pkg, - package_version = if (!is.na(pkg)) - as.character(packageVersion(pkg)) else NA, - arguments = args, - gene_set_library = NA, #placeholder - gene_set_library_version = NA, - organism = NA, - runtime = start_time, - session = sessionInfo() + # capture metadata from the used function and arguments + metadata <- .EMMA_get_metadata( + function_name = function_name, + package_name = package_name, + args = args ) - # store the emma records as attribute of the results obj + # record everything in EMMA_record + EMMA_record <- .EMMA_build_record(call, function_name, package_name, + package_version, args, + arg_list, args_form, metadata, + start_time, session) + + # store the EMMA_record as attribute of the results obj attr(results, "EMMA_record") <- EMMA_record - + return(results) } From 2b3ea7f9bce37ff1906f22b34f35c3bd6830f5e5 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:17:41 +0200 Subject: [PATCH 03/92] update the functions EMMA_show, EMMA_freeze and EMMA_explain with prototype functions/placeholders/docs --- R/EMMA_explain.R | 3 +++ R/EMMA_freeze.R | 44 +++++++++++++++++++++++++++++++++++++++++--- R/EMMA_show.R | 37 ++++++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/R/EMMA_explain.R b/R/EMMA_explain.R index 3a32aea..b8ce103 100644 --- a/R/EMMA_explain.R +++ b/R/EMMA_explain.R @@ -4,6 +4,9 @@ #' to a Materials and Methods section of a paper, by summarizing the executed #' call, the parameters, software context, and reference databases used. #' +#' @param res A functional enrichment analysis results object as returned by +#' `EMMA_run()`. Its attributes contain `EMMA_record` of class `EMMARecord`, which +#' contains all provenance information of the performed FEA EMMA_explain <- function(res){ # code } \ No newline at end of file diff --git a/R/EMMA_freeze.R b/R/EMMA_freeze.R index 0b2efb0..c71e18c 100644 --- a/R/EMMA_freeze.R +++ b/R/EMMA_freeze.R @@ -1,8 +1,46 @@ #' EMMA_freeze #' #' This function records the R environment during analysis runtime and generates -#' a lockfile that can be used with tools such as `renv` +#' a lockfile that can be used with tools such as `renv`. +#' By default, all currently loaded namespaces are recorded. #' -EMMA_freeze <- function(){ - #code +#' @param project Character string corresponding to the path to the project +#' directory where the lockfile should be written. If the directory does not +#' exist, it will be created. It defaults to the current working directory +#' @param file Character string refering to the name of the lockfile to generate. +#' It defaults to "renv.lock" +#' @param pkgs Character vector of package names to snapshot. It defaults to all +#' currently loaded namespaces via `loadedNamespaces()` +#' +#' @details +#' This function calls `renv::snapshot()` with the specified packages. +#' The resulting lockfile can later be restored with `renv::restore()` to +#' recreate the same package environment. +#' +#' @returns TODO json file like to create a lock.file? +#' +#' @seealso \code{\link[renv]{snapshot}}, \code{\link[renv]{restore}} +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' # Create a lockfile in a separate directory +#' EMMA_freeze(project = "emma_env") +#' +#' # Restore later with renv +#' renv::restore(project = "emma_env") +#' } +EMMA_freeze <- function(project = getwd(), + file = "renv.lock", + pkgs = loadedNamespaces()){ + + # if (!dir.exists(project)) dir.create(project, recursive = TRUE) + # + # renv::snapshot( + # project = project, + # lockfile = file, + # packages = pkgs + # ) + # } \ No newline at end of file diff --git a/R/EMMA_show.R b/R/EMMA_show.R index b8ad0a2..9746a2e 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -6,18 +6,45 @@ #' @param res A functional enrichment analysis results object as returned by #' `EMMA_run()` #' -#' @returns A list of the recorded information +#' @returns NULL #' @export #' #' @examples -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP")) +#' data("de_res_IFNg_vs_naive", package = "EMMA") +#' data("universe", package = "EMMA") +#' library("clusterProfiler") +#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP")) #' EMMA_show(res) EMMA_show <- function(res){ if ("EMMA_record" %in% names(attributes(res))) { message("Found EMMA record!!") emma_rec <- attr(res, "EMMA_record") - # should think of a better way to show the records - str(emma_rec) + if (!is.list(emma_rec)) { + stop("'EMMA_record' must be a list!") + } + + if (is.list(res) && "result" %in% names(res)) { + + cat("Number of Pathways: ", NROW(res$result), "\n") + } else { + cat("Number of Pathways: ", NROW(res), "\n") + } + + pkg_info <- emma_rec$method + db_info <- emma_rec$annotation + + cat("Call: ", paste(deparse(emma_rec$call), collapse = " "), " \n") + cat("Package: ", paste(pkg_info$package_name , "v.", + pkg_info$package_version), " \n") + cat("Organism : ", db_info$organism, " \n") + cat("Gene set library : ", paste(db_info$gene_set_db, collapse = ", "), " \n") + cat("Gene set library version : ",db_info$gene_set_db_version, " \n") + cat("\n") + + } else { + warning("No `EMMA_record` attribute was found") } -} \ No newline at end of file +} From e5ea209b43c707639c23f54208fcdfed3dae9411 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:18:18 +0200 Subject: [PATCH 04/92] update man --- man/EMMA_explain.Rd | 5 +++++ man/EMMA_freeze.Rd | 36 ++++++++++++++++++++++++++++++++++-- man/EMMA_run.Rd | 21 ++++++++++++++++++--- man/EMMA_show.Rd | 10 ++++++---- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/man/EMMA_explain.Rd b/man/EMMA_explain.Rd index 03e177e..76ca399 100644 --- a/man/EMMA_explain.Rd +++ b/man/EMMA_explain.Rd @@ -6,6 +6,11 @@ \usage{ EMMA_explain(res) } +\arguments{ +\item{res}{A functional enrichment analysis results object as returned by +\code{EMMA_run()}. Its attributes contain \code{EMMA_record} of class \code{EMMARecord}, which +contains all provenance information of the performed FEA} +} \description{ This function generates a human-readable description of the FEA, similar to a Materials and Methods section of a paper, by summarizing the executed diff --git a/man/EMMA_freeze.Rd b/man/EMMA_freeze.Rd index 7f7c69f..49813f4 100644 --- a/man/EMMA_freeze.Rd +++ b/man/EMMA_freeze.Rd @@ -4,9 +4,41 @@ \alias{EMMA_freeze} \title{EMMA_freeze} \usage{ -EMMA_freeze() +EMMA_freeze(project = getwd(), file = "renv.lock", pkgs = loadedNamespaces()) +} +\arguments{ +\item{project}{Character string corresponding to the path to the project +directory where the lockfile should be written. If the directory does not +exist, it will be created. It defaults to the current working directory} + +\item{file}{Character string refering to the name of the lockfile to generate. +It defaults to "renv.lock"} + +\item{pkgs}{Character vector of package names to snapshot. It defaults to all +currently loaded namespaces via \code{loadedNamespaces()}} +} +\value{ +TODO json file like to create a lock.file? } \description{ This function records the R environment during analysis runtime and generates -a lockfile that can be used with tools such as \code{renv} +a lockfile that can be used with tools such as \code{renv}. +By default, all currently loaded namespaces are recorded. +} +\details{ +This function calls \code{renv::snapshot()} with the specified packages. +The resulting lockfile can later be restored with \code{renv::restore()} to +recreate the same package environment. +} +\examples{ +\dontrun{ +# Create a lockfile in a separate directory +EMMA_freeze(project = "emma_env") + +# Restore later with renv +renv::restore(project = "emma_env") +} +} +\seealso{ +\code{\link[renv]{snapshot}}, \code{\link[renv]{restore}} } diff --git a/man/EMMA_run.Rd b/man/EMMA_run.Rd index bebb97d..f6068ae 100644 --- a/man/EMMA_run.Rd +++ b/man/EMMA_run.Rd @@ -4,7 +4,12 @@ \alias{EMMA_run} \title{EMMA_run} \usage{ -EMMA_run(expr, envir = parent.frame()) +EMMA_run( + expr, + envir = parent.frame(), + session = TRUE, + args_form = c("evaluated", "unevaluated") +) } \arguments{ \item{expr}{A function call that performs functional enrichment analysis. @@ -12,6 +17,12 @@ The call is captured and executed by EMMA to record analysis parameters and provenance information} \item{envir}{An environment in which to evaluate \code{expr}} + +\item{session}{Logical, indicating whether to store sessionInfo or not. +It defaults to (\code{TRUE}) saving the session} + +\item{args_form}{A character string indicating whether to store the evaluated +or the unevaluated arguments. It default to store the evaluated arguments} } \value{ Functional enrichment analysis results in the native format @@ -20,8 +31,12 @@ returned by the original \code{expr} \description{ This function executes functional enrichment analysis using existing tools and captures the associated parameters and provenance information for the -analysis during runtime. +analysis when available during runtime. } \examples{ -EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP")) +data("de_res_IFNg_vs_naive", package = "EMMA") +data("universe", package = "EMMA") +EMMA_run(clusterProfiler::enrichGO(gene = rownames(de_res_IFNg_vs_naive), +universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +ont = "BP")) } diff --git a/man/EMMA_show.Rd b/man/EMMA_show.Rd index 14f6ee9..f6e5e32 100644 --- a/man/EMMA_show.Rd +++ b/man/EMMA_show.Rd @@ -10,15 +10,17 @@ EMMA_show(res) \item{res}{A functional enrichment analysis results object as returned by \code{EMMA_run()}} } -\value{ -A list of the recorded information -} \description{ This function prints the EMMA record associated with a functional enrichment analysis object, including the executed call, parameters, and provenance information. } \examples{ -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP")) +data("de_res_IFNg_vs_naive", package = "EMMA") +data("universe", package = "EMMA") +library("clusterProfiler") +res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +ont = "BP")) EMMA_show(res) } From ef2b2d3259011dcea06bb1713426c2447ffe2f4c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:18:38 +0200 Subject: [PATCH 05/92] update namespace --- NAMESPACE | 6 ++++++ R/EMMA-pkg.R | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 4ee2689..012134d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,11 @@ # Generated by roxygen2: do not edit by hand +export(EMMA_freeze) export(EMMA_run) export(EMMA_show) +export(getEMMARecord) +import(GO.db) +import(methods) +importFrom(AnnotationDbi,metadata) importFrom(utils,packageVersion) +importFrom(utils,sessionInfo) diff --git a/R/EMMA-pkg.R b/R/EMMA-pkg.R index 328fa03..8af20dd 100644 --- a/R/EMMA-pkg.R +++ b/R/EMMA-pkg.R @@ -3,8 +3,10 @@ #' EMMA stands for Enrichment Methods Matter. #' And EMMA stands to help you in realizing it. #' -#' -#' @importFrom utils packageVersion +#' @import methods +#' @import GO.db +#' @importFrom AnnotationDbi metadata +#' @importFrom utils packageVersion sessionInfo #' @name EMMA-pkg #' @docType package "_PACKAGE" From f50039480e4e2ded9b0826933f8c71cbb70e8b7f Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:19:13 +0200 Subject: [PATCH 06/92] add example data --- R/EMMA-data.R | 46 +++++++++++++++++++++++++ inst/scripts/create_datasets_examples.R | 2 +- man/de_res_IFNg_vs_naive.Rd | 31 +++++++++++++++++ man/getEMMARecord.Rd | 26 ++++++++++++++ man/universe.Rd | 30 ++++++++++++++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 R/EMMA-data.R create mode 100644 man/de_res_IFNg_vs_naive.Rd create mode 100644 man/getEMMARecord.Rd create mode 100644 man/universe.Rd diff --git a/R/EMMA-data.R b/R/EMMA-data.R new file mode 100644 index 0000000..2d8b7ea --- /dev/null +++ b/R/EMMA-data.R @@ -0,0 +1,46 @@ +#' A sample `data.frame` containing Differential Expression Analysis, generated +#' with `DESeq2` +#' +#' @details This `data.frame` object contains the results of a Differential +#' Expression Analysis performed on data from the `macrophage` package, more +#' precisely contrasting the counts from naive macrophage to those associated +#' with IFNg. +#' +#' The code to create said object can be found in the folder `/inst/scripts` in +#' the EMMA package, the file is called `create_datasets_examples.R`. +#' +#' @return A sample `data.frame` object, extracted from `DESeq2` results +#' +#' @format A `data.frame` object +#' +#' +#' @references Alasoo, et al. "Shared genetic effects on chromatin and gene +#' expression indicate a role for enhancer priming in immune response", +#' Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. +#' +#' @name de_res_IFNg_vs_naive +#' @docType data +NULL + +#' A sample `character vector` containing the background gene list used to +#' perform FEA on the `macrophage` dataset +#' +#' @details This `character vector` object that contains the assay's `rownames` +#' of the `macrophage` data +#' +#' The code to create said object can be found in the folder `/inst/scripts` in +#' the EMMA package, the file is called `create_datasets_examples.R`. +#' +#' @return A sample `character vector` containing the assay's `rownames` +#' of the `macrophage` data +#' +#' @format A `character vector` +#' +#' +#' @references Alasoo, et al. "Shared genetic effects on chromatin and gene +#' expression indicate a role for enhancer priming in immune response", +#' Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. +#' +#' @name universe +#' @docType data +NULL \ No newline at end of file diff --git a/inst/scripts/create_datasets_examples.R b/inst/scripts/create_datasets_examples.R index 1603ca9..cdf1187 100644 --- a/inst/scripts/create_datasets_examples.R +++ b/inst/scripts/create_datasets_examples.R @@ -14,7 +14,7 @@ dds_macrophage # set seed for reproducibility set.seed(42) -# sample randomly for 1k genes +# sample randomly for 2k genes selected_genes <- sample(rownames(dds_macrophage), 2000) dds_macrophage <- dds_macrophage[selected_genes, ] diff --git a/man/de_res_IFNg_vs_naive.Rd b/man/de_res_IFNg_vs_naive.Rd new file mode 100644 index 0000000..dae7c8f --- /dev/null +++ b/man/de_res_IFNg_vs_naive.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/EMMA-data.R +\docType{data} +\name{de_res_IFNg_vs_naive} +\alias{de_res_IFNg_vs_naive} +\title{A sample \code{data.frame} containing Differential Expression Analysis, generated +with \code{DESeq2}} +\format{ +A \code{data.frame} object +} +\value{ +A sample \code{data.frame} object, extracted from \code{DESeq2} results +} +\description{ +A sample \code{data.frame} containing Differential Expression Analysis, generated +with \code{DESeq2} +} +\details{ +This \code{data.frame} object contains the results of a Differential +Expression Analysis performed on data from the \code{macrophage} package, more +precisely contrasting the counts from naive macrophage to those associated +with IFNg. + +The code to create said object can be found in the folder \verb{/inst/scripts} in +the EMMA package, the file is called \code{create_datasets_examples.R}. +} +\references{ +Alasoo, et al. "Shared genetic effects on chromatin and gene +expression indicate a role for enhancer priming in immune response", +Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. +} diff --git a/man/getEMMARecord.Rd b/man/getEMMARecord.Rd new file mode 100644 index 0000000..cdc4023 --- /dev/null +++ b/man/getEMMARecord.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/geEMMARecord.R +\name{getEMMARecord} +\alias{getEMMARecord} +\title{getEMMARecord} +\usage{ +getEMMARecord(res) +} +\arguments{ +\item{res}{Functional Enrichment Analysis results (enrichResult, gseaResult ...)} +} +\value{ +list of metadata recorded during FEA runtime +} +\description{ +getEMMARecord +} +\examples{ +data("de_res_IFNg_vs_naive", package = "EMMA") +data("universe", package = "EMMA") +library("clusterProfiler") +res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +ont = "BP")) +getEMMARecord(res) +} diff --git a/man/universe.Rd b/man/universe.Rd new file mode 100644 index 0000000..7a7e772 --- /dev/null +++ b/man/universe.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/EMMA-data.R +\docType{data} +\name{universe} +\alias{universe} +\title{A sample \verb{character vector} containing the background gene list used to +perform FEA on the \code{macrophage} dataset} +\format{ +A \verb{character vector} +} +\value{ +A sample \verb{character vector} containing the assay's \code{rownames} +of the \code{macrophage} data +} +\description{ +A sample \verb{character vector} containing the background gene list used to +perform FEA on the \code{macrophage} dataset +} +\details{ +This \verb{character vector} object that contains the assay's \code{rownames} +of the \code{macrophage} data + +The code to create said object can be found in the folder \verb{/inst/scripts} in +the EMMA package, the file is called \code{create_datasets_examples.R}. +} +\references{ +Alasoo, et al. "Shared genetic effects on chromatin and gene +expression indicate a role for enhancer priming in immune response", +Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. +} From 50cb3ed8ad968dce65cad48dea21fab9b6d42b1b Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:20:08 +0200 Subject: [PATCH 07/92] add helper functions to capture metadata base on the call passed to EMMA_run --- R/EMMA_internal-utils.R | 281 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 R/EMMA_internal-utils.R diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R new file mode 100644 index 0000000..0e3656e --- /dev/null +++ b/R/EMMA_internal-utils.R @@ -0,0 +1,281 @@ +# metadata capture ------------------------------------------------------------- +#' get the basic structure for the metadata elements to be captured depending on +#' the method +#' +#' @noRd +.EMMA_empty_metadata <- function() { + list( + organism = NA_character_, + gene_set_db = NA_character_, + gene_set_db_version = NA_character_ + ) +} + +#' depending on the function/pkg, decide the right function dispatch +#' +#' @param function_name A character string specifying the function name used to +#' perform FEA +#' @param package_name A character string containing the package name used to +#' perform FEA +#' @param args A list containing the evaluated arguments passed into the +#' function call to perform FEA +#' +#' @noRd +.EMMA_get_metadata <- function(function_name, + package_name, + args) { + switch( + package_name, + clusterProfiler = .EMMA_get_clusterprofiler_metadata(function_name, args), + gprofiler2 = .EMMA_get_gprofiler2_metadata(args), + mosdef = .EMMA_cp_GO_metadata(args$mapping), + .EMMA_empty_metadata() + ) +} + +#' get metadata from clusterProfiler functions +#' @param function_name A character string specifying the function name used to +#' perform FEA +#' +#' @param args A list containing the evaluated arguments passed into the +#' function call to perform FEA +#' +#' @noRd +.EMMA_get_clusterprofiler_metadata <- function(function_name, args) { + switch( + function_name, + enrichGO = .EMMA_cp_GO_metadata(args$OrgDb), + gseGO = .EMMA_cp_GO_metadata(args$OrgDb), + groupGO = .EMMA_cp_GO_metadata(args$OrgDb), + enrichKEGG = .EMMA_cp_KEGG_metadata(args), + gseKEGG = .EMMA_cp_KEGG_metadata(args), + .EMMA_empty_metadata() + ) +} + +#' extract the organism from org,*.eg.db objects +#' @param orgdb Organism object from org.*.eg.db packages +#' +#' @noRd +.EMMA_get_organism_from_OrgDb <- function(orgdb) { + + if (is.null(orgdb)) { + return(NA_character_) + } + + if (is.character(orgdb) && length(orgdb) == 1) { + if (!exists(orgdb, mode = "S4")) { + stop(orgdb, + " was provided as a string, but no loaded object with that name was found. ", + "Please load the corresponding org.*.db library") + } + orgdb <- get(orgdb) + } + + md <- AnnotationDbi::metadata(orgdb) + organism <- md$value[md$name == "ORGANISM"] + + if (length(organism) != 1L) { + return(NA_character_) + } + return(organism) +} + + +#' assemble metadata element from clusterProfiler GO analyses +#' +#' @param org +#' +#' @noRd +.EMMA_cp_GO_metadata <- function(org) { + meta <- list() + + meta$organism <- .EMMA_get_organism_from_OrgDb(org) + meta$gene_set_db <- "GO" + meta$gene_set_db_version <- if (requireNamespace("GO.db", quietly = TRUE)) { + as.character(utils::packageVersion("GO.db")) + } else { + NA_character_ + } + + return(meta) +} + + +### not tested yet since the kegg server is down and the function couldn't work + +#' assemble metadata element from clusterProfiler KEGG analyses +#' +#' @param args A list containing the evaluated arguments passed into the +#' function call to perform FEA +#' +#' @noRd +.EMMA_cp_KEGG_metadata <- function(args) { + meta <- list() + + meta$organism <- args$organism + meta$gene_set_db <- "KEGG" + meta$gene_set_db_version <- NA_character_ + + return(meta) +} + +#' assemble metadata element from gprofiler2 +#' +#' @param args A list containing the evaluated arguments passed into the +#' function call to perform FEA +#' +#' @noRd +.EMMA_get_gprofiler2_metadata <- function(args) { + meta <- list() + + version_info <- gprofiler2::get_version_info() + + meta$organism <- if (!is.null(args$organism)) args$organism else NA_character_ + + sources_used <- if (!is.null(args$sources)) { + args$sources + } else { + names(version_info[["sources"]]) + } + + # in case the source is GO, we extract all the dbs ... + if ("GO" %in% sources_used) { + sources_used <- unique(c( + setdiff(sources_used, "GO"), + "GO:BP", "GO:CC", "GO:MF" + )) + } + + meta$gene_set_db <- sources_used + + meta$gene_set_db_version <- vapply( + version_info[["sources"]][sources_used], function(x) x[["version"]], + character(1) + ) # needs more work when only GO, no need to print 3 times the same version + # also, differentiate between which version corresponds to which db + + return(meta) +} + +# call capture --------------------- +#' @noRd +.EMMA_capture_call_info <- function(call, envir = parent.frame()) { + + # param checks + if (!is.call(call)) { + stop("`call` must be a function call", call. = FALSE) + } + + # capture function name + call_name <- call[[1]] + + # when we only use function name e.g. enrichGO(...) + if (is.symbol(call_name)) { + function_name <- as.character(call_name) + + fun <- get(function_name, envir = envir, mode = "function") + + pkg <- utils::packageName(environment(fun)) + package_name <- if (is.null(pkg) || pkg == "" ) NA_character_ else pkg + + pkg_version <- if (!is.na(package_name)) { + as.character(packageVersion(package_name))} + else NA_character_ + + return(list( + function_name = function_name, + package_name = package_name, + package_version = pkg_version + )) + } + + # when we use function name with namespace e.g. clusterProfiler::enrichGO(...) + if (is.call(call_name) && + length(call_name) == 3L && + identical(call_name[[1]], as.symbol("::"))) { + + package_name <- as.character(call_name[[2]]) + function_name <- as.character(call_name[[3]]) + pkg_version <- if (!is.na(package_name)) { + as.character(utils::packageVersion(package_name)) + } else { + NA_character_ + } + + return(list( + function_name = function_name, + package_name = package_name, + package_version = pkg_version + )) + } + + stop( + "Unsupported call format. Use a direct function call like `enrichGO(...)` or `pkg::fun(...)`", + call. = FALSE + ) +} + + + +# build EMMA_record ----------- +#' @noRd +.EMMA_build_record <- function(call, function_name, package_name, + package_version, args, arg_list, + args_form, metadata, + start_time,session) { + emma_rec <- list( + method = list( + call = call, + function_name = function_name, + package_name = package_name, + package_version = package_version + ), + input = list( + arguments = if (args_form == "evaluated") args else arg_list + ), + annotation = list( + organism = metadata$organism, + gene_set_db = metadata$gene_set_db, + gene_set_db_version = metadata$gene_set_db_version + ), + timestamp = start_time, + session_info = if (isTRUE(session)) sessionInfo() else NULL, + user_metadata = list(),# free form user additions + emma_version = as.character(packageVersion(pkg = "EMMA")) + ) + + return(emma_rec) + +} + +# good practice warnings --------- +#' @noRd +.EMMA_warnings <- function(arg_names, function_name){ + checks <- list( + list( + params = c("pAdjustMethod", "correction_method", "do_padj"), + label = "multiple-testing correction method" + ), + list( + params = c("universe", "background", "custom_bg", "bg_genes"), + label = "background gene set" + ) + ) + + for (check in checks) { + if (!any(check$params %in% arg_names)) { + warning( + sprintf( + "No %s was specified for %s(). +Consider using the corresponding parameter for your call.", + check$label, + function_name + ), + call. = FALSE + ) + } + } + invisible() +} + From 1c4eef12bcf8282f0f6b09b3ce49892700137c19 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:20:20 +0200 Subject: [PATCH 08/92] setup tests --- tests/testthat/setuptests_EMMA.R | 9 ++++++++ tests/testthat/test-EMMA.R | 38 ++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index 9d480ed..f0e1294 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -1,2 +1,11 @@ +suppressPackageStartupMessages( + library("clusterProfiler") +) + +suppressPackageStartupMessages( + library("org.Hs.eg.db") +) + data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") + diff --git a/tests/testthat/test-EMMA.R b/tests/testthat/test-EMMA.R index 713438a..ab51bea 100644 --- a/tests/testthat/test-EMMA.R +++ b/tests/testthat/test-EMMA.R @@ -12,7 +12,9 @@ test_that("EMMA_run", { expect_true("EMMA_record" %in% names(attributes(fea_res))) - expect_type(attr(fea_res, "EMMA_record"), "list") + expect_true(is.list(attr(fea_res, "EMMA_record"))) + + expect_true(length(attr(fea_res, "EMMA_record")) == 7) expect_error(EMMA_run(enrichGO,gene = rownames(de_res_IFNg_vs_naive), keyType = "ENSEMBL", @@ -23,4 +25,36 @@ test_that("EMMA_run", { universe = universe, readable = TRUE)) -}) \ No newline at end of file + expect_error(EMMA_run("enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = 'ENSEMBL', + OrgDb = org.Hs.eg.db)")) + + expect_error(EMMA_run(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE)) + + expect_warning(EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE))) + + expect_warning(EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + pAdjustMethod = "BH", + readable = TRUE))) + + + +}) + From 52cdd99d2df6c263ed3d2da05298edde0b5a2409 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:20:29 +0200 Subject: [PATCH 09/92] update vignette --- vignettes/Introduction_to_EMMA.Rmd | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 3e45a11..f3425b4 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -46,6 +46,8 @@ knitr::opts_chunk$set( # Introduction {#introduction} +TODO: add a paragraph of why we need this pkg, and which gap is this filling + This vignette describes how to use the `r BiocStyle::Biocpkg("EMMA")` package to perform Functional Enrichment Analysis using existing tools (e.g. `clusterProfiler`, `topGO`, `Enrichr`, `gprofiler2`), while systematically @@ -176,7 +178,7 @@ de_res_list <- list() for (i in names(contrast_list)) { de_res <- as.data.frame(contrast_list[[i]]) de_res <- de_res[order(de_res$padj), ] - de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] + #de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] de_res_list[[i]] <- de_res } @@ -199,20 +201,21 @@ it is, while capturing the associated parameters and provenance information: de_res_IFNg_vs_naive <- de_res_list$IFNg_vs_naive # perform FEA, but with EMMA! -fea_res <- enrichGO(gene = de_res_IFNg_vs_naive$SYMBOL, - keyType = "SYMBOL", +fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", pAdjustMethod = "BH", pvalueCutoff = 0.05, - qvalueCutoff = 0.1) |> + qvalueCutoff = 0.1, + universe = universe) |> EMMA_run() # simply pipe your call to EMMA_run() ``` ... or you can simply wrap `EMMA_run()` around your call: ```{r EMMA_run_2} -fea_res <- EMMA_run(enrichGO(gene = de_res_IFNg_vs_naive$SYMBOL, +fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", From 78bb115063dea4b6f411b625b03b9abde81a5935 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 7 Apr 2026 13:21:38 +0200 Subject: [PATCH 10/92] add getEMMARecord to fetch the captured info from the attr (in raw state) --- R/getEMMARecord.R | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 R/getEMMARecord.R diff --git a/R/getEMMARecord.R b/R/getEMMARecord.R new file mode 100644 index 0000000..7f85f6f --- /dev/null +++ b/R/getEMMARecord.R @@ -0,0 +1,18 @@ +#' getEMMARecord +#' +#' @param res Functional Enrichment Analysis results (enrichResult, gseaResult ...) +#' +#' @return list of metadata recorded during FEA runtime +#' @export +#' +#' @examples +#' data("de_res_IFNg_vs_naive", package = "EMMA") +#' data("universe", package = "EMMA") +#' library("clusterProfiler") +#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP")) +#' getEMMARecord(res) +getEMMARecord <- function(res){ + attr(res, "EMMA_record") +} \ No newline at end of file From ca719832a25550ed41ca5eec2ca3aa1f5282f5f5 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 12:33:27 +0200 Subject: [PATCH 11/92] fix the printing of the call in EMMA_show() --- R/EMMA_show.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index 9746a2e..a95c4fb 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -32,12 +32,12 @@ EMMA_show <- function(res){ cat("Number of Pathways: ", NROW(res), "\n") } - pkg_info <- emma_rec$method + method_info <- emma_rec$method db_info <- emma_rec$annotation - cat("Call: ", paste(deparse(emma_rec$call), collapse = " "), " \n") - cat("Package: ", paste(pkg_info$package_name , "v.", - pkg_info$package_version), " \n") + cat("Call: ", paste(deparse(method_info$call), collapse = " "), " \n") + cat("Package: ", paste(method_info$package_name , "v.", + method_info$package_version), " \n") cat("Organism : ", db_info$organism, " \n") cat("Gene set library : ", paste(db_info$gene_set_db, collapse = ", "), " \n") cat("Gene set library version : ",db_info$gene_set_db_version, " \n") From 8b03865162ca57b15b83ea85fd15f29df8186d36 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 12:33:37 +0200 Subject: [PATCH 12/92] add news --- NEWS.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/NEWS.md b/NEWS.md index bbd341d..dc2d3bb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# EMMA 0.1.0 + +* `EMMA_run()` captures a function call, executes the FEA analysis, and returns +the results in their native format while attaching structured metadata to the +result object as attribute. +* `EMMA_run()` can now capture information from functions in `mosdef`, +`gprofiler2`, and 5 commonly used functions from `clusterProfiler`. +* `EMMA_show()` prints the captured metadata in a more user-friendly format. +* Added `getEMMARecord()` to fetch all metadata stored in the attributes of an +object returned by `EMMA_run()`. +* Added toy data. + + # EMMA 0.0.1 * Added the initial implementation of `EMMA_run()` and `EMMA_show()`. From a52d9796dfc22cea30503a95c3fa24b406908d30 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 12:33:58 +0200 Subject: [PATCH 13/92] fix typo in man page --- man/getEMMARecord.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/getEMMARecord.Rd b/man/getEMMARecord.Rd index cdc4023..c0f9bbe 100644 --- a/man/getEMMARecord.Rd +++ b/man/getEMMARecord.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/geEMMARecord.R +% Please edit documentation in R/getEMMARecord.R \name{getEMMARecord} \alias{getEMMARecord} \title{getEMMARecord} From 9e54d5d3cd958898a2618584a9139b501aa5427f Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 15:01:48 +0200 Subject: [PATCH 14/92] update description --- DESCRIPTION | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 120316a..3e2fdd8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: EMMA Title: EMMA: Enrichment Methods Matter for enabeling fully reproducible and provenance-aware pathway analysis -Version: 0.0.1 +Version: 0.1.0 Authors@R: c( person( @@ -43,6 +43,6 @@ Suggests: VignetteBuilder: knitr URL: https://github.com/imbeimainz/EMMA BugReports: https://github.com/imbeimainz/EMMA/issues -biocViews: Pathways, GO, KEGG, GeneSetEnrichment, Annotation, ImmunoOncology, - ReproducibleResearch, Transcriptomics, SingleCell, Software +biocViews: Software, Pathways, GO, KEGG, GeneSetEnrichment, ImmunoOncology, + ReproducibleResearch, Transcriptomics, SingleCell Config/testthat/edition: 3 From cf184b0d0311c4d64b9f8cc1b6d35516494d227c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 15:02:57 +0200 Subject: [PATCH 15/92] add returned value by EMMA_show --- R/EMMA_show.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index a95c4fb..2ebae26 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -47,4 +47,6 @@ EMMA_show <- function(res){ warning("No `EMMA_record` attribute was found") } + invisible(NULL) + } From b85ee3b065765ae2efa80a3fa7ef18a36beaa757 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 15:03:12 +0200 Subject: [PATCH 16/92] adding more tests --- tests/testthat/setuptests_EMMA.R | 1 + tests/testthat/test-EMMA.R | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index f0e1294..2a9aff3 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -6,6 +6,7 @@ suppressPackageStartupMessages( library("org.Hs.eg.db") ) + data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") diff --git a/tests/testthat/test-EMMA.R b/tests/testthat/test-EMMA.R index ab51bea..fb02737 100644 --- a/tests/testthat/test-EMMA.R +++ b/tests/testthat/test-EMMA.R @@ -54,7 +54,50 @@ test_that("EMMA_run", { pAdjustMethod = "BH", readable = TRUE))) +}) + + +test_that("getEMMARecord", { + fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE)) + + expect_true(is.list(getEMMARecord(fea_res))) + expect_length(getEMMARecord(fea_res), 7) + +}) + +test_that("testing the record content", { + + fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE)) + + emma_rec <- attr(fea_res, "EMMA_record") + + org <- emma_rec$annotation$organism + + db <- emma_rec$annotation$gene_set_db + + expect_true(is.list(emma_rec$annotation)) + expect_true(is.list(emma_rec$method)) + expect_true(is.list(emma_rec$input)) + + expect_identical(org, "Homo sapiens") + expect_identical(db, "GO") + + }) From 1bb2682fe2d69e5949887479b41fe828cc961b5e Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 15:20:10 +0200 Subject: [PATCH 17/92] add more tests --- tests/testthat/test-EMMA.R | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/testthat/test-EMMA.R b/tests/testthat/test-EMMA.R index fb02737..a31f19d 100644 --- a/tests/testthat/test-EMMA.R +++ b/tests/testthat/test-EMMA.R @@ -101,3 +101,20 @@ test_that("testing the record content", { }) + +test_that("EMMA_show", { + + fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE) + + + expect_warning(EMMA_show(fea_res)) +}) + + From 8eadb423cc7df208a82af5a2accdf9a64a476436 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 17:13:23 +0200 Subject: [PATCH 18/92] update vignette to reflect the state/content of the package --- vignettes/Introduction_to_EMMA.Rmd | 99 +++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 17 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index f3425b4..934bdb0 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -46,12 +46,28 @@ knitr::opts_chunk$set( # Introduction {#introduction} -TODO: add a paragraph of why we need this pkg, and which gap is this filling +Functional Enrichment Analysis (FEA) is a key downstream step in omics workflows, +commonly applied after differential expression analysis to support biological +interpretation and generate pathway-level hypotheses. A wide range of tools and +methods are available, leading to substantial heterogeneity in analytical choices +and reported results. + +Despite its widespread use, FEA is often insufficiently documented. Critical +parameters such as background gene sets or multiple testing correction methods +are frequently missing or inconsistently reported in scientific papers, +limiting reproducibility and interpretability. Currently, no standardized method +exists to ensure transparent and reproducible documentation of FEA workflows. + +To address this gap, we introduce `r BiocStyle::Biocpkg("EMMA")`, a framework +that automatically captures key analytical parameters and provenance information +during the execution of FEA methods. + +This vignette demonstrates how `EMMA` integrates with existing tools +(e.g. `clusterProfiler`, `topGO`, `Enrichr`, `gprofiler2`) to execute enrichment +analyses while systematically capturing analysis parameters and provenance +information during runtime, and returning enrichment results in their native +format alongside structured and reusable metadata. -This vignette describes how to use the `r BiocStyle::Biocpkg("EMMA")` -package to perform Functional Enrichment Analysis using existing tools (e.g. -`clusterProfiler`, `topGO`, `Enrichr`, `gprofiler2`), while systematically -capturing analysis parameters and provenance information during runtime. # Getting started {#gettingstarted} @@ -215,6 +231,8 @@ fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), ... or you can simply wrap `EMMA_run()` around your call: ```{r EMMA_run_2} +# you can also pass the function name and its namespace +# e.g. `clusterProfiler::enrichGO(...)` fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, @@ -228,25 +246,72 @@ fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), fea_res ``` -As you can see, EMMA returns the FEA results in their **native/standard** format. -EMMA also warns you about good practices when performing FEA, like in this +As you can see, `EMMA` returns the FEA results in their **native/standard** format. +`EMMA` also warns you about good practices when performing FEA, like in this example, we didn't define a list of background genes, so we get warned about that. -To check what EMMA captured while we ran the analysis, we use `EMMA_show()`: +To get a quick summary of what `EMMA` captured while we ran the analysis, we use +`EMMA_show()`: ```{r EMMA_show} EMMA_show(fea_res) ``` -EMMA aims to capture the following elements: - - * the full call used to perform the FEA - * the package used and its version - * the evaluated arguments used - * the gene set library (GO, KEGG, Reactome ...) and its version - * the organism type - * the runtime, i.e. when the analysis started - * Information about the current R session (`sessionInfo`) +`EMMA` stores the captured metadata within the attributes of the results object. +That's why it is always a good practice to save the original results, and not +only the subsets of interest. +To see the full recorded information, we use `getEMMARecord()`: + +```{r getEMMARecord} +emma_record <- getEMMARecord(fea_res) + +emma_record +``` + +`EMMA` aims to capture the following elements: + +``` +├── EMMA_record +│ ├── method +│ │ ├── call +│ │ └── function_name +│ │ └── package_name +│ │ └── package_version +│ └── input +│ │ └── arguments +│ └── annotation +│ │ ├── organism +│ │ └── gene_set_db +│ │ └── gene_set_db_version +│ └── timestamp +│ └── session_info +│ └── user_metadata # the user can pass any extra metadata freely in this field +│ ├── emma_version +``` + +As we can see a lot of information was recorded. With `EMMA_run()`, we can decide +whether we want to save the value of arguments used in our call or not. For this, +we can use the argument `args_form`: + +```{r argument_form} +fea_res_no_param <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), + universe = universe, + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + ont = "BP", + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + readable = TRUE) |> + EMMA_run(args_form = "unevaluated") # if we don't want the values stored + # else set to evaluated (default) + +# check +getEMMARecord(fea_res_no_param) +``` + +We can also choose whether to save the session info with the record using the +argument `session`, which defaults to true. `EMMA_freeze()` records the R environment during analysis runtime and generates a lockfile that can be used with tools such as `renv`, to facilitate From 5078c9e073bd3fa8a6baf342c81617bb83f12bbe Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Thu, 9 Apr 2026 17:31:21 +0200 Subject: [PATCH 19/92] update workflow --- .github/workflows/R-CMD-check.yaml | 143 ++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 12 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 5bef95c..b97a276 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,28 +1,147 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: [main, master] pull_request: - branches: [main, master] + branches: + - devel + schedule: + - cron: '0 8 * * 5' name: R-CMD-check jobs: R-CMD-check: - runs-on: ubuntu-latest + runs-on: ${{ matrix.config.os }} + container: ${{ matrix.config.image }} + + name: ${{ matrix.config.os }} (${{ matrix.config.bioc }} - ${{ matrix.config.image }}) + + strategy: + fail-fast: false + matrix: + config: + - { os: windows-latest, bioc: 'devel'} + - { os: macOS-latest, bioc: 'devel', curlConfigPath: '/usr/bin/'} + - { os: ubuntu-latest, bioc: 'devel'} + # - { os: ubuntu-latest, bioc: 'devel', cran: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} + - { os: ubuntu-latest, image: 'bioconductor/bioconductor_docker:devel'} + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + CRAN: ${{ matrix.config.cran }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes + CURL_CONFIG: ${{ matrix.config.curlConfigPath }}curl-config + steps: - - uses: actions/checkout@v2 + - name: Check out repo + uses: actions/checkout@v2 - - uses: r-lib/actions/setup-r@v1 + - name: Set up R and install BiocManager + uses: grimbough/bioc-actions/setup-bioc@v1 + if: matrix.config.image == null with: - use-public-rspm: true + bioc-version: ${{ matrix.config.bioc }} + + - name: Set up pandoc + uses: r-lib/actions/setup-pandoc@v2 + if: matrix.config.image == null + + - name: Install remotes + run: | + install.packages('remotes') + shell: Rscript {0} - - uses: r-lib/actions/setup-r-dependencies@v1 + - name: Query dependencies + run: | + saveRDS(remotes::dev_package_deps(dependencies = TRUE, repos = c(getOption('repos'), BiocManager::repositories())), 'depends.Rds', version = 2) + shell: Rscript {0} + + - name: Cache R packages + if: runner.os != 'Windows' && matrix.config.image == null + uses: actions/cache@v4 + with: + path: ${{ env.R_LIBS_USER }} + key: ${{ runner.os }}-bioc-${{ matrix.config.bioc }}-${{ hashFiles('depends.Rds') }} + restore-keys: ${{ runner.os }}-bioc-${{ matrix.config.bioc }}- + + - name: Install system dependencies + if: runner.os == 'Linux' + env: + RHUB_PLATFORM: linux-x86_64-ubuntu-gcc + uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: rcmdcheck + extra-packages: any::rcmdcheck + pak-version: devel + + - name: Install system dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew install cairo + brew install --cask xquartz + brew install harfbuzz + brew install fribidi + brew install fftw + + # - name: Set up gfortran symlinks (macOS) + # if: runner.os == 'macOS' + # run: | + # set -x + # sudo ln -s /usr/local/Cellar/gcc@11/*/lib/gcc/11 /usr/local/gfortran/lib + # gfortran --version + + - name: Install dependencies + run: | + local_deps <- remotes::local_package_deps(dependencies = TRUE) + deps <- remotes::dev_package_deps(dependencies = TRUE, repos = BiocManager::repositories()) + BiocManager::install(local_deps[local_deps %in% deps$package[deps$diff != 0]], Ncpu = 2L) + BiocManager::install(c("GenomeInfoDbData", "GO.db"), Ncpu = 2L) + remotes::install_cran('rcmdcheck', Ncpu = 2L) + shell: Rscript {0} + + - name: Session info + run: | + options(width = 100) + pkgs <- installed.packages()[, "Package"] + sessioninfo::session_info(pkgs, include_base = TRUE) + shell: Rscript {0} + + - name: Build, Install, Check + uses: grimbough/bioc-actions/build-install-check@v1 + + - name: Run BiocCheck + uses: grimbough/bioc-actions/run-BiocCheck@v1 + with: + arguments: '--no-check-bioc-views --no-check-bioc-help' + error-on: 'error' + + # - name: Upload check results + # if: failure() + # uses: actions/upload-artifact@master + # with: + # name: ${{ runner.os }}-r${{ matrix.config.r }}-results + # path: check + + - name: Show testthat output + if: always() + run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload check results + if: failure() + uses: actions/upload-artifact@master + with: + name: ${{ runner.os }}-bioc-${{ matrix.config.bioc }}-results + path: check + + - name: Test coverage + if: matrix.config.os == 'macOS-latest' + run: | + install.packages("covr") + covr::codecov(token = "${{secrets.CODECOV_TOKEN}}") + shell: Rscript {0} - - uses: r-lib/actions/check-r-package@v1 + - name: Deploy + if: github.event_name == 'push' && github.ref == 'refs/heads/devel' && matrix.config.os == 'macOS-latest' + run: | + R CMD INSTALL . + Rscript -e "remotes::install_dev('pkgdown'); pkgdown::deploy_to_branch(new_process = FALSE)" From c74d859bdd3f8c4f1bf6fd7ddd58c153e633d30b Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 10 Apr 2026 16:46:22 +0200 Subject: [PATCH 20/92] update description with pkg for vignettes and tests --- DESCRIPTION | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3e2fdd8..3429ecf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -36,13 +36,16 @@ Imports: Suggests: knitr, rmarkdown, + macrophage, + DESeq2, clusterProfiler, gprofiler2, org.Hs.eg.db, + mosdef, testthat (>= 3.0.0) VignetteBuilder: knitr URL: https://github.com/imbeimainz/EMMA BugReports: https://github.com/imbeimainz/EMMA/issues -biocViews: Software, Pathways, GO, KEGG, GeneSetEnrichment, ImmunoOncology, +biocViews: Software, Pathways, GO, KEGG, GeneSetEnrichment, ImmunoOncology, ReproducibleResearch, Transcriptomics, SingleCell Config/testthat/edition: 3 From 7d1d8ecfe08ac4634d0918987704c372830724b8 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 10 Apr 2026 16:47:02 +0200 Subject: [PATCH 21/92] initial implementation of EMMA_explain --- NAMESPACE | 1 + NEWS.md | 1 + R/EMMA_explain.R | 84 +++++++++++++++++++++++++++++++++++++++++++-- man/EMMA_explain.Rd | 16 ++++++++- 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 012134d..2effdbe 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(EMMA_explain) export(EMMA_freeze) export(EMMA_run) export(EMMA_show) diff --git a/NEWS.md b/NEWS.md index dc2d3bb..10bd428 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,7 @@ result object as attribute. * Added `getEMMARecord()` to fetch all metadata stored in the attributes of an object returned by `EMMA_run()`. * Added toy data. +* Added the initial implementation of `EMMA_explain`. # EMMA 0.0.1 diff --git a/R/EMMA_explain.R b/R/EMMA_explain.R index b8ce103..02e3eea 100644 --- a/R/EMMA_explain.R +++ b/R/EMMA_explain.R @@ -5,8 +5,88 @@ #' call, the parameters, software context, and reference databases used. #' #' @param res A functional enrichment analysis results object as returned by -#' `EMMA_run()`. Its attributes contain `EMMA_record` of class `EMMARecord`, which +#' `EMMA_run()`. Its attributes contain `EMMA_record`, which #' contains all provenance information of the performed FEA +#' @returns A character string describing how the FEA was performed using the +#' recorded metadata +#' @export +#' @examples +#' data("de_res_IFNg_vs_naive", package = "EMMA") +#' data("universe", package = "EMMA") +#' library("clusterProfiler") +#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP", pAdjustMethod = "BH")) +#' EMMA_explain(res) +#' EMMA_explain <- function(res){ - # code + + emma_rec <- getEMMARecord(res) + + function_name <- emma_rec$method$function_name + pkg_name <- emma_rec$method$package_name + pkg_version <- emma_rec$method$package_version + db <- emma_rec$annotation$gene_set_db + db_version <- emma_rec$annotation$gene_set_db_version + + args <- emma_rec$input$arguments + arg_names <- names(args) + + message("You can always grab information using `getEMMARecord()` to complete the text with any addition metadata of your choice!") + + text <- paste0("Functional Enrichment Analysis was performed using the ", + function_name, "() function") + + # checks to avoid text with NA + if (!is.null(pkg_name) && !is.na(pkg_name)) { + text <- paste0(text, " from the ", pkg_name, " package") + } + + if (!is.null(pkg_version) && !is.na(pkg_version)) { + text <- paste0(text, " (version ", pkg_version, ")") + } + + if (!is.null(db) && !all(is.na(db))) { + text <- paste0(text, " with the ", + paste(db, collapse = ", "), " database") + } + + if (!is.null(db_version) && !all(is.na(db_version))) { + text <- paste0(text, " (version ", + paste(db_version, collapse = ", "),")") + } + + text <- paste0(text, ".") + + ### info abt bg genes + bg_arg <- intersect(c("universe", "background", "custom_bg", "bg_genes"), + arg_names) + fdr_arg <- intersect(c("correction_method", "pAdjustMethod"), arg_names) + + if (length(bg_arg) == 1) { + text <- paste0(text, + " A custom background gene set was provided (n = ", + length(args[[bg_arg]]), + ")." + ) + } else { + text <- paste0(text, " A custom background gene set was provided.") + } + + + ### info abt the fdr correction + if (length(fdr_arg) == 1) { + text <- paste0(text, + " Multiple testing correction was performed using the ", + args[[fdr_arg]], " method." + ) + } else if ("do_padj" %in% arg_names) { + if (isTRUE(args[["do_padj"]])) { + text <- paste0(text, " Multiple testing correction was applied.") + } else if (identical(args[["do_padj"]], FALSE)) { + text <- paste0(text, " Multiple testing correction was not applied.") + } + } + + return(text) } \ No newline at end of file diff --git a/man/EMMA_explain.Rd b/man/EMMA_explain.Rd index 76ca399..4807582 100644 --- a/man/EMMA_explain.Rd +++ b/man/EMMA_explain.Rd @@ -8,11 +8,25 @@ EMMA_explain(res) } \arguments{ \item{res}{A functional enrichment analysis results object as returned by -\code{EMMA_run()}. Its attributes contain \code{EMMA_record} of class \code{EMMARecord}, which +\code{EMMA_run()}. Its attributes contain \code{EMMA_record}, which contains all provenance information of the performed FEA} } +\value{ +A character string describing how the FEA was performed using the +recorded metadata +} \description{ This function generates a human-readable description of the FEA, similar to a Materials and Methods section of a paper, by summarizing the executed call, the parameters, software context, and reference databases used. } +\examples{ +data("de_res_IFNg_vs_naive", package = "EMMA") +data("universe", package = "EMMA") +library("clusterProfiler") +res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +ont = "BP", pAdjustMethod = "BH")) +EMMA_explain(res) + +} From 8644053636be94daef6d8f30e54e321858b76b72 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 10 Apr 2026 16:47:41 +0200 Subject: [PATCH 22/92] update documentation --- R/EMMA_show.R | 5 +---- R/getEMMARecord.R | 4 ++++ man/EMMA_show.Rd | 3 +++ man/getEMMARecord.Rd | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index 2ebae26..847e2e8 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -6,7 +6,7 @@ #' @param res A functional enrichment analysis results object as returned by #' `EMMA_run()` #' -#' @returns NULL +#' @returns A summary of the executed FEA #' @export #' #' @examples @@ -21,9 +21,6 @@ EMMA_show <- function(res){ if ("EMMA_record" %in% names(attributes(res))) { message("Found EMMA record!!") emma_rec <- attr(res, "EMMA_record") - if (!is.list(emma_rec)) { - stop("'EMMA_record' must be a list!") - } if (is.list(res) && "result" %in% names(res)) { diff --git a/R/getEMMARecord.R b/R/getEMMARecord.R index 7f85f6f..db93aab 100644 --- a/R/getEMMARecord.R +++ b/R/getEMMARecord.R @@ -1,6 +1,7 @@ #' getEMMARecord #' #' @param res Functional Enrichment Analysis results (enrichResult, gseaResult ...) +#' generated by `EMMA_run()` #' #' @return list of metadata recorded during FEA runtime #' @export @@ -14,5 +15,8 @@ #' ont = "BP")) #' getEMMARecord(res) getEMMARecord <- function(res){ + if(is.null(attr(res, "EMMA_record"))) { + stop("No EMMA record was found! Try running `EMMA_run()` first.") + } attr(res, "EMMA_record") } \ No newline at end of file diff --git a/man/EMMA_show.Rd b/man/EMMA_show.Rd index f6e5e32..5909638 100644 --- a/man/EMMA_show.Rd +++ b/man/EMMA_show.Rd @@ -10,6 +10,9 @@ EMMA_show(res) \item{res}{A functional enrichment analysis results object as returned by \code{EMMA_run()}} } +\value{ +A summary of the executed FEA +} \description{ This function prints the EMMA record associated with a functional enrichment analysis object, including the executed call, parameters, and provenance diff --git a/man/getEMMARecord.Rd b/man/getEMMARecord.Rd index c0f9bbe..3d55b1f 100644 --- a/man/getEMMARecord.Rd +++ b/man/getEMMARecord.Rd @@ -7,7 +7,8 @@ getEMMARecord(res) } \arguments{ -\item{res}{Functional Enrichment Analysis results (enrichResult, gseaResult ...)} +\item{res}{Functional Enrichment Analysis results (enrichResult, gseaResult ...) +generated by \code{EMMA_run()}} } \value{ list of metadata recorded during FEA runtime From 2fe47a2e32273050f3acb45910b2f1f55ce902af Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 10 Apr 2026 16:48:37 +0200 Subject: [PATCH 23/92] remove the dot for all unexported functions --- R/EMMA_internal-utils.R | 42 ++++++++++++++++++++--------------------- R/EMMA_run.R | 8 ++++---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index 0e3656e..a163213 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -3,7 +3,7 @@ #' the method #' #' @noRd -.EMMA_empty_metadata <- function() { +EMMA_empty_metadata <- function() { list( organism = NA_character_, gene_set_db = NA_character_, @@ -21,15 +21,15 @@ #' function call to perform FEA #' #' @noRd -.EMMA_get_metadata <- function(function_name, +EMMA_get_metadata <- function(function_name, package_name, args) { switch( package_name, - clusterProfiler = .EMMA_get_clusterprofiler_metadata(function_name, args), - gprofiler2 = .EMMA_get_gprofiler2_metadata(args), - mosdef = .EMMA_cp_GO_metadata(args$mapping), - .EMMA_empty_metadata() + clusterProfiler = EMMA_get_clusterprofiler_metadata(function_name, args), + gprofiler2 = EMMA_get_gprofiler2_metadata(args), + mosdef = EMMA_cp_GO_metadata(args$mapping), + EMMA_empty_metadata() ) } @@ -41,15 +41,15 @@ #' function call to perform FEA #' #' @noRd -.EMMA_get_clusterprofiler_metadata <- function(function_name, args) { +EMMA_get_clusterprofiler_metadata <- function(function_name, args) { switch( function_name, - enrichGO = .EMMA_cp_GO_metadata(args$OrgDb), - gseGO = .EMMA_cp_GO_metadata(args$OrgDb), - groupGO = .EMMA_cp_GO_metadata(args$OrgDb), - enrichKEGG = .EMMA_cp_KEGG_metadata(args), - gseKEGG = .EMMA_cp_KEGG_metadata(args), - .EMMA_empty_metadata() + enrichGO = EMMA_cp_GO_metadata(args$OrgDb), + gseGO = EMMA_cp_GO_metadata(args$OrgDb), + groupGO = EMMA_cp_GO_metadata(args$OrgDb), + enrichKEGG = EMMA_cp_KEGG_metadata(args), + gseKEGG = EMMA_cp_KEGG_metadata(args), + EMMA_empty_metadata() ) } @@ -57,7 +57,7 @@ #' @param orgdb Organism object from org.*.eg.db packages #' #' @noRd -.EMMA_get_organism_from_OrgDb <- function(orgdb) { +EMMA_get_organism_from_OrgDb <- function(orgdb) { if (is.null(orgdb)) { return(NA_character_) @@ -87,10 +87,10 @@ #' @param org #' #' @noRd -.EMMA_cp_GO_metadata <- function(org) { +EMMA_cp_GO_metadata <- function(org) { meta <- list() - meta$organism <- .EMMA_get_organism_from_OrgDb(org) + meta$organism <- EMMA_get_organism_from_OrgDb(org) meta$gene_set_db <- "GO" meta$gene_set_db_version <- if (requireNamespace("GO.db", quietly = TRUE)) { as.character(utils::packageVersion("GO.db")) @@ -110,7 +110,7 @@ #' function call to perform FEA #' #' @noRd -.EMMA_cp_KEGG_metadata <- function(args) { +EMMA_cp_KEGG_metadata <- function(args) { meta <- list() meta$organism <- args$organism @@ -126,7 +126,7 @@ #' function call to perform FEA #' #' @noRd -.EMMA_get_gprofiler2_metadata <- function(args) { +EMMA_get_gprofiler2_metadata <- function(args) { meta <- list() version_info <- gprofiler2::get_version_info() @@ -160,7 +160,7 @@ # call capture --------------------- #' @noRd -.EMMA_capture_call_info <- function(call, envir = parent.frame()) { +EMMA_capture_call_info <- function(call, envir = parent.frame()) { # param checks if (!is.call(call)) { @@ -220,7 +220,7 @@ # build EMMA_record ----------- #' @noRd -.EMMA_build_record <- function(call, function_name, package_name, +EMMA_build_record <- function(call, function_name, package_name, package_version, args, arg_list, args_form, metadata, start_time,session) { @@ -251,7 +251,7 @@ # good practice warnings --------- #' @noRd -.EMMA_warnings <- function(arg_names, function_name){ +EMMA_warnings <- function(arg_names, function_name){ checks <- list( list( params = c("pAdjustMethod", "correction_method", "do_padj"), diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 60e997f..4a24db3 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -36,7 +36,7 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, } # capture call information - info_call <- .EMMA_capture_call_info(call = call) + info_call <- EMMA_capture_call_info(call = call) function_name <- info_call$function_name package_name <- info_call$package_name package_version <- info_call$package_version @@ -47,7 +47,7 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, # some good practice warning, i.e. when multiple testing correction is skipped # or bg geneset not set - .EMMA_warnings(arg_names = arg_names, + EMMA_warnings(arg_names = arg_names, function_name = function_name) #capture analysis time @@ -64,14 +64,14 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, results <- do.call(fun, args) # capture metadata from the used function and arguments - metadata <- .EMMA_get_metadata( + metadata <- EMMA_get_metadata( function_name = function_name, package_name = package_name, args = args ) # record everything in EMMA_record - EMMA_record <- .EMMA_build_record(call, function_name, package_name, + EMMA_record <- EMMA_build_record(call, function_name, package_name, package_version, args, arg_list, args_form, metadata, start_time, session) From 7ed84e577c6e0d531f0695572baec0704995a970 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 10 Apr 2026 16:49:04 +0200 Subject: [PATCH 24/92] add more tests --- tests/testthat/setuptests_EMMA.R | 3 +++ tests/testthat/test-EMMA.R | 40 ++++++++++++++------------------ tests/testthat/test-EMMA_show.R | 14 +++++++++++ 3 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 tests/testthat/test-EMMA_show.R diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index 2a9aff3..06ec1e8 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -6,6 +6,9 @@ suppressPackageStartupMessages( library("org.Hs.eg.db") ) +suppressPackageStartupMessages( + library("mosdef") +) data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") diff --git a/tests/testthat/test-EMMA.R b/tests/testthat/test-EMMA.R index a31f19d..f63af15 100644 --- a/tests/testthat/test-EMMA.R +++ b/tests/testthat/test-EMMA.R @@ -70,19 +70,28 @@ test_that("getEMMARecord", { expect_true(is.list(getEMMARecord(fea_res))) expect_length(getEMMARecord(fea_res), 7) + fea_no_emma <- mosdef::run_cluPro(de_genes = + rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = "org.Hs.eg.db", + keyType = "ENSEMBL", + ont = "BP", + pAdjustMethod = "BH") + + expect_error(getEMMARecord(fea_no_emma)) + }) test_that("testing the record content", { - fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), - keyType = "ENSEMBL", - OrgDb = org.Hs.eg.db, - pAdjustMethod = "BH", - pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - universe = universe, - readable = TRUE)) + fea_res <- EMMA_run(mosdef::run_cluPro(de_genes = + rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = "org.Hs.eg.db", + keyType = "ENSEMBL", + ont = "BP", + pAdjustMethod = "BH")) emma_rec <- attr(fea_res, "EMMA_record") @@ -102,19 +111,6 @@ test_that("testing the record content", { }) -test_that("EMMA_show", { - - fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), - keyType = "ENSEMBL", - OrgDb = org.Hs.eg.db, - pAdjustMethod = "BH", - pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - universe = universe, - readable = TRUE) - - - expect_warning(EMMA_show(fea_res)) -}) + diff --git a/tests/testthat/test-EMMA_show.R b/tests/testthat/test-EMMA_show.R new file mode 100644 index 0000000..69608c2 --- /dev/null +++ b/tests/testthat/test-EMMA_show.R @@ -0,0 +1,14 @@ +test_that("EMMA_show", { + + fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE) + + + expect_warning(EMMA_show(fea_res)) +}) \ No newline at end of file From 896add05dbfe844b5b4cab28f4f9d2b5e679d747 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 10 Apr 2026 16:49:14 +0200 Subject: [PATCH 25/92] update vignette --- vignettes/Introduction_to_EMMA.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 934bdb0..21876d3 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -39,7 +39,7 @@ knitr::opts_chunk$set( comment = "#>", error = FALSE, warning = FALSE, - eval = FALSE, + eval = TRUE, message = FALSE ) ``` @@ -183,7 +183,7 @@ Both_vs_naive Now let's sort the results based on the adjusted p-value: -```{r} +```{r DEGs} # have a list of de results per contrast contrast_list <- list(IFNg_vs_naive = IFNg_vs_naive, Salm_vs_naive = Salm_vs_naive, @@ -311,14 +311,14 @@ getEMMARecord(fea_res_no_param) ``` We can also choose whether to save the session info with the record using the -argument `session`, which defaults to true. +argument `session`, which defaults to `TRUE`. `EMMA_freeze()` records the R environment during analysis runtime and generates a lockfile that can be used with tools such as `renv`, to facilitate reproducible reconstruction of the analysis environment. ```{r EMMA_freeze} -EMMA_freeze() +#EMMA_freeze() ``` `EMMA_explain()` generates a human-readable description of the FEA, similar From 04bc8f522897217057e8e317b7f7b49d55666723 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 17 Apr 2026 16:45:07 +0200 Subject: [PATCH 26/92] fixing the odd implementation of EMMA_build_record --- R/EMMA_internal-utils.R | 29 +++++++++++++++++++---------- R/EMMA_run.R | 15 +++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index a163213..9d4955d 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -183,10 +183,15 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { as.character(packageVersion(package_name))} else NA_character_ + # capture args (unevaluated) + arg_list <- as.list(call)[-1] + return(list( + call = call, function_name = function_name, package_name = package_name, - package_version = pkg_version + package_version = pkg_version, + arg_list = arg_list )) } @@ -203,10 +208,15 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { NA_character_ } + # capture args (unevaluated) + arg_list <- as.list(call)[-1] + return(list( + call = call, function_name = function_name, package_name = package_name, - package_version = pkg_version + package_version = pkg_version, + arg_list = arg_list )) } @@ -220,19 +230,18 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { # build EMMA_record ----------- #' @noRd -EMMA_build_record <- function(call, function_name, package_name, - package_version, args, arg_list, - args_form, metadata, +EMMA_build_record <- function(info_call, args_form, metadata, start_time,session) { emma_rec <- list( method = list( - call = call, - function_name = function_name, - package_name = package_name, - package_version = package_version + call = info_call$call, + function_name = info_call$function_name, + package_name = info_call$package_name, + package_version = info_call$package_version ), input = list( - arguments = if (args_form == "evaluated") args else arg_list + arguments = if (args_form == "evaluated") info_call$args + else info_call$arg_list ), annotation = list( organism = metadata$organism, diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 4a24db3..c99d4a6 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -37,12 +37,11 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, # capture call information info_call <- EMMA_capture_call_info(call = call) - function_name <- info_call$function_name - package_name <- info_call$package_name - package_version <- info_call$package_version + function_name <- info_call$function_name + package_name <- info_call$package_name # capture args (unevaluated) - arg_list <- as.list(call)[-1] + arg_list <- info_call$arg_list arg_names <- names(arg_list) # some good practice warning, i.e. when multiple testing correction is skipped @@ -56,6 +55,8 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, # capture the value of the arguments args <- lapply(arg_list, eval, envir = envir) + info_call$args <- args + # get the function fun <- eval(call[[1]], envir = envir) @@ -71,10 +72,8 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, ) # record everything in EMMA_record - EMMA_record <- EMMA_build_record(call, function_name, package_name, - package_version, args, - arg_list, args_form, metadata, - start_time, session) + EMMA_record <- EMMA_build_record(info_call, args_form, metadata, + start_time, session) # store the EMMA_record as attribute of the results obj attr(results, "EMMA_record") <- EMMA_record From 77234d267867fa74f64ab33de00cd153fa913c67 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 17 Apr 2026 17:51:32 +0200 Subject: [PATCH 27/92] updating documentation --- R/EMMA_internal-utils.R | 68 ++++++++++++++++++++++++++++++----------- R/EMMA_run.R | 23 ++++++++------ R/EMMA_show.R | 9 +++--- man/EMMA_run.Rd | 23 ++++++++------ man/EMMA_show.Rd | 7 ++--- 5 files changed, 85 insertions(+), 45 deletions(-) diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index 9d4955d..3ba5dc1 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -120,10 +120,12 @@ EMMA_cp_KEGG_metadata <- function(args) { return(meta) } -#' assemble metadata element from gprofiler2 +#' This function assembles annotation metadata from a `gprofiler2` enrichment call #' #' @param args A list containing the evaluated arguments passed into the -#' function call to perform FEA +#' function call to perform FEA +#' +#' @return A named list #' #' @noRd EMMA_get_gprofiler2_metadata <- function(args) { @@ -158,34 +160,39 @@ EMMA_get_gprofiler2_metadata <- function(args) { return(meta) } -# call capture --------------------- +# call capture ----------------------------------------------------------------- + +#' EMMA_capture_call_info +#' +#' This function extracts call related metadata. It handles two call +#' formats: bare function calls (e.g. `enrichGO(...)`) and namespace-qualified +#' calls (e.g. `clusterProfiler::enrichGO(...)`). +#' +#' @param call A call object +#' @param envir The environment in which to look up the function when a bare +#' call is used. Defaults to `base::parent.frame()`. +#' +#' @return A named list #' @noRd EMMA_capture_call_info <- function(call, envir = parent.frame()) { - # param checks if (!is.call(call)) { stop("`call` must be a function call", call. = FALSE) } - # capture function name call_name <- call[[1]] - # when we only use function name e.g. enrichGO(...) if (is.symbol(call_name)) { function_name <- as.character(call_name) - fun <- get(function_name, envir = envir, mode = "function") - pkg <- utils::packageName(environment(fun)) package_name <- if (is.null(pkg) || pkg == "" ) NA_character_ else pkg - pkg_version <- if (!is.na(package_name)) { as.character(packageVersion(package_name))} else NA_character_ - # capture args (unevaluated) arg_list <- as.list(call)[-1] - + return(list( call = call, function_name = function_name, @@ -199,7 +206,6 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { if (is.call(call_name) && length(call_name) == 3L && identical(call_name[[1]], as.symbol("::"))) { - package_name <- as.character(call_name[[2]]) function_name <- as.character(call_name[[3]]) pkg_version <- if (!is.na(package_name)) { @@ -207,7 +213,6 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { } else { NA_character_ } - # capture args (unevaluated) arg_list <- as.list(call)[-1] @@ -222,16 +227,30 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { stop( "Unsupported call format. Use a direct function call like `enrichGO(...)` or `pkg::fun(...)`", - call. = FALSE - ) + call. = FALSE) } +# build EMMA record ------------------------------------------------------------ -# build EMMA_record ----------- +#' EMMA_build_record +#' +#' This function assembles the structured provenance record that is stored as +#' an attribute on the FEA results object. +#' +#' @param info_call A list returned by `EMMA_capture_call_info()` +#' @param args_form A character string, either `"evaluated"` or `"unevaluated"` +#' to decide how to store the arguments +#' @param metadata A list returned by `EMMA_get_metadata()` +#' @param start_time A timestamp marking when the enrichment analysis started +#' @param session Logical. If `TRUE`, `sessionInfo()` is captured and stored in +#' the record; if `FALSE` the `session_info` slot is `NULL` +#' +#' @return A named list of the recorded metadata +#' #' @noRd EMMA_build_record <- function(info_call, args_form, metadata, - start_time,session) { + start_time, session) { emma_rec <- list( method = list( call = info_call$call, @@ -258,7 +277,20 @@ EMMA_build_record <- function(info_call, args_form, metadata, } -# good practice warnings --------- +# good practice warnings ------------------------------------------------------- + +#' EMMA_warnings +#' +#' This function warns about missing good-practice arguments in enrichment calls. +#' A warning is raised if none of the synonyms for a given category appear +#' in `arg_names` +#' +#' @param arg_names A character vector of argument names as written in the +#' user's call, obtained within `EMMA_run()` +#' @param function_name A character corresponding to the name of the enrichment +#' function called, used only for constructing the warning message +#' +#' @return `base::invisible()` #' @noRd EMMA_warnings <- function(arg_names, function_name){ checks <- list( diff --git a/R/EMMA_run.R b/R/EMMA_run.R index c99d4a6..1fe9721 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -1,20 +1,25 @@ #' EMMA_run #' -#' This function executes functional enrichment analysis using existing tools -#' and captures the associated parameters and provenance information for the -#' analysis when available during runtime. +#' This function executes any supported functional enrichment analysis function +#' and automatically captures the call, its associated parameters and provenance +#' information when available during runtime as an `EMMA_record` attribute +#' on the returned results object. #' #' @param expr A function call that performs functional enrichment analysis. #' The call is captured and executed by EMMA to record analysis parameters and -#' provenance information +#' provenance information. Both bare calls (`enrichGO(...)`) and namespace-qualified +#' calls (`clusterProfiler::enrichGO(...)`) are supported. Any other form +#' (e.g. `do.call`, `get()`) will raise an error #' @param envir An environment in which to evaluate `expr` -#' @param session Logical, indicating whether to store sessionInfo or not. -#' It defaults to (`TRUE`) saving the session +#' @param session Logical, indicating whether to store the output of +#' `sessionInfo()` or not. If `TRUE` (default), the session is stored in the +#' provenance record #' @param args_form A character string indicating whether to store the evaluated -#' or the unevaluated arguments. It default to store the evaluated arguments +#' or the unevaluated arguments in the provenance record. It default to `"evaluated"` #' -#' @returns Functional enrichment analysis results in the native format -#' returned by the original `expr` +#' @returns The result object returned by the enrichment function in `expr`, +#' unmodified except for an added `EMMA_record` attribute containing the +#' provenance information. Use `getEMMARecord()` to access it #' @export #' #' @examples diff --git a/R/EMMA_show.R b/R/EMMA_show.R index 847e2e8..a378d98 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -1,12 +1,11 @@ #' EMMA_show #' -#' This function prints the EMMA record associated with a functional enrichment -#' analysis object, including the executed call, parameters, and provenance -#' information. +#' This function displays a human-readable summary of the `EMMA_record` attribute +#' attached to a result object produced by `EMMA_run()` #' @param res A functional enrichment analysis results object as returned by #' `EMMA_run()` #' -#' @returns A summary of the executed FEA +#' @returns Returns `base::invisible()` #' @export #' #' @examples @@ -44,6 +43,6 @@ EMMA_show <- function(res){ warning("No `EMMA_record` attribute was found") } - invisible(NULL) + invisible() } diff --git a/man/EMMA_run.Rd b/man/EMMA_run.Rd index f6068ae..e62720a 100644 --- a/man/EMMA_run.Rd +++ b/man/EMMA_run.Rd @@ -14,24 +14,29 @@ EMMA_run( \arguments{ \item{expr}{A function call that performs functional enrichment analysis. The call is captured and executed by EMMA to record analysis parameters and -provenance information} +provenance information. Both bare calls (\code{enrichGO(...)}) and namespace-qualified +calls (\code{clusterProfiler::enrichGO(...)}) are supported. Any other form +(e.g. \code{do.call}, \code{get()}) will raise an error} \item{envir}{An environment in which to evaluate \code{expr}} -\item{session}{Logical, indicating whether to store sessionInfo or not. -It defaults to (\code{TRUE}) saving the session} +\item{session}{Logical, indicating whether to store the output of +\code{sessionInfo()} or not. If \code{TRUE} (default), the session is stored in the +provenance record} \item{args_form}{A character string indicating whether to store the evaluated -or the unevaluated arguments. It default to store the evaluated arguments} +or the unevaluated arguments in the provenance record. It default to \code{"evaluated"}} } \value{ -Functional enrichment analysis results in the native format -returned by the original \code{expr} +The result object returned by the enrichment function in \code{expr}, +unmodified except for an added \code{EMMA_record} attribute containing the +provenance information. Use \code{getEMMARecord()} to access it } \description{ -This function executes functional enrichment analysis using existing tools -and captures the associated parameters and provenance information for the -analysis when available during runtime. +This function executes any supported functional enrichment analysis function +and automatically captures the call, its associated parameters and provenance +information when available during runtime as an \code{EMMA_record} attribute +on the returned results object. } \examples{ data("de_res_IFNg_vs_naive", package = "EMMA") diff --git a/man/EMMA_show.Rd b/man/EMMA_show.Rd index 5909638..026f466 100644 --- a/man/EMMA_show.Rd +++ b/man/EMMA_show.Rd @@ -11,12 +11,11 @@ EMMA_show(res) \code{EMMA_run()}} } \value{ -A summary of the executed FEA +Returns \code{base::invisible()} } \description{ -This function prints the EMMA record associated with a functional enrichment -analysis object, including the executed call, parameters, and provenance -information. +This function displays a human-readable summary of the \code{EMMA_record} attribute +attached to a result object produced by \code{EMMA_run()} } \examples{ data("de_res_IFNg_vs_naive", package = "EMMA") From 3e9441fca0f7dcc184335e1911935df85d4de922 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:12:02 +0200 Subject: [PATCH 28/92] add code to EMMA_explain --- R/EMMA_explain.R | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/R/EMMA_explain.R b/R/EMMA_explain.R index 02e3eea..e915f93 100644 --- a/R/EMMA_explain.R +++ b/R/EMMA_explain.R @@ -32,10 +32,15 @@ EMMA_explain <- function(res){ args <- emma_rec$input$arguments arg_names <- names(args) - message("You can always grab information using `getEMMARecord()` to complete the text with any addition metadata of your choice!") + message("You can always complete your text with additional information from `getEMMARecord()`!") - text <- paste0("Functional Enrichment Analysis was performed using the ", - function_name, "() function") + if (emma_rec$method$wrapper) { + text <- paste0("Functional Enrichment Analysis was performed using a wrapper function ", + function_name, "()") + } else { + text <- paste0("Functional Enrichment Analysis was performed using the ", + function_name, "() function") + } # checks to avoid text with NA if (!is.null(pkg_name) && !is.na(pkg_name)) { @@ -69,9 +74,11 @@ EMMA_explain <- function(res){ length(args[[bg_arg]]), ")." ) - } else { - text <- paste0(text, " A custom background gene set was provided.") - } + } else if (length(bg_arg) > 1L) { + text <- paste0(text, " A custom background gene set was provided.") + } else { + text <- paste0(text, " No custom background gene set was recorded.") + } ### info abt the fdr correction From 1ad7ee3f34a5d3b546c1a6dc4da19db2b7fecdbc Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:12:28 +0200 Subject: [PATCH 29/92] update pkg version --- DESCRIPTION | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3429ecf..a00d977 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: EMMA Title: EMMA: Enrichment Methods Matter for enabeling fully reproducible and provenance-aware pathway analysis -Version: 0.1.0 +Version: 0.2.0 Authors@R: c( person( @@ -42,6 +42,7 @@ Suggests: gprofiler2, org.Hs.eg.db, mosdef, + topGO, testthat (>= 3.0.0) VignetteBuilder: knitr URL: https://github.com/imbeimainz/EMMA From 9ec20d8ad0f0e4a2c9177221dbcdfa880051e371 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:12:35 +0200 Subject: [PATCH 30/92] add news --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 10bd428..2fffc1d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,12 @@ +# EMMA 0.2.0 + +* `EMMA_run()` can accept custom functions and wrappers and collect metadata +based on which function was used in the wrapper. +* Added `EMMA_add_custom_metadata()` to give the user manual/easy access to modify +`user_metdata` field in `EMMA_record`. +* Added a fully runnable vignette. + + # EMMA 0.1.0 * `EMMA_run()` captures a function call, executes the FEA analysis, and returns From 5cf683c9fce9c7afe18427c20d5b3d26102d60dc Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:13:41 +0200 Subject: [PATCH 31/92] update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 55e4485..913b805 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # EMMA -EMMA aims to explicitly capture analytical parameters during functional -enrichment analysis runtime, while returning native enrichment results together -with structured metadata. +EMMA enables the execution of Functional Enrichment Analyses using existing +tools (e.g. `clusterProfiler`, `topGO`, `gprofiler2`) while systematically capturing +analysis parameters and provenance information during runtime, and returning +enrichment results in their standard format alongside structured and reusable metadata. ## Installation From f8dd505c4d531cda360b511c696fb98adea604d8 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:14:04 +0200 Subject: [PATCH 32/92] update EMMA_show --- R/EMMA_show.R | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index a378d98..5bfaca0 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -2,10 +2,11 @@ #' #' This function displays a human-readable summary of the `EMMA_record` attribute #' attached to a result object produced by `EMMA_run()` +#' #' @param res A functional enrichment analysis results object as returned by #' `EMMA_run()` #' -#' @returns Returns `base::invisible()` +#' @returns `base::invisible()` #' @export #' #' @examples @@ -17,13 +18,26 @@ #' ont = "BP")) #' EMMA_show(res) EMMA_show <- function(res){ + if ("EMMA_record" %in% names(attributes(res))) { - message("Found EMMA record!!") - emma_rec <- attr(res, "EMMA_record") + message("Found EMMA record!") - if (is.list(res) && "result" %in% names(res)) { + emma_rec <- getEMMARecord(res) + + if (is.list(res) && !is.data.frame(res)) { + # let's say if we have of list of FEAs (returned by custom function) + cat("Number of FEAs: ", length(res), "\n") + + nms <- names(res) + if (is.null(nms) || any(nms == "")) { + nms <- paste0("FEA_", seq_along(res)) + } + + for (i in seq_along(res)) { + # check the number of pathways for each element of the list + cat(" -", nms[i], ": ", NROW(res[[i]]), " pathways\n") + } - cat("Number of Pathways: ", NROW(res$result), "\n") } else { cat("Number of Pathways: ", NROW(res), "\n") } @@ -32,6 +46,7 @@ EMMA_show <- function(res){ db_info <- emma_rec$annotation cat("Call: ", paste(deparse(method_info$call), collapse = " "), " \n") + cat("Wrapper: ", method_info$wrapper, " \n") cat("Package: ", paste(method_info$package_name , "v.", method_info$package_version), " \n") cat("Organism : ", db_info$organism, " \n") From b7ab70309140e3fb3d63455796071dceab6a534b Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:14:26 +0200 Subject: [PATCH 33/92] update getEMMARecord --- R/getEMMARecord.R | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/R/getEMMARecord.R b/R/getEMMARecord.R index db93aab..f982bed 100644 --- a/R/getEMMARecord.R +++ b/R/getEMMARecord.R @@ -15,8 +15,16 @@ #' ont = "BP")) #' getEMMARecord(res) getEMMARecord <- function(res){ - if(is.null(attr(res, "EMMA_record"))) { - stop("No EMMA record was found! Try running `EMMA_run()` first.") + + rec <- attr(res, "EMMA_record") + + if (is.null(rec)) { + stop("No `EMMA_record` was found. Try running `EMMA_run()` first.") } + + if (!is.list(rec)) { + stop("Invalid structure. `EMMA_record` must be a `list`") + } + attr(res, "EMMA_record") } \ No newline at end of file From 98d21308491a21a38dbb2222c72ccccba88f0263 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:15:26 +0200 Subject: [PATCH 34/92] add EMMA_add_custom_metadata to give the user possibility to modify the content of extra in EMMA_record --- R/EMMA_add_custom_metadata.R | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 R/EMMA_add_custom_metadata.R diff --git a/R/EMMA_add_custom_metadata.R b/R/EMMA_add_custom_metadata.R new file mode 100644 index 0000000..3a854a9 --- /dev/null +++ b/R/EMMA_add_custom_metadata.R @@ -0,0 +1,44 @@ +#' EMMA_add_custom_metadata +#' +#' Append or replace the `extra` field in the `EMMA_record` attribute +#' of a result object returned by `EMMA_run()`. This allows users to manually +#' provide additional annotation or contextual information that could not be +#' captured automatically +#' +#' @param res A functional enrichment analysis results object as returned by +#' `EMMA_run()` +#' @param extra A named list of user-defined metadata elements to store in +#' the `extra` field +#' +#' @returns The input result object with updated `EMMA_record` attribute +#' @export +#' +#' @examples +#' data("de_res_IFNg_vs_naive", package = "EMMA") +#' data("universe", package = "EMMA") +#' library("clusterProfiler") +#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP")) +#' res <- EMMA_add_custom_metadata(res, extra = +#' list(note = "The background gene set list was all expressed genes in the assay")) +EMMA_add_custom_metadata <- function(res, + extra = list()) { + + if (!is.list(extra)) { + stop("`extra` must be a list!") + } + + if (length(extra) > 0L && is.null(names(extra))) { + stop("`extra` must be a named list!") + } + + emma_rec <- getEMMARecord(res = res) + + emma_rec$extra <- extra + + # update + attr(res, "EMMA_record") <- emma_rec + + return(res) +} \ No newline at end of file From 136d4d6b90933ace8d2e895789c5d391d20f1ad3 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:15:55 +0200 Subject: [PATCH 35/92] update man --- man/EMMA_add_custom_metadata.Rd | 34 +++++++++++++++++++++++++++++++++ man/EMMA_run.Rd | 26 +++++++++++++++---------- man/EMMA_show.Rd | 2 +- 3 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 man/EMMA_add_custom_metadata.Rd diff --git a/man/EMMA_add_custom_metadata.Rd b/man/EMMA_add_custom_metadata.Rd new file mode 100644 index 0000000..c3849f5 --- /dev/null +++ b/man/EMMA_add_custom_metadata.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/EMMA_add_custom_metadata.R +\name{EMMA_add_custom_metadata} +\alias{EMMA_add_custom_metadata} +\title{EMMA_add_custom_metadata} +\usage{ +EMMA_add_custom_metadata(res, extra = list()) +} +\arguments{ +\item{res}{A functional enrichment analysis results object as returned by +\code{EMMA_run()}} + +\item{extra}{A named list of user-defined metadata elements to store in +the \code{extra} field} +} +\value{ +The input result object with updated \code{EMMA_record} attribute +} +\description{ +Append or replace the \code{extra} field in the \code{EMMA_record} attribute +of a result object returned by \code{EMMA_run()}. This allows users to manually +provide additional annotation or contextual information that could not be +captured automatically +} +\examples{ +data("de_res_IFNg_vs_naive", package = "EMMA") +data("universe", package = "EMMA") +library("clusterProfiler") +res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, +ont = "BP")) +res <- EMMA_add_custom_metadata(res, extra = +list(note = "The background gene set list was all expressed genes in the assay")) +} diff --git a/man/EMMA_run.Rd b/man/EMMA_run.Rd index e62720a..caed5d4 100644 --- a/man/EMMA_run.Rd +++ b/man/EMMA_run.Rd @@ -14,9 +14,8 @@ EMMA_run( \arguments{ \item{expr}{A function call that performs functional enrichment analysis. The call is captured and executed by EMMA to record analysis parameters and -provenance information. Both bare calls (\code{enrichGO(...)}) and namespace-qualified -calls (\code{clusterProfiler::enrichGO(...)}) are supported. Any other form -(e.g. \code{do.call}, \code{get()}) will raise an error} +provenance information. Both bare calls (\code{enrichGO(...)}) and +namespace-qualified calls (\code{clusterProfiler::enrichGO(...)}) are supported.} \item{envir}{An environment in which to evaluate \code{expr}} @@ -25,18 +24,25 @@ calls (\code{clusterProfiler::enrichGO(...)}) are supported. Any other form provenance record} \item{args_form}{A character string indicating whether to store the evaluated -or the unevaluated arguments in the provenance record. It default to \code{"evaluated"}} +or the unevaluated arguments in the provenance record. It default to +\code{"evaluated"}} } \value{ The result object returned by the enrichment function in \code{expr}, -unmodified except for an added \code{EMMA_record} attribute containing the -provenance information. Use \code{getEMMARecord()} to access it +in standard format, with an additional \code{EMMA_record} attribute containing the +provenance information. Use \code{getEMMARecord()} to retrieve this record } \description{ -This function executes any supported functional enrichment analysis function -and automatically captures the call, its associated parameters and provenance -information when available during runtime as an \code{EMMA_record} attribute -on the returned results object. +This function executes any functional enrichment analysis function and +attaches a provenance record (\code{EMMA_record}) describing the analysis. The +captured record includes the original call, metadata derived from the call +and its arguments, runtime information, and optionally the current session +information. +} +\details{ +\code{EMMA_run()} accepts both direct calls to known enrichment functions +(\code{enrichGO()}, \code{GSEA()}, \code{fgsea()} ...) and calls to wrapper functions that +internally invoke a know enrichment function. } \examples{ data("de_res_IFNg_vs_naive", package = "EMMA") diff --git a/man/EMMA_show.Rd b/man/EMMA_show.Rd index 026f466..8207750 100644 --- a/man/EMMA_show.Rd +++ b/man/EMMA_show.Rd @@ -11,7 +11,7 @@ EMMA_show(res) \code{EMMA_run()}} } \value{ -Returns \code{base::invisible()} +\code{base::invisible()} } \description{ This function displays a human-readable summary of the \code{EMMA_record} attribute From 0ff0442dc0fa2e5d023520c5b514646e46358358 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:16:09 +0200 Subject: [PATCH 36/92] update namespace --- NAMESPACE | 1 + 1 file changed, 1 insertion(+) diff --git a/NAMESPACE b/NAMESPACE index 2effdbe..8893a13 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(EMMA_add_custom_metadata) export(EMMA_explain) export(EMMA_freeze) export(EMMA_run) From 075eb80f5c811c9fc96f8ae3e81944512202c6e7 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:16:36 +0200 Subject: [PATCH 37/92] adding tests --- tests/testthat/setuptests_EMMA.R | 16 +-- tests/testthat/test-EMMA_explain.R | 57 ++++++++++ tests/testthat/test-EMMA_metadata.R | 105 ++++++++++++++++++ .../testthat/{test-EMMA.R => test-EMMA_run.R} | 78 ++++--------- tests/testthat/test-EMMA_show.R | 63 +++++++++-- tests/testthat/test-getEMMArecord.R | 28 +++++ 6 files changed, 271 insertions(+), 76 deletions(-) create mode 100644 tests/testthat/test-EMMA_explain.R create mode 100644 tests/testthat/test-EMMA_metadata.R rename tests/testthat/{test-EMMA.R => test-EMMA_run.R} (56%) create mode 100644 tests/testthat/test-getEMMArecord.R diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index 06ec1e8..e73a0fe 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -1,14 +1,8 @@ -suppressPackageStartupMessages( - library("clusterProfiler") -) - -suppressPackageStartupMessages( - library("org.Hs.eg.db") -) - -suppressPackageStartupMessages( - library("mosdef") -) +invisible(lapply( + c("clusterProfiler", "org.Hs.eg.db", "mosdef", "topGO", "gprofiler2"), + function(pkg) suppressPackageStartupMessages( + library(pkg, character.only = TRUE)) +)) data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") diff --git a/tests/testthat/test-EMMA_explain.R b/tests/testthat/test-EMMA_explain.R new file mode 100644 index 0000000..114b182 --- /dev/null +++ b/tests/testthat/test-EMMA_explain.R @@ -0,0 +1,57 @@ +test_that("EMMA_explain", { + res <- data.frame(ID = "GO:0000001") + + attr(res, "EMMA_record") <- list( + method = list( + function_name = "enrichGO", + package_name = "clusterProfiler", + package_version = "4.10.0", + wrapper = FALSE + ), + input = list( + arguments = list( + gene = c("g1", "g2"), + universe = c("g1", "g2", "g3", "g4"), + pAdjustMethod = "BH" + ) + ), + annotation = list( + gene_set_db = "GO", + gene_set_db_version = "3.18.0" + ) + ) + + expect_message( + txt <- EMMA_explain(res), + "You can always complete your text" + ) + + expect_match(txt, "Functional Enrichment Analysis was performed using the enrichGO\\(\\) function") + expect_match(txt, "from the clusterProfiler package") + expect_match(txt, "with the GO database") + expect_match(txt, "A custom background gene set was provided \\(n = 4\\)") + expect_match(txt, "Multiple testing correction was performed using the BH method") + expect_false(grepl("NA", txt)) + + de_res <- de_res_IFNg_vs_naive[!(is.na(de_res_IFNg_vs_naive$padj)) & de_res_IFNg_vs_naive$padj <= 0.05, ] + + + res_topGO <- run_topGO(de_genes = rownames(de_res), + bg_genes = universe, + ontology = "BP", + gene_id = "ENSEMBL", + mapping = "org.Hs.eg.db", + add_gene_to_terms = TRUE, + do_padj = TRUE) |> EMMA_run() + + expect_message( + txt <- EMMA_explain(res_topGO), + "You can always complete your text" + ) + + expect_match(txt, + "Functional Enrichment Analysis was performed using a wrapper function run_topGO\\(\\)") + + expect_match(txt,"Multiple testing correction was applied") + +}) diff --git a/tests/testthat/test-EMMA_metadata.R b/tests/testthat/test-EMMA_metadata.R new file mode 100644 index 0000000..f59e0d4 --- /dev/null +++ b/tests/testthat/test-EMMA_metadata.R @@ -0,0 +1,105 @@ +test_that("test metadata content & structure", { + + fea_res <- EMMA_run(mosdef::run_cluPro(de_genes = + rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = "org.Hs.eg.db", + keyType = "ENSEMBL", + ont = "BP", + pAdjustMethod = "BH")) + + emma_rec <- attr(fea_res, "EMMA_record") + + org <- emma_rec$annotation$organism + + db <- emma_rec$annotation$gene_set_db + + expect_true(is.list(emma_rec$annotation)) + expect_true(is.list(emma_rec$method)) + expect_true(is.list(emma_rec$input)) + + expect_identical(org, "Homo sapiens") + expect_identical(db, "GO") + + expect_true(emma_rec$method$wrapper) + + expect_null(EMMA_find_original_wrapped_fun("mosdef::run_cluPro(de_genes = rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = 'org.Hs.eg.db', + keyType = 'ENSEMBL', + ont = 'BP')")) + + + custom <- EMMA_classify_call(substitute(summary(getEMMARecord(fea_res)))) + + expect_equal(custom$type, "custom") + + + expect_warning(res <- EMMA_run(mosdef::run_goseq(de_genes = rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = "org.Hs.eg.db", + id = "ensGene", + genome = "hg19"))) + + + res <- EMMA_add_custom_metadata(res, + extra = list( + note = "The background gene set list was all expressed genes in the assay")) + + rec <- getEMMARecord(res) + + expect_equal( + rec$extra$note, + "The background gene set list was all expressed genes in the assay" + ) + + expect_null(rec$extra$notfoud) + + expect_error(res <- EMMA_add_custom_metadata(res, + extra = list("The background gene set list was all expressed genes in the assay")) + ) + + expect_error(res <- EMMA_add_custom_metadata(res, + extra = "The background gene set list was all expressed genes in the assay")) + + + fea_res <- gprofiler2::gost(query = de_res_IFNg_vs_naive$SYMBOL, + organism = "hsapiens", + correction_method = "fdr", + custom_bg = universe) |> EMMA_run() + + rec <- getEMMARecord(fea_res) + + expect_true(length(rec$annotation$gene_set_db_version) != 1) + + + custom_fun <- function(gene, OrgDb) { + clusterProfiler::groupGO( + gene = gene, + OrgDb = OrgDb, + keyType = "ENSEMBL", + ont = "BP", + level = 2, + readable = FALSE + ) + } + + expect_warning(wrapper <- EMMA_run(custom_fun(rownames(de_res_IFNg_vs_naive), + org.Hs.eg.db))) + + expect_equal(getEMMARecord(wrapper)$method$function_name, "custom_fun") + expect_equal(getEMMARecord(wrapper)$method$wrapped_package, "clusterProfiler") + expect_equal(getEMMARecord(wrapper)$method$wrapped_function, "groupGO") + expect_true(getEMMARecord(wrapper)$method$wrapper) + + + empty <- EMMA_run(summary(rec$method)) + + expect_null(getEMMARecord(empty)$annotation$organism) + expect_null(getEMMARecord(empty)$annotation$gene_set_db) + expect_null(getEMMARecord(empty)$annotation$gene_set_db_version) + + +}) + + diff --git a/tests/testthat/test-EMMA.R b/tests/testthat/test-EMMA_run.R similarity index 56% rename from tests/testthat/test-EMMA.R rename to tests/testthat/test-EMMA_run.R index f63af15..b6f30a7 100644 --- a/tests/testthat/test-EMMA.R +++ b/tests/testthat/test-EMMA_run.R @@ -16,6 +16,19 @@ test_that("EMMA_run", { expect_true(length(attr(fea_res, "EMMA_record")) == 7) + info <- EMMA_capture_call_info(substitute(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE))) + + expect_type(info, "list") + expect_length(info, 5) + expect_length(info$arg_list, 8) + expect_error(EMMA_run(enrichGO,gene = rownames(de_res_IFNg_vs_naive), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, @@ -54,63 +67,18 @@ test_that("EMMA_run", { pAdjustMethod = "BH", readable = TRUE))) -}) - - -test_that("getEMMARecord", { - fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), - keyType = "ENSEMBL", - OrgDb = org.Hs.eg.db, - pAdjustMethod = "BH", - pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - universe = universe, - readable = TRUE)) + expect_error(EMMA_run(groupGO(gene = rownames(de_res_IFNg_vs_naive), + OrgDb = org.Hs.eg.db, + keyType = "ENSEMBL", + ont = "CC", + level = 2), envir = getwd())) - expect_true(is.list(getEMMARecord(fea_res))) - expect_length(getEMMARecord(fea_res), 7) - - fea_no_emma <- mosdef::run_cluPro(de_genes = - rownames(de_res_IFNg_vs_naive), - bg_genes = universe, - mapping = "org.Hs.eg.db", - keyType = "ENSEMBL", - ont = "BP", - pAdjustMethod = "BH") - - expect_error(getEMMARecord(fea_no_emma)) + expect_error(EMMA_run(groupGO(gene = rownames(de_res_IFNg_vs_naive), + OrgDb = org.Hs.eg.db, + keyType = "ENSEMBL", + ont = "CC", + level = 2), session = "yes")) }) -test_that("testing the record content", { - - fea_res <- EMMA_run(mosdef::run_cluPro(de_genes = - rownames(de_res_IFNg_vs_naive), - bg_genes = universe, - mapping = "org.Hs.eg.db", - keyType = "ENSEMBL", - ont = "BP", - pAdjustMethod = "BH")) - - emma_rec <- attr(fea_res, "EMMA_record") - - org <- emma_rec$annotation$organism - - db <- emma_rec$annotation$gene_set_db - - expect_true(is.list(emma_rec$annotation)) - expect_true(is.list(emma_rec$method)) - expect_true(is.list(emma_rec$input)) - - expect_identical(org, "Homo sapiens") - expect_identical(db, "GO") - - - -}) - - - - - diff --git a/tests/testthat/test-EMMA_show.R b/tests/testthat/test-EMMA_show.R index 69608c2..333a0df 100644 --- a/tests/testthat/test-EMMA_show.R +++ b/tests/testthat/test-EMMA_show.R @@ -1,14 +1,57 @@ test_that("EMMA_show", { - fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), - keyType = "ENSEMBL", - OrgDb = org.Hs.eg.db, - pAdjustMethod = "BH", - pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - universe = universe, - readable = TRUE) - + fea_res <- data.frame(ID = "term1") expect_warning(EMMA_show(fea_res)) -}) \ No newline at end of file + + + res <- data.frame(ID = c("term1", "term2")) + + attr(res, "EMMA_record") <- list( + method = list( + call = substitute(fake_enrich(gene = genes)), + wrapper = FALSE, + package_name = "fakepkg", + package_version = "1.0.0" + ), + annotation = list( + organism = "Homo sapiens", + gene_set_db = "GO", + gene_set_db_version = "3.22.0" + ) + ) + + expect_message( + expect_output( + EMMA_show(res), + "Number of Pathways" + ), + "Found EMMA record" + ) + + expect_output(EMMA_show(res), "Package") + expect_output(EMMA_show(res), "Organism") + expect_output(EMMA_show(res), "Gene set library") + + + fea2 <- list(fea_res, + res) + + attr(fea2, "EMMA_record") <- list( + method = list( + call = substitute(fake_enrich(gene = genes)), + wrapper = TRUE, + package_name = NA, + package_version = NA + ), + annotation = list( + organism = "Homo sapiens", + gene_set_db = "KEGG", + gene_set_db_version = NA + ) + ) + expect_output(EMMA_show(fea2), "Number of FEAs: 2") + expect_output(EMMA_show(fea2), "- FEA_1") + expect_output(EMMA_show(fea2),"FEA_2") + +}) diff --git a/tests/testthat/test-getEMMArecord.R b/tests/testthat/test-getEMMArecord.R new file mode 100644 index 0000000..9152707 --- /dev/null +++ b/tests/testthat/test-getEMMArecord.R @@ -0,0 +1,28 @@ +test_that("getEMMARecord", { + fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + universe = universe, + readable = TRUE)) + + expect_true(is.list(getEMMARecord(fea_res))) + expect_length(getEMMARecord(fea_res), 7) + + fea_no_emma <- mosdef::run_cluPro(de_genes = + rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = "org.Hs.eg.db", + keyType = "ENSEMBL", + ont = "BP", + pAdjustMethod = "BH") + + expect_error(getEMMARecord(fea_no_emma)) + + attr(fea_no_emma, "EMMA_record") <- c() + + expect_error(getEMMARecord(fea_no_emma)) + +}) \ No newline at end of file From 3947e09499e4b8a5f00d13e44f38eca75463c9ad Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:16:56 +0200 Subject: [PATCH 38/92] update vignette --- vignettes/Introduction_to_EMMA.Rmd | 250 +++++++++++++++++++---------- 1 file changed, 169 insertions(+), 81 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 21876d3..5416a15 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -68,6 +68,15 @@ analyses while systematically capturing analysis parameters and provenance information during runtime, and returning enrichment results in their native format alongside structured and reusable metadata. +## What do you get with `EMMA`? + +Using `EMMA` allows you to: + +* Record the exact function call and parameters used for FEA +* Automatically track annotation sources (e.g. organism, gene set database) +* Retain provenance directly within the results object +* Generate reproducible summaries of the analysis (e.g. Methods sections) +* Facilitate sharing of results together with their analysis context # Getting started {#gettingstarted} @@ -113,6 +122,9 @@ library("macrophage") library("DESeq2") library("org.Hs.eg.db") library("clusterProfiler") +library("mosdef") +library("topGO") +library("GO.db") ``` We will show an example of how `r BiocStyle::Biocpkg("EMMA")` fits into a @@ -146,7 +158,8 @@ dds_macrophage <- DESeq(dds_macrophage) resultsNames(dds_macrophage) ``` -Let's extract the DE results for each contrast. We will do three contrasts: +Let's extract the DE results for each contrast. For the sake of demonstration, +we will do one contrast: ```{r get_de_res} # get de res for 1st contrast @@ -158,46 +171,14 @@ IFNg_vs_naive <- lfcShrink(dds_macrophage, coef = "condition_IFNg_vs_naive", type = "apeglm") IFNg_vs_naive$SYMBOL <- rowData(dds_macrophage)$SYMBOL IFNg_vs_naive - -# get de res for 2nd contrast -Salm_vs_naive <- results(dds_macrophage, - contrast = c("condition", "SL1344", "naive"), - lfcThreshold = 1, alpha = 0.05) -Salm_vs_naive <- lfcShrink(dds_macrophage, coef = "condition_SL1344_vs_naive", - res = Salm_vs_naive, - type = "apeglm") -Salm_vs_naive$SYMBOL <- rowData(dds_macrophage)$SYMBOL -Salm_vs_naive - -# get de res for 3rd contrast -Both_vs_naive <- results(dds_macrophage, - contrast = c("condition", "IFNg_SL1344", "naive"), - lfcThreshold = 1, alpha = 0.05) -Both_vs_naive <- lfcShrink(dds_macrophage, - coef = "condition_IFNg_SL1344_vs_naive", - res = Both_vs_naive, - type = "apeglm") -Both_vs_naive$SYMBOL <- rowData(dds_macrophage)$SYMBOL -Both_vs_naive ``` -Now let's sort the results based on the adjusted p-value: +Now we'll sort the results based on the adjusted p-value: ```{r DEGs} -# have a list of de results per contrast -contrast_list <- list(IFNg_vs_naive = IFNg_vs_naive, - Salm_vs_naive = Salm_vs_naive, - Both_vs_naive = Both_vs_naive) - -de_res_list <- list() # sort -for (i in names(contrast_list)) { - de_res <- as.data.frame(contrast_list[[i]]) - de_res <- de_res[order(de_res$padj), ] - #de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] - - de_res_list[[i]] <- de_res -} +de_res <- IFNg_vs_naive[order(IFNg_vs_naive$padj), ] +de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] # set universe universe <- rownames(dds_macrophage) @@ -205,35 +186,38 @@ universe <- rownames(dds_macrophage) # Perform Functional Enrichment Analysis (FEA) -Now that we have a list of DE genes for each contrast, we can perform Functional -Enrichment Analysis. For the first contrast `IFNg_vs_naive`, we will use the +## `EMMA` with available common packages/functions + +Now that we have a list of DE genes for this contrast, we can perform Functional +Enrichment Analysis. In the following example, we will use the function `enrichGO()` from `r BiocStyle::Biocpkg("clusterProfiler")` +### Capturing record with `EMMA_run()` + `EMMA_run()` accepts a function call (e.g. `enrichGO(...)`) and executes it as it is, while capturing the associated parameters and provenance information: ```{r EMMA_run_1} -# get de res from the list -de_res_IFNg_vs_naive <- de_res_list$IFNg_vs_naive - # perform FEA, but with EMMA! -fea_res <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), +fea_res <- enrichGO(gene = rownames(de_res), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", pAdjustMethod = "BH", pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - universe = universe) |> + qvalueCutoff = 0.1) |> EMMA_run() # simply pipe your call to EMMA_run() +# check res +fea_res ``` ... or you can simply wrap `EMMA_run()` around your call: -```{r EMMA_run_2} +```{r EMMA_run_2, eval=FALSE} # you can also pass the function name and its namespace # e.g. `clusterProfiler::enrichGO(...)` -fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), +fea_res <- EMMA_run(clusterProfiler::enrichGO( + gene = rownames(de_res), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", @@ -241,14 +225,14 @@ fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), pvalueCutoff = 0.05, qvalueCutoff = 0.1, readable = TRUE)) - -# check res -fea_res ``` As you can see, `EMMA` returns the FEA results in their **native/standard** format. `EMMA` also warns you about good practices when performing FEA, like in this -example, we didn't define a list of background genes, so we get warned about that. +example, we didn't define a list of background genes (which can influence the +results), so we get warned about that. + +### Retrieving recorded information with `getEMMARecord()` & `EMMA_show()` To get a quick summary of what `EMMA` captured while we ran the analysis, we use `EMMA_show()`: @@ -257,44 +241,54 @@ To get a quick summary of what `EMMA` captured while we ran the analysis, we use EMMA_show(fea_res) ``` -`EMMA` stores the captured metadata within the attributes of the results object. -That's why it is always a good practice to save the original results, and not -only the subsets of interest. -To see the full recorded information, we use `getEMMARecord()`: +`EMMA` attaches the captured metadata to the attributes of the +results object. That's why it is always a good practice to save the original +results, and not only the subsets of interest. + +To access the full recorded information, we use `getEMMARecord()`: ```{r getEMMARecord} emma_record <- getEMMARecord(fea_res) +# get all the record emma_record ``` -`EMMA` aims to capture the following elements: +`EMMA` structures the `EMMA_record` attribute (i.e. the recorded provenance +information) into a list of elements: -``` +```{EMMA_record_str} ├── EMMA_record -│ ├── method +│ ├── method # how the analysis was performed │ │ ├── call -│ │ └── function_name -│ │ └── package_name -│ │ └── package_version -│ └── input +│ │ ├── function_name +│ │ ├── package_name +│ │ ├── package_version +│ │ ├── wrapped_function +│ │ ├── wrapped_package +│ │ └── wrapper +│ ├── input # inputs used for the analysis │ │ └── arguments -│ └── annotation +│ ├── annotation # annotation context │ │ ├── organism -│ │ └── gene_set_db +│ │ ├── gene_set_db │ │ └── gene_set_db_version -│ └── timestamp -│ └── session_info -│ └── user_metadata # the user can pass any extra metadata freely in this field -│ ├── emma_version +│ ├── timestamp # when the analysis was run +│ ├── session_info # R session information +│ ├── extra # user-defined additions +│ └── emma_version +``` + +```{r EMMA_method} +# get the method record +emma_record$method ``` -As we can see a lot of information was recorded. With `EMMA_run()`, we can decide -whether we want to save the value of arguments used in our call or not. For this, -we can use the argument `args_form`: +With `EMMA_run()`, we can decide whether we want to save the value of arguments +used in our call or not. For this, we can use the argument `args_form`: ```{r argument_form} -fea_res_no_param <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), +fea_res_no_param <- enrichGO(gene = rownames(de_res), universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, @@ -303,23 +297,17 @@ fea_res_no_param <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), pvalueCutoff = 0.05, qvalueCutoff = 0.1, readable = TRUE) |> - EMMA_run(args_form = "unevaluated") # if we don't want the values stored + EMMA_run(args_form = "unevaluated") # when we don't want the values stored # else set to evaluated (default) # check getEMMARecord(fea_res_no_param) ``` -We can also choose whether to save the session info with the record using the -argument `session`, which defaults to `TRUE`. +We can also choose whether to save the R session information with the record +using the argument `session`, which defaults to `TRUE`. -`EMMA_freeze()` records the R environment during analysis runtime and generates -a lockfile that can be used with tools such as `renv`, to facilitate -reproducible reconstruction of the analysis environment. - -```{r EMMA_freeze} -#EMMA_freeze() -``` +### Summarizing captured information into text `EMMA_explain()` generates a human-readable description of the FEA, similar to a Materials and Methods section of a paper, by summarizing the executed call, @@ -329,6 +317,106 @@ the parameters, software context, and reference databases used. EMMA_explain(fea_res) ``` +## `EMMA` with custom/wrapper functions + +You can also use a custom function that you developed, or a wrapper function +(from packages such as `r BiocStyle::Biocpkg("mosdef")`). In this case, +`EMMA_run()` will attempt to capture as much metadata as possible: + +```{r mosdef_eg} +fea_res <- mosdef::run_goseq(de_genes = rownames(de_res), + bg_genes = universe, + mapping = "org.Hs.eg.db", + id = "ensGene", + genome = "hg19") |> EMMA_run(session = FALSE, + args_form = "unevaluated") + +# quick inspection +getEMMARecord(fea_res) +``` + +```{r custom_eg} +# a custom function (not from a package) +my_custom_function <- function(gene, universe = NULL, + ontology = "BP", id_type = "ENTREZID", + org_db_name = "org.Hs.eg.db", + organism = "hsapiens") { + # a wrapper of a wrapper :D + res1 <- mosdef::run_topGO(de_genes = gene, + bg_genes = universe, + ontology = ontology, + gene_id = id_type, + mapping = org_db_name, + add_gene_to_terms = TRUE) + + res2 <- gprofiler2::gost(query = gene, + organism = organism, + custom_bg = universe) + + return(list(topGO_res = res1, + gost_res = res2 + )) +} + +# run analysis with EMMA +frankenstein_fea <- my_custom_function( + gene = rownames(de_res), + universe = universe, + ontology = "BP", + id_type = "ENSEMBL", + org_db_name = "org.Hs.eg.db", + organism = "hsapiens" + ) |> EMMA_run(session = FALSE, args_form = "unevaluated") + +# quick inspection +getEMMARecord(frankenstein_fea) +``` + +# Adding extra information + +The user can always attach extra metadata that `EMMA` might not be able to +capture automatically. To keep everything organized, we can use +`EMMA_add_custom_metadata()` function + +```{r add_custom_metadata} + +frankenstein_fea <- EMMA_add_custom_metadata(res = frankenstein_fea, + extra = list( + wrapped_function_topGO = "runTest", + notes = "any other meaningful info")) + +getEMMARecord(frankenstein_fea)$extra +``` + +Since the `EMMA_record` is attached as attribute to the original results objects, +it can be preserved when integrating results into structured containers +such as `DeeDeeExperiment`. This enables both FEA results and +their associated provenance information to be stored and managed together, +facilitating reproducibility, organization, and sharing of complex omics analyses. + +```{r emma&dde, eval=FALSE} +#### should we have this chunk runnable and suggest dde? +dde <- DeeDeeExperiment::DeeDeeExperiment(sce = dds_macrophage, + de_results = IFNg_vs_naive, + enrich_results = list( + IFNg_vs_naive = fea_res_no_param)) + +fea <- DeeDeeExperiment::getFEA(dde, format = "original") + +getEMMARecord(fea) +``` + + +# Recording the Analysis Environment with `EMMA_freeze()` + +`EMMA_freeze()` records the R environment during analysis runtime and generates +a lockfile that can be used with tools such as `renv`, to facilitate +reproducible reconstruction of the analysis environment. + +```{r EMMA_freeze} +#EMMA_freeze() +``` + # Session info {.unnumbered .smaller} ```{r sessioinfo} From 0e1718ada53c9c118fb04c373208507d78beaa2c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 27 Apr 2026 18:18:05 +0200 Subject: [PATCH 39/92] updating EMMA_run to handle wrappers/custom functions --- R/EMMA_internal-utils.R | 403 +++++++++++++++++++++++++++++++++++----- R/EMMA_run.R | 67 ++++--- 2 files changed, 404 insertions(+), 66 deletions(-) diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index 3ba5dc1..e532c10 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -1,44 +1,74 @@ # metadata capture ------------------------------------------------------------- -#' get the basic structure for the metadata elements to be captured depending on -#' the method +#' This function creates a standardized metadata structure used to store +#' annotation info for FEAs. This ensures a consistent structure across +#' different enrichment methods and packages #' #' @noRd -EMMA_empty_metadata <- function() { +EMMA_empty_metadata <- function() { list( organism = NA_character_, gene_set_db = NA_character_, gene_set_db_version = NA_character_ - ) +) } -#' depending on the function/pkg, decide the right function dispatch + +#' This function acts as a dispatcher, routing to package-specific helpers +#' (e.g. for `clusterProfiler`, `gprofiler2` ...) to standardize metadata +#' collection across different enrichment tools #' -#' @param function_name A character string specifying the function name used to -#' perform FEA -#' @param package_name A character string containing the package name used to -#' perform FEA -#' @param args A list containing the evaluated arguments passed into the -#' function call to perform FEA +#' @param info_call A list containing captured call information, including +#' at least `function_name`, `package_name`, and the original `call` +#' +#' @param args A list of evaluated arguments passed to the enrichment function +#' +#' @param envir The environment in which the original call was evaluated +#' +#' @return A list containing annotation metadata(organism, gene set database and +#' its version), depending on the originating package. It returns an empty +#' metadata structure if no package-specific method is available #' #' @noRd -EMMA_get_metadata <- function(function_name, - package_name, - args) { - switch( +EMMA_get_metadata <- function(call_class, + args, + envir = parent.frame()) { + info_call <- call_class$info_call + function_name <- info_call$function_name + package_name <- info_call$package_name + call <- info_call$call + + meta <- EMMA_empty_metadata() + + if (is.null(package_name) || is.na(package_name) || package_name == "") { + package_name <- "custom" + } + + meta <- switch( package_name, clusterProfiler = EMMA_get_clusterprofiler_metadata(function_name, args), gprofiler2 = EMMA_get_gprofiler2_metadata(args), mosdef = EMMA_cp_GO_metadata(args$mapping), - EMMA_empty_metadata() + custom = EMMA_get_custom_metadata(call_class, + args, + envir = parent.frame()) ) + + return(meta) + } -#' get metadata from clusterProfiler functions -#' @param function_name A character string specifying the function name used to -#' perform FEA +#' This function retrieves metadata associated with FEAs performed using +#' functions from the `clusterProfiler` package. The specific metadata +#' extraction method is selected based on the enrichment function used +#' +#' @param function_name A character string specifying the name of the +#' `clusterProfiler` function used to perform FEA #' #' @param args A list containing the evaluated arguments passed into the #' function call to perform FEA +#' +#' @return A list containing annotation metadata (organism, gene set database +#' and its version), depending on the underlying database used (e.g. GO vs KEGG) #' #' @noRd EMMA_get_clusterprofiler_metadata <- function(function_name, args) { @@ -50,11 +80,17 @@ EMMA_get_clusterprofiler_metadata <- function(function_name, args) { enrichKEGG = EMMA_cp_KEGG_metadata(args), gseKEGG = EMMA_cp_KEGG_metadata(args), EMMA_empty_metadata() + # more to be added ) } -#' extract the organism from org,*.eg.db objects -#' @param orgdb Organism object from org.*.eg.db packages +#' This function extracts the organism from OrgDb objects +#' +#' @param orgdb An `OrgDb` object or a single character string giving the name +#' of such an object (e.g. `"org.Hs.eg.db"`) +#' +#' @return A character string containing the organism name (e.g. `"Homo sapiens"`) +#' and returns `NA_character_` if the organism cannot be determined #' #' @noRd EMMA_get_organism_from_OrgDb <- function(orgdb) { @@ -82,13 +118,19 @@ EMMA_get_organism_from_OrgDb <- function(orgdb) { } -#' assemble metadata element from clusterProfiler GO analyses +#' This function assembles standardized metadata describing GO enrichment +#' analyses performed with `clusterProfiler`. The metadata includes the +#' organism, the geneset database used, and its version when available #' -#' @param org +#' @param org An `OrgDb` object or a single character string giving the name of +#' such an object (e.g. `"org.Hs.eg.db"`) +#' +#' @return A named list containing annotation metadata, mainly the organism, the +#' geneset database and its version #' #' @noRd EMMA_cp_GO_metadata <- function(org) { - meta <- list() + meta <- EMMA_empty_metadata() meta$organism <- EMMA_get_organism_from_OrgDb(org) meta$gene_set_db <- "GO" @@ -104,14 +146,20 @@ EMMA_cp_GO_metadata <- function(org) { ### not tested yet since the kegg server is down and the function couldn't work -#' assemble metadata element from clusterProfiler KEGG analyses +#' This function assembles standardized metadata describing KEGG enrichment +#' analyses performed with `clusterProfiler`. The metadata includes the +#' organism, the geneset database used. The database version is currently not +#' captured #' #' @param args A list containing the evaluated arguments passed into the #' function call to perform FEA +#' +#' @return A named list containing annotation metadata, mainly the organism, the +#' geneset database and its version #' #' @noRd EMMA_cp_KEGG_metadata <- function(args) { - meta <- list() + meta <- EMMA_empty_metadata() meta$organism <- args$organism meta$gene_set_db <- "KEGG" @@ -120,16 +168,19 @@ EMMA_cp_KEGG_metadata <- function(args) { return(meta) } -#' This function assembles annotation metadata from a `gprofiler2` enrichment call + +#' This function retrieves metadata associated with FEAs performed using +#' functions from the `gprofiler2` package #' #' @param args A list containing the evaluated arguments passed into the #' function call to perform FEA #' -#' @return A named list +#' @return A named list containing annotation metadata, mainly the organism, the +#' geneset database and its version #' #' @noRd EMMA_get_gprofiler2_metadata <- function(args) { - meta <- list() + meta <- EMMA_empty_metadata() version_info <- gprofiler2::get_version_info() @@ -160,17 +211,69 @@ EMMA_get_gprofiler2_metadata <- function(args) { return(meta) } -# call capture ----------------------------------------------------------------- -#' EMMA_capture_call_info #' +#' @noRd +EMMA_get_custom_metadata <- function(call_class, args, + envir = parent.frame()) { + + meta <- EMMA_empty_metadata() + + if (call_class$type == "wrapper") { + # get all known functions used in the wrapper + wrapped_fun <- unique(call_class$wrapped_original$fun) + + go_funs <- c("enrichGO", "gseGO", "groupGO") + kegg_funs <- c("enrichKEGG", "gseKEGG") + # dont use EMMA_get_clusterprofiler_metadata cause the switch wont work with + # more than 1 wrapped fun + meta_list <- list() + if (any(wrapped_fun %in% go_funs)) { + meta_list$GO <- EMMA_cp_GO_metadata(args$OrgDb) + } + + if (any(wrapped_fun %in% kegg_funs)) { + meta_list$KEGG <- EMMA_cp_KEGG_metadata(args) + } + + if (any(wrapped_fun %in% c("gost"))) { + meta_list$gprofiler2 <- EMMA_get_gprofiler2_metadata(args) + } + + if (length(meta_list) == 0L) { + return(meta) + } + + meta$organism <- unique(unlist(lapply(meta_list, `[[`, "organism"))) + meta$gene_set_db <- unique(unlist(lapply(meta_list, `[[`, "gene_set_db"))) + meta$gene_set_db_version <- unique(unlist(lapply(meta_list, `[[`, "gene_set_db_version"))) + + return(meta) + + } + + if (call_class$type == "custom") { + # if 100% custom + message("You used a custom function, so `EMMA` wasn't able to record annotation-", + "related information. Please consider adding the `organism`, `geneset database`", + " and its `version` manually into the `extra` field in EMMA_record.") + return(meta) + } + + return(meta) +} + + + +# call info capture ----------------------------------------------------------------- + #' This function extracts call related metadata. It handles two call -#' formats: bare function calls (e.g. `enrichGO(...)`) and namespace-qualified -#' calls (e.g. `clusterProfiler::enrichGO(...)`). +#' formats: bare function calls (e.g. `fun(...)`) and namespace-qualified +#' calls (e.g. `pkg::fun(...)`) #' #' @param call A call object #' @param envir The environment in which to look up the function when a bare -#' call is used. Defaults to `base::parent.frame()`. +#' call is used #' #' @return A named list #' @noRd @@ -184,12 +287,18 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { # when we only use function name e.g. enrichGO(...) if (is.symbol(call_name)) { function_name <- as.character(call_name) + fun <- get(function_name, envir = envir, mode = "function") + pkg <- utils::packageName(environment(fun)) + package_name <- if (is.null(pkg) || pkg == "" ) NA_character_ else pkg + pkg_version <- if (!is.na(package_name)) { as.character(packageVersion(package_name))} + else NA_character_ + # capture args (unevaluated) arg_list <- as.list(call)[-1] @@ -226,17 +335,220 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { } stop( - "Unsupported call format. Use a direct function call like `enrichGO(...)` or `pkg::fun(...)`", + "Unsupported call format. Use a direct function call like `fun(...)` or `pkg::fun(...)`", call. = FALSE) } -# build EMMA record ------------------------------------------------------------ +#' This function, used in in `EMMA_walk()`, converts the head of a call into a character +#' string representing the function being called. It supports both bare calls +#' (e.g. `fun`) and namespace-qualified calls (e.g. `pkg::fun`) +#' +#' @param x The head of a call (the function being called) +#' +#' @return A character string representing the function name +#' +#' @noRd +EMMA_call_name <- function(x) { + if (is.symbol(x)) { + return(as.character(x)) + } + if ( + is.call(x) && + identical(x[[1]], as.name("::")) && + length(x) == 3 + ) { + return(paste0(as.character(x[[2]]), "::", as.character(x[[3]]))) + } + NULL +} + + +#' This function recursively collects function names/operators from a call. It +#' is used to inspect the body of a wrapper function and to identify whether it +#' calls any known functional enrichment functions (e.g. `enrichGO()`) +#' +#' @param x A call object +#' +#' @return A character vector containing the names of all functions and +#' operators found in `x`, including nested calls +#' +#' @noRd +EMMA_walk <- function(x) { + out <- character() + + if (is.call(x)) { + # extract function/operator name + nm <- EMMA_call_name(x[[1]]) + if (!is.null(nm)) { + out <- c(out, nm) + } + + } + if (is.call(x) || is.pairlist(x) || is.expression(x)) { + #check children/nested elements + for (i in seq_along(x)) { + out <- c(out, EMMA_walk(x[[i]])) + } + } + + return(out) +} + -#' EMMA_build_record +#' This function inspects the body of a function call and determines whether +#' it wraps one or more known FEA functions (e.g. from `clusterProfiler` or +#' any other package). This is used internally by `EMMA_run()` when the top-level +#' call is not itself a known enrichment function, but may be a user-defined +#' wrapper around one +#' +#' @param call An unevaluated call +#' @param envir An environment in which to evaluate `call` +#' +#' @return `NULL` if no known enrichment function is detected in the wrapper body. +#' Otherwise, a list with two components: `pkg`, a character vector of package +#' names, and `fun`, character vector of corresponding function names used in the +#' wrapper #' +#' @noRd +EMMA_find_original_wrapped_fun <- function(call, envir = parent.frame()) { + + if (!is.call(call)) { + # do i want it to fail here ? + return(NULL) + } + + available <- list( + clusterProfiler = c("enrichGO", "gseGO", "groupGO", "enrichKEGG", "gseKEGG"), + gprofiler2 = c("gost"), + goseq = c("goseq"), + mosdef = c("run_topGO", "run_cluPro", "run_goseq"), + topGO = c("runTest") + # will add more later here + ) + + # flatten available into full names + full_targets <- mapply(function(pkg, funs){ + paste0(pkg, "::", funs)}, + names(available), #pkg + available, #funs + SIMPLIFY = FALSE) |> unlist(use.names = FALSE) + + # get wrapper name + call_head <- call[[1]] + + # get function object, from which we'll extract the body + # if we have a bare wrapper name + if (is.symbol(call_head)) { + fun <- get(as.character(call_head), envir = envir, mode = "function") + } + # if the wrapper is namespace qualified function name, + # extract the fun name to get the body + else if (is.call(call_head) && + length(call_head) == 3L && + identical(call_head[[1]], as.symbol("::"))) { + pkg <- as.character(call_head[[2]]) + fn <- as.character(call_head[[3]]) + fun <- getExportedValue(pkg, fn) + } else { + stop( + "Only `fun(...)` and `pkg::fun(...)` are supported", + call. = FALSE + ) + } + + + found <- EMMA_walk(body(fun)) |> unique() + + matched <- unique(c( + intersect(found, full_targets), + #remove the pkg name (what s before ::) + full_targets[sub("^.*::", "", full_targets) %in% found] + )) + + if (length(matched) == 0) { + return(NULL) + } + + parts <- strsplit(matched, "::", fixed = TRUE) + + return(list( + pkg = vapply(parts, `[`, character(1), 1), + fun = vapply(parts, `[`, character(1), 2) + )) +} + + +#' decide if top-level function passed to EMMA_run is a known fun or a wrapper +#' +#' @noRd +EMMA_classify_call <- function(call, envir = parent.frame()) { + # listing all the functions that are not wrappers + originals <- list( + clusterProfiler = c("enrichGO", "gseGO", "groupGO", "enrichKEGG", "gseKEGG"), + gprofiler2 = c("gost"), + goseq = c("goseq"), + topGO = c("runTest") + ) + + full_originals <- mapply( function(pkg, funs) paste0(pkg, "::", funs), + names(originals), + originals, + SIMPLIFY = FALSE) |> unlist(use.names = FALSE) + + # capture call information + info_call <- EMMA_capture_call_info(call = call, envir = envir) + function_name <- info_call$function_name + package_name <- info_call$package_name + + top_name <- if (!is.na(package_name) && !is.null(package_name) + && package_name != "" && length(package_name) == 1L) { + paste0(package_name, "::", function_name) + } else { + function_name + } + + is_original_top_level <- if (!is.na(package_name) && !is.null(package_name) + && package_name != "" && length(package_name) == 1L) { + paste0(package_name, "::", function_name) %in% full_originals + } else { + function_name %in% sub("^.*::", "", full_originals) + } + + wrapped_original <- NULL + wrapper <- FALSE # default + + if (!is_original_top_level) { + # inspect body of expr function, because this could be a wrapper + wrapped_original <- EMMA_find_original_wrapped_fun(call = call, + envir = envir) + wrapper <- !is.null(wrapped_original) + } + + type <- if (is_original_top_level) { + "original" + } else if (wrapper) { + "wrapper" + } else { + "custom" + } + + return(list( + type = type, + is_original_top_level = is_original_top_level, + wrapper = wrapper, + wrapped_original = wrapped_original, + info_call = info_call + )) + +} + + +# build EMMA record ------------------------------------------------------------ + + #' This function assembles the structured provenance record that is stored as -#' an attribute on the FEA results object. +#' an attribute on the FEA results object #' #' @param info_call A list returned by `EMMA_capture_call_info()` #' @param args_form A character string, either `"evaluated"` or `"unevaluated"` @@ -250,13 +562,19 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { #' #' @noRd EMMA_build_record <- function(info_call, args_form, metadata, - start_time, session) { + wrapped_original, wrapper, + start_time, session) { emma_rec <- list( method = list( call = info_call$call, function_name = info_call$function_name, package_name = info_call$package_name, - package_version = info_call$package_version + package_version = info_call$package_version, + wrapped_function = if (!is.null(wrapped_original)) + wrapped_original$fun else NULL, + wrapped_package = if (!is.null(wrapped_original)) + wrapped_original$pkg else NULL, + wrapper = wrapper ), input = list( arguments = if (args_form == "evaluated") info_call$args @@ -269,7 +587,7 @@ EMMA_build_record <- function(info_call, args_form, metadata, ), timestamp = start_time, session_info = if (isTRUE(session)) sessionInfo() else NULL, - user_metadata = list(),# free form user additions + extra = list(),# free field for extra metadata (added by user) emma_version = as.character(packageVersion(pkg = "EMMA")) ) @@ -279,8 +597,7 @@ EMMA_build_record <- function(info_call, args_form, metadata, # good practice warnings ------------------------------------------------------- -#' EMMA_warnings -#' + #' This function warns about missing good-practice arguments in enrichment calls. #' A warning is raised if none of the synonyms for a given category appear #' in `arg_names` diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 1fe9721..c3e72cd 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -1,25 +1,32 @@ #' EMMA_run #' -#' This function executes any supported functional enrichment analysis function -#' and automatically captures the call, its associated parameters and provenance -#' information when available during runtime as an `EMMA_record` attribute -#' on the returned results object. +#' This function executes any functional enrichment analysis function and +#' attaches a provenance record (`EMMA_record`) describing the analysis. The +#' captured record includes the original call, metadata derived from the call +#' and its arguments, runtime information, and optionally the current session +#' information. +#' +#' `EMMA_run()` accepts both direct calls to known enrichment functions +#' (`enrichGO()`, `GSEA()`, `fgsea()` ...) and calls to wrapper functions that +#' internally invoke a know enrichment function. +#' #' #' @param expr A function call that performs functional enrichment analysis. #' The call is captured and executed by EMMA to record analysis parameters and -#' provenance information. Both bare calls (`enrichGO(...)`) and namespace-qualified -#' calls (`clusterProfiler::enrichGO(...)`) are supported. Any other form -#' (e.g. `do.call`, `get()`) will raise an error +#' provenance information. Both bare calls (`enrichGO(...)`) and +#' namespace-qualified calls (`clusterProfiler::enrichGO(...)`) are supported. #' @param envir An environment in which to evaluate `expr` #' @param session Logical, indicating whether to store the output of #' `sessionInfo()` or not. If `TRUE` (default), the session is stored in the #' provenance record #' @param args_form A character string indicating whether to store the evaluated -#' or the unevaluated arguments in the provenance record. It default to `"evaluated"` +#' or the unevaluated arguments in the provenance record. It default to +#' `"evaluated"` #' #' @returns The result object returned by the enrichment function in `expr`, -#' unmodified except for an added `EMMA_record` attribute containing the -#' provenance information. Use `getEMMARecord()` to access it +#' in standard format, with an additional `EMMA_record` attribute containing the +#' provenance information. Use `getEMMARecord()` to retrieve this record +#' #' @export #' #' @examples @@ -28,8 +35,11 @@ #' EMMA_run(clusterProfiler::enrichGO(gene = rownames(de_res_IFNg_vs_naive), #' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, #' ont = "BP")) -EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, +EMMA_run <- function(expr, + envir = parent.frame(), + session = TRUE, args_form = c("evaluated", "unevaluated")) { + args_form <- match.arg(args_form) # capture call @@ -40,11 +50,22 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, stop("`expr` must be a function call, e.g. enrichGO(...)!") } - # capture call information - info_call <- EMMA_capture_call_info(call = call) - function_name <- info_call$function_name - package_name <- info_call$package_name - + if (!is.environment(envir)) { + stop("`envir` must be an environment!") + } + + if (!is.logical(session) || length(session) != 1L || is.na(session)) { + stop("`session` must be a single TRUE or FALSE value") + } + + # decide whether we are dealing with a known function or a wrapper + call_class <- EMMA_classify_call(call, envir = envir) + info_call <- call_class$info_call + function_name <- info_call$function_name + package_name <- info_call$package_name + wrapper <- call_class$wrapper + wrapped_original <- call_class$wrapped_original + # capture args (unevaluated) arg_list <- info_call$arg_list arg_names <- names(arg_list) @@ -52,7 +73,7 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, # some good practice warning, i.e. when multiple testing correction is skipped # or bg geneset not set EMMA_warnings(arg_names = arg_names, - function_name = function_name) + function_name = function_name) #capture analysis time start_time <- Sys.time() @@ -62,7 +83,6 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, args <- lapply(arg_list, eval, envir = envir) info_call$args <- args - # get the function fun <- eval(call[[1]], envir = envir) @@ -71,16 +91,17 @@ EMMA_run <- function(expr, envir = parent.frame(), session = TRUE, # capture metadata from the used function and arguments metadata <- EMMA_get_metadata( - function_name = function_name, - package_name = package_name, - args = args + call_class, + args, + envir = envir ) # record everything in EMMA_record - EMMA_record <- EMMA_build_record(info_call, args_form, metadata, + EMMA_record <- EMMA_build_record(info_call, args_form, metadata, + wrapped_original,wrapper, start_time, session) - # store the EMMA_record as attribute of the results obj + # attach EMMA_record as attribute of the results obj attr(results, "EMMA_record") <- EMMA_record return(results) From 848c99dfb13684d355bb741a718f8be8f32bafbe Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Thu, 30 Apr 2026 16:57:46 +0200 Subject: [PATCH 40/92] typos fixed --- DESCRIPTION | 2 +- R/EMMA_freeze.R | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a00d977..5bc8d6c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: EMMA -Title: EMMA: Enrichment Methods Matter for enabeling fully reproducible and +Title: EMMA: Enrichment Methods Matter for enabling fully reproducible and provenance-aware pathway analysis Version: 0.2.0 Authors@R: diff --git a/R/EMMA_freeze.R b/R/EMMA_freeze.R index c71e18c..99df609 100644 --- a/R/EMMA_freeze.R +++ b/R/EMMA_freeze.R @@ -1,5 +1,5 @@ #' EMMA_freeze -#' +#' #' This function records the R environment during analysis runtime and generates #' a lockfile that can be used with tools such as `renv`. #' By default, all currently loaded namespaces are recorded. @@ -7,40 +7,40 @@ #' @param project Character string corresponding to the path to the project #' directory where the lockfile should be written. If the directory does not #' exist, it will be created. It defaults to the current working directory -#' @param file Character string refering to the name of the lockfile to generate. +#' @param file Character string referring to the name of the lockfile to generate. #' It defaults to "renv.lock" #' @param pkgs Character vector of package names to snapshot. It defaults to all #' currently loaded namespaces via `loadedNamespaces()` -#' +#' #' @details #' This function calls `renv::snapshot()` with the specified packages. #' The resulting lockfile can later be restored with `renv::restore()` to #' recreate the same package environment. -#' +#' #' @returns TODO json file like to create a lock.file? -#' +#' #' @seealso \code{\link[renv]{snapshot}}, \code{\link[renv]{restore}} -#' +#' #' @export -#' +#' #' @examples #' \dontrun{ -#' # Create a lockfile in a separate directory -#' EMMA_freeze(project = "emma_env") +#' # Create a lockfile in a separate directory +#' EMMA_freeze(project = "emma_env") #' -#' # Restore later with renv -#' renv::restore(project = "emma_env") +#' # Restore later with renv +#' renv::restore(project = "emma_env") #' } EMMA_freeze <- function(project = getwd(), file = "renv.lock", pkgs = loadedNamespaces()){ - + # if (!dir.exists(project)) dir.create(project, recursive = TRUE) - # + # # renv::snapshot( # project = project, # lockfile = file, # packages = pkgs # ) - # -} \ No newline at end of file + # +} From c8dd29a8beb0d0b657b34f09a4eebfd715791463 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Thu, 30 Apr 2026 16:58:31 +0200 Subject: [PATCH 41/92] spacing in the examples --- R/EMMA_add_custom_metadata.R | 32 ++++++++++++++---------- R/EMMA_explain.R | 44 ++++++++++++++++++--------------- man/EMMA_add_custom_metadata.Rd | 16 ++++++++---- man/EMMA_explain.Rd | 10 +++++--- man/EMMA_freeze.Rd | 10 ++++---- 5 files changed, 66 insertions(+), 46 deletions(-) diff --git a/R/EMMA_add_custom_metadata.R b/R/EMMA_add_custom_metadata.R index 3a854a9..56b3bc8 100644 --- a/R/EMMA_add_custom_metadata.R +++ b/R/EMMA_add_custom_metadata.R @@ -1,5 +1,5 @@ #' EMMA_add_custom_metadata -#' +#' #' Append or replace the `extra` field in the `EMMA_record` attribute #' of a result object returned by `EMMA_run()`. This allows users to manually #' provide additional annotation or contextual information that could not be @@ -17,28 +17,34 @@ #' data("de_res_IFNg_vs_naive", package = "EMMA") #' data("universe", package = "EMMA") #' library("clusterProfiler") -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP")) -#' res <- EMMA_add_custom_metadata(res, extra = -#' list(note = "The background gene set list was all expressed genes in the assay")) +#' res <- EMMA_run( +#' enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", +#' OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP" +#' ) +#' ) +#' res <- EMMA_add_custom_metadata( +#' res, +#' extra = list(note = "The background gene set list was all expressed genes in the assay") +#' ) EMMA_add_custom_metadata <- function(res, extra = list()) { - + if (!is.list(extra)) { stop("`extra` must be a list!") } - + if (length(extra) > 0L && is.null(names(extra))) { stop("`extra` must be a named list!") } - + emma_rec <- getEMMARecord(res = res) - + emma_rec$extra <- extra - + # update attr(res, "EMMA_record") <- emma_rec - + return(res) -} \ No newline at end of file +} diff --git a/R/EMMA_explain.R b/R/EMMA_explain.R index e915f93..33790d4 100644 --- a/R/EMMA_explain.R +++ b/R/EMMA_explain.R @@ -14,26 +14,30 @@ #' data("de_res_IFNg_vs_naive", package = "EMMA") #' data("universe", package = "EMMA") #' library("clusterProfiler") -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP", pAdjustMethod = "BH")) +#' res <- EMMA_run( +#' enrichGO(gene = rownames(de_res_IFNg_vs_naive), +#' universe = universe, keyType = "ENSEMBL", +#' OrgDb = org.Hs.eg.db::org.Hs.eg.db, +#' ont = "BP", pAdjustMethod = "BH" +#' ) +#' ) #' EMMA_explain(res) -#' +#' EMMA_explain <- function(res){ - + emma_rec <- getEMMARecord(res) - + function_name <- emma_rec$method$function_name pkg_name <- emma_rec$method$package_name pkg_version <- emma_rec$method$package_version db <- emma_rec$annotation$gene_set_db db_version <- emma_rec$annotation$gene_set_db_version - + args <- emma_rec$input$arguments arg_names <- names(args) - + message("You can always complete your text with additional information from `getEMMARecord()`!") - + if (emma_rec$method$wrapper) { text <- paste0("Functional Enrichment Analysis was performed using a wrapper function ", function_name, "()") @@ -41,33 +45,33 @@ EMMA_explain <- function(res){ text <- paste0("Functional Enrichment Analysis was performed using the ", function_name, "() function") } - + # checks to avoid text with NA if (!is.null(pkg_name) && !is.na(pkg_name)) { text <- paste0(text, " from the ", pkg_name, " package") } - + if (!is.null(pkg_version) && !is.na(pkg_version)) { text <- paste0(text, " (version ", pkg_version, ")") } - + if (!is.null(db) && !all(is.na(db))) { text <- paste0(text, " with the ", paste(db, collapse = ", "), " database") } - + if (!is.null(db_version) && !all(is.na(db_version))) { text <- paste0(text, " (version ", paste(db_version, collapse = ", "),")") } - + text <- paste0(text, ".") - + ### info abt bg genes bg_arg <- intersect(c("universe", "background", "custom_bg", "bg_genes"), arg_names) fdr_arg <- intersect(c("correction_method", "pAdjustMethod"), arg_names) - + if (length(bg_arg) == 1) { text <- paste0(text, " A custom background gene set was provided (n = ", @@ -79,8 +83,8 @@ EMMA_explain <- function(res){ } else { text <- paste0(text, " No custom background gene set was recorded.") } - - + + ### info abt the fdr correction if (length(fdr_arg) == 1) { text <- paste0(text, @@ -94,6 +98,6 @@ EMMA_explain <- function(res){ text <- paste0(text, " Multiple testing correction was not applied.") } } - + return(text) -} \ No newline at end of file +} diff --git a/man/EMMA_add_custom_metadata.Rd b/man/EMMA_add_custom_metadata.Rd index c3849f5..7b092bb 100644 --- a/man/EMMA_add_custom_metadata.Rd +++ b/man/EMMA_add_custom_metadata.Rd @@ -26,9 +26,15 @@ captured automatically data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") library("clusterProfiler") -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP")) -res <- EMMA_add_custom_metadata(res, extra = -list(note = "The background gene set list was all expressed genes in the assay")) +res <- EMMA_run( + enrichGO(gene = rownames(de_res_IFNg_vs_naive), + universe = universe, keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db::org.Hs.eg.db, + ont = "BP" + ) +) +res <- EMMA_add_custom_metadata( + res, + extra = list(note = "The background gene set list was all expressed genes in the assay") +) } diff --git a/man/EMMA_explain.Rd b/man/EMMA_explain.Rd index 4807582..6e1317a 100644 --- a/man/EMMA_explain.Rd +++ b/man/EMMA_explain.Rd @@ -24,9 +24,13 @@ call, the parameters, software context, and reference databases used. data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") library("clusterProfiler") -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP", pAdjustMethod = "BH")) +res <- EMMA_run( + enrichGO(gene = rownames(de_res_IFNg_vs_naive), + universe = universe, keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db::org.Hs.eg.db, + ont = "BP", pAdjustMethod = "BH" + ) +) EMMA_explain(res) } diff --git a/man/EMMA_freeze.Rd b/man/EMMA_freeze.Rd index 49813f4..0e20767 100644 --- a/man/EMMA_freeze.Rd +++ b/man/EMMA_freeze.Rd @@ -11,7 +11,7 @@ EMMA_freeze(project = getwd(), file = "renv.lock", pkgs = loadedNamespaces()) directory where the lockfile should be written. If the directory does not exist, it will be created. It defaults to the current working directory} -\item{file}{Character string refering to the name of the lockfile to generate. +\item{file}{Character string referring to the name of the lockfile to generate. It defaults to "renv.lock"} \item{pkgs}{Character vector of package names to snapshot. It defaults to all @@ -32,11 +32,11 @@ recreate the same package environment. } \examples{ \dontrun{ -# Create a lockfile in a separate directory -EMMA_freeze(project = "emma_env") + # Create a lockfile in a separate directory + EMMA_freeze(project = "emma_env") -# Restore later with renv -renv::restore(project = "emma_env") + # Restore later with renv + renv::restore(project = "emma_env") } } \seealso{ From ee0455a4993d7d0f627f13571622fbf904a07171 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Thu, 30 Apr 2026 16:58:41 +0200 Subject: [PATCH 42/92] suppress all messages in setup of tests --- tests/testthat/setuptests_EMMA.R | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index e73a0fe..d7ad68c 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -1,8 +1,10 @@ -invisible(lapply( - c("clusterProfiler", "org.Hs.eg.db", "mosdef", "topGO", "gprofiler2"), - function(pkg) suppressPackageStartupMessages( - library(pkg, character.only = TRUE)) -)) +suppressPackageStartupMessages({ + library("clusterProfiler") + library("org.Hs.eg.db") + library("mosdef") + library("topGO") + library("gprofiler2") +}) data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") From 44f92df4fca5892d0fd88a7126e82a6da8c48b70 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:38:41 +0200 Subject: [PATCH 43/92] update build ignore --- .Rbuildignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.Rbuildignore b/.Rbuildignore index b7aa75c..2a7eb9b 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,3 +7,5 @@ ^_pkgdown\.yml$ ^docs$ ^pkgdown$ +^\.positai$ +^\.claude$ From 2349b5562db2c2c252164bba0f80207890a8ef4f Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:39:23 +0200 Subject: [PATCH 44/92] update description file --- DESCRIPTION | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a00d977..d09d2f9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,11 +28,10 @@ Description: EMMA is a package that provides a provenance-aware execution License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 +RoxygenNote: 8.0.0 Imports: - methods, AnnotationDbi, - GO.db + cli Suggests: knitr, rmarkdown, @@ -41,12 +40,15 @@ Suggests: clusterProfiler, gprofiler2, org.Hs.eg.db, + GO.db, mosdef, topGO, + renv, + DeeDeeExperiment, testthat (>= 3.0.0) VignetteBuilder: knitr URL: https://github.com/imbeimainz/EMMA BugReports: https://github.com/imbeimainz/EMMA/issues biocViews: Software, Pathways, GO, KEGG, GeneSetEnrichment, ImmunoOncology, - ReproducibleResearch, Transcriptomics, SingleCell + Transcriptomics, SingleCell, GeneExpression, DifferentialExpression Config/testthat/edition: 3 From cc8d9331b43feccca4a8b183f80f415039a4957c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:41:57 +0200 Subject: [PATCH 45/92] update man page --- man/EMMA-pkg.Rd | 1 + man/EMMA_add_custom_metadata.Rd | 9 ++----- man/EMMA_explain.Rd | 19 +++++++-------- man/EMMA_freeze.Rd | 42 +++++++++++++++++++++++++-------- man/EMMA_get_record.Rd | 22 +++++++++++++++++ man/EMMA_run.Rd | 14 ++++++----- man/fea_res.Rd | 32 +++++++++++++++++++++++++ man/getEMMARecord.Rd | 27 --------------------- man/universe.Rd | 2 +- 9 files changed, 107 insertions(+), 61 deletions(-) create mode 100644 man/EMMA_get_record.Rd create mode 100644 man/fea_res.Rd delete mode 100644 man/getEMMARecord.Rd diff --git a/man/EMMA-pkg.Rd b/man/EMMA-pkg.Rd index c4f1850..e3590ec 100644 --- a/man/EMMA-pkg.Rd +++ b/man/EMMA-pkg.Rd @@ -23,6 +23,7 @@ Useful links: Authors: \itemize{ + \item Najla Abassi \email{abassi.nejla96@gmail.com} (\href{https://orcid.org/0000-0001-8357-0938}{ORCID}) \item Annekathrin Nedwed \email{anneludt@uni-mainz.de} (\href{https://orcid.org/0000-0002-2475-4945}{ORCID}) \item Federico Marini \email{marinif@uni-mainz.de} (\href{https://orcid.org/0000-0003-3252-7758}{ORCID}) } diff --git a/man/EMMA_add_custom_metadata.Rd b/man/EMMA_add_custom_metadata.Rd index c3849f5..502249f 100644 --- a/man/EMMA_add_custom_metadata.Rd +++ b/man/EMMA_add_custom_metadata.Rd @@ -23,12 +23,7 @@ provide additional annotation or contextual information that could not be captured automatically } \examples{ -data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") -library("clusterProfiler") -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP")) -res <- EMMA_add_custom_metadata(res, extra = +data("fea_res", package = "EMMA") +fea_res <- EMMA_add_custom_metadata(fea_res, extra = list(note = "The background gene set list was all expressed genes in the assay")) } diff --git a/man/EMMA_explain.Rd b/man/EMMA_explain.Rd index 4807582..b3f5d4b 100644 --- a/man/EMMA_explain.Rd +++ b/man/EMMA_explain.Rd @@ -4,12 +4,16 @@ \alias{EMMA_explain} \title{EMMA_explain} \usage{ -EMMA_explain(res) +EMMA_explain(res, get_citation = TRUE) } \arguments{ \item{res}{A functional enrichment analysis results object as returned by -\code{EMMA_run()}. Its attributes contain \code{EMMA_record}, which -contains all provenance information of the performed FEA} +\code{EMMA_run()}. Its attributes contain \code{EMMA_record}, which contains all +provenance information of the performed FEA} + +\item{get_citation}{Logical indicating whether to display the citations of +the packages used in the FEA. It only prints the citations in an interactive +session (e.g console). Defaults to \code{TRUE}} } \value{ A character string describing how the FEA was performed using the @@ -21,12 +25,7 @@ to a Materials and Methods section of a paper, by summarizing the executed call, the parameters, software context, and reference databases used. } \examples{ -data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") -library("clusterProfiler") -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP", pAdjustMethod = "BH")) -EMMA_explain(res) +data("fea_res", package = "EMMA") +EMMA_explain(fea_res) } diff --git a/man/EMMA_freeze.Rd b/man/EMMA_freeze.Rd index 49813f4..bb8eca1 100644 --- a/man/EMMA_freeze.Rd +++ b/man/EMMA_freeze.Rd @@ -4,7 +4,13 @@ \alias{EMMA_freeze} \title{EMMA_freeze} \usage{ -EMMA_freeze(project = getwd(), file = "renv.lock", pkgs = loadedNamespaces()) +EMMA_freeze( + project = getwd(), + file = "renv.lock", + pkgs = loadedNamespaces(), + prompt = interactive(), + force = TRUE +) } \arguments{ \item{project}{Character string corresponding to the path to the project @@ -16,27 +22,43 @@ It defaults to "renv.lock"} \item{pkgs}{Character vector of package names to snapshot. It defaults to all currently loaded namespaces via \code{loadedNamespaces()}} + +\item{prompt}{Logical indicating whether to prompt before taking actions. +Defaults to \code{interactive()}} + +\item{force}{Logical indicating whether to force creation of the lockfile. +Defaults to \code{TRUE}} } \value{ -TODO json file like to create a lock.file? +Invisibly returns the path to the generated lockfile. +The lockfile is written in JSON format and can be used with \code{renv::restore()} +to recreate the package environment } \description{ -This function records the R environment during analysis runtime and generates -a lockfile that can be used with tools such as \code{renv}. -By default, all currently loaded namespaces are recorded. +This function records the R environment at the time of analysis and generates +a lockfile that can be used with tools such as \code{renv} } \details{ This function calls \code{renv::snapshot()} with the specified packages. The resulting lockfile can later be restored with \code{renv::restore()} to recreate the same package environment. + +By default, the lockfile is created with \code{force = TRUE}, allowing snapshot +creation even if inconsistencies are detected in the environment. } \examples{ -\dontrun{ -# Create a lockfile in a separate directory -EMMA_freeze(project = "emma_env") +# create a lockfile +if (requireNamespace("renv", quietly = TRUE)) { +tmp <- tempfile("emma_env") +dir.create(tmp) + +EMMA_freeze(project = tmp) + +# inspect generated files +list.files(tmp) -# Restore later with renv -renv::restore(project = "emma_env") +# to restore the environment later +# renv::restore(project = tmp) } } \seealso{ diff --git a/man/EMMA_get_record.Rd b/man/EMMA_get_record.Rd new file mode 100644 index 0000000..a2c2617 --- /dev/null +++ b/man/EMMA_get_record.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/EMMA_get_record.R +\name{EMMA_get_record} +\alias{EMMA_get_record} +\title{EMMA_get_record} +\usage{ +EMMA_get_record(res) +} +\arguments{ +\item{res}{Functional Enrichment Analysis results (enrichResult, gseaResult ...) +generated by \code{EMMA_run()}} +} +\value{ +list of metadata recorded during FEA runtime +} +\description{ +EMMA_get_record +} +\examples{ +data("fea_res", package = "EMMA") +EMMA_get_record(fea_res) +} diff --git a/man/EMMA_run.Rd b/man/EMMA_run.Rd index caed5d4..72dee43 100644 --- a/man/EMMA_run.Rd +++ b/man/EMMA_run.Rd @@ -7,7 +7,7 @@ EMMA_run( expr, envir = parent.frame(), - session = TRUE, + store_session_info = TRUE, args_form = c("evaluated", "unevaluated") ) } @@ -19,7 +19,7 @@ namespace-qualified calls (\code{clusterProfiler::enrichGO(...)}) are supported. \item{envir}{An environment in which to evaluate \code{expr}} -\item{session}{Logical, indicating whether to store the output of +\item{store_session_info}{Logical, indicating whether to store the output of \code{sessionInfo()} or not. If \code{TRUE} (default), the session is stored in the provenance record} @@ -30,7 +30,7 @@ or the unevaluated arguments in the provenance record. It default to \value{ The result object returned by the enrichment function in \code{expr}, in standard format, with an additional \code{EMMA_record} attribute containing the -provenance information. Use \code{getEMMARecord()} to retrieve this record +provenance information. Use \code{EMMA_get_record()} to retrieve this record } \description{ This function executes any functional enrichment analysis function and @@ -47,7 +47,9 @@ internally invoke a know enrichment function. \examples{ data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") -EMMA_run(clusterProfiler::enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP")) +library(gprofiler2) + +EMMA_run(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", +correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), +store_session_info = FALSE, args_form = "unevaluated") } diff --git a/man/fea_res.Rd b/man/fea_res.Rd new file mode 100644 index 0000000..b08224c --- /dev/null +++ b/man/fea_res.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/EMMA-data.R +\docType{data} +\name{fea_res} +\alias{fea_res} +\title{A sample \code{list} containing Functional Enrichment Analysis results, +generated with \code{gprofiler2}} +\format{ +A \code{list} +} +\value{ +A sample \code{list} containing the FEA results \code{result} and \code{metadata}. +This results object has the \code{EMMA_record} attribute. +} +\description{ +A sample \code{list} containing Functional Enrichment Analysis results, +generated with \code{gprofiler2} +} +\details{ +This \code{list} object contains the result table and metadata of the +functional enrichment analysis (FEA) performed on the \code{macrophage} data, +specifically using the \code{gost()} function from the \code{gprofiler2} package, and +wrapped in \code{EMMA_run()} + +The code to create said object can be found in the folder \verb{/inst/scripts} in +the EMMA package, the file is called \code{create_datasets_examples.R}. +} +\references{ +Alasoo, et al. "Shared genetic effects on chromatin and gene +expression indicate a role for enhancer priming in immune response", +Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. +} diff --git a/man/getEMMARecord.Rd b/man/getEMMARecord.Rd deleted file mode 100644 index 3d55b1f..0000000 --- a/man/getEMMARecord.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/getEMMARecord.R -\name{getEMMARecord} -\alias{getEMMARecord} -\title{getEMMARecord} -\usage{ -getEMMARecord(res) -} -\arguments{ -\item{res}{Functional Enrichment Analysis results (enrichResult, gseaResult ...) -generated by \code{EMMA_run()}} -} -\value{ -list of metadata recorded during FEA runtime -} -\description{ -getEMMARecord -} -\examples{ -data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") -library("clusterProfiler") -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP")) -getEMMARecord(res) -} diff --git a/man/universe.Rd b/man/universe.Rd index 7a7e772..aae40db 100644 --- a/man/universe.Rd +++ b/man/universe.Rd @@ -17,7 +17,7 @@ A sample \verb{character vector} containing the background gene list used to perform FEA on the \code{macrophage} dataset } \details{ -This \verb{character vector} object that contains the assay's \code{rownames} +This \verb{character vector} object contains the assay's \code{rownames} of the \code{macrophage} data The code to create said object can be found in the folder \verb{/inst/scripts} in From 91064d2c68b5b7cde7d797955efe38c37a47b7f7 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:42:51 +0200 Subject: [PATCH 46/92] expending vignette --- vignettes/EMMA_bibliography.bib | 80 ++++++++++- vignettes/Introduction_to_EMMA.Rmd | 204 +++++++++++++++++------------ 2 files changed, 199 insertions(+), 85 deletions(-) diff --git a/vignettes/EMMA_bibliography.bib b/vignettes/EMMA_bibliography.bib index 1c783b0..5cd3758 100644 --- a/vignettes/EMMA_bibliography.bib +++ b/vignettes/EMMA_bibliography.bib @@ -13,4 +13,82 @@ @article{Alasoo2018 url = {http://dx.doi.org/10.1038/s41588-018-0046-7}, volume = {50}, year = {2018} -} \ No newline at end of file +} + +@article{Khatri2012, + title = {Ten Years of Pathway Analysis: Current Approaches and Outstanding Challenges}, + volume = {8}, + ISSN = {1553-7358}, + url = {http://dx.doi.org/10.1371/journal.pcbi.1002375}, + DOI = {10.1371/journal.pcbi.1002375}, + number = {2}, + journal = {PLoS Computational Biology}, + publisher = {Public Library of Science (PLoS)}, + author = {Khatri, Purvesh and Sirota, Marina and Butte, Atul J.}, + editor = {Ouzounis, Christos A.}, + year = {2012}, + month = Feb, + pages = {e1002375} +} + +@article{Subramanian2005, + title = {Gene set enrichment analysis: A knowledge-based approach for interpreting genome-wide expression profiles}, + volume = {102}, + ISSN = {1091-6490}, + url = {http://dx.doi.org/10.1073/pnas.0506580102}, + DOI = {10.1073/pnas.0506580102}, + number = {43}, + journal = {Proceedings of the National Academy of Sciences}, + publisher = {Proceedings of the National Academy of Sciences}, + author = {Subramanian, Aravind and Tamayo, Pablo and Mootha, Vamsi K. and Mukherjee, Sayan and Ebert, Benjamin L. and Gillette, Michael A. and Paulovich, Amanda and Pomeroy, Scott L. and Golub, Todd R. and Lander, Eric S. and Mesirov, Jill P.}, + year = {2005}, + month = Sept, + pages = {15545–15550} +} + +@article{Wijesooriya2022, + title = {Urgent need for consistent standards in functional enrichment analysis}, + volume = {18}, + ISSN = {1553-7358}, + url = {http://dx.doi.org/10.1371/journal.pcbi.1009935}, + DOI = {10.1371/journal.pcbi.1009935}, + number = {3}, + journal = {PLOS Computational Biology}, + publisher = {Public Library of Science (PLoS)}, + author = {Wijesooriya, Kaumadi and Jadaan, Sameer A. and Perera, Kaushalya L. and Kaur, Tanuveer and Ziemann, Mark}, + editor = {Kemp, Melissa L.}, + year = {2022}, + month = Mar, + pages = {e1009935} +} + +@article{Brazma2001, + title = {Minimum information about a microarray experiment (MIAME)—toward standards for microarray data}, + volume = {29}, + ISSN = {1546-1718}, + url = {http://dx.doi.org/10.1038/ng1201-365}, + DOI = {10.1038/ng1201-365}, + number = {4}, + journal = {Nature Genetics}, + publisher = {Springer Science and Business Media LLC}, + author = {Brazma, Alvis and Hingamp, Pascal and Quackenbush, John and Sherlock, Gavin and Spellman, Paul and Stoeckert, Chris and Aach, John and Ansorge, Wilhelm and Ball, Catherine A. and Causton, Helen C. and Gaasterland, Terry and Glenisson, Patrick and Holstege, Frank C.P. and Kim, Irene F. and Markowitz, Victor and Matese, John C. and Parkinson, Helen and Robinson, Alan and Sarkans, Ugis and Schulze-Kremer, Steffen and Stewart, Jason and Taylor, Ronald and Vilo, Jaak and Vingron, Martin}, + year = {2001}, + month = Dec, + pages = {365–371} +} + + + + + + + + + + + + + + + + diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 5416a15..c34b6b1 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -34,13 +34,19 @@ editor_options: bibliography: EMMA_bibliography.bib --- -```{r, include = FALSE} + + +```{r knitr, include = FALSE} knitr::opts_chunk$set( - comment = "#>", - error = FALSE, - warning = FALSE, - eval = TRUE, - message = FALSE + comment = "#>", + error = FALSE, + warning = FALSE, + eval = TRUE, + message = FALSE ) ``` @@ -49,24 +55,30 @@ knitr::opts_chunk$set( Functional Enrichment Analysis (FEA) is a key downstream step in omics workflows, commonly applied after differential expression analysis to support biological interpretation and generate pathway-level hypotheses. A wide range of tools and -methods are available, leading to substantial heterogeneity in analytical choices -and reported results. - -Despite its widespread use, FEA is often insufficiently documented. Critical -parameters such as background gene sets or multiple testing correction methods -are frequently missing or inconsistently reported in scientific papers, -limiting reproducibility and interpretability. Currently, no standardized method -exists to ensure transparent and reproducible documentation of FEA workflows. +methods are available, mainly Over-Representation Analysis (ORA) [@Khatri2012] [doi:10.1371/journal.pcbi.1002375](https://doi.org/10.1371/journal.pcbi.1002375) +and Gene Set Enrichment Analysis (GSEA) [@Subramanian2005] [doi:10.1073/pnas.0506580102](https://doi.org/10.1073/pnas.0506580102), leading +to substantial heterogeneity in analytical choices and reported results. + +Despite its widespread use, FEA is often insufficiently documented [@Wijesooriya2022] +[doi:10.1371/journal.pcbi.1009935](https://doi.org/10.1371/journal.pcbi.1009935). +Critical parameters such as background gene sets or multiple testing correction +methods are frequently missing or inconsistently reported in scientific papers, +limiting reproducibility and interpretability. Currently, no standardized framework +exists to ensure transparent and reproducible documentation of FEA workflows, +comparable to the MIAME guidelines [@Brazma2001] +[doi:10.1038/ng1201-365]( https://doi.org/10.1038/ng1201-365). To address this gap, we introduce `r BiocStyle::Biocpkg("EMMA")`, a framework that automatically captures key analytical parameters and provenance information during the execution of FEA methods. This vignette demonstrates how `EMMA` integrates with existing tools -(e.g. `clusterProfiler`, `topGO`, `Enrichr`, `gprofiler2`) to execute enrichment +(e.g. `r BiocStyle::Biocpkg("clusterProfiler")`, +`r BiocStyle::Biocpkg("topGO")`, `r BiocStyle::Biocpkg("Enrichr")`, +`r BiocStyle::Biocpkg("gprofiler2")`) to execute enrichment analyses while systematically capturing analysis parameters and provenance -information during runtime, and returning enrichment results in their native -format alongside structured and reusable metadata. +information during runtime, returning enrichment results +**without altering their original format**, alongside structured and reusable metadata. ## What do you get with `EMMA`? @@ -78,15 +90,30 @@ Using `EMMA` allows you to: * Generate reproducible summaries of the analysis (e.g. Methods sections) * Facilitate sharing of results together with their analysis context +## How does `EMMA` work? + +`EMMA` works by wrapping an enrichment call, executing it, and capturing relevant +provenance information and parameters during runtime. The recorded metadata is then +attached directly to the result object using R’s **attribute system**. + +This approach enables `EMMA` to preserve provenance information +**without modifying the original result structure or introducing new classes**, +allowing users to continue working seamlessly with standard outputs from existing tools. + +While Bioconductor provides dedicated metadata slots for certain S4 classes +(e.g. via `metadata()`), these are not consistently available across all enrichment +result types. By relying on attributes, provenance information can be attached +to any result object regardless of its underlying class. + # Getting started {#gettingstarted} -To install the development version of this package, start R and enter: +To install this package, start R and enter: ```{r install, eval = FALSE} -library("remotes") -remotes::install_github("imbeimainz/EMMA", - dependencies = TRUE, - build_vignettes = TRUE) +if (!requireNamespace("BiocManager", quietly = TRUE)) { + install.packages("BiocManager")} + +BiocManager::install("EMMA") ``` Once installed, the package can be loaded and attached to the current workspace @@ -96,12 +123,25 @@ as follows: library("EMMA") ``` + +# A new section here: TODO? + +MAybe where we describe a schematics of the intuition overall? +Like: stepwise with bullet point +- run DE +- run FEA as usual, passing the call (with a pipe or in a call itself) to EMMA_run +- show how to retrieve info (just the names) +- summarizing the info +- explaining it +(in my eyes, a "chunk not evaluated could be also good") + a figure would be fantastic with a simple diagram? +Maybe a diagram that would show what EMMA puts in and avoids you to take notes of? + + # `EMMA` on the `macrophage` dataset -In the remainder of this vignette, we will illustrate the main features of `r BiocStyle::Biocpkg("EMMA")` on a publicly available dataset from -Alasoo, et al. "Shared genetic effects on chromatin and gene expression indicate -a role for enhancer priming in immune response", published in Nature Genetics, -January 2018 +In the remainder of this vignette, we will illustrate the main features of `r BiocStyle::Biocpkg("EMMA")` on a publicly available dataset from Alasoo, et al. "Shared genetic effects on chromatin +and gene expression indicate a role for enhancer priming in immune response", +published in Nature Genetics, January 2018 [@Alasoo2018] [doi:10.1038/s41588-018-0046-7](https://doi.org/10.1038/s41588-018-0046-7). The data is made available via the `r BiocStyle::Biocpkg("macrophage")` @@ -135,7 +175,7 @@ regular bulk RNA-seq data analysis workflow. For this, we will load the `macrophage` data and perform Differential Expression Analysis with `r BiocStyle::Biocpkg("DESeq2")` -```{r dds} +```{r differential_expression} # load data data(gse, "macrophage") # set up design @@ -145,23 +185,10 @@ rownames(dds_macrophage) <- substr(rownames(dds_macrophage), 1, 15) keep <- rowSums(counts(dds_macrophage) >= 10) >= 6 dds_macrophage <- dds_macrophage[keep, ] -dds_macrophage -``` - -We then run the main `DESeq()` function and check the `resultsNames` being -generated: -```{r run_deseq} # run DESeq dds_macrophage <- DESeq(dds_macrophage) -# contrasts -resultsNames(dds_macrophage) -``` - -Let's extract the DE results for each contrast. For the sake of demonstration, -we will do one contrast: -```{r get_de_res} # get de res for 1st contrast IFNg_vs_naive <- results(dds_macrophage, contrast = c("condition", "IFNg", "naive"), @@ -170,21 +197,16 @@ IFNg_vs_naive <- lfcShrink(dds_macrophage, coef = "condition_IFNg_vs_naive", res = IFNg_vs_naive, type = "apeglm") IFNg_vs_naive$SYMBOL <- rowData(dds_macrophage)$SYMBOL -IFNg_vs_naive -``` - -Now we'll sort the results based on the adjusted p-value: -```{r DEGs} -# sort +# sort by adjusted p value de_res <- IFNg_vs_naive[order(IFNg_vs_naive$padj), ] de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] -# set universe -universe <- rownames(dds_macrophage) +# set background gene list +gene_universe <- rownames(dds_macrophage) ``` -# Perform Functional Enrichment Analysis (FEA) +# Perform Functional Enrichment Analysis (FEA) ## `EMMA` with available common packages/functions @@ -192,7 +214,7 @@ Now that we have a list of DE genes for this contrast, we can perform Functional Enrichment Analysis. In the following example, we will use the function `enrichGO()` from `r BiocStyle::Biocpkg("clusterProfiler")` -### Capturing record with `EMMA_run()` +### `EMMA_run()` :Capturing the recorded information `EMMA_run()` accepts a function call (e.g. `enrichGO(...)`) and executes it as it is, while capturing the associated parameters and provenance information: @@ -232,7 +254,7 @@ As you can see, `EMMA` returns the FEA results in their **native/standard** form example, we didn't define a list of background genes (which can influence the results), so we get warned about that. -### Retrieving recorded information with `getEMMARecord()` & `EMMA_show()` +### `EMMA_show()` : Summarizing the recorded information To get a quick summary of what `EMMA` captured while we ran the analysis, we use `EMMA_show()`: @@ -245,10 +267,12 @@ EMMA_show(fea_res) results object. That's why it is always a good practice to save the original results, and not only the subsets of interest. -To access the full recorded information, we use `getEMMARecord()`: +### `EMMA_get_record()` : Retrieving the recorded information + +To access the full recorded information, we use `EMMA_get_record()`: -```{r getEMMARecord} -emma_record <- getEMMARecord(fea_res) +```{r EMMA_get_record} +emma_record <- EMMA_get_record(fea_res) # get all the record emma_record @@ -257,7 +281,7 @@ emma_record `EMMA` structures the `EMMA_record` attribute (i.e. the recorded provenance information) into a list of elements: -```{EMMA_record_str} +```{bash EMMA_record_str, eval=FALSE} ├── EMMA_record │ ├── method # how the analysis was performed │ │ ├── call @@ -285,11 +309,12 @@ emma_record$method ``` With `EMMA_run()`, we can decide whether we want to save the value of arguments -used in our call or not. For this, we can use the argument `args_form`: +used in our call or not. This can be useful, for example, to avoid unnecessarily +increasing the size of the result object. For this, we can use the argument `args_form`: ```{r argument_form} fea_res_no_param <- enrichGO(gene = rownames(de_res), - universe = universe, + universe = gene_universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", @@ -301,20 +326,20 @@ fea_res_no_param <- enrichGO(gene = rownames(de_res), # else set to evaluated (default) # check -getEMMARecord(fea_res_no_param) +EMMA_get_record(fea_res_no_param) ``` We can also choose whether to save the R session information with the record -using the argument `session`, which defaults to `TRUE`. +using the argument `store_session_info`, which defaults to `TRUE`. -### Summarizing captured information into text +### `EMMA_explain()`: Summarizing recorded information into text `EMMA_explain()` generates a human-readable description of the FEA, similar to a Materials and Methods section of a paper, by summarizing the executed call, the parameters, software context, and reference databases used. -```{r EMMA_explain} -EMMA_explain(fea_res) +```{r EMMA_explain, results = 'asis', message=TRUE} +EMMA_explain(fea_res, get_citation = TRUE) ``` ## `EMMA` with custom/wrapper functions @@ -324,15 +349,16 @@ You can also use a custom function that you developed, or a wrapper function `EMMA_run()` will attempt to capture as much metadata as possible: ```{r mosdef_eg} -fea_res <- mosdef::run_goseq(de_genes = rownames(de_res), - bg_genes = universe, +mosdef_fea_res <- mosdef::run_goseq(de_genes = rownames(de_res), + bg_genes = gene_universe, mapping = "org.Hs.eg.db", id = "ensGene", - genome = "hg19") |> EMMA_run(session = FALSE, - args_form = "unevaluated") + genome = "hg19") |> + EMMA_run(store_session_info = FALSE, + args_form = "unevaluated") # quick inspection -getEMMARecord(fea_res) +EMMA_get_record(mosdef_fea_res) ``` ```{r custom_eg} @@ -343,7 +369,7 @@ my_custom_function <- function(gene, universe = NULL, organism = "hsapiens") { # a wrapper of a wrapper :D res1 <- mosdef::run_topGO(de_genes = gene, - bg_genes = universe, + bg_genes = gene_universe, ontology = ontology, gene_id = id_type, mapping = org_db_name, @@ -351,7 +377,7 @@ my_custom_function <- function(gene, universe = NULL, res2 <- gprofiler2::gost(query = gene, organism = organism, - custom_bg = universe) + custom_bg = gene_universe) return(list(topGO_res = res1, gost_res = res2 @@ -361,31 +387,31 @@ my_custom_function <- function(gene, universe = NULL, # run analysis with EMMA frankenstein_fea <- my_custom_function( gene = rownames(de_res), - universe = universe, + universe = gene_universe, ontology = "BP", id_type = "ENSEMBL", org_db_name = "org.Hs.eg.db", organism = "hsapiens" - ) |> EMMA_run(session = FALSE, args_form = "unevaluated") + ) |> EMMA_run(store_session_info = FALSE, + args_form = "unevaluated") # quick inspection -getEMMARecord(frankenstein_fea) +EMMA_get_record(frankenstein_fea) ``` -# Adding extra information +# `EMMA_add_custom_metadata()`: Adding extra information The user can always attach extra metadata that `EMMA` might not be able to capture automatically. To keep everything organized, we can use `EMMA_add_custom_metadata()` function ```{r add_custom_metadata} - -frankenstein_fea <- EMMA_add_custom_metadata(res = frankenstein_fea, +frankenstein_fea2 <- EMMA_add_custom_metadata(res = frankenstein_fea, extra = list( wrapped_function_topGO = "runTest", notes = "any other meaningful info")) -getEMMARecord(frankenstein_fea)$extra +EMMA_get_record(frankenstein_fea2)$extra ``` Since the `EMMA_record` is attached as attribute to the original results objects, @@ -394,8 +420,7 @@ such as `DeeDeeExperiment`. This enables both FEA results and their associated provenance information to be stored and managed together, facilitating reproducibility, organization, and sharing of complex omics analyses. -```{r emma&dde, eval=FALSE} -#### should we have this chunk runnable and suggest dde? +```{r emma_and_dde} dde <- DeeDeeExperiment::DeeDeeExperiment(sce = dds_macrophage, de_results = IFNg_vs_naive, enrich_results = list( @@ -403,18 +428,29 @@ dde <- DeeDeeExperiment::DeeDeeExperiment(sce = dds_macrophage, fea <- DeeDeeExperiment::getFEA(dde, format = "original") -getEMMARecord(fea) +EMMA_get_record(fea) ``` +# `EMMA_freeze()`: Recording the Analysis Environment -# Recording the Analysis Environment with `EMMA_freeze()` +`EMMA_freeze()` records the R environment at the time of analysis by generating +a lockfile using `renv`. By default, the snapshot is created with `force = TRUE`, +allowing the environment to be recorded even when inconsistencies (e.g. version +mismatches) are present. -`EMMA_freeze()` records the R environment during analysis runtime and generates -a lockfile that can be used with tools such as `renv`, to facilitate -reproducible reconstruction of the analysis environment. +This behavior reflects the goal of preserving the analysis environment as it was +used in practice, rather than attempting to enforce a fully consistent state. ```{r EMMA_freeze} -#EMMA_freeze() +if (requireNamespace("renv", quietly = TRUE)) { + project_path <- tempfile("my_project_with_emma") + dir.create(project_path) +EMMA_freeze(project = project_path, + file = "analysis.lock", + pkgs = loadedNamespaces(), + prompt = FALSE, + force = TRUE) +} ``` # Session info {.unnumbered .smaller} From 5cfe8f2ed88fa65b08b44d86d26810a545796a22 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:43:17 +0200 Subject: [PATCH 47/92] adding toy fea created with EMMA_run --- R/EMMA-data.R | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/R/EMMA-data.R b/R/EMMA-data.R index 2d8b7ea..c66f05f 100644 --- a/R/EMMA-data.R +++ b/R/EMMA-data.R @@ -13,7 +13,6 @@ #' #' @format A `data.frame` object #' -#' #' @references Alasoo, et al. "Shared genetic effects on chromatin and gene #' expression indicate a role for enhancer priming in immune response", #' Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. @@ -25,7 +24,7 @@ NULL #' A sample `character vector` containing the background gene list used to #' perform FEA on the `macrophage` dataset #' -#' @details This `character vector` object that contains the assay's `rownames` +#' @details This `character vector` object contains the assay's `rownames` #' of the `macrophage` data #' #' The code to create said object can be found in the folder `/inst/scripts` in @@ -43,4 +42,29 @@ NULL #' #' @name universe #' @docType data -NULL \ No newline at end of file +NULL + +#' A sample `list` containing Functional Enrichment Analysis results, +#' generated with `gprofiler2` +#' +#' @details This `list` object contains the result table and metadata of the +#' functional enrichment analysis (FEA) performed on the `macrophage` data, +#' specifically using the `gost()` function from the `gprofiler2` package, and +#' wrapped in `EMMA_run()` +#' +#' The code to create said object can be found in the folder `/inst/scripts` in +#' the EMMA package, the file is called `create_datasets_examples.R`. +#' +#' @return A sample `list` containing the FEA results `result` and `metadata`. +#' This results object has the `EMMA_record` attribute. +#' +#' @format A `list` +#' +#' +#' @references Alasoo, et al. "Shared genetic effects on chromatin and gene +#' expression indicate a role for enhancer priming in immune response", +#' Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. +#' +#' @name fea_res +#' @docType data +NULL From 61cf847cc742517f359e7a494dec231f97edb0ff Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:47:47 +0200 Subject: [PATCH 48/92] beautify msgs with cli --- R/EMMA-pkg.R | 1 + R/EMMA_explain.R | 5 ++++- R/EMMA_freeze.R | 2 ++ R/EMMA_internal-utils.R | 6 +++--- R/EMMA_run.R | 4 +++- R/EMMA_show.R | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/R/EMMA-pkg.R b/R/EMMA-pkg.R index 8af20dd..b11a174 100644 --- a/R/EMMA-pkg.R +++ b/R/EMMA-pkg.R @@ -7,6 +7,7 @@ #' @import GO.db #' @importFrom AnnotationDbi metadata #' @importFrom utils packageVersion sessionInfo +#' @importFrom cli cli_alert_info #' @name EMMA-pkg #' @docType package "_PACKAGE" diff --git a/R/EMMA_explain.R b/R/EMMA_explain.R index e915f93..e86cf61 100644 --- a/R/EMMA_explain.R +++ b/R/EMMA_explain.R @@ -32,7 +32,10 @@ EMMA_explain <- function(res){ args <- emma_rec$input$arguments arg_names <- names(args) - message("You can always complete your text with additional information from `getEMMARecord()`!") + cli::cli_alert_info( + "You can always complete your text with additional information from `getEMMARecord()`!" + ) + if (emma_rec$method$wrapper) { text <- paste0("Functional Enrichment Analysis was performed using a wrapper function ", diff --git a/R/EMMA_freeze.R b/R/EMMA_freeze.R index c71e18c..3e42e3d 100644 --- a/R/EMMA_freeze.R +++ b/R/EMMA_freeze.R @@ -43,4 +43,6 @@ EMMA_freeze <- function(project = getwd(), # packages = pkgs # ) # + cli::cli_alert_info( + "Environment snapshot saved to: {.val {lockfile}}. \nTo recreate this environment, use `renv::restore()`.") } \ No newline at end of file diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index e532c10..a635449 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -254,9 +254,9 @@ EMMA_get_custom_metadata <- function(call_class, args, if (call_class$type == "custom") { # if 100% custom - message("You used a custom function, so `EMMA` wasn't able to record annotation-", - "related information. Please consider adding the `organism`, `geneset database`", - " and its `version` manually into the `extra` field in EMMA_record.") + cli::cli_alert_info( + "You used a custom function, so `EMMA` wasn't able to record annotation-related information. Please consider adding the `organism`, `geneset database` and its `version` manually into the `extra` field in EMMA_record using EMMA_add_custom_metadata.") + return(meta) } diff --git a/R/EMMA_run.R b/R/EMMA_run.R index c3e72cd..7c1fc14 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -77,7 +77,9 @@ EMMA_run <- function(expr, #capture analysis time start_time <- Sys.time() - message("Running Enrichment Analysis with ", function_name, " ...") + + cli::cli_alert_info( + "Running Enrichment Analysis with {.val {function_name}} ...") # capture the value of the arguments args <- lapply(arg_list, eval, envir = envir) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index 5bfaca0..6d57a6d 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -20,7 +20,7 @@ EMMA_show <- function(res){ if ("EMMA_record" %in% names(attributes(res))) { - message("Found EMMA record!") + cli::cli_alert_info("Found EMMA record!") emma_rec <- getEMMARecord(res) From f57080fe3641f86fcce01a5e261d0d84d680f6a5 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:50:02 +0200 Subject: [PATCH 49/92] upding the internal functions nomenclature to use the dot before the name --- R/EMMA_internal-utils.R | 101 +++++++++++++++++++--------------------- R/EMMA_run.R | 8 ++-- 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index a635449..0db2ae1 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -4,7 +4,7 @@ #' different enrichment methods and packages #' #' @noRd -EMMA_empty_metadata <- function() { +.EMMA_empty_metadata <- function() { list( organism = NA_character_, gene_set_db = NA_character_, @@ -29,7 +29,7 @@ EMMA_empty_metadata <- function() { #' metadata structure if no package-specific method is available #' #' @noRd -EMMA_get_metadata <- function(call_class, +.EMMA_get_metadata <- function(call_class, args, envir = parent.frame()) { info_call <- call_class$info_call @@ -37,7 +37,7 @@ EMMA_get_metadata <- function(call_class, package_name <- info_call$package_name call <- info_call$call - meta <- EMMA_empty_metadata() + meta <- .EMMA_empty_metadata() if (is.null(package_name) || is.na(package_name) || package_name == "") { package_name <- "custom" @@ -45,10 +45,10 @@ EMMA_get_metadata <- function(call_class, meta <- switch( package_name, - clusterProfiler = EMMA_get_clusterprofiler_metadata(function_name, args), - gprofiler2 = EMMA_get_gprofiler2_metadata(args), - mosdef = EMMA_cp_GO_metadata(args$mapping), - custom = EMMA_get_custom_metadata(call_class, + clusterProfiler = .EMMA_get_clusterprofiler_metadata(function_name, args), + gprofiler2 = .EMMA_get_gprofiler2_metadata(args), + mosdef = .EMMA_cp_GO_metadata(args$mapping), + custom = .EMMA_get_custom_metadata(call_class, args, envir = parent.frame()) ) @@ -71,15 +71,15 @@ EMMA_get_metadata <- function(call_class, #' and its version), depending on the underlying database used (e.g. GO vs KEGG) #' #' @noRd -EMMA_get_clusterprofiler_metadata <- function(function_name, args) { +.EMMA_get_clusterprofiler_metadata <- function(function_name, args) { switch( function_name, - enrichGO = EMMA_cp_GO_metadata(args$OrgDb), - gseGO = EMMA_cp_GO_metadata(args$OrgDb), - groupGO = EMMA_cp_GO_metadata(args$OrgDb), - enrichKEGG = EMMA_cp_KEGG_metadata(args), - gseKEGG = EMMA_cp_KEGG_metadata(args), - EMMA_empty_metadata() + enrichGO = .EMMA_cp_GO_metadata(args$OrgDb), + gseGO = .EMMA_cp_GO_metadata(args$OrgDb), + groupGO = .EMMA_cp_GO_metadata(args$OrgDb), + enrichKEGG = .EMMA_cp_KEGG_metadata(args), + gseKEGG = .EMMA_cp_KEGG_metadata(args), + .EMMA_empty_metadata() # more to be added ) } @@ -93,7 +93,7 @@ EMMA_get_clusterprofiler_metadata <- function(function_name, args) { #' and returns `NA_character_` if the organism cannot be determined #' #' @noRd -EMMA_get_organism_from_OrgDb <- function(orgdb) { +.EMMA_get_organism_from_OrgDb <- function(orgdb) { if (is.null(orgdb)) { return(NA_character_) @@ -129,10 +129,10 @@ EMMA_get_organism_from_OrgDb <- function(orgdb) { #' geneset database and its version #' #' @noRd -EMMA_cp_GO_metadata <- function(org) { - meta <- EMMA_empty_metadata() +.EMMA_cp_GO_metadata <- function(org) { + meta <- .EMMA_empty_metadata() - meta$organism <- EMMA_get_organism_from_OrgDb(org) + meta$organism <- .EMMA_get_organism_from_OrgDb(org) meta$gene_set_db <- "GO" meta$gene_set_db_version <- if (requireNamespace("GO.db", quietly = TRUE)) { as.character(utils::packageVersion("GO.db")) @@ -158,8 +158,8 @@ EMMA_cp_GO_metadata <- function(org) { #' geneset database and its version #' #' @noRd -EMMA_cp_KEGG_metadata <- function(args) { - meta <- EMMA_empty_metadata() +.EMMA_cp_KEGG_metadata <- function(args) { + meta <- .EMMA_empty_metadata() meta$organism <- args$organism meta$gene_set_db <- "KEGG" @@ -179,8 +179,8 @@ EMMA_cp_KEGG_metadata <- function(args) { #' geneset database and its version #' #' @noRd -EMMA_get_gprofiler2_metadata <- function(args) { - meta <- EMMA_empty_metadata() +.EMMA_get_gprofiler2_metadata <- function(args) { + meta <- .EMMA_empty_metadata() version_info <- gprofiler2::get_version_info() @@ -214,10 +214,10 @@ EMMA_get_gprofiler2_metadata <- function(args) { #' #' @noRd -EMMA_get_custom_metadata <- function(call_class, args, +.EMMA_get_custom_metadata <- function(call_class, args, envir = parent.frame()) { - meta <- EMMA_empty_metadata() + meta <- .EMMA_empty_metadata() if (call_class$type == "wrapper") { # get all known functions used in the wrapper @@ -225,19 +225,19 @@ EMMA_get_custom_metadata <- function(call_class, args, go_funs <- c("enrichGO", "gseGO", "groupGO") kegg_funs <- c("enrichKEGG", "gseKEGG") - # dont use EMMA_get_clusterprofiler_metadata cause the switch wont work with + # dont use .EMMA_get_clusterprofiler_metadata cause the switch wont work with # more than 1 wrapped fun meta_list <- list() if (any(wrapped_fun %in% go_funs)) { - meta_list$GO <- EMMA_cp_GO_metadata(args$OrgDb) + meta_list$GO <- .EMMA_cp_GO_metadata(args$OrgDb) } if (any(wrapped_fun %in% kegg_funs)) { - meta_list$KEGG <- EMMA_cp_KEGG_metadata(args) + meta_list$KEGG <- .EMMA_cp_KEGG_metadata(args) } if (any(wrapped_fun %in% c("gost"))) { - meta_list$gprofiler2 <- EMMA_get_gprofiler2_metadata(args) + meta_list$gprofiler2 <- .EMMA_get_gprofiler2_metadata(args) } if (length(meta_list) == 0L) { @@ -277,10 +277,10 @@ EMMA_get_custom_metadata <- function(call_class, args, #' #' @return A named list #' @noRd -EMMA_capture_call_info <- function(call, envir = parent.frame()) { +.EMMA_capture_call_info <- function(call, envir = parent.frame()) { # param checks if (!is.call(call)) { - stop("`call` must be a function call", call. = FALSE) + stop("`call` must be a function call") } # capture function name call_name <- call[[1]] @@ -335,12 +335,11 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { } stop( - "Unsupported call format. Use a direct function call like `fun(...)` or `pkg::fun(...)`", - call. = FALSE) + "Unsupported call format. Use a direct function call like `fun(...)` or `pkg::fun(...)`") } -#' This function, used in in `EMMA_walk()`, converts the head of a call into a character +#' This function, used in in `.EMMA_walk()`, converts the head of a call into a character #' string representing the function being called. It supports both bare calls #' (e.g. `fun`) and namespace-qualified calls (e.g. `pkg::fun`) #' @@ -349,7 +348,7 @@ EMMA_capture_call_info <- function(call, envir = parent.frame()) { #' @return A character string representing the function name #' #' @noRd -EMMA_call_name <- function(x) { +.EMMA_call_name <- function(x) { if (is.symbol(x)) { return(as.character(x)) } @@ -374,12 +373,12 @@ EMMA_call_name <- function(x) { #' operators found in `x`, including nested calls #' #' @noRd -EMMA_walk <- function(x) { +.EMMA_walk <- function(x) { out <- character() if (is.call(x)) { # extract function/operator name - nm <- EMMA_call_name(x[[1]]) + nm <- .EMMA_call_name(x[[1]]) if (!is.null(nm)) { out <- c(out, nm) } @@ -388,7 +387,7 @@ EMMA_walk <- function(x) { if (is.call(x) || is.pairlist(x) || is.expression(x)) { #check children/nested elements for (i in seq_along(x)) { - out <- c(out, EMMA_walk(x[[i]])) + out <- c(out, .EMMA_walk(x[[i]])) } } @@ -411,7 +410,7 @@ EMMA_walk <- function(x) { #' wrapper #' #' @noRd -EMMA_find_original_wrapped_fun <- function(call, envir = parent.frame()) { +.EMMA_find_original_wrapped_fun <- function(call, envir = parent.frame()) { if (!is.call(call)) { # do i want it to fail here ? @@ -452,13 +451,12 @@ EMMA_find_original_wrapped_fun <- function(call, envir = parent.frame()) { fun <- getExportedValue(pkg, fn) } else { stop( - "Only `fun(...)` and `pkg::fun(...)` are supported", - call. = FALSE + "Only `fun(...)` and `pkg::fun(...)` are supported" ) } - found <- EMMA_walk(body(fun)) |> unique() + found <- .EMMA_walk(body(fun)) |> unique() matched <- unique(c( intersect(found, full_targets), @@ -482,7 +480,7 @@ EMMA_find_original_wrapped_fun <- function(call, envir = parent.frame()) { #' decide if top-level function passed to EMMA_run is a known fun or a wrapper #' #' @noRd -EMMA_classify_call <- function(call, envir = parent.frame()) { +.EMMA_classify_call <- function(call, envir = parent.frame()) { # listing all the functions that are not wrappers originals <- list( clusterProfiler = c("enrichGO", "gseGO", "groupGO", "enrichKEGG", "gseKEGG"), @@ -497,7 +495,7 @@ EMMA_classify_call <- function(call, envir = parent.frame()) { SIMPLIFY = FALSE) |> unlist(use.names = FALSE) # capture call information - info_call <- EMMA_capture_call_info(call = call, envir = envir) + info_call <- .EMMA_capture_call_info(call = call, envir = envir) function_name <- info_call$function_name package_name <- info_call$package_name @@ -520,7 +518,7 @@ EMMA_classify_call <- function(call, envir = parent.frame()) { if (!is_original_top_level) { # inspect body of expr function, because this could be a wrapper - wrapped_original <- EMMA_find_original_wrapped_fun(call = call, + wrapped_original <- .EMMA_find_original_wrapped_fun(call = call, envir = envir) wrapper <- !is.null(wrapped_original) } @@ -550,20 +548,20 @@ EMMA_classify_call <- function(call, envir = parent.frame()) { #' This function assembles the structured provenance record that is stored as #' an attribute on the FEA results object #' -#' @param info_call A list returned by `EMMA_capture_call_info()` +#' @param info_call A list returned by `.EMMA_capture_call_info()` #' @param args_form A character string, either `"evaluated"` or `"unevaluated"` #' to decide how to store the arguments -#' @param metadata A list returned by `EMMA_get_metadata()` +#' @param metadata A list returned by `.EMMA_get_metadata()` #' @param start_time A timestamp marking when the enrichment analysis started -#' @param session Logical. If `TRUE`, `sessionInfo()` is captured and stored in +#' @param store_session_info Logical. If `TRUE`, `sessionInfo()` is captured and stored in #' the record; if `FALSE` the `session_info` slot is `NULL` #' #' @return A named list of the recorded metadata #' #' @noRd -EMMA_build_record <- function(info_call, args_form, metadata, +.EMMA_build_record <- function(info_call, args_form, metadata, wrapped_original, wrapper, - start_time, session) { + start_time, store_session_info) { emma_rec <- list( method = list( call = info_call$call, @@ -586,7 +584,7 @@ EMMA_build_record <- function(info_call, args_form, metadata, gene_set_db_version = metadata$gene_set_db_version ), timestamp = start_time, - session_info = if (isTRUE(session)) sessionInfo() else NULL, + session_info = if (isTRUE(store_session_info)) sessionInfo() else NULL, extra = list(),# free field for extra metadata (added by user) emma_version = as.character(packageVersion(pkg = "EMMA")) ) @@ -609,7 +607,7 @@ EMMA_build_record <- function(info_call, args_form, metadata, #' #' @return `base::invisible()` #' @noRd -EMMA_warnings <- function(arg_names, function_name){ +.EMMA_warnings <- function(arg_names, function_name){ checks <- list( list( params = c("pAdjustMethod", "correction_method", "do_padj"), @@ -636,4 +634,3 @@ Consider using the corresponding parameter for your call.", } invisible() } - diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 7c1fc14..2537243 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -59,7 +59,7 @@ EMMA_run <- function(expr, } # decide whether we are dealing with a known function or a wrapper - call_class <- EMMA_classify_call(call, envir = envir) + call_class <- .EMMA_classify_call(call, envir = envir) info_call <- call_class$info_call function_name <- info_call$function_name package_name <- info_call$package_name @@ -72,7 +72,7 @@ EMMA_run <- function(expr, # some good practice warning, i.e. when multiple testing correction is skipped # or bg geneset not set - EMMA_warnings(arg_names = arg_names, + .EMMA_warnings(arg_names = arg_names, function_name = function_name) #capture analysis time @@ -92,14 +92,14 @@ EMMA_run <- function(expr, results <- do.call(fun, args) # capture metadata from the used function and arguments - metadata <- EMMA_get_metadata( + metadata <- .EMMA_get_metadata( call_class, args, envir = envir ) # record everything in EMMA_record - EMMA_record <- EMMA_build_record(info_call, args_form, metadata, + EMMA_record <- .EMMA_build_record(info_call, args_form, metadata, wrapped_original,wrapper, start_time, session) From 7812a81da994ba02a4f2a627702e1dbb39834872 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:50:33 +0200 Subject: [PATCH 50/92] updating tests --- tests/testthat/setuptests_EMMA.R | 1 + tests/testthat/test-EMMA_freeze.R | 34 +++++++++++++++++++++++++++ tests/testthat/test-EMMA_get_record.R | 27 +++++++++++++++++++++ tests/testthat/test-EMMA_metadata.R | 22 ++++++++--------- tests/testthat/test-EMMA_run.R | 4 ++-- tests/testthat/test-EMMA_show.R | 24 +++++++++++++++++++ tests/testthat/test-getEMMArecord.R | 28 ---------------------- 7 files changed, 99 insertions(+), 41 deletions(-) create mode 100644 tests/testthat/test-EMMA_freeze.R create mode 100644 tests/testthat/test-EMMA_get_record.R delete mode 100644 tests/testthat/test-getEMMArecord.R diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index e73a0fe..1d08be5 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -6,4 +6,5 @@ invisible(lapply( data("de_res_IFNg_vs_naive", package = "EMMA") data("universe", package = "EMMA") +data("fea_res", package = "EMMA") diff --git a/tests/testthat/test-EMMA_freeze.R b/tests/testthat/test-EMMA_freeze.R new file mode 100644 index 0000000..2e54fb0 --- /dev/null +++ b/tests/testthat/test-EMMA_freeze.R @@ -0,0 +1,34 @@ +test_that("EMMA_freeze", { + + tmp <- tempfile("emma_env") + dir.create(tmp) + + lockfile <- file.path(tmp, "renv.lock") + + res_path <- EMMA_freeze( + project = tmp, + file = "renv.lock", + pkgs = "stats", + prompt = TRUE, + force = TRUE + ) + + expect_true(dir.exists(tmp)) + + expect_true(file.exists(lockfile)) + + expect_identical(res_path, lockfile) + + expect_error(EMMA_freeze(project = 2)) + + expect_error(EMMA_freeze(file = NULL)) + + expect_error(EMMA_freeze(file = NA_character_)) + + expect_error(EMMA_freeze(pkgs = 1:3)) + + expect_error(EMMA_freeze(promt = "no")) + + expect_error(EMMA_freeze(force = "yes")) + +}) diff --git a/tests/testthat/test-EMMA_get_record.R b/tests/testthat/test-EMMA_get_record.R new file mode 100644 index 0000000..44df8b9 --- /dev/null +++ b/tests/testthat/test-EMMA_get_record.R @@ -0,0 +1,27 @@ +test_that("EMMA_get_record", { + expect_true(is.list(EMMA_get_record(fea_res))) + expect_length(EMMA_get_record(fea_res), 7) + + fea_no_emma <- mosdef::run_cluPro(de_genes = + rownames(de_res_IFNg_vs_naive), + bg_genes = universe, + mapping = "org.Hs.eg.db", + keyType = "ENSEMBL", + ont = "BP", + pAdjustMethod = "BH") + + expect_error(EMMA_get_record(fea_no_emma)) + + attr(fea_no_emma, "EMMA_record") <- c() + + expect_error(EMMA_get_record(fea_no_emma)) + + expect_error(EMMA_get_record("guiga")) + + attr(fea_res, "EMMA_record") <- NULL + expect_error(EMMA_get_record(fea_res)) + + attr(fea_res, "EMMA_record") <- "i am a corrupted rec" + expect_error(EMMA_get_record(fea_res)) + +}) \ No newline at end of file diff --git a/tests/testthat/test-EMMA_metadata.R b/tests/testthat/test-EMMA_metadata.R index f59e0d4..f11fee9 100644 --- a/tests/testthat/test-EMMA_metadata.R +++ b/tests/testthat/test-EMMA_metadata.R @@ -23,14 +23,14 @@ test_that("test metadata content & structure", { expect_true(emma_rec$method$wrapper) - expect_null(EMMA_find_original_wrapped_fun("mosdef::run_cluPro(de_genes = rownames(de_res_IFNg_vs_naive), + expect_null(.EMMA_find_original_wrapped_fun("mosdef::run_cluPro(de_genes = rownames(de_res_IFNg_vs_naive), bg_genes = universe, mapping = 'org.Hs.eg.db', keyType = 'ENSEMBL', ont = 'BP')")) - custom <- EMMA_classify_call(substitute(summary(getEMMARecord(fea_res)))) + custom <- .EMMA_classify_call(substitute(summary(EMMA_get_record(fea_res)))) expect_equal(custom$type, "custom") @@ -46,7 +46,7 @@ test_that("test metadata content & structure", { extra = list( note = "The background gene set list was all expressed genes in the assay")) - rec <- getEMMARecord(res) + rec <- EMMA_get_record(res) expect_equal( rec$extra$note, @@ -68,7 +68,7 @@ test_that("test metadata content & structure", { correction_method = "fdr", custom_bg = universe) |> EMMA_run() - rec <- getEMMARecord(fea_res) + rec <- EMMA_get_record(fea_res) expect_true(length(rec$annotation$gene_set_db_version) != 1) @@ -87,17 +87,17 @@ test_that("test metadata content & structure", { expect_warning(wrapper <- EMMA_run(custom_fun(rownames(de_res_IFNg_vs_naive), org.Hs.eg.db))) - expect_equal(getEMMARecord(wrapper)$method$function_name, "custom_fun") - expect_equal(getEMMARecord(wrapper)$method$wrapped_package, "clusterProfiler") - expect_equal(getEMMARecord(wrapper)$method$wrapped_function, "groupGO") - expect_true(getEMMARecord(wrapper)$method$wrapper) + expect_equal(EMMA_get_record(wrapper)$method$function_name, "custom_fun") + expect_equal(EMMA_get_record(wrapper)$method$wrapped_package, "clusterProfiler") + expect_equal(EMMA_get_record(wrapper)$method$wrapped_function, "groupGO") + expect_true(EMMA_get_record(wrapper)$method$wrapper) empty <- EMMA_run(summary(rec$method)) - expect_null(getEMMARecord(empty)$annotation$organism) - expect_null(getEMMARecord(empty)$annotation$gene_set_db) - expect_null(getEMMARecord(empty)$annotation$gene_set_db_version) + expect_null(EMMA_get_record(empty)$annotation$organism) + expect_null(EMMA_get_record(empty)$annotation$gene_set_db) + expect_null(EMMA_get_record(empty)$annotation$gene_set_db_version) }) diff --git a/tests/testthat/test-EMMA_run.R b/tests/testthat/test-EMMA_run.R index b6f30a7..1c862c6 100644 --- a/tests/testthat/test-EMMA_run.R +++ b/tests/testthat/test-EMMA_run.R @@ -16,7 +16,7 @@ test_that("EMMA_run", { expect_true(length(attr(fea_res, "EMMA_record")) == 7) - info <- EMMA_capture_call_info(substitute(enrichGO(gene = rownames(de_res_IFNg_vs_naive), + info <- .EMMA_capture_call_info(substitute(enrichGO(gene = rownames(de_res_IFNg_vs_naive), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, pAdjustMethod = "BH", @@ -77,7 +77,7 @@ test_that("EMMA_run", { OrgDb = org.Hs.eg.db, keyType = "ENSEMBL", ont = "CC", - level = 2), session = "yes")) + level = 2), store_session_info = "yes")) }) diff --git a/tests/testthat/test-EMMA_show.R b/tests/testthat/test-EMMA_show.R index 333a0df..db0ec52 100644 --- a/tests/testthat/test-EMMA_show.R +++ b/tests/testthat/test-EMMA_show.R @@ -54,4 +54,28 @@ test_that("EMMA_show", { expect_output(EMMA_show(fea2), "- FEA_1") expect_output(EMMA_show(fea2),"FEA_2") + + fea_gost <- fea2 + names(fea_gost)[2] <- "result" + + attr(fea_gost, "EMMA_record") <- list( + method = list( + call = substitute(gost(query = de_res_IFNg_vs_naive$SYMBOL, + organism = "hsapiens", + correction_method = "fdr", custom_bg = universe, + sources = "GO:BP")), + wrapper = FALSE, + package_name = "gprofiler2", + package_version = NA + ), + annotation = list( + organism = "Homo sapiens", + gene_set_db = "GO", + gene_set_db_version = NA + ) + ) + + expect_output(EMMA_show(fea_gost), "Number of Pathways: 2") + + }) diff --git a/tests/testthat/test-getEMMArecord.R b/tests/testthat/test-getEMMArecord.R deleted file mode 100644 index 9152707..0000000 --- a/tests/testthat/test-getEMMArecord.R +++ /dev/null @@ -1,28 +0,0 @@ -test_that("getEMMARecord", { - fea_res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), - keyType = "ENSEMBL", - OrgDb = org.Hs.eg.db, - pAdjustMethod = "BH", - pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - universe = universe, - readable = TRUE)) - - expect_true(is.list(getEMMARecord(fea_res))) - expect_length(getEMMARecord(fea_res), 7) - - fea_no_emma <- mosdef::run_cluPro(de_genes = - rownames(de_res_IFNg_vs_naive), - bg_genes = universe, - mapping = "org.Hs.eg.db", - keyType = "ENSEMBL", - ont = "BP", - pAdjustMethod = "BH") - - expect_error(getEMMARecord(fea_no_emma)) - - attr(fea_no_emma, "EMMA_record") <- c() - - expect_error(getEMMARecord(fea_no_emma)) - -}) \ No newline at end of file From 55d98412556d0f3351bb421c9fc3f6dbcbb3060c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:51:29 +0200 Subject: [PATCH 51/92] remove unused pkg from import --- NAMESPACE | 2 -- R/EMMA-pkg.R | 2 -- 2 files changed, 4 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 8893a13..841d1e7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,8 +6,6 @@ export(EMMA_freeze) export(EMMA_run) export(EMMA_show) export(getEMMARecord) -import(GO.db) -import(methods) importFrom(AnnotationDbi,metadata) importFrom(utils,packageVersion) importFrom(utils,sessionInfo) diff --git a/R/EMMA-pkg.R b/R/EMMA-pkg.R index b11a174..1537c72 100644 --- a/R/EMMA-pkg.R +++ b/R/EMMA-pkg.R @@ -3,8 +3,6 @@ #' EMMA stands for Enrichment Methods Matter. #' And EMMA stands to help you in realizing it. #' -#' @import methods -#' @import GO.db #' @importFrom AnnotationDbi metadata #' @importFrom utils packageVersion sessionInfo #' @importFrom cli cli_alert_info From 0cb4185e8510b0a36f1db9e5b48e3719ca1f3f76 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:51:43 +0200 Subject: [PATCH 52/92] update pkg version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index d09d2f9..0d4de97 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: EMMA Title: EMMA: Enrichment Methods Matter for enabeling fully reproducible and provenance-aware pathway analysis -Version: 0.2.0 +Version: 0.3.0 Authors@R: c( person( From b0718a6d6d013e2546e4f7bc47b579af0fb9de2e Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:52:39 +0200 Subject: [PATCH 53/92] update example data --- data/de_res_IFNg_vs_naive.RData | Bin 5612 -> 676 bytes data/fea_res.RData | Bin 0 -> 5584 bytes data/universe.RData | Bin 6080 -> 1756 bytes inst/scripts/create_datasets_examples.R | 26 +++++++++++++++--------- 4 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 data/fea_res.RData diff --git a/data/de_res_IFNg_vs_naive.RData b/data/de_res_IFNg_vs_naive.RData index 8c20de5c186c0c4e1dbe96d72beea5581528c37c..15286019999dd0cf794b177a35696ba9e502f162 100644 GIT binary patch delta 663 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 G0a;plzvE>9 diff --git a/data/fea_res.RData b/data/fea_res.RData new file mode 100644 index 0000000000000000000000000000000000000000..4c5a56a7d9e2e1a0f0539fb0a3aec5c06da4b1b4 GIT binary patch literal 5584 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 G0a;p3u*Q-A diff --git a/inst/scripts/create_datasets_examples.R b/inst/scripts/create_datasets_examples.R index cdf1187..0f4ff02 100644 --- a/inst/scripts/create_datasets_examples.R +++ b/inst/scripts/create_datasets_examples.R @@ -7,15 +7,10 @@ rownames(dds_macrophage) <- substr(rownames(dds_macrophage), 1, 15) dds_macrophage # DE run -keep <- rowSums(counts(dds_macrophage) >= 10) >= 6 -dds_macrophage <- dds_macrophage[keep, ] -dds_macrophage - - # set seed for reproducibility -set.seed(42) +set.seed(2711) # sample randomly for 2k genes -selected_genes <- sample(rownames(dds_macrophage), 2000) +selected_genes <- sample(rownames(dds_macrophage), 500) dds_macrophage <- dds_macrophage[selected_genes, ] @@ -38,10 +33,21 @@ de_res_IFNg_vs_naive <- de_res_IFNg_vs_naive[order(de_res_IFNg_vs_naive$padj), ] de_res_IFNg_vs_naive <- de_res_IFNg_vs_naive[!(is.na(de_res_IFNg_vs_naive$padj)) & de_res_IFNg_vs_naive$padj <= 0.05, ] -universe <- rownames(dds_macrophage) +# define gene universe +gene_universe <- rownames(dds_macrophage) -save(de_res_IFNg_vs_naive, file = "de_res_IFNg_vs_naive.RData", compress = "xz") -save(universe, file = "universe.RData", compress = "xz") +# perform FEA +library("gprofiler2") +fea_res <- gprofiler2::gost(query = de_res_IFNg_vs_naive$SYMBOL, + organism = "hsapiens", + correction_method = "fdr", + custom_bg = universe, + sources = "GO:BP") |> EMMA_run( + store_session_info = FALSE, + args_form = "unevaluated") +save(de_res_IFNg_vs_naive, file = "de_res_IFNg_vs_naive.RData", compress = "xz") +save(universe, file = "universe.RData", compress = "xz") +save(fea_res, file = "fea_res.RData", compress = "xz") From 9b7d274eeadfaf5aee5e42b8952d22b431482d1a Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:54:08 +0200 Subject: [PATCH 54/92] fix EMMA_show to display the number of pathways/FEAs correctly, and update the example --- R/EMMA_show.R | 37 ++++++++++++++++++------------------- man/EMMA_show.Rd | 9 ++------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index 6d57a6d..4d249bd 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -10,34 +10,33 @@ #' @export #' #' @examples -#' data("de_res_IFNg_vs_naive", package = "EMMA") -#' data("universe", package = "EMMA") -#' library("clusterProfiler") -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP")) -#' EMMA_show(res) +#' data("fea_res", package = "EMMA") +#' EMMA_show(fea_res) EMMA_show <- function(res){ if ("EMMA_record" %in% names(attributes(res))) { cli::cli_alert_info("Found EMMA record!") - emma_rec <- getEMMARecord(res) + emma_rec <- EMMA_get_record(res) if (is.list(res) && !is.data.frame(res)) { - # let's say if we have of list of FEAs (returned by custom function) - cat("Number of FEAs: ", length(res), "\n") - - nms <- names(res) - if (is.null(nms) || any(nms == "")) { - nms <- paste0("FEA_", seq_along(res)) - } + # e.g. case of gost, returns a list but it's 1 FEA (result) + if ("result" %in% names(res)) { + cat("Number of Pathways: ", NROW(res$result), "\n") + } else { + # let's say if we have of list of FEAs (returned by custom function) + cat("Number of FEAs: ", length(res), "\n") + + nms <- names(res) + if (is.null(nms) || any(nms == "")) { + nms <- paste0("FEA_", seq_along(res)) + } - for (i in seq_along(res)) { - # check the number of pathways for each element of the list - cat(" -", nms[i], ": ", NROW(res[[i]]), " pathways\n") + for (i in seq_along(res)) { + # check the number of pathways for each element of the list + cat(" -", nms[i], ": ", NROW(res[[i]]), " pathways\n") + } } - } else { cat("Number of Pathways: ", NROW(res), "\n") } diff --git a/man/EMMA_show.Rd b/man/EMMA_show.Rd index 8207750..cd30ba3 100644 --- a/man/EMMA_show.Rd +++ b/man/EMMA_show.Rd @@ -18,11 +18,6 @@ This function displays a human-readable summary of the \code{EMMA_record} attrib attached to a result object produced by \code{EMMA_run()} } \examples{ -data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") -library("clusterProfiler") -res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -ont = "BP")) -EMMA_show(res) +data("fea_res", package = "EMMA") +EMMA_show(fea_res) } From 3a31b236e789a2b436fc22d8534b3dd264dc023c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:54:37 +0200 Subject: [PATCH 55/92] rename getEMMARecord to EMMA_get_record --- R/{getEMMARecord.R => EMMA_get_record.R} | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) rename R/{getEMMARecord.R => EMMA_get_record.R} (56%) diff --git a/R/getEMMARecord.R b/R/EMMA_get_record.R similarity index 56% rename from R/getEMMARecord.R rename to R/EMMA_get_record.R index f982bed..5dd2e86 100644 --- a/R/getEMMARecord.R +++ b/R/EMMA_get_record.R @@ -1,4 +1,4 @@ -#' getEMMARecord +#' EMMA_get_record #' #' @param res Functional Enrichment Analysis results (enrichResult, gseaResult ...) #' generated by `EMMA_run()` @@ -7,14 +7,9 @@ #' @export #' #' @examples -#' data("de_res_IFNg_vs_naive", package = "EMMA") -#' data("universe", package = "EMMA") -#' library("clusterProfiler") -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP")) -#' getEMMARecord(res) -getEMMARecord <- function(res){ +#' data("fea_res", package = "EMMA") +#' EMMA_get_record(fea_res) +EMMA_get_record <- function(res){ rec <- attr(res, "EMMA_record") From a9f5be978ac5e8f6326fdbeb9af4c54e894d6ce3 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:55:26 +0200 Subject: [PATCH 56/92] updating documentation, arg names, and example for EMMA_run --- R/EMMA_run.R | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 2537243..3b461f8 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -16,7 +16,7 @@ #' provenance information. Both bare calls (`enrichGO(...)`) and #' namespace-qualified calls (`clusterProfiler::enrichGO(...)`) are supported. #' @param envir An environment in which to evaluate `expr` -#' @param session Logical, indicating whether to store the output of +#' @param store_session_info Logical, indicating whether to store the output of #' `sessionInfo()` or not. If `TRUE` (default), the session is stored in the #' provenance record #' @param args_form A character string indicating whether to store the evaluated @@ -25,19 +25,21 @@ #' #' @returns The result object returned by the enrichment function in `expr`, #' in standard format, with an additional `EMMA_record` attribute containing the -#' provenance information. Use `getEMMARecord()` to retrieve this record +#' provenance information. Use `EMMA_get_record()` to retrieve this record #' #' @export #' #' @examples #' data("de_res_IFNg_vs_naive", package = "EMMA") #' data("universe", package = "EMMA") -#' EMMA_run(clusterProfiler::enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP")) +#' library(gprofiler2) +#' +#' EMMA_run(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", +#' correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), +#' store_session_info = FALSE, args_form = "unevaluated") EMMA_run <- function(expr, envir = parent.frame(), - session = TRUE, + store_session_info = TRUE, args_form = c("evaluated", "unevaluated")) { args_form <- match.arg(args_form) @@ -54,8 +56,8 @@ EMMA_run <- function(expr, stop("`envir` must be an environment!") } - if (!is.logical(session) || length(session) != 1L || is.na(session)) { - stop("`session` must be a single TRUE or FALSE value") + if (!is.logical(store_session_info) || length(store_session_info) != 1L || is.na(store_session_info)) { + stop("`store_session_info` must be a single TRUE or FALSE value") } # decide whether we are dealing with a known function or a wrapper @@ -101,7 +103,7 @@ EMMA_run <- function(expr, # record everything in EMMA_record EMMA_record <- .EMMA_build_record(info_call, args_form, metadata, wrapped_original,wrapper, - start_time, session) + start_time, store_session_info) # attach EMMA_record as attribute of the results obj attr(results, "EMMA_record") <- EMMA_record From d47c5ad98a8bd126dbd4b59c151d80698f392e4f Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:55:49 +0200 Subject: [PATCH 57/92] add EMMA_freeze --- R/EMMA_freeze.R | 102 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 19 deletions(-) diff --git a/R/EMMA_freeze.R b/R/EMMA_freeze.R index 3e42e3d..2ce47d4 100644 --- a/R/EMMA_freeze.R +++ b/R/EMMA_freeze.R @@ -1,8 +1,7 @@ #' EMMA_freeze #' -#' This function records the R environment during analysis runtime and generates -#' a lockfile that can be used with tools such as `renv`. -#' By default, all currently loaded namespaces are recorded. +#' This function records the R environment at the time of analysis and generates +#' a lockfile that can be used with tools such as `renv` #' #' @param project Character string corresponding to the path to the project #' directory where the lockfile should be written. If the directory does not @@ -11,38 +10,103 @@ #' It defaults to "renv.lock" #' @param pkgs Character vector of package names to snapshot. It defaults to all #' currently loaded namespaces via `loadedNamespaces()` +#' @param prompt Logical indicating whether to prompt before taking actions. +#' Defaults to `interactive()` +#' @param force Logical indicating whether to force creation of the lockfile. +#' Defaults to `TRUE` #' #' @details #' This function calls `renv::snapshot()` with the specified packages. #' The resulting lockfile can later be restored with `renv::restore()` to #' recreate the same package environment. #' -#' @returns TODO json file like to create a lock.file? +#' By default, the lockfile is created with `force = TRUE`, allowing snapshot +#' creation even if inconsistencies are detected in the environment. +#' +#' @returns Invisibly returns the path to the generated lockfile. +#' The lockfile is written in JSON format and can be used with `renv::restore()` +#' to recreate the package environment #' #' @seealso \code{\link[renv]{snapshot}}, \code{\link[renv]{restore}} #' #' @export #' #' @examples -#' \dontrun{ -#' # Create a lockfile in a separate directory -#' EMMA_freeze(project = "emma_env") +#' # create a lockfile +#' if (requireNamespace("renv", quietly = TRUE)) { +#' tmp <- tempfile("emma_env") +#' dir.create(tmp) +#' +#' EMMA_freeze(project = tmp) +#' +#' # inspect generated files +#' list.files(tmp) #' -#' # Restore later with renv -#' renv::restore(project = "emma_env") +#' # to restore the environment later +#' # renv::restore(project = tmp) #' } EMMA_freeze <- function(project = getwd(), file = "renv.lock", - pkgs = loadedNamespaces()){ - - # if (!dir.exists(project)) dir.create(project, recursive = TRUE) - # - # renv::snapshot( - # project = project, - # lockfile = file, - # packages = pkgs - # ) - # + pkgs = loadedNamespaces(), + prompt = interactive(), + force = TRUE){ + + # when using EMMA_freeze, we only need renv loaded (without attaching) + # to avoid extra heavy dependency. + if (!requireNamespace("renv", quietly = TRUE)) { + stop( + "The 'renv' package is required for EMMA_freeze(). Please install it." + ) + } + + # checks on args + if (!is.character(project) || length(project) != 1L || is.na(project)) { + stop("`project` must be a single character string.") + } + + if (!is.character(file) || length(file) != 1L || is.na(file)) { + stop("`file` must be a single character string.") + } + + if (!is.character(pkgs)) { + stop("`pkgs` must be a character vector of package names.") + } + + if (!is.logical(prompt) || length(prompt) != 1L || is.na(prompt)) { + stop("`prompt` must be TRUE or FALSE.") + } + + if (!is.logical(force) || length(force) != 1L || is.na(force)) { + stop("`force` must be TRUE or FALSE.") + } + + + # create project dir if needed + if (!dir.exists(project)) { + dir.create(project, recursive = TRUE) + } + + lockfile <- file.path(project, file) + + prompt <- isTRUE(prompt) # if interactive it s TRUE, else takes what the user passes + + # no prompt in non interactive sessions + if (!interactive()) { + prompt <- FALSE + } + + ##### snapshot environment + renv::snapshot( + project = project, + lockfile = lockfile, + packages = pkgs, + prompt = prompt, + force = force + ) + cli::cli_alert_info( "Environment snapshot saved to: {.val {lockfile}}. \nTo recreate this environment, use `renv::restore()`.") + + invisible(lockfile) + } \ No newline at end of file From 3d8f2dbf5dd70de2f2b4d933d8a4bff1d6dd2188 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:56:57 +0200 Subject: [PATCH 58/92] update EMMA_explain to return also citation --- R/EMMA_explain.R | 65 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/R/EMMA_explain.R b/R/EMMA_explain.R index e86cf61..e1b95d3 100644 --- a/R/EMMA_explain.R +++ b/R/EMMA_explain.R @@ -5,23 +5,22 @@ #' call, the parameters, software context, and reference databases used. #' #' @param res A functional enrichment analysis results object as returned by -#' `EMMA_run()`. Its attributes contain `EMMA_record`, which -#' contains all provenance information of the performed FEA +#' `EMMA_run()`. Its attributes contain `EMMA_record`, which contains all +#' provenance information of the performed FEA +#' @param get_citation Logical indicating whether to display the citations of +#' the packages used in the FEA. It only prints the citations in an interactive +#' session (e.g console). Defaults to `TRUE` #' @returns A character string describing how the FEA was performed using the -#' recorded metadata +#' recorded metadata +#' #' @export #' @examples -#' data("de_res_IFNg_vs_naive", package = "EMMA") -#' data("universe", package = "EMMA") -#' library("clusterProfiler") -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP", pAdjustMethod = "BH")) -#' EMMA_explain(res) +#' data("fea_res", package = "EMMA") +#' EMMA_explain(fea_res) #' -EMMA_explain <- function(res){ +EMMA_explain <- function(res, get_citation = TRUE){ - emma_rec <- getEMMARecord(res) + emma_rec <- EMMA_get_record(res) function_name <- emma_rec$method$function_name pkg_name <- emma_rec$method$package_name @@ -37,7 +36,7 @@ EMMA_explain <- function(res){ ) - if (emma_rec$method$wrapper) { + if (isTRUE(emma_rec$method$wrapper)) { text <- paste0("Functional Enrichment Analysis was performed using a wrapper function ", function_name, "()") } else { @@ -72,13 +71,19 @@ EMMA_explain <- function(res){ fdr_arg <- intersect(c("correction_method", "pAdjustMethod"), arg_names) if (length(bg_arg) == 1) { + bg_value <- args[[bg_arg]] + + if (length(bg_value) > 1) { + # evaluated arguments text <- paste0(text, - " A custom background gene set was provided (n = ", - length(args[[bg_arg]]), - ")." - ) - } else if (length(bg_arg) > 1L) { - text <- paste0(text, " A custom background gene set was provided.") + " A custom background gene set was provided (n = ", + length(bg_value),").") + + } else if (length(bg_value) == 1) { + # unevaluated arguments. + text <- paste0(text, " A custom background gene set was provided.") + } + } else { text <- paste0(text, " No custom background gene set was recorded.") } @@ -86,9 +91,10 @@ EMMA_explain <- function(res){ ### info abt the fdr correction if (length(fdr_arg) == 1) { + fdr_value <- args[[fdr_arg]] text <- paste0(text, " Multiple testing correction was performed using the ", - args[[fdr_arg]], " method." + fdr_value, " method." ) } else if ("do_padj" %in% arg_names) { if (isTRUE(args[["do_padj"]])) { @@ -97,6 +103,23 @@ EMMA_explain <- function(res){ text <- paste0(text, " Multiple testing correction was not applied.") } } - + + ### get citations + + if (get_citation) { + pkgs <- unique(stats::na.omit(c( + emma_rec$method$package_name, + emma_rec$method$wrapped_package + ))) + + if (length(pkgs) > 0L) { + cli::cli_alert_info("References:") + for (pkg in pkgs) { + cli::cli_verbatim(paste(format(utils::citation(pkg)), + collapse = "\n")) + } + } + + } return(text) } \ No newline at end of file From 2d2eeb86559c4a091dd77d7f3bde7bf129688383 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:57:28 +0200 Subject: [PATCH 59/92] update example and fun name --- R/EMMA_add_custom_metadata.R | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/R/EMMA_add_custom_metadata.R b/R/EMMA_add_custom_metadata.R index 3a854a9..a59884f 100644 --- a/R/EMMA_add_custom_metadata.R +++ b/R/EMMA_add_custom_metadata.R @@ -14,13 +14,8 @@ #' @export #' #' @examples -#' data("de_res_IFNg_vs_naive", package = "EMMA") -#' data("universe", package = "EMMA") -#' library("clusterProfiler") -#' res <- EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), -#' universe = universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db::org.Hs.eg.db, -#' ont = "BP")) -#' res <- EMMA_add_custom_metadata(res, extra = +#' data("fea_res", package = "EMMA") +#' fea_res <- EMMA_add_custom_metadata(fea_res, extra = #' list(note = "The background gene set list was all expressed genes in the assay")) EMMA_add_custom_metadata <- function(res, extra = list()) { @@ -33,7 +28,7 @@ EMMA_add_custom_metadata <- function(res, stop("`extra` must be a named list!") } - emma_rec <- getEMMARecord(res = res) + emma_rec <- EMMA_get_record(res = res) emma_rec$extra <- extra From 7b8b76cd96c55a441690bd7b5a92bfc15c7b1317 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:57:39 +0200 Subject: [PATCH 60/92] update namespace --- NAMESPACE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index 841d1e7..b47fb89 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,9 +3,10 @@ export(EMMA_add_custom_metadata) export(EMMA_explain) export(EMMA_freeze) +export(EMMA_get_record) export(EMMA_run) export(EMMA_show) -export(getEMMARecord) importFrom(AnnotationDbi,metadata) +importFrom(cli,cli_alert_info) importFrom(utils,packageVersion) importFrom(utils,sessionInfo) From 32a0543106096c506842210e22fca09978baf334 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 18:57:51 +0200 Subject: [PATCH 61/92] update the news --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 2fffc1d..9b52e36 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,12 @@ +# EMMA 0.3.0 + +* Added the initial implementation of `EMMA_freeze()`. +* `EMMA_explain()` now returns also citations of the used packages. +* Renamed `getEMMARecord()` to `EMMA_get_record()` for consistency. +* Resizing toy data to speed up examples. +* Updated the vignette. +* Updating unit tests. + # EMMA 0.2.0 * `EMMA_run()` can accept custom functions and wrappers and collect metadata From 864dd6bb13a20dfeb332f1d74d8c7801e9a648c4 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 11 May 2026 19:20:38 +0200 Subject: [PATCH 62/92] adding minimal pkgdown yaml --- _pkgdown.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 _pkgdown.yml diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..81d9b19 --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,12 @@ +authors: + Najla Abassi: + href: https://najlaabassi.github.io/ + Annekathrin Nedwed: + href: https://github.com/AnnekathrinSilvia + Federico Marini: + href: https://federicomarini.github.io + +url: ~ +template: + bootstrap: 5 + bootswatch: minty \ No newline at end of file From 3ad16857197dc7469cf822c140a34197f35e03ee Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 11:44:29 +0200 Subject: [PATCH 63/92] minimal fixes on the indentation --- vignettes/Introduction_to_EMMA.Rmd | 53 ++++++++++++++++-------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index c34b6b1..807b1cf 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -238,21 +238,24 @@ fea_res ```{r EMMA_run_2, eval=FALSE} # you can also pass the function name and its namespace # e.g. `clusterProfiler::enrichGO(...)` -fea_res <- EMMA_run(clusterProfiler::enrichGO( - gene = rownames(de_res), - keyType = "ENSEMBL", - OrgDb = org.Hs.eg.db, - ont = "BP", - pAdjustMethod = "BH", - pvalueCutoff = 0.05, - qvalueCutoff = 0.1, - readable = TRUE)) +fea_res <- EMMA_run( + clusterProfiler::enrichGO( + gene = rownames(de_res), + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + ont = "BP", + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + readable = TRUE + ) +) ``` As you can see, `EMMA` returns the FEA results in their **native/standard** format. `EMMA` also warns you about good practices when performing FEA, like in this example, we didn't define a list of background genes (which can influence the -results), so we get warned about that. +results TODO leading to a larger amount of false discoveries as a consequence of smaller p-values, see Wiiesorija again?), so we get warned about that. ### `EMMA_show()` : Summarizing the recorded information @@ -350,10 +353,10 @@ You can also use a custom function that you developed, or a wrapper function ```{r mosdef_eg} mosdef_fea_res <- mosdef::run_goseq(de_genes = rownames(de_res), - bg_genes = gene_universe, - mapping = "org.Hs.eg.db", - id = "ensGene", - genome = "hg19") |> + bg_genes = gene_universe, + mapping = "org.Hs.eg.db", + id = "ensGene", + genome = "hg19") |> EMMA_run(store_session_info = FALSE, args_form = "unevaluated") @@ -407,16 +410,16 @@ capture automatically. To keep everything organized, we can use ```{r add_custom_metadata} frankenstein_fea2 <- EMMA_add_custom_metadata(res = frankenstein_fea, - extra = list( - wrapped_function_topGO = "runTest", - notes = "any other meaningful info")) + extra = list( + wrapped_function_topGO = "runTest", + notes = "any other meaningful info")) EMMA_get_record(frankenstein_fea2)$extra ``` Since the `EMMA_record` is attached as attribute to the original results objects, it can be preserved when integrating results into structured containers -such as `DeeDeeExperiment`. This enables both FEA results and +such as `DeeDeeExperiment` TODO link with BiocPkg? + link to citation, let's ride the wave!. This enables both FEA results and their associated provenance information to be stored and managed together, facilitating reproducibility, organization, and sharing of complex omics analyses. @@ -443,13 +446,13 @@ used in practice, rather than attempting to enforce a fully consistent state. ```{r EMMA_freeze} if (requireNamespace("renv", quietly = TRUE)) { - project_path <- tempfile("my_project_with_emma") - dir.create(project_path) -EMMA_freeze(project = project_path, - file = "analysis.lock", - pkgs = loadedNamespaces(), - prompt = FALSE, - force = TRUE) + project_path <- tempfile("my_project_with_emma") + dir.create(project_path) + EMMA_freeze(project = project_path, + file = "analysis.lock", + pkgs = loadedNamespaces(), + prompt = FALSE, + force = TRUE) } ``` From b9ecd18228cdc726c799efb54490549d9d013f7a Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 11:48:49 +0200 Subject: [PATCH 64/92] removing the extra links --- vignettes/Introduction_to_EMMA.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 807b1cf..a2113b6 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -142,7 +142,7 @@ Maybe a diagram that would show what EMMA puts in and avoids you to take notes o In the remainder of this vignette, we will illustrate the main features of `r BiocStyle::Biocpkg("EMMA")` on a publicly available dataset from Alasoo, et al. "Shared genetic effects on chromatin and gene expression indicate a role for enhancer priming in immune response", published in Nature Genetics, January 2018 -[@Alasoo2018] [doi:10.1038/s41588-018-0046-7](https://doi.org/10.1038/s41588-018-0046-7). +[@Alasoo2018]. The data is made available via the `r BiocStyle::Biocpkg("macrophage")` Bioconductor package, which contains the files output from the Salmon From 3b595bdc985856499249246067edc32176ee44a4 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 11:49:11 +0200 Subject: [PATCH 65/92] adding citation to DeeDeeExp --- vignettes/Introduction_to_EMMA.Rmd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index a2113b6..62bd014 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -419,7 +419,7 @@ EMMA_get_record(frankenstein_fea2)$extra Since the `EMMA_record` is attached as attribute to the original results objects, it can be preserved when integrating results into structured containers -such as `DeeDeeExperiment` TODO link with BiocPkg? + link to citation, let's ride the wave!. This enables both FEA results and +such as `r BiocStyle::Biocpkg("DeeDeeExperiment")` [@Abassi2026]. This enables both FEA results and their associated provenance information to be stored and managed together, facilitating reproducibility, organization, and sharing of complex omics analyses. @@ -427,7 +427,8 @@ facilitating reproducibility, organization, and sharing of complex omics analyse dde <- DeeDeeExperiment::DeeDeeExperiment(sce = dds_macrophage, de_results = IFNg_vs_naive, enrich_results = list( - IFNg_vs_naive = fea_res_no_param)) + IFNg_vs_naive = fea_res_no_param) + ) fea <- DeeDeeExperiment::getFEA(dde, format = "original") From 098175b3805f5581927427e662da73dd2aed5974 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 11:49:18 +0200 Subject: [PATCH 66/92] bibtex updated --- vignettes/EMMA_bibliography.bib | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/vignettes/EMMA_bibliography.bib b/vignettes/EMMA_bibliography.bib index 5cd3758..6238d17 100644 --- a/vignettes/EMMA_bibliography.bib +++ b/vignettes/EMMA_bibliography.bib @@ -77,7 +77,20 @@ @article{Brazma2001 pages = {365–371} } - +@article{Abassi2026, + title = {DeeDeeExperiment: building an infrastructure for integrating and managing omics data analysis results in R/Bioconductor}, + volume = {42}, + ISSN = {1367-4811}, + url = {http://dx.doi.org/10.1093/bioinformatics/btag157}, + DOI = {10.1093/bioinformatics/btag157}, + number = {4}, + journal = {Bioinformatics}, + publisher = {Oxford University Press (OUP)}, + author = {Abassi, Najla and Schwarz, Lea and Filippi, Edoardo and Marini, Federico}, + editor = {Schwartz, Russell}, + year = {2026}, + month = Mar +} From c614baf74db9edec7a461faf8eb575486c614f78 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 11:49:43 +0200 Subject: [PATCH 67/92] some styling for pkgdown landing page added --- _pkgdown.yml | 11 +++++- pkgdown/extra.css | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 pkgdown/extra.css diff --git a/_pkgdown.yml b/_pkgdown.yml index 81d9b19..f753f9e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -9,4 +9,13 @@ authors: url: ~ template: bootstrap: 5 - bootswatch: minty \ No newline at end of file + bslib: + preset: "bootstrap" + font_scale: 1.0 + base_font: + google: "Atkinson Hyperlegible" + code_font: + google: "IBM Plex Mono" + +navbar: + type: light diff --git a/pkgdown/extra.css b/pkgdown/extra.css new file mode 100644 index 0000000..c51e05b --- /dev/null +++ b/pkgdown/extra.css @@ -0,0 +1,97 @@ +/* +Developed and maintained by Kevin Rue-Albrecht (@kevinrue) +*/ + +/* +#0092ac blue +#00758a darker blue (active menu) +#c4d931 green (on blue) +#87b13f green (on white) +*/ + +.headroom { + background-color: #0092ac; +} + +.navbar-default .navbar-link { + color: #ffffff; +} + +.navbar-default .navbar-link:hover { + color: #c4d931; +} + +.navbar-default .navbar-nav>.active>a, +.navbar-default .navbar-nav>.active>a:hover, +.navbar-default .navbar-nav>.active>a:focus { + color: #c4d931; + background-color: #00758a; +} + +.navbar-default .navbar-nav>.open>a, +.navbar-default .navbar-nav>.open>a:hover, +.navbar-default .navbar-nav>.open>a:focus { + color: #c4d931; + background-color: #00758a; +} + +.dropdown-menu>.active>a, +.dropdown-menu>.active>a:hover, +.dropdown-menu>.active>a:focus { + color: #c4d931; + background-color: #00758a; +} + +.navbar-default .navbar-nav>li>a:hover, +.navbar-default .navbar-nav>li>a:focus { + color: #c4d931; +} + +.dropdown-menu>li>a:hover { + color: #87b13f; + background-color: #ffffff; +} + +.navbar-default .navbar-nav>li>a { + color: #ffffff; +} + +h1 { + color: #87b13f; +} + +h2 { + color: #1a81c2; +} + +h3 { + color: #1a81c2; + font-weight: bold; +} + +.btn-copy-ex { + color: #ffffff; + background-color: #0092ac; + border-color: #0092ac; +} + +.btn-copy-ex:hover { + color: #ffffff; + background-color: #00758a; + border-color: #00758a; +} + +.btn-copy-ex:active:focus { + color: #c4d931; + background-color: #00758a; + border-color: #0092ac; +} + +p>.fa, +p>.fas { + color: #0092ac; +} + +img { + width: auto; +} From 2df7bdddde3f6fb041aa13b9d263006aa4130dd3 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 11:50:54 +0200 Subject: [PATCH 68/92] fixed the spot with correct citation --- vignettes/Introduction_to_EMMA.Rmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 62bd014..849bcea 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -255,7 +255,8 @@ fea_res <- EMMA_run( As you can see, `EMMA` returns the FEA results in their **native/standard** format. `EMMA` also warns you about good practices when performing FEA, like in this example, we didn't define a list of background genes (which can influence the -results TODO leading to a larger amount of false discoveries as a consequence of smaller p-values, see Wiiesorija again?), so we get warned about that. +results leading e.g. to a larger amount of false discoveries, as a consequence +of over-optimistic smaller p-values [@Wijesooriya2022]), so we get warned about that. ### `EMMA_show()` : Summarizing the recorded information From 6f1e4f9916c1d84257f466597dc27c215196f82b Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 12:10:09 +0200 Subject: [PATCH 69/92] structuring a bit differently the vignette --- vignettes/Introduction_to_EMMA.Rmd | 43 ++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 849bcea..069e0bb 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -139,6 +139,8 @@ Maybe a diagram that would show what EMMA puts in and avoids you to take notes o # `EMMA` on the `macrophage` dataset +## About the data + In the remainder of this vignette, we will illustrate the main features of `r BiocStyle::Biocpkg("EMMA")` on a publicly available dataset from Alasoo, et al. "Shared genetic effects on chromatin and gene expression indicate a role for enhancer priming in immune response", published in Nature Genetics, January 2018 @@ -170,7 +172,7 @@ library("GO.db") We will show an example of how `r BiocStyle::Biocpkg("EMMA")` fits into a regular bulk RNA-seq data analysis workflow. -# Get a list of Differentially Expressed Genes +## Getting a list of Differentially Expressed Genes For this, we will load the `macrophage` data and perform Differential Expression Analysis with `r BiocStyle::Biocpkg("DESeq2")` @@ -206,15 +208,33 @@ de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] gene_universe <- rownames(dds_macrophage) ``` -# Perform Functional Enrichment Analysis (FEA) - -## `EMMA` with available common packages/functions +## Performing Functional Enrichment Analysis (FEA) Now that we have a list of DE genes for this contrast, we can perform Functional -Enrichment Analysis. In the following example, we will use the +Enrichment Analysis. +We do this first *without* the functionality provided by `EMMA`. + +In the following example, we will use the function `enrichGO()` from `r BiocStyle::Biocpkg("clusterProfiler")` -### `EMMA_run()` :Capturing the recorded information +```{r withoutemma} +fea_res <- enrichGO(gene = rownames(de_res), + # TODO add universe, to have the first one "done right" + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + ont = "BP", + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1) +``` + +# Entering the `EMMA` framework + +From now on, we will complement the existing workflow with the functionality +provided by `EMMA`. + + +## `EMMA_run()`: Capturing the recorded information `EMMA_run()` accepts a function call (e.g. `enrichGO(...)`) and executes it as it is, while capturing the associated parameters and provenance information: @@ -222,6 +242,7 @@ it is, while capturing the associated parameters and provenance information: ```{r EMMA_run_1} # perform FEA, but with EMMA! fea_res <- enrichGO(gene = rownames(de_res), + # TODO add universe, to have the first one "done right" keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", @@ -258,7 +279,7 @@ example, we didn't define a list of background genes (which can influence the results leading e.g. to a larger amount of false discoveries, as a consequence of over-optimistic smaller p-values [@Wijesooriya2022]), so we get warned about that. -### `EMMA_show()` : Summarizing the recorded information +## `EMMA_show()`: Summarizing the recorded information To get a quick summary of what `EMMA` captured while we ran the analysis, we use `EMMA_show()`: @@ -271,7 +292,7 @@ EMMA_show(fea_res) results object. That's why it is always a good practice to save the original results, and not only the subsets of interest. -### `EMMA_get_record()` : Retrieving the recorded information +## `EMMA_get_record()`: Retrieving the recorded information To access the full recorded information, we use `EMMA_get_record()`: @@ -336,7 +357,7 @@ EMMA_get_record(fea_res_no_param) We can also choose whether to save the R session information with the record using the argument `store_session_info`, which defaults to `TRUE`. -### `EMMA_explain()`: Summarizing recorded information into text +## `EMMA_explain()`: Summarizing recorded information into text `EMMA_explain()` generates a human-readable description of the FEA, similar to a Materials and Methods section of a paper, by summarizing the executed call, @@ -403,7 +424,7 @@ frankenstein_fea <- my_custom_function( EMMA_get_record(frankenstein_fea) ``` -# `EMMA_add_custom_metadata()`: Adding extra information +## `EMMA_add_custom_metadata()`: Adding extra information The user can always attach extra metadata that `EMMA` might not be able to capture automatically. To keep everything organized, we can use @@ -436,7 +457,7 @@ fea <- DeeDeeExperiment::getFEA(dde, format = "original") EMMA_get_record(fea) ``` -# `EMMA_freeze()`: Recording the Analysis Environment +## `EMMA_freeze()`: Recording the Analysis Environment `EMMA_freeze()` records the R environment at the time of analysis by generating a lockfile using `renv`. By default, the snapshot is created with `force = TRUE`, From 33c3afef638ac4dc335e8eab790329c8eeeae305 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 12:51:07 +0200 Subject: [PATCH 70/92] scollout & smaller css tricks are in --- vignettes/Introduction_to_EMMA.Rmd | 48 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 069e0bb..04c9749 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -1,6 +1,6 @@ --- title: > - The `EMMA` User's Guide + The `EMMA` User's Guide - Enrichment Method MAtters author: - name: Najla Abassi affiliation: @@ -34,11 +34,6 @@ editor_options: bibliography: EMMA_bibliography.bib --- - ```{r knitr, include = FALSE} knitr::opts_chunk$set( @@ -68,8 +63,9 @@ exists to ensure transparent and reproducible documentation of FEA workflows, comparable to the MIAME guidelines [@Brazma2001] [doi:10.1038/ng1201-365]( https://doi.org/10.1038/ng1201-365). -To address this gap, we introduce `r BiocStyle::Biocpkg("EMMA")`, a framework -that automatically captures key analytical parameters and provenance information +To address this gap, we introduce `r BiocStyle::Biocpkg("EMMA")` (standing for +**E**nrichment **M**ethods **MA**tters), a framework that automatically captures key +analytical parameters and provenance information during the execution of FEA methods. This vignette demonstrates how `EMMA` integrates with existing tools @@ -137,7 +133,8 @@ Like: stepwise with bullet point Maybe a diagram that would show what EMMA puts in and avoids you to take notes of? -# `EMMA` on the `macrophage` dataset + +# Usage example of `EMMA`: the `macrophage` dataset ## About the data @@ -296,7 +293,18 @@ results, and not only the subsets of interest. To access the full recorded information, we use `EMMA_get_record()`: +```{css} +/*| echo: false */ + +.scrollout { +max-height: 500px; +overflow-y: scroll; +} +``` + ```{r EMMA_get_record} +#| class-output: scrollout + emma_record <- EMMA_get_record(fea_res) # get all the record @@ -338,6 +346,8 @@ used in our call or not. This can be useful, for example, to avoid unnecessarily increasing the size of the result object. For this, we can use the argument `args_form`: ```{r argument_form} +#| class-output: scrollout + fea_res_no_param <- enrichGO(gene = rownames(de_res), universe = gene_universe, keyType = "ENSEMBL", @@ -374,6 +384,8 @@ You can also use a custom function that you developed, or a wrapper function `EMMA_run()` will attempt to capture as much metadata as possible: ```{r mosdef_eg} +#| class-output: scrollout + mosdef_fea_res <- mosdef::run_goseq(de_genes = rownames(de_res), bg_genes = gene_universe, mapping = "org.Hs.eg.db", @@ -387,6 +399,8 @@ EMMA_get_record(mosdef_fea_res) ``` ```{r custom_eg} +#| class-output: scrollout + # a custom function (not from a package) my_custom_function <- function(gene, universe = NULL, ontology = "BP", id_type = "ENTREZID", @@ -431,6 +445,8 @@ capture automatically. To keep everything organized, we can use `EMMA_add_custom_metadata()` function ```{r add_custom_metadata} +#| class-output: scrollout + frankenstein_fea2 <- EMMA_add_custom_metadata(res = frankenstein_fea, extra = list( wrapped_function_topGO = "runTest", @@ -446,6 +462,8 @@ their associated provenance information to be stored and managed together, facilitating reproducibility, organization, and sharing of complex omics analyses. ```{r emma_and_dde} +#| class-output: scrollout + dde <- DeeDeeExperiment::DeeDeeExperiment(sce = dds_macrophage, de_results = IFNg_vs_naive, enrich_results = list( @@ -479,9 +497,19 @@ if (requireNamespace("renv", quietly = TRUE)) { } ``` -# Session info {.unnumbered .smaller} +# Session info {.unnumbered} + +```{css} +/*| echo: false */ + +.smaller { + font-size: 10px +} +``` ```{r sessioinfo} +#| class-output: smaller + sessionInfo() ``` From 49a3014ab8b3e9d14fa777128d1794903120d862 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 12:51:24 +0200 Subject: [PATCH 71/92] remove "redundancy" in doi + cit + links --- vignettes/Introduction_to_EMMA.Rmd | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 04c9749..555959c 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -50,18 +50,16 @@ knitr::opts_chunk$set( Functional Enrichment Analysis (FEA) is a key downstream step in omics workflows, commonly applied after differential expression analysis to support biological interpretation and generate pathway-level hypotheses. A wide range of tools and -methods are available, mainly Over-Representation Analysis (ORA) [@Khatri2012] [doi:10.1371/journal.pcbi.1002375](https://doi.org/10.1371/journal.pcbi.1002375) -and Gene Set Enrichment Analysis (GSEA) [@Subramanian2005] [doi:10.1073/pnas.0506580102](https://doi.org/10.1073/pnas.0506580102), leading +methods are available, mainly Over-Representation Analysis (ORA) [@Khatri2012] +and Gene Set Enrichment Analysis (GSEA) [@Subramanian2005], leading to substantial heterogeneity in analytical choices and reported results. -Despite its widespread use, FEA is often insufficiently documented [@Wijesooriya2022] -[doi:10.1371/journal.pcbi.1009935](https://doi.org/10.1371/journal.pcbi.1009935). +Despite its widespread use, FEA is often insufficiently documented [@Wijesooriya2022]. Critical parameters such as background gene sets or multiple testing correction methods are frequently missing or inconsistently reported in scientific papers, limiting reproducibility and interpretability. Currently, no standardized framework exists to ensure transparent and reproducible documentation of FEA workflows, -comparable to the MIAME guidelines [@Brazma2001] -[doi:10.1038/ng1201-365]( https://doi.org/10.1038/ng1201-365). +comparable to the MIAME guidelines [@Brazma2001]. To address this gap, we introduce `r BiocStyle::Biocpkg("EMMA")` (standing for **E**nrichment **M**ethods **MA**tters), a framework that automatically captures key From c4fca5b9d414b684b5781a36090410c44cd3c3f9 Mon Sep 17 00:00:00 2001 From: Federico Marini Date: Tue, 12 May 2026 12:51:46 +0200 Subject: [PATCH 72/92] update title in the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 913b805..56ae0f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# EMMA +# EMMA - Enrichment Methods MAtter EMMA enables the execution of Functional Enrichment Analyses using existing tools (e.g. `clusterProfiler`, `topGO`, `gprofiler2`) while systematically capturing From 5222d3985e82b5b3f2d4e7a6e3385819fac8c32d Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 12 May 2026 12:53:59 +0200 Subject: [PATCH 73/92] updating the readme content --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 913b805..5bff5ce 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # EMMA -EMMA enables the execution of Functional Enrichment Analyses using existing -tools (e.g. `clusterProfiler`, `topGO`, `gprofiler2`) while systematically capturing -analysis parameters and provenance information during runtime, and returning -enrichment results in their standard format alongside structured and reusable metadata. +EMMA enables the execution of Functional Enrichment Analyses using a wide range +of existing tools (e.g. `clusterProfiler`, `topGO`, `gprofiler2` among others) +while systematically capturing analysis parameters and provenance information +during runtime, and returning enrichment results in their standard format +alongside structured and reusable metadata. ## Installation @@ -34,6 +35,11 @@ fea_results <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), ``` +## Usage Overview + +You can find the rendered version of the documentation of `EMMA` at the project +website https://imbeimainz.github.io/EMMA/ + ## Development If you encounter a bug, have usage questions, or want to share ideas and From ca9df8b1b0e1600a087371d2011b52b699242849 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 12 May 2026 13:04:10 +0200 Subject: [PATCH 74/92] updating the code of conduct --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0717a18..d4fdc85 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ functionality to make this package better, feel free to file an ## Code of Conduct Please note that the EMMA project is released with a [Contributor Code -of Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). +of Conduct](https://contributor-covenant.org/version/3/0/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. ## License From dacaff2015b97bfe127e13ce2f29d2741b59bd73 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 12 May 2026 13:40:41 +0200 Subject: [PATCH 75/92] adding bg genes for the first example and remove it from the second --- vignettes/Introduction_to_EMMA.Rmd | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 555959c..a2cbd59 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -214,7 +214,7 @@ function `enrichGO()` from `r BiocStyle::Biocpkg("clusterProfiler")` ```{r withoutemma} fea_res <- enrichGO(gene = rownames(de_res), - # TODO add universe, to have the first one "done right" + universe = gene_universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", @@ -237,7 +237,7 @@ it is, while capturing the associated parameters and provenance information: ```{r EMMA_run_1} # perform FEA, but with EMMA! fea_res <- enrichGO(gene = rownames(de_res), - # TODO add universe, to have the first one "done right" + universe = gene_universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP", @@ -251,11 +251,12 @@ fea_res ... or you can simply wrap `EMMA_run()` around your call: -```{r EMMA_run_2, eval=FALSE} +```{r EMMA_run_2} # you can also pass the function name and its namespace # e.g. `clusterProfiler::enrichGO(...)` -fea_res <- EMMA_run( +fea_res_nobg <- EMMA_run( clusterProfiler::enrichGO( + # no universe set gene = rownames(de_res), keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, @@ -404,7 +405,6 @@ my_custom_function <- function(gene, universe = NULL, ontology = "BP", id_type = "ENTREZID", org_db_name = "org.Hs.eg.db", organism = "hsapiens") { - # a wrapper of a wrapper :D res1 <- mosdef::run_topGO(de_genes = gene, bg_genes = gene_universe, ontology = ontology, From ba994b56ddb1d7843f6c0751c0160bda64899f49 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 15 May 2026 14:24:05 +0200 Subject: [PATCH 76/92] updating internal documentation --- R/EMMA_internal-utils.R | 51 +++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/R/EMMA_internal-utils.R b/R/EMMA_internal-utils.R index 0db2ae1..62e8a0e 100644 --- a/R/EMMA_internal-utils.R +++ b/R/EMMA_internal-utils.R @@ -17,11 +17,11 @@ #' (e.g. for `clusterProfiler`, `gprofiler2` ...) to standardize metadata #' collection across different enrichment tools #' -#' @param info_call A list containing captured call information, including -#' at least `function_name`, `package_name`, and the original `call` -#' -#' @param args A list of evaluated arguments passed to the enrichment function #' +#' @param call_class A named list returned by `.EMMA_classify_call()`. It must +#' contain an `info_call` list with at least `function_name`, `package_name`, +#' and the original `call` +#' @param args A list of evaluated arguments passed to the enrichment function #' @param envir The environment in which the original call was evaluated #' #' @return A list containing annotation metadata(organism, gene set database and @@ -63,7 +63,6 @@ #' #' @param function_name A character string specifying the name of the #' `clusterProfiler` function used to perform FEA -#' #' @param args A list containing the evaluated arguments passed into the #' function call to perform FEA #' @@ -143,9 +142,6 @@ return(meta) } - -### not tested yet since the kegg server is down and the function couldn't work - #' This function assembles standardized metadata describing KEGG enrichment #' analyses performed with `clusterProfiler`. The metadata includes the #' organism, the geneset database used. The database version is currently not @@ -212,10 +208,19 @@ } +#' This function retrieves metadata associated with FEAs performed using +#' user-defined wrapper functions or fully custom functions #' +#' @param call_class A named list returned by `.EMMA_classify_call()` +#' @param args A list of evaluated arguments passed to the enrichment function +#' @param envir The environment in which the original call was evaluated +#' +#' @return A named list +#' #' @noRd -.EMMA_get_custom_metadata <- function(call_class, args, - envir = parent.frame()) { +.EMMA_get_custom_metadata <- function(call_class, + args, + envir = parent.frame()) { meta <- .EMMA_empty_metadata() @@ -265,7 +270,7 @@ -# call info capture ----------------------------------------------------------------- +# call info capture ------------------------------------------------------------ #' This function extracts call related metadata. It handles two call #' formats: bare function calls (e.g. `fun(...)`) and namespace-qualified @@ -339,9 +344,9 @@ } -#' This function, used in in `.EMMA_walk()`, converts the head of a call into a character -#' string representing the function being called. It supports both bare calls -#' (e.g. `fun`) and namespace-qualified calls (e.g. `pkg::fun`) +#' This function, used in in `.EMMA_walk()`, converts the head of a call into +#' a character string representing the function being called. It supports both +#' bare calls (e.g. `fun`) and namespace-qualified calls (e.g. `pkg::fun`) #' #' @param x The head of a call (the function being called) #' @@ -479,6 +484,11 @@ #' decide if top-level function passed to EMMA_run is a known fun or a wrapper #' +#' @param call A call object passed to `EMMA_run()` +#' @param envir The environment in which `call` should be evaluated +#' +#' @return A named list +#' #' @noRd .EMMA_classify_call <- function(call, envir = parent.frame()) { # listing all the functions that are not wrappers @@ -553,15 +563,15 @@ #' to decide how to store the arguments #' @param metadata A list returned by `.EMMA_get_metadata()` #' @param start_time A timestamp marking when the enrichment analysis started -#' @param store_session_info Logical. If `TRUE`, `sessionInfo()` is captured and stored in -#' the record; if `FALSE` the `session_info` slot is `NULL` +#' @param store_session_info Logical. If `TRUE`, `sessionInfo()` is captured +#' and stored in the record; if `FALSE` the `session_info` slot is `NULL` #' #' @return A named list of the recorded metadata #' #' @noRd .EMMA_build_record <- function(info_call, args_form, metadata, - wrapped_original, wrapper, - start_time, store_session_info) { + wrapped_original, wrapper, + start_time, store_session_info) { emma_rec <- list( method = list( call = info_call$call, @@ -584,8 +594,9 @@ gene_set_db_version = metadata$gene_set_db_version ), timestamp = start_time, - session_info = if (isTRUE(store_session_info)) sessionInfo() else NULL, - extra = list(),# free field for extra metadata (added by user) + session_info = if (isTRUE(store_session_info)) sessionInfo() + else NULL, + extra = list(),# free field for extra metadata added by user emma_version = as.character(packageVersion(pkg = "EMMA")) ) From 627ba5da908815ee1307f1fce63ed688738a4790 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 15 May 2026 14:28:16 +0200 Subject: [PATCH 77/92] adding more tests --- tests/testthat/test-EMMA_metadata.R | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/testthat/test-EMMA_metadata.R b/tests/testthat/test-EMMA_metadata.R index f11fee9..6d3135a 100644 --- a/tests/testthat/test-EMMA_metadata.R +++ b/tests/testthat/test-EMMA_metadata.R @@ -100,6 +100,30 @@ test_that("test metadata content & structure", { expect_null(EMMA_get_record(empty)$annotation$gene_set_db_version) + geneList <- de_res_IFNg_vs_naive$log2FoldChange + names(geneList) <- rownames(de_res_IFNg_vs_naive) + geneList <- sort(geneList, decreasing = TRUE) + + expect_warning(gse_res <- gseGO( geneList = geneList, keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, ont = "BP", + minGSSize = 100, maxGSSize = 500, + pvalueCutoff = 0.05, verbose = FALSE, + pAdjustMethod = "BH") |> EMMA_run()) + + expect_equal(EMMA_get_record(gse_res)$method$function_name, "gseGO") + expect_null(EMMA_get_record(gse_res)$method$wrapped_function) + expect_null(EMMA_get_record(gse_res)$method$wrapped_package) + expect_equal(EMMA_get_record(gse_res)$annotation$gene_set_db, "GO") + + # pretending a function that doesn't exist in any package, so fallback + # to empty metadata + expect_warning(user_only_fea <- summary(as.data.frame(gse_res)) |> + EMMA_run(store_session_info = FALSE, args_form = "unevaluated")) + + expect_null(EMMA_get_record(user_only_fea)$annotation$organism) + expect_null(EMMA_get_record(user_only_fea)$annotation$gene_set_db) + expect_null(EMMA_get_record(user_only_fea)$annotation$gene_set_db_version) + }) From 17e6758eedff2ee05e91208145fbb73e2eb17ff4 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Fri, 15 May 2026 14:28:51 +0200 Subject: [PATCH 78/92] adding workflow figure and updating vignette --- vignettes/EMMA_workflow.png | Bin 0 -> 114552 bytes vignettes/Introduction_to_EMMA.Rmd | 29 +++++++++-------------------- 2 files changed, 9 insertions(+), 20 deletions(-) create mode 100644 vignettes/EMMA_workflow.png diff --git a/vignettes/EMMA_workflow.png b/vignettes/EMMA_workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f9da255b16b6f82c8c07d9a6d458c49e1e19c6 GIT binary patch literal 114552 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

}b9M2t*>jK5bh? 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ÅU~ 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 Date: Fri, 15 May 2026 15:02:26 +0200 Subject: [PATCH 79/92] adding reference to tidylog and omicslog --- vignettes/Introduction_to_EMMA.Rmd | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 1d6892f..ac44db6 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -61,6 +61,14 @@ limiting reproducibility and interpretability. Currently, no standardized framew exists to ensure transparent and reproducible documentation of FEA workflows, comparable to the MIAME guidelines [@Brazma2001]. +Similar approaches have been developed outside the Bioconductor ecosystem to +improve transparency and reproducibility in other analytical contexts. +For example, `tidylog` records operations performed with `dplyr` and `tidyr`, +while [`omicslog`](https://github.com/tidyomics/omicslog) tracks transformations +applied to omics-oriented objects. +However, no tool specifically addresses the metadata and provenance requirements +of FEA workflows. + To address this gap, we introduce `r BiocStyle::Biocpkg("EMMA")` (standing for **E**nrichment **M**ethods **MA**tters), a framework that automatically captures key analytical parameters and provenance information From 18a9a77b6eaf8800cd48303e545c68912013133d Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:06:20 +0200 Subject: [PATCH 80/92] removing unecessary installations from gha yaml --- .github/workflows/R-CMD-check.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index b97a276..5631f06 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -94,7 +94,6 @@ jobs: local_deps <- remotes::local_package_deps(dependencies = TRUE) deps <- remotes::dev_package_deps(dependencies = TRUE, repos = BiocManager::repositories()) BiocManager::install(local_deps[local_deps %in% deps$package[deps$diff != 0]], Ncpu = 2L) - BiocManager::install(c("GenomeInfoDbData", "GO.db"), Ncpu = 2L) remotes::install_cran('rcmdcheck', Ncpu = 2L) shell: Rscript {0} From f5e8873410d68e619b97da773e3060825733f441 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:07:29 +0200 Subject: [PATCH 81/92] update the universe data name and fix comments --- R/EMMA-data.R | 2 +- data/gene_universe.RData | Bin 0 -> 1780 bytes data/universe.RData | Bin 1756 -> 0 bytes inst/scripts/create_datasets_examples.R | 6 +++--- 4 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 data/gene_universe.RData delete mode 100644 data/universe.RData diff --git a/R/EMMA-data.R b/R/EMMA-data.R index c66f05f..d7ad654 100644 --- a/R/EMMA-data.R +++ b/R/EMMA-data.R @@ -40,7 +40,7 @@ NULL #' expression indicate a role for enhancer priming in immune response", #' Nature Genetics, January 2018 doi: 10.1038/s41588-018-0046-7. #' -#' @name universe +#' @name gene_universe #' @docType data NULL diff --git a/data/gene_universe.RData b/data/gene_universe.RData new file mode 100644 index 0000000000000000000000000000000000000000..4a0f0218dabbf8d8c63634e94fadd7a3afc8e8b4 GIT binary patch literal 1780 zcmVvQ&2UKVgRpfklK zA+`t`^LEM5g0JmSTuuD9u`sR7lvVPha}QUrhTl|Qgsl}LRQu+o4U*))D^8M*NH@y!HMEm?#CyB&pS)<^>1zzazBur_g)ir$M6V^Fm7@iP8V?k! zAL(Owy7J!gTY*r^6}gd7zJeZ7=jHz#o%1$HYHUu*hp=|JlU1FDI`7|T7BNEnO_w?_FSJws-bnePNM`e}IbHX}(7B&tZsv!2D7! zrnaGS7@%iHz6mf?s?o(WBDd`@gCI2d1AR>CnKb^KK{YRvfVb} ztSTvRV|oV{`1+NeEe z^u-I37ti5N%h6D!)ny9pw891+6o=wY%_p|u1eMR<F;u{6+MvPG~Pw{YLmf7NJRVtZu-oVRWu|nC!?$EPR#YBf?T$Bz)O?j zEovj`URi8K2=p1>5Qb%WLe6p;jbd`@eee8((Z!tRiNL@Mw8;_HDOtF+NgpPSvVKON zEr$m>aqV{BkH2l2PNI8-R3yZQ%4eVd)9{aG+Ok`ULZIrF6L8{95 zs3NeYFpfuy)&-O;C=FaTLmI#@7GMY8Fm!KfhWozTnpMD>&FnfD$w&@^Tk(9gv-=2} zVlX#FXv1cZ?OqbbrjzTSiQG$JJlMCxSy?C^Z|zI`b9|28f{%kTQ~?DDS)Y8x!p8pM z8~faH*rGq4vJ(5Ji6(kIGQim5lnou%p|=$rmkD385sanHma`0QQhbHbUS=bNvQ&2UKVgRpfklK zA+`t`^LEM5g0JmSTuuD9u`sR7lvVPfyqs-C2`X*g$FXF@Vf-KrBKoy z-5)p_@$tCQRs)!MY79i+!i}BM8O$erVo4MQA|^&cn0h;E3rPmEuOl*rYN^ z5J1Blz+W3#azvdO8*pL?v6eaB!F%32F;lq7x;7?FtN<-(vQ~`XA@y;+iOIP zs-!QkKbFEYZeZ_0K{UPeW!S$4(n|XNbK=Ruc^4%qnc~lXAXyYWxlF~nw9_HRxUSO= zXeMVTrY*9EIcIst=@#iO;KIz*ePTt}T8FDI=H55f z0zB#rlk+*5*(s=7Fg95Svh?BeLU)xoDer$snWi22y#MB^b2H^3gm+6e%^wNe90kNN%#a zc7C0uC8)!p%At=DgU+W7jI>>;$QAcU_$!^N3m0mX4(OOp51F&OCG=rCZ#Xwkb0EzN@GWo|tRhVb0v+*|a;8jAQwqnh=q$7z|*8mjW@wP5|GdUAM(l2{Hf@g1@~KMq2lRdN{ud( zS=LU)4Jq^2xYQ&Oke_%Hw!~Tr}4%-!?Ux3Pw4j5!~(|8=cJ2-8PONjGTB3cUql-{YqsR|DlMdg!9VeL z{zpEF$ILf&pj7PSasen-=mx0JdtX1>i@cn^L%b3@d+RkVm?o>#_vXC&BKOQQ<^g=V zr0f3cDZ=!SER_b<%^JHbgn^{KUyqZ;MQpO**br!_2&UHq&|);)n*=_vwOqP4GGpBj zmBdJcP&unT!=pdxhz}FE66eScmZ4v6!KT+mgZ0QS@<;zuhQY=mlL*E|AKeH<>v0`@ z5)Ss=meJ~V>TC{F^__79%1C>>K|K)0D;Kyw+M@)5x~$}ZAL38P-7*n)E53BG(A*XH zxFv!Cq;JiAj*Mkp={X%@VcHDlRHdQ)-1%>nOtbEm`NQ3-wVd;df@D(r!W;S9j`5?i z)2TXsdJoiwQU>bS1sDZPRWpFZtsSOitXokhRYlQ`(LGOGY7Vm}5#Zto7-w?_68sGW zX&p+w>_EIN1>t%5KUblvjqJTnWb)rgHr=qRwDoEHBI0d8c`?8h!bQAM;#U%Y0ynSr z>YfHNLsE;BaDg=eHq3Cp4Ckj=7X2Yh_bVq*vCE)mr9VH1{s9sUEQpSb7MsGMm;bCB zhPoerY0IYCtY#JNR)x!8kz&H*z)(+bEK92-_I&+_@yO%7zdh>#kHs@ZNMw*hm6D5) z0bmsqe{NGcR1=F|GN2*u5av;sDru6o)`DTCE`k0i)ky5JwRAq1huNZ3Ax}F>kMb{@ yOCisE(K;l11Iwn)Fn9e`&;S5p=F4UP0k{pUS^xmAjLNG%Fb#_W000000a;r7^;Z7? diff --git a/inst/scripts/create_datasets_examples.R b/inst/scripts/create_datasets_examples.R index 0f4ff02..d388a9c 100644 --- a/inst/scripts/create_datasets_examples.R +++ b/inst/scripts/create_datasets_examples.R @@ -9,7 +9,7 @@ dds_macrophage # DE run # set seed for reproducibility set.seed(2711) -# sample randomly for 2k genes +# sample randomly for 500 genes selected_genes <- sample(rownames(dds_macrophage), 500) dds_macrophage <- dds_macrophage[selected_genes, ] @@ -42,12 +42,12 @@ library("gprofiler2") fea_res <- gprofiler2::gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", correction_method = "fdr", - custom_bg = universe, + custom_bg = gene_universe, sources = "GO:BP") |> EMMA_run( store_session_info = FALSE, args_form = "unevaluated") save(de_res_IFNg_vs_naive, file = "de_res_IFNg_vs_naive.RData", compress = "xz") -save(universe, file = "universe.RData", compress = "xz") +save(gene_universe, file = "gene_universe.RData", compress = "xz") save(fea_res, file = "fea_res.RData", compress = "xz") From 4f7d289ee83d610f314653089d0808841e0c1730 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:07:50 +0200 Subject: [PATCH 82/92] update EMMA figure --- vignettes/EMMA_workflow.png | Bin 114552 -> 114513 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/vignettes/EMMA_workflow.png b/vignettes/EMMA_workflow.png index d0f9da255b16b6f82c8c07d9a6d458c49e1e19c6..dc8ba88ad7bede4b1e14c8279ab88e9a757705fe 100644 GIT binary patch literal 114513 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�RI!VnOhqg{v70fHJDo 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

}b9M2t*>jK5bh? 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ÅU~ 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 Date: Mon, 18 May 2026 12:08:26 +0200 Subject: [PATCH 83/92] add indentation in examples --- R/EMMA_add_custom_metadata.R | 2 +- R/EMMA_run.R | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/EMMA_add_custom_metadata.R b/R/EMMA_add_custom_metadata.R index a41a6fc..675cd0b 100644 --- a/R/EMMA_add_custom_metadata.R +++ b/R/EMMA_add_custom_metadata.R @@ -16,7 +16,7 @@ #' @examples #' data("fea_res", package = "EMMA") #' fea_res <- EMMA_add_custom_metadata(fea_res, extra = -#' list(note = "The background gene set list was all expressed genes in the assay")) +#' list(note = "The background gene set list was all expressed genes in the assay")) EMMA_add_custom_metadata <- function(res, extra = list()) { diff --git a/R/EMMA_run.R b/R/EMMA_run.R index 3b461f8..d7cff40 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -35,8 +35,8 @@ #' library(gprofiler2) #' #' EMMA_run(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", -#' correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), -#' store_session_info = FALSE, args_form = "unevaluated") +#' correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), +#' store_session_info = FALSE, args_form = "unevaluated") EMMA_run <- function(expr, envir = parent.frame(), store_session_info = TRUE, From 84b2a2371c14ffd370f9f71d4485a23eb74fa680 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:08:56 +0200 Subject: [PATCH 84/92] updating the news before submission --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 9b52e36..667354d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# EMMA 0.99.0 + +* Ready for Bioconductor submission! + # EMMA 0.3.0 * Added the initial implementation of `EMMA_freeze()`. From dca216cdded3f0c19a995b6975d393b14ffa6ac6 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:26:22 +0200 Subject: [PATCH 85/92] adding the current supported functions ... we can update it further when we expand the pkg --- R/EMMA_get_record.R | 3 +++ man/EMMA_get_record.Rd | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/R/EMMA_get_record.R b/R/EMMA_get_record.R index 5dd2e86..f105ef2 100644 --- a/R/EMMA_get_record.R +++ b/R/EMMA_get_record.R @@ -5,6 +5,9 @@ #' #' @return list of metadata recorded during FEA runtime #' @export +#' +#' @seealso [enrichGO()], [groupGO()], [gseGO()], [gseKEGG()], [enrichKEGG()], +#' [gost()], [run_cluPro()], [run_topGO()], [run_goseq()] #' #' @examples #' data("fea_res", package = "EMMA") diff --git a/man/EMMA_get_record.Rd b/man/EMMA_get_record.Rd index a2c2617..7678801 100644 --- a/man/EMMA_get_record.Rd +++ b/man/EMMA_get_record.Rd @@ -20,3 +20,7 @@ EMMA_get_record data("fea_res", package = "EMMA") EMMA_get_record(fea_res) } +\seealso{ +\code{\link[clusterProfiler:enrichGO]{enrichGO()}}, \code{\link[clusterProfiler:groupGO]{groupGO()}}, \code{\link[clusterProfiler:gseGO]{gseGO()}}, \code{\link[clusterProfiler:gseKEGG]{gseKEGG()}}, \code{\link[clusterProfiler:enrichKEGG]{enrichKEGG()}}, +\code{\link[gprofiler2:gost]{gost()}}, \code{\link[mosdef:run_cluPro]{run_cluPro()}}, \code{\link[mosdef:run_topGO]{run_topGO()}}, \code{\link[mosdef:run_goseq]{run_goseq()}} +} From e35c6c8b09f744ec3d0ea920716f7b60fddf19f2 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:26:36 +0200 Subject: [PATCH 86/92] update man page --- man/EMMA_add_custom_metadata.Rd | 2 +- man/EMMA_run.Rd | 4 ++-- man/{universe.Rd => gene_universe.Rd} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename man/{universe.Rd => gene_universe.Rd} (95%) diff --git a/man/EMMA_add_custom_metadata.Rd b/man/EMMA_add_custom_metadata.Rd index 502249f..782521b 100644 --- a/man/EMMA_add_custom_metadata.Rd +++ b/man/EMMA_add_custom_metadata.Rd @@ -25,5 +25,5 @@ captured automatically \examples{ data("fea_res", package = "EMMA") fea_res <- EMMA_add_custom_metadata(fea_res, extra = -list(note = "The background gene set list was all expressed genes in the assay")) + list(note = "The background gene set list was all expressed genes in the assay")) } diff --git a/man/EMMA_run.Rd b/man/EMMA_run.Rd index 72dee43..8eb62c6 100644 --- a/man/EMMA_run.Rd +++ b/man/EMMA_run.Rd @@ -50,6 +50,6 @@ data("universe", package = "EMMA") library(gprofiler2) EMMA_run(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", -correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), -store_session_info = FALSE, args_form = "unevaluated") + correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), + store_session_info = FALSE, args_form = "unevaluated") } diff --git a/man/universe.Rd b/man/gene_universe.Rd similarity index 95% rename from man/universe.Rd rename to man/gene_universe.Rd index aae40db..aaa6359 100644 --- a/man/universe.Rd +++ b/man/gene_universe.Rd @@ -1,8 +1,8 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/EMMA-data.R \docType{data} -\name{universe} -\alias{universe} +\name{gene_universe} +\alias{gene_universe} \title{A sample \verb{character vector} containing the background gene list used to perform FEA on the \code{macrophage} dataset} \format{ From 808656dacb9cd6187ff2753d15a4f30cec9bed7a Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 12:53:12 +0200 Subject: [PATCH 87/92] update examples and tests to match the universe new name --- R/EMMA_run.R | 4 ++-- README.md | 4 ++-- man/EMMA_run.Rd | 4 ++-- tests/testthat/setuptests_EMMA.R | 2 +- tests/testthat/test-EMMA_explain.R | 2 +- tests/testthat/test-EMMA_get_record.R | 2 +- tests/testthat/test-EMMA_metadata.R | 8 ++++---- tests/testthat/test-EMMA_run.R | 10 +++++----- tests/testthat/test-EMMA_show.R | 3 ++- 9 files changed, 20 insertions(+), 19 deletions(-) diff --git a/R/EMMA_run.R b/R/EMMA_run.R index d7cff40..5926519 100644 --- a/R/EMMA_run.R +++ b/R/EMMA_run.R @@ -31,11 +31,11 @@ #' #' @examples #' data("de_res_IFNg_vs_naive", package = "EMMA") -#' data("universe", package = "EMMA") +#' data("gene_universe", package = "EMMA") #' library(gprofiler2) #' #' EMMA_run(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", -#' correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), +#' correction_method = "fdr", custom_bg = gene_universe, sources = "GO:BP"), #' store_session_info = FALSE, args_form = "unevaluated") EMMA_run <- function(expr, envir = parent.frame(), diff --git a/README.md b/README.md index d4fdc85..685fc19 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,11 @@ remotes::install_github("imbeimainz/EMMA", library(EMMA) # load data data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") +data("gene_universe", package = "EMMA") # run analysis fea_results <- enrichGO(gene = rownames(de_res_IFNg_vs_naive), - universe = universe, + universe = gene_universe, keyType = "ENSEMBL", OrgDb = org.Hs.eg.db, ont = "BP") |> diff --git a/man/EMMA_run.Rd b/man/EMMA_run.Rd index 8eb62c6..c503f82 100644 --- a/man/EMMA_run.Rd +++ b/man/EMMA_run.Rd @@ -46,10 +46,10 @@ internally invoke a know enrichment function. } \examples{ data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") +data("gene_universe", package = "EMMA") library(gprofiler2) EMMA_run(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", - correction_method = "fdr", custom_bg = universe, sources = "GO:BP"), + correction_method = "fdr", custom_bg = gene_universe, sources = "GO:BP"), store_session_info = FALSE, args_form = "unevaluated") } diff --git a/tests/testthat/setuptests_EMMA.R b/tests/testthat/setuptests_EMMA.R index dcc56a1..104f1ae 100644 --- a/tests/testthat/setuptests_EMMA.R +++ b/tests/testthat/setuptests_EMMA.R @@ -7,6 +7,6 @@ suppressPackageStartupMessages({ }) data("de_res_IFNg_vs_naive", package = "EMMA") -data("universe", package = "EMMA") +data("gene_universe", package = "EMMA") data("fea_res", package = "EMMA") diff --git a/tests/testthat/test-EMMA_explain.R b/tests/testthat/test-EMMA_explain.R index 114b182..f063cb6 100644 --- a/tests/testthat/test-EMMA_explain.R +++ b/tests/testthat/test-EMMA_explain.R @@ -37,7 +37,7 @@ test_that("EMMA_explain", { res_topGO <- run_topGO(de_genes = rownames(de_res), - bg_genes = universe, + bg_genes = gene_universe, ontology = "BP", gene_id = "ENSEMBL", mapping = "org.Hs.eg.db", diff --git a/tests/testthat/test-EMMA_get_record.R b/tests/testthat/test-EMMA_get_record.R index 44df8b9..358897f 100644 --- a/tests/testthat/test-EMMA_get_record.R +++ b/tests/testthat/test-EMMA_get_record.R @@ -4,7 +4,7 @@ test_that("EMMA_get_record", { fea_no_emma <- mosdef::run_cluPro(de_genes = rownames(de_res_IFNg_vs_naive), - bg_genes = universe, + bg_genes = gene_universe, mapping = "org.Hs.eg.db", keyType = "ENSEMBL", ont = "BP", diff --git a/tests/testthat/test-EMMA_metadata.R b/tests/testthat/test-EMMA_metadata.R index 6d3135a..023a21f 100644 --- a/tests/testthat/test-EMMA_metadata.R +++ b/tests/testthat/test-EMMA_metadata.R @@ -2,7 +2,7 @@ test_that("test metadata content & structure", { fea_res <- EMMA_run(mosdef::run_cluPro(de_genes = rownames(de_res_IFNg_vs_naive), - bg_genes = universe, + bg_genes = gene_universe, mapping = "org.Hs.eg.db", keyType = "ENSEMBL", ont = "BP", @@ -24,7 +24,7 @@ test_that("test metadata content & structure", { expect_true(emma_rec$method$wrapper) expect_null(.EMMA_find_original_wrapped_fun("mosdef::run_cluPro(de_genes = rownames(de_res_IFNg_vs_naive), - bg_genes = universe, + bg_genes = gene_universe, mapping = 'org.Hs.eg.db', keyType = 'ENSEMBL', ont = 'BP')")) @@ -36,7 +36,7 @@ test_that("test metadata content & structure", { expect_warning(res <- EMMA_run(mosdef::run_goseq(de_genes = rownames(de_res_IFNg_vs_naive), - bg_genes = universe, + bg_genes = gene_universe, mapping = "org.Hs.eg.db", id = "ensGene", genome = "hg19"))) @@ -66,7 +66,7 @@ test_that("test metadata content & structure", { fea_res <- gprofiler2::gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", correction_method = "fdr", - custom_bg = universe) |> EMMA_run() + custom_bg = gene_universe) |> EMMA_run() rec <- EMMA_get_record(fea_res) diff --git a/tests/testthat/test-EMMA_run.R b/tests/testthat/test-EMMA_run.R index 1c862c6..ef70d52 100644 --- a/tests/testthat/test-EMMA_run.R +++ b/tests/testthat/test-EMMA_run.R @@ -6,7 +6,7 @@ test_that("EMMA_run", { pAdjustMethod = "BH", pvalueCutoff = 0.05, qvalueCutoff = 0.1, - universe = universe, + universe = gene_universe, readable = TRUE)) expect_s4_class(fea_res, "enrichResult") @@ -22,7 +22,7 @@ test_that("EMMA_run", { pAdjustMethod = "BH", pvalueCutoff = 0.05, qvalueCutoff = 0.1, - universe = universe, + universe = gene_universe, readable = TRUE))) expect_type(info, "list") @@ -35,7 +35,7 @@ test_that("EMMA_run", { pAdjustMethod = "BH", pvalueCutoff = 0.05, qvalueCutoff = 0.1, - universe = universe, + universe = gene_universe, readable = TRUE)) expect_error(EMMA_run("enrichGO(gene = rownames(de_res_IFNg_vs_naive), @@ -48,7 +48,7 @@ test_that("EMMA_run", { pAdjustMethod = "BH", pvalueCutoff = 0.05, qvalueCutoff = 0.1, - universe = universe, + universe = gene_universe, readable = TRUE)) expect_warning(EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), @@ -56,7 +56,7 @@ test_that("EMMA_run", { OrgDb = org.Hs.eg.db, pvalueCutoff = 0.05, qvalueCutoff = 0.1, - universe = universe, + universe = gene_universe, readable = TRUE))) expect_warning(EMMA_run(enrichGO(gene = rownames(de_res_IFNg_vs_naive), diff --git a/tests/testthat/test-EMMA_show.R b/tests/testthat/test-EMMA_show.R index db0ec52..e994932 100644 --- a/tests/testthat/test-EMMA_show.R +++ b/tests/testthat/test-EMMA_show.R @@ -62,7 +62,8 @@ test_that("EMMA_show", { method = list( call = substitute(gost(query = de_res_IFNg_vs_naive$SYMBOL, organism = "hsapiens", - correction_method = "fdr", custom_bg = universe, + correction_method = "fdr", + custom_bg = gene_universe, sources = "GO:BP")), wrapper = FALSE, package_name = "gprofiler2", From 723f28c1682c98b86bbdced7359277a58b29c5a5 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 15:24:02 +0200 Subject: [PATCH 88/92] add supported methods and future extensions section --- vignettes/Introduction_to_EMMA.Rmd | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index ac44db6..25712cb 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -492,6 +492,16 @@ if (requireNamespace("renv", quietly = TRUE)) { } ``` +# Supported methods and future extensions + +`EMMA` currently provides metadata capture support for several commonly used +FEA tools, including methods from `r BiocStyle::Biocpkg("clusterProfiler")`, +`r BiocStyle::Biocpkg("gprofiler2")`, and related wrappers/custom functions +such as `r BiocStyle::Biocpkg("mosdef")`. + +`EMMA` is designed to be extensible, and support for additional FEA methods +and packages will continue to expand over time. + # Session info {.unnumbered} ```{css} From e58c66b5492e88b7e3855d9ee86a1c4e7b4b9daa Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Mon, 18 May 2026 15:26:14 +0200 Subject: [PATCH 89/92] update version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1c616ae..da41875 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: EMMA Title: EMMA: Enrichment Methods Matter for enabling fully reproducible and provenance-aware pathway analysis -Version: 0.3.0 +Version: 0.99.0 Authors@R: c( person( From 11bb21255efdb5f43586515701081d3060955d48 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 19 May 2026 12:21:06 +0200 Subject: [PATCH 90/92] fix spaces in EMMA_show() --- R/EMMA_show.R | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/R/EMMA_show.R b/R/EMMA_show.R index 4d249bd..58c5657 100644 --- a/R/EMMA_show.R +++ b/R/EMMA_show.R @@ -22,10 +22,10 @@ EMMA_show <- function(res){ if (is.list(res) && !is.data.frame(res)) { # e.g. case of gost, returns a list but it's 1 FEA (result) if ("result" %in% names(res)) { - cat("Number of Pathways: ", NROW(res$result), "\n") + cat("Number of Pathways:", NROW(res$result), "\n") } else { # let's say if we have of list of FEAs (returned by custom function) - cat("Number of FEAs: ", length(res), "\n") + cat("Number of FEAs:", length(res), "\n") nms <- names(res) if (is.null(nms) || any(nms == "")) { @@ -34,23 +34,23 @@ EMMA_show <- function(res){ for (i in seq_along(res)) { # check the number of pathways for each element of the list - cat(" -", nms[i], ": ", NROW(res[[i]]), " pathways\n") + cat(" -", nms[i], ":", NROW(res[[i]]), " pathways\n") } } } else { - cat("Number of Pathways: ", NROW(res), "\n") + cat("Number of Pathways:", NROW(res), "\n") } method_info <- emma_rec$method db_info <- emma_rec$annotation - cat("Call: ", paste(deparse(method_info$call), collapse = " "), " \n") - cat("Wrapper: ", method_info$wrapper, " \n") - cat("Package: ", paste(method_info$package_name , "v.", + cat("Call:", paste(deparse(method_info$call), collapse = " "), " \n") + cat("Wrapper:", method_info$wrapper, " \n") + cat("Package:", paste0(method_info$package_name , " v. ", method_info$package_version), " \n") - cat("Organism : ", db_info$organism, " \n") - cat("Gene set library : ", paste(db_info$gene_set_db, collapse = ", "), " \n") - cat("Gene set library version : ",db_info$gene_set_db_version, " \n") + cat("Organism:", db_info$organism, " \n") + cat("Gene set library:", paste(db_info$gene_set_db, collapse = ","), " \n") + cat("Gene set library version:",db_info$gene_set_db_version, " \n") cat("\n") } else { From d1cb7f933226563de5ed86a7b5aa3d96efa82e55 Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 19 May 2026 12:21:19 +0200 Subject: [PATCH 91/92] update tests --- tests/testthat/test-EMMA_show.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-EMMA_show.R b/tests/testthat/test-EMMA_show.R index e994932..b5fb514 100644 --- a/tests/testthat/test-EMMA_show.R +++ b/tests/testthat/test-EMMA_show.R @@ -50,7 +50,7 @@ test_that("EMMA_show", { gene_set_db_version = NA ) ) - expect_output(EMMA_show(fea2), "Number of FEAs: 2") + expect_output(EMMA_show(fea2), "Number of FEAs: 2") expect_output(EMMA_show(fea2), "- FEA_1") expect_output(EMMA_show(fea2),"FEA_2") @@ -76,7 +76,7 @@ test_that("EMMA_show", { ) ) - expect_output(EMMA_show(fea_gost), "Number of Pathways: 2") + expect_output(EMMA_show(fea_gost), "Number of Pathways: 2") }) From 73983e5449b9868b83190ee03a1e5a3e6773176c Mon Sep 17 00:00:00 2001 From: Najla Abassi Date: Tue, 19 May 2026 12:22:07 +0200 Subject: [PATCH 92/92] add workflow example with lapply in the vignette --- vignettes/Introduction_to_EMMA.Rmd | 77 ++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/vignettes/Introduction_to_EMMA.Rmd b/vignettes/Introduction_to_EMMA.Rmd index 25712cb..b9ac952 100644 --- a/vignettes/Introduction_to_EMMA.Rmd +++ b/vignettes/Introduction_to_EMMA.Rmd @@ -176,9 +176,15 @@ dds_macrophage <- DESeqDataSet(gse, design = ~ line + condition) # preprocess rownames(dds_macrophage) <- substr(rownames(dds_macrophage), 1, 15) keep <- rowSums(counts(dds_macrophage) >= 10) >= 6 - dds_macrophage <- dds_macrophage[keep, ] +# set seed for reproducibility +set.seed(2711) +# sample randomly for 2k genes +selected_genes <- sample(rownames(dds_macrophage), 2000) + +dds_macrophage <- dds_macrophage[selected_genes, ] + # run DESeq dds_macrophage <- DESeq(dds_macrophage) @@ -191,10 +197,26 @@ IFNg_vs_naive <- lfcShrink(dds_macrophage, coef = "condition_IFNg_vs_naive", type = "apeglm") IFNg_vs_naive$SYMBOL <- rowData(dds_macrophage)$SYMBOL +# get de res for 2st contrast +SL1344_vs_naive <- results(dds_macrophage, + contrast = c("condition", "SL1344", "naive"), + lfcThreshold = 1, alpha = 0.05) +SL1344_vs_naive <- lfcShrink(dds_macrophage, coef = "condition_SL1344_vs_naive", + res = SL1344_vs_naive, + type = "apeglm") +SL1344_vs_naive$SYMBOL <- rowData(dds_macrophage)$SYMBOL + # sort by adjusted p value -de_res <- IFNg_vs_naive[order(IFNg_vs_naive$padj), ] -de_res <- de_res[!(is.na(de_res$padj)) & de_res$padj <= 0.05, ] +de_list <- list( + IFNg_vs_naive = IFNg_vs_naive, + SL1344_vs_naive = SL1344_vs_naive +) +de_list <- lapply(de_list, function(df) { + df <- df[order(df$padj), ] + df <- df[!is.na(df$padj) & df$padj <= 0.05, ] + df +}) # set background gene list gene_universe <- rownames(dds_macrophage) ``` @@ -209,6 +231,8 @@ In the following example, we will use the function `enrichGO()` from `r BiocStyle::Biocpkg("clusterProfiler")` ```{r withoutemma} +de_res <- de_list$IFNg_vs_naive + fea_res <- enrichGO(gene = rownames(de_res), universe = gene_universe, keyType = "ENSEMBL", @@ -482,7 +506,7 @@ used in practice, rather than attempting to enforce a fully consistent state. ```{r EMMA_freeze} if (requireNamespace("renv", quietly = TRUE)) { - project_path <- tempfile("my_project_with_emma") + project_path <- tempdir() dir.create(project_path) EMMA_freeze(project = project_path, file = "analysis.lock", @@ -492,6 +516,51 @@ if (requireNamespace("renv", quietly = TRUE)) { } ``` +## `EMMA` with iterative workflows + +In real-world analyses, multiple FEA results from different contrasts +are often analyzed in parallel This can be conveniently handled using +iterative approaches such as `lapply()`. + +```{r emma_with_lapply} +fea_res_list <- lapply(de_list, function(dea){ + #### perform fea with EMMA_run #### + enrichGO(gene = rownames(dea), + universe = gene_universe, + keyType = "ENSEMBL", + OrgDb = org.Hs.eg.db, + ont = "BP", + pAdjustMethod = "BH", + pvalueCutoff = 0.05, + qvalueCutoff = 0.1, + readable = TRUE) |> + EMMA_run() +}) + +#### print the metadata summary for each contrast #### +invisible(lapply(names(fea_res_list), function(nm) { + cat("\n###", nm) + EMMA_show(fea_res_list[[nm]]) +})) + +#### add more info to the metadata #### +fea_res_list <- setNames(lapply(names(fea_res_list), function(nm) { + EMMA_add_custom_metadata(fea_res_list[[nm]], extra = list(contrast_name = nm)) + }), +names(fea_res_list)) + +#### get the raw metadata #### +record_list <- list() + +record_list <- setNames(lapply(names(fea_res_list), function(mn){ + EMMA_get_record(fea_res_list[[mn]]) + }), +names(fea_res_list)) + +record_list$SL1344_vs_naive$extra +record_list$SL1344_vs_naive$method$package_name +``` + # Supported methods and future extensions `EMMA` currently provides metadata capture support for several commonly used