From cbbf9424a61c517786349210c99a9714c1d4cec1 Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:07:51 -0700 Subject: [PATCH 01/17] All stat and plot generation functions --- R/amRdataPlots.R | 412 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 R/amRdataPlots.R diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R new file mode 100644 index 0000000..2d9f3ee --- /dev/null +++ b/R/amRdataPlots.R @@ -0,0 +1,412 @@ + +#' Generate a summary report for AMR metadata +#' +#' @param metadata_parquet Character string. Path to a Parquet file containing +#' standardized AMR metadata. +#' +#' @return Prints a structured, human‑readable summary report. +#' +#' @import dplyr +#' @import arrow +#' @import kableExtra +#' +#' @examples +#' generateSummary(metadata_parquet = "results/metadata.parquet", +#' out_path = "results/") +#' +#' @export +generateSummary <- function(metadata_parquet, + out_path) { + + # Normalize path and load metadata table + metadata_parquet <- normalizePath(metadata_parquet) + metadata <- arrow::read_parquet(metadata_parquet) + md_path <- file.path(out_path, paste0("amr_metadata_summary.md")) + # Error if metadata table is empty + if (nrow(metadata) == 0) { + stop("The output table is empty. Please check your query or input data.") + } + + # Total and unique genomes + TotalEntryCount <- metadata |> dplyr::count() + CleanEntryCount <- metadata |> dplyr::distinct(genome_drug.genome_id) |> dplyr::count() + + # Antibiotics and classes + Antibiotics <- metadata |> dplyr::distinct(genome_drug.antibiotic) |> dplyr::pull() |> sort() + AntibioticClasses <- metadata |> dplyr::distinct(drug_class) |> dplyr::pull() |> sort() + + # Lab methods + LabMethods <- metadata |> + dplyr::group_by(genome_drug.laboratory_typing_method) |> + dplyr::count() + + # PubMed IDs + PubMed_ids <- metadata |> + dplyr::distinct(genome_drug.pmid) |> + dplyr::filter(!is.na(genome_drug.pmid), genome_drug.pmid != "") |> + dplyr::pull() + + # Phenotype counts + PhenotypeCount <- metadata |> + dplyr::group_by(genome_drug.resistant_phenotype) |> + dplyr::count() + + # Phenotype × Drug + PhenotypebyDrugCount <- metadata |> + dplyr::group_by(genome_drug.resistant_phenotype, genome_drug.antibiotic) |> + dplyr::count() + + # Resistance proportion per drug + ResPropbyDrug <- metadata |> + dplyr::group_by(genome_drug.antibiotic) |> + dplyr::count(genome_drug.resistant_phenotype) |> + dplyr::mutate(prop = n / sum(n)) |> + dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> + dplyr::transmute(res_prop = round(prop, 3)) + + # Phenotype × Antibiotic Class (with consistency filtering) + PhenotypebyDrugClassCount <- metadata |> + dplyr::group_by(genome_drug.genome_id, drug_class) |> + dplyr::filter(!(any(genome_drug.resistant_phenotype == "Resistant") & + genome_drug.resistant_phenotype == "Susceptible")) |> + dplyr::ungroup() |> + dplyr::group_by(genome_drug.genome_id, drug_class) |> + dplyr::slice_head(n = 1) |> + dplyr::ungroup() |> + dplyr::group_by(genome_drug.resistant_phenotype, drug_class) |> + dplyr::count() + + # Resistance proportion × drug class + ResPropbyDrugClass <- metadata |> + dplyr::group_by(drug_class) |> + dplyr::count(genome_drug.resistant_phenotype) |> + dplyr::mutate(prop = n / sum(n)) |> + dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> + dplyr::transmute(res_prop = round(prop, 3)) + + # Year summaries + Year <- metadata |> + dplyr::distinct(genome.collection_year) |> + dplyr::filter(!is.na(genome.collection_year)) |> + dplyr::pull() |> sort() + + YearCount <- metadata |> + dplyr::group_by(genome.collection_year) |> + dplyr::filter(!is.na(genome.collection_year)) |> + dplyr::count() + + # Country summaries + Country <- metadata |> + dplyr::distinct(genome.isolation_country) |> + dplyr::filter(!is.na(genome.isolation_country), genome.isolation_country != "") |> + dplyr::pull() |> sort() + + CountryCount <- metadata |> + dplyr::group_by(genome.isolation_country) |> + dplyr::filter(!is.na(genome.isolation_country), genome.isolation_country != "") |> + dplyr::count() + + # Isolation sources + Source <- metadata |> + dplyr::distinct(genome.isolation_source) |> + dplyr::filter(!is.na(genome.isolation_source), genome.isolation_source != "") |> + dplyr::pull() |> sort() + + SourceCount <- metadata |> + dplyr::group_by(genome.isolation_source) |> + dplyr::filter(!is.na(genome.isolation_source), genome.isolation_source != "") |> + dplyr::count() + + # Host names + Host <- metadata |> + dplyr::distinct(genome.host_common_name) |> + dplyr::filter(!is.na(genome.host_common_name), genome.host_common_name != "") |> + dplyr::pull() |> sort() + + # helper to write markdown tables + md_tbl <- function(df) { + knitr::kable(df, format = "pipe") + } + + # --------- PRINT SUMMARY --------- + # ---- Write Markdown (clean tables) ---- + cat("# AMR Summary Report\n\n", file = md_path) + cat(sprintf("- **Entries**: %s\n- **Unique genome IDs**: %s\n\n", + TotalEntryCount[[1]], CleanEntryCount[[1]]), + file = md_path, append = TRUE) + cat(sprintf("- **Publications** (%d): %s\n\n", + length(PubMed_ids), + if (length(PubMed_ids)) paste(PubMed_ids, collapse = ", ") + else "None"), + file = md_path, append = TRUE) + cat(sprintf("## Antibiotics (%d)\n\n%s\n\n", + length(Antibiotics), paste(Antibiotics, collapse = ", ")), + file = md_path, append = TRUE) + cat(sprintf("## Antibiotic Classes (%d)\n\n%s\n\n", + length(AntibioticClasses), + paste(AntibioticClasses, collapse = ", ")), + file = md_path, append = TRUE) + cat("## Phenotype Counts\n\n", + md_tbl(PhenotypeCount), "\n\n", + file = md_path, append = TRUE) + cat("## Phenotype × Antibiotic\n\n", + md_tbl(PhenotypebyDrugCount), "\n\n", + file = md_path, append = TRUE) + cat("## Resistant Proportion per Antibiotic\n\n", + md_tbl(ResPropbyDrug), "\n\n", + file = md_path, append = TRUE) + cat("## Phenotype × Antibiotic Class\n\n", + md_tbl(PhenotypebyDrugClassCount), "\n\n", + file = md_path, append = TRUE) + cat("## Resistant Proportion per Antibiotic Class\n\n", + md_tbl(ResPropbyDrugClass), "\n\n", + file = md_path, append = TRUE) + cat("## Laboratory Methods\n\n", + md_tbl(LabMethods), "\n\n", + file = md_path, append = TRUE) + cat("## Year Counts\n\n", + md_tbl(YearCount), "\n\n", + file = md_path, append = TRUE) + cat("## Country Counts\n\n", + md_tbl(CountryCount), "\n\n", + file = md_path, append = TRUE) + cat("## Isolation Sources\n\n", + md_tbl(SourceCount), "\n\n", + file = md_path, append = TRUE) + if (length(Host)) { + cat("## Hosts\n\n", + paste("- ", Host, collapse = "\n"), "\n", + file = md_path, append = TRUE) + } + } + + +#' Write all summary plots to file(s) +#' +#' @param metadata_parquet Character. Path to the Parquet metadata file. +#' @param out_path Character. Output directory for plot files. +#' +#' @return Invisibly returns a vector of pdf file paths written. +#' @export +generatePlots <- function(metadata_parquet, + out_path) { + + device <- "pdf" + if(!dir.exists(out_path)) {dir.create(out_path, showWarnings = FALSE, recursive = TRUE)} + + metadata <- arrow::read_parquet(normalizePath(metadata_parquet)) + + # --------- Build plots (same visuals as your generatePlots) ---------- + # 1) Phenotypes across antibiotics and time + df_year <- metadata |> + dplyr::filter(!is.na(genome.collection_year)) |> + dplyr::select( + genome_drug.genome_id, + genome_drug.antibiotic, + genome_drug.resistant_phenotype, + genome.isolation_country, + genome.collection_year + ) + + summary_year <- df_year |> + dplyr::group_by( + genome_drug.antibiotic, + genome_drug.resistant_phenotype, + genome.collection_year + ) |> + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + p1 <- ggplot2::ggplot( + summary_year, + ggplot2::aes(x = genome.collection_year, + y = count, + colour = genome_drug.resistant_phenotype) + ) + + ggplot2::geom_line() + + ggplot2::geom_point() + + ggplot2::facet_wrap(~ genome_drug.antibiotic, scales = "free_y") + + ggplot2::labs( + title = "Resistant phenotypes across antibiotics and time", + x = "Year", y = "Number of isolates", + colour = "Phenotype" + ) + + ggplot2::scale_color_brewer(palette = "Pastel1") + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + legend.position = "bottom", + axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + + # 2) Resistance only over time + + + # 1) Levels for antibiotics (from the same subset used in p2) + abx_levels <- summary_year |> + dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> + dplyr::distinct(genome_drug.antibiotic) |> + dplyr::arrange(genome_drug.antibiotic) |> + dplyr::pull(genome_drug.antibiotic) + + # 2) Base Okabe–Ito (CVD-friendly) and pastelizer + okabe_ito_base <- c( + "#000000", # black + "#E69F00", # orange + "#56B4E9", # sky blue + "#009E73", # bluish green + "#F0E442", # yellow + "#0072B2", # blue + "#D55E00", # vermillion + "#CC79A7" # reddish purple + ) + + okabe_ito_pastel <- function(n, lighten = 0.15) { + # Interpolate if more than 8 needed + cols <- if (n <= length(okabe_ito_base)) { + okabe_ito_base[seq_len(n)] + } else { + grDevices::colorRampPalette(okabe_ito_base)(n) + } + to_rgb <- function(hex) grDevices::col2rgb(hex) / 255 + blend_with_white <- function(hex, a = lighten) { + rgb <- to_rgb(hex) + out <- (1 - a) * rgb + a * c(1, 1, 1) + grDevices::rgb(out[1], out[2], out[3]) + } + vapply(cols, blend_with_white, character(1), a = lighten) + } + + # 3) Build a NAMED palette aligned to factor levels + pal_vals <- okabe_ito_pastel(length(abx_levels), lighten = 0.15) + pal_named <- stats::setNames(pal_vals, abx_levels) + + # 4) Plot with factor levels + named palette (no warnings, distinct colors) + p2 <- ggplot2::ggplot( + summary_year |> + dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> + dplyr::mutate( + antibiotic_fac = factor(genome_drug.antibiotic, levels = abx_levels) + ), + ggplot2::aes(x = genome.collection_year, y = count, + colour = antibiotic_fac) + ) + + ggplot2::geom_line() + + ggplot2::geom_point() + + ggplot2::labs( + title = "Distribution of resistance data over time", + x = "Year", + y = "Number of resistant isolates", + colour = "Antibiotic" + ) + + ggplot2::scale_color_manual(values = pal_named, drop = TRUE) + # <- named palette + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme( + text = ggplot2::element_text(colour = "#2D2D2D"), + legend.position = "bottom" + ) + + # 3) Time × geography × phenotype + df_country <- metadata |> + dplyr::filter(genome.isolation_country != "") |> + dplyr::select( + genome_drug.genome_id, + genome_drug.antibiotic, + genome_drug.resistant_phenotype, + genome.isolation_country, + genome.collection_year + ) + + summary_country_year <- df_country |> + dplyr::group_by( + genome.collection_year, + genome_drug.resistant_phenotype, + genome.isolation_country + ) |> + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + p3 <- ggplot2::ggplot( + summary_country_year, + ggplot2::aes(x = genome.collection_year, + y = genome.isolation_country, + size = count, + color = genome_drug.resistant_phenotype) + ) + + ggplot2::geom_point(alpha = 0.75) + + ggplot2::scale_size(range = c(3, 15)) + + ggplot2::scale_color_viridis_d(option = "C", begin = 0.25, end = 0.95) + + ggplot2::labs( + title = "AMR isolates across time and geography", + x = "Year", y = "Country", + size = "Count", color = "Phenotype" + ) + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + legend.position = "bottom") + + # 4) Phenotype proportion per antibiotic (stacked, normalized) + p4 <- ggplot2::ggplot( + metadata, + ggplot2::aes(x = genome_drug.antibiotic, + fill = genome_drug.resistant_phenotype) + ) + + ggplot2::geom_bar(position = "fill") + + ggplot2::coord_flip() + + ggplot2::labs( + title = "Phenotype proportion per antibiotic", + x = "Antibiotic", y = "Proportion", fill = "Phenotype" + ) + ggplot2::scale_fill_brewer(palette = "Pastel1") + # <- keep pastel + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + legend.position = "bottom", + axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + + # 5) Treemap of isolation sources + summary_isolation_source <- metadata |> + dplyr::filter(genome.isolation_source != "") |> + dplyr::group_by(genome.isolation_source, genome_drug.resistant_phenotype) |> + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + p5 <- ggplot2::ggplot( + summary_isolation_source, + ggplot2::aes(area = count, fill = genome.isolation_source) + ) + + treemapify::geom_treemap() + + treemapify::geom_treemap_text( + ggplot2::aes(label = genome_drug.resistant_phenotype), + color = "grey15", grow = FALSE + ) + + ggplot2::labs( + title = "Distribution of AMR isolates by isolation source", + fill = "Isolation source" + ) + ggplot2::scale_fill_brewer(palette = "Pastel2") + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + legend.position = "bottom", + plot.title = ggplot2::element_text(face = "bold")) + + # 6) Histogram of resistant classes per genome + p6 <- ggplot2::ggplot(metadata, ggplot2::aes(num_resistant_classes)) + + ggplot2::geom_histogram(binwidth = 1, fill = "steelblue") + + ggplot2::labs( + title = "Distribution of resistant classes per genome", + x = "# Resistant Classes", y = "Count" + ) + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + legend.position = "bottom") + + plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) + + # --------- Write to device ---------- + paths <- character(0) + + pdf_path <- file.path(out_path, paste0("amRdata_exploratory_plots.pdf")) + grDevices::pdf(pdf_path, onefile = TRUE) + on.exit(grDevices::dev.off(), add = TRUE) + for (nm in names(plots)) { + print(plots[[nm]]) + } + paths <- c(paths, pdf_path) + + + invisible(paths) +} + From b0e7ead4a7e76d5c0fc723bcc4d4fbcd9f737797 Mon Sep 17 00:00:00 2001 From: Evan Pierce Brenner <108823789+epbrenner@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:42:59 -0700 Subject: [PATCH 02/17] Revised markdown code to fix weird formatting Updated the Markdown design because the tables were coming out as weird piles of spaghetti instead. Added a helper function to simplify some of the repeated operations. Added new package to DESCRIPTION file. Plots have issues with labels clipping out of view, TBA. --- DESCRIPTION | 3 +- R/amRdataPlots.R | 230 ++++++++++++++++++++++++----------------------- 2 files changed, 122 insertions(+), 111 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1fcdb5c..afc7d4a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -42,7 +42,8 @@ Imports: reshape2, stringr, tibble, - tidyr + tidyr, + treemapify biocViews: GenomeAssembly, Annotation, Sequencing VignetteBuilder: knitr Suggests: diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 2d9f3ee..a87306e 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -1,70 +1,96 @@ - #' Generate a summary report for AMR metadata #' #' @param metadata_parquet Character string. Path to a Parquet file containing #' standardized AMR metadata. +#' @param out_path Character string. Directory where the Markdown report is written. #' -#' @return Prints a structured, human‑readable summary report. +#' @return Writes a structured, human‑readable summary report to +#' "/amr_metadata_summary.md". #' #' @import dplyr #' @import arrow #' @import kableExtra -#' +#' #' @examples #' generateSummary(metadata_parquet = "results/metadata.parquet", -#' out_path = "results/") -#' +#' out_path = "results/") +#' #' @export -generateSummary <- function(metadata_parquet, - out_path) { +generateSummary <- function(metadata_parquet, out_path) { + + # Little helper to apply distinct + non-empty + sorted vector + clean_distinct <- function(df, col) { + df |> + dplyr::distinct({{col}}) |> + dplyr::filter(!is.na({{col}}), {{col}} != "") |> + dplyr::arrange({{col}}) |> + dplyr::pull({{col}}) + } + + # Format for Markdown + md_tbl <- function(df) { + knitr::kable(df, format = "pipe") + } - # Normalize path and load metadata table + # Create a file + write_new <- function(path, lines) { + con <- file(path, open = "w", encoding = "UTF-8") + on.exit(close(con), add = TRUE) + writeLines(lines, con = con, sep = "\n", useBytes = TRUE) + } + + # Add lines to file + append_lines <- function(path, lines) { + con <- file(path, open = "a", encoding = "UTF-8") + on.exit(close(con), add = TRUE) + writeLines(lines, con = con, sep = "\n", useBytes = TRUE) + } + + # Setting paths + if (!dir.exists(out_path)) dir.create(out_path, recursive = TRUE, showWarnings = FALSE) metadata_parquet <- normalizePath(metadata_parquet) metadata <- arrow::read_parquet(metadata_parquet) - md_path <- file.path(out_path, paste0("amr_metadata_summary.md")) - # Error if metadata table is empty + md_path <- file.path(out_path, "amr_metadata_summary.md") + + # Validation (got any data?) if (nrow(metadata) == 0) { stop("The output table is empty. Please check your query or input data.") } - # Total and unique genomes - TotalEntryCount <- metadata |> dplyr::count() - CleanEntryCount <- metadata |> dplyr::distinct(genome_drug.genome_id) |> dplyr::count() + # Core summaries + TotalEntryCount <- metadata |> dplyr::count() + CleanEntryCount <- metadata |> + dplyr::distinct(genome_drug.genome_id) |> + dplyr::count() - # Antibiotics and classes - Antibiotics <- metadata |> dplyr::distinct(genome_drug.antibiotic) |> dplyr::pull() |> sort() - AntibioticClasses <- metadata |> dplyr::distinct(drug_class) |> dplyr::pull() |> sort() + Antibiotics <- clean_distinct(metadata, genome_drug.antibiotic) + AntibioticClasses <- clean_distinct(metadata, drug_class) - # Lab methods LabMethods <- metadata |> dplyr::group_by(genome_drug.laboratory_typing_method) |> - dplyr::count() + dplyr::count() |> + dplyr::ungroup() - # PubMed IDs - PubMed_ids <- metadata |> - dplyr::distinct(genome_drug.pmid) |> - dplyr::filter(!is.na(genome_drug.pmid), genome_drug.pmid != "") |> - dplyr::pull() + PubMed_ids <- clean_distinct(metadata, genome_drug.pmid) - # Phenotype counts PhenotypeCount <- metadata |> dplyr::group_by(genome_drug.resistant_phenotype) |> - dplyr::count() + dplyr::count() |> + dplyr::ungroup() - # Phenotype × Drug PhenotypebyDrugCount <- metadata |> dplyr::group_by(genome_drug.resistant_phenotype, genome_drug.antibiotic) |> - dplyr::count() + dplyr::count() |> + dplyr::ungroup() - # Resistance proportion per drug ResPropbyDrug <- metadata |> dplyr::group_by(genome_drug.antibiotic) |> dplyr::count(genome_drug.resistant_phenotype) |> dplyr::mutate(prop = n / sum(n)) |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> - dplyr::transmute(res_prop = round(prop, 3)) + dplyr::transmute(genome_drug.antibiotic, res_prop = round(prop, 3)) |> + dplyr::ungroup() - # Phenotype × Antibiotic Class (with consistency filtering) PhenotypebyDrugClassCount <- metadata |> dplyr::group_by(genome_drug.genome_id, drug_class) |> dplyr::filter(!(any(genome_drug.resistant_phenotype == "Resistant") & @@ -74,111 +100,95 @@ generateSummary <- function(metadata_parquet, dplyr::slice_head(n = 1) |> dplyr::ungroup() |> dplyr::group_by(genome_drug.resistant_phenotype, drug_class) |> - dplyr::count() + dplyr::count() |> + dplyr::ungroup() - # Resistance proportion × drug class ResPropbyDrugClass <- metadata |> dplyr::group_by(drug_class) |> dplyr::count(genome_drug.resistant_phenotype) |> dplyr::mutate(prop = n / sum(n)) |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> - dplyr::transmute(res_prop = round(prop, 3)) + dplyr::transmute(drug_class, res_prop = round(prop, 3)) |> + dplyr::ungroup() - # Year summaries Year <- metadata |> dplyr::distinct(genome.collection_year) |> dplyr::filter(!is.na(genome.collection_year)) |> - dplyr::pull() |> sort() + dplyr::pull() |> + sort() YearCount <- metadata |> dplyr::group_by(genome.collection_year) |> dplyr::filter(!is.na(genome.collection_year)) |> - dplyr::count() - - # Country summaries - Country <- metadata |> - dplyr::distinct(genome.isolation_country) |> - dplyr::filter(!is.na(genome.isolation_country), genome.isolation_country != "") |> - dplyr::pull() |> sort() + dplyr::count() |> + dplyr::ungroup() + Country <- clean_distinct(metadata, genome.isolation_country) CountryCount <- metadata |> dplyr::group_by(genome.isolation_country) |> dplyr::filter(!is.na(genome.isolation_country), genome.isolation_country != "") |> - dplyr::count() - - # Isolation sources - Source <- metadata |> - dplyr::distinct(genome.isolation_source) |> - dplyr::filter(!is.na(genome.isolation_source), genome.isolation_source != "") |> - dplyr::pull() |> sort() + dplyr::count() |> + dplyr::ungroup() + Source <- clean_distinct(metadata, genome.isolation_source) SourceCount <- metadata |> dplyr::group_by(genome.isolation_source) |> dplyr::filter(!is.na(genome.isolation_source), genome.isolation_source != "") |> - dplyr::count() - - # Host names - Host <- metadata |> - dplyr::distinct(genome.host_common_name) |> - dplyr::filter(!is.na(genome.host_common_name), genome.host_common_name != "") |> - dplyr::pull() |> sort() + dplyr::count() |> + dplyr::ungroup() + + Host <- clean_distinct(metadata, genome.host_common_name) + + # Header + write_new(md_path, "# AMR summary report\n") + + # Basic stats + append_lines( + md_path, + c( + sprintf("- **Entries**: %s", TotalEntryCount[[1]]), + sprintf("- **Unique genome IDs**: %s", CleanEntryCount[[1]]), + "", + sprintf( + "- **Publications** (%d): %s", + length(PubMed_ids), + if (length(PubMed_ids)) paste(PubMed_ids, collapse = ", ") else "None" + ), + "" + ) + ) - # helper to write markdown tables - md_tbl <- function(df) { - knitr::kable(df, format = "pipe") - } + # Lists + append_lines( + md_path, + c( + sprintf("## Antibiotics (%d)", length(Antibiotics)), + "", + paste(Antibiotics, collapse = ", "), + "", + sprintf("## Antibiotic classes (%d)", length(AntibioticClasses)), + "", + paste(AntibioticClasses, collapse = ", "), + "" + ) + ) - # --------- PRINT SUMMARY --------- - # ---- Write Markdown (clean tables) ---- - cat("# AMR Summary Report\n\n", file = md_path) - cat(sprintf("- **Entries**: %s\n- **Unique genome IDs**: %s\n\n", - TotalEntryCount[[1]], CleanEntryCount[[1]]), - file = md_path, append = TRUE) - cat(sprintf("- **Publications** (%d): %s\n\n", - length(PubMed_ids), - if (length(PubMed_ids)) paste(PubMed_ids, collapse = ", ") - else "None"), - file = md_path, append = TRUE) - cat(sprintf("## Antibiotics (%d)\n\n%s\n\n", - length(Antibiotics), paste(Antibiotics, collapse = ", ")), - file = md_path, append = TRUE) - cat(sprintf("## Antibiotic Classes (%d)\n\n%s\n\n", - length(AntibioticClasses), - paste(AntibioticClasses, collapse = ", ")), - file = md_path, append = TRUE) - cat("## Phenotype Counts\n\n", - md_tbl(PhenotypeCount), "\n\n", - file = md_path, append = TRUE) - cat("## Phenotype × Antibiotic\n\n", - md_tbl(PhenotypebyDrugCount), "\n\n", - file = md_path, append = TRUE) - cat("## Resistant Proportion per Antibiotic\n\n", - md_tbl(ResPropbyDrug), "\n\n", - file = md_path, append = TRUE) - cat("## Phenotype × Antibiotic Class\n\n", - md_tbl(PhenotypebyDrugClassCount), "\n\n", - file = md_path, append = TRUE) - cat("## Resistant Proportion per Antibiotic Class\n\n", - md_tbl(ResPropbyDrugClass), "\n\n", - file = md_path, append = TRUE) - cat("## Laboratory Methods\n\n", - md_tbl(LabMethods), "\n\n", - file = md_path, append = TRUE) - cat("## Year Counts\n\n", - md_tbl(YearCount), "\n\n", - file = md_path, append = TRUE) - cat("## Country Counts\n\n", - md_tbl(CountryCount), "\n\n", - file = md_path, append = TRUE) - cat("## Isolation Sources\n\n", - md_tbl(SourceCount), "\n\n", - file = md_path, append = TRUE) - if (length(Host)) { - cat("## Hosts\n\n", - paste("- ", Host, collapse = "\n"), "\n", - file = md_path, append = TRUE) - } + # Tables! + append_lines(md_path, c("## Phenotype counts", "", md_tbl(PhenotypeCount), "", "")) + append_lines(md_path, c("## Phenotype x antibiotic", "", md_tbl(PhenotypebyDrugCount), "", "")) + append_lines(md_path, c("## Resistant proportion per antibiotic", "", md_tbl(ResPropbyDrug), "", "")) + append_lines(md_path, c("## Phenotype x antibiotic class", "", md_tbl(PhenotypebyDrugClassCount), "", "")) + append_lines(md_path, c("## Resistant proportion per antibiotic class", "", md_tbl(ResPropbyDrugClass), "", "")) + append_lines(md_path, c("## Laboratory methods", "", md_tbl(LabMethods), "", "")) + append_lines(md_path, c("## Year counts", "", md_tbl(YearCount), "", "")) + append_lines(md_path, c("## Country counts", "", md_tbl(CountryCount), "", "")) + append_lines(md_path, c("## Isolation sources", "", md_tbl(SourceCount), "", "")) + + # Hosts as a simple list + if (length(Host)) { + append_lines(md_path, c("## Hosts", "", paste0("- ", Host), "", "")) } +} #' Write all summary plots to file(s) From bf443408984ce2efe7d75ed4c1ba45ad30c64839 Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Tue, 20 Jan 2026 20:56:35 -0700 Subject: [PATCH 03/17] Replace 'antibiotic' with 'drug_abbr' in plots --- R/amRdataPlots.R | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index a87306e..a508530 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -212,7 +212,7 @@ generatePlots <- function(metadata_parquet, dplyr::filter(!is.na(genome.collection_year)) |> dplyr::select( genome_drug.genome_id, - genome_drug.antibiotic, + drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, genome.collection_year @@ -220,7 +220,7 @@ generatePlots <- function(metadata_parquet, summary_year <- df_year |> dplyr::group_by( - genome_drug.antibiotic, + drug_abbr, genome_drug.resistant_phenotype, genome.collection_year ) |> @@ -234,7 +234,7 @@ generatePlots <- function(metadata_parquet, ) + ggplot2::geom_line() + ggplot2::geom_point() + - ggplot2::facet_wrap(~ genome_drug.antibiotic, scales = "free_y") + + ggplot2::facet_wrap(~ drug_abbr, scales = "free_y") + ggplot2::labs( title = "Resistant phenotypes across antibiotics and time", x = "Year", y = "Number of isolates", @@ -252,9 +252,9 @@ generatePlots <- function(metadata_parquet, # 1) Levels for antibiotics (from the same subset used in p2) abx_levels <- summary_year |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> - dplyr::distinct(genome_drug.antibiotic) |> - dplyr::arrange(genome_drug.antibiotic) |> - dplyr::pull(genome_drug.antibiotic) + dplyr::distinct(drug_abbr) |> + dplyr::arrange(drug_abbr) |> + dplyr::pull(drug_abbr) # 2) Base Okabe–Ito (CVD-friendly) and pastelizer okabe_ito_base <- c( @@ -293,7 +293,7 @@ generatePlots <- function(metadata_parquet, summary_year |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::mutate( - antibiotic_fac = factor(genome_drug.antibiotic, levels = abx_levels) + antibiotic_fac = factor(drug_abbr, levels = abx_levels) ), ggplot2::aes(x = genome.collection_year, y = count, colour = antibiotic_fac) @@ -318,7 +318,7 @@ generatePlots <- function(metadata_parquet, dplyr::filter(genome.isolation_country != "") |> dplyr::select( genome_drug.genome_id, - genome_drug.antibiotic, + drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, genome.collection_year @@ -354,7 +354,7 @@ generatePlots <- function(metadata_parquet, # 4) Phenotype proportion per antibiotic (stacked, normalized) p4 <- ggplot2::ggplot( metadata, - ggplot2::aes(x = genome_drug.antibiotic, + ggplot2::aes(x = drug_abbr, fill = genome_drug.resistant_phenotype) ) + ggplot2::geom_bar(position = "fill") + From 99f8066e6a75090d97c46aa9a2651c56608d7eca Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:52:58 -0600 Subject: [PATCH 04/17] Refactor genome_id references in amRdataPlots.R --- R/amRdataPlots.R | 53 ++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index a508530..9f4ba7a 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -60,7 +60,7 @@ generateSummary <- function(metadata_parquet, out_path) { # Core summaries TotalEntryCount <- metadata |> dplyr::count() CleanEntryCount <- metadata |> - dplyr::distinct(genome_drug.genome_id) |> + dplyr::distinct(genome.genome_id) |> dplyr::count() Antibiotics <- clean_distinct(metadata, genome_drug.antibiotic) @@ -92,11 +92,11 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::ungroup() PhenotypebyDrugClassCount <- metadata |> - dplyr::group_by(genome_drug.genome_id, drug_class) |> + dplyr::group_by(genome.genome_id, drug_class) |> dplyr::filter(!(any(genome_drug.resistant_phenotype == "Resistant") & genome_drug.resistant_phenotype == "Susceptible")) |> dplyr::ungroup() |> - dplyr::group_by(genome_drug.genome_id, drug_class) |> + dplyr::group_by(genome.genome_id, drug_class) |> dplyr::slice_head(n = 1) |> dplyr::ungroup() |> dplyr::group_by(genome_drug.resistant_phenotype, drug_class) |> @@ -211,7 +211,7 @@ generatePlots <- function(metadata_parquet, df_year <- metadata |> dplyr::filter(!is.na(genome.collection_year)) |> dplyr::select( - genome_drug.genome_id, + genome.genome_id, drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, @@ -242,10 +242,14 @@ generatePlots <- function(metadata_parquet, ) + ggplot2::scale_color_brewer(palette = "Pastel1") + ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + ggplot2::theme(text = ggplot2::element_text(colour = "black"), legend.position = "bottom", - axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) + axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, colour = "black"), + axis.text.y = ggplot2::element_text(colour = "black"), + axis.title = ggplot2::element_text(colour = "black"), + panel.grid.minor = element_blank()) + p1 # 2) Resistance only over time @@ -317,7 +321,7 @@ generatePlots <- function(metadata_parquet, df_country <- metadata |> dplyr::filter(genome.isolation_country != "") |> dplyr::select( - genome_drug.genome_id, + genome.genome_id, drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, @@ -348,8 +352,8 @@ generatePlots <- function(metadata_parquet, size = "Count", color = "Phenotype" ) + ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), - legend.position = "bottom") + ggplot2::theme(text = ggplot2::element_text(colour = "black"), + legend.position = "right") # 4) Phenotype proportion per antibiotic (stacked, normalized) p4 <- ggplot2::ggplot( @@ -364,7 +368,7 @@ generatePlots <- function(metadata_parquet, x = "Antibiotic", y = "Proportion", fill = "Phenotype" ) + ggplot2::scale_fill_brewer(palette = "Pastel1") + # <- keep pastel ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), + ggplot2::theme(text = ggplot2::element_text(colour = "black"), legend.position = "bottom", axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) @@ -372,25 +376,32 @@ generatePlots <- function(metadata_parquet, summary_isolation_source <- metadata |> dplyr::filter(genome.isolation_source != "") |> dplyr::group_by(genome.isolation_source, genome_drug.resistant_phenotype) |> - dplyr::summarise(count = dplyr::n(), .groups = "drop") + dplyr::summarise(count = dplyr::n(), .groups = "drop") |> + dplyr::filter(genome_drug.resistant_phenotype == "Resistant") p5 <- ggplot2::ggplot( summary_isolation_source, ggplot2::aes(area = count, fill = genome.isolation_source) ) + treemapify::geom_treemap() + + # treemapify::geom_treemap_text( + # ggplot2::aes(label = genome_drug.resistant_phenotype), + # color = "grey15", grow = FALSE + # ) + treemapify::geom_treemap_text( - ggplot2::aes(label = genome_drug.resistant_phenotype), + ggplot2::aes(label = genome.isolation_source), color = "grey15", grow = FALSE ) + ggplot2::labs( - title = "Distribution of AMR isolates by isolation source", + title = "Distribution of Resistant isolates by isolation source", fill = "Isolation source" - ) + ggplot2::scale_fill_brewer(palette = "Pastel2") + + ) + + ggplot2::scale_fill_manual( + values = colorRampPalette(RColorBrewer::brewer.pal(8, "Pastel2"))(n_distinct(summary_isolation_source$genome.isolation_source)) + ) + ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), - legend.position = "bottom", - plot.title = ggplot2::element_text(face = "bold")) + ggplot2::theme(text = ggplot2::element_text(colour = "black"), + legend.position = "none") # 6) Histogram of resistant classes per genome p6 <- ggplot2::ggplot(metadata, ggplot2::aes(num_resistant_classes)) + @@ -400,8 +411,11 @@ generatePlots <- function(metadata_parquet, x = "# Resistant Classes", y = "Count" ) + ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "#2D2D2D"), - legend.position = "bottom") + ggplot2::theme(text = ggplot2::element_text(colour = "black"), + legend.position = "bottom", + axis.text = ggplot2::element_text(colour = "black"), + axis.title = ggplot2::element_text(colour = "black"), + panel.grid.minor = element_blank()) plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) @@ -419,4 +433,3 @@ generatePlots <- function(metadata_parquet, invisible(paths) } - From 630e0c84a9f605395dfc178329c7b76042a1e1e1 Mon Sep 17 00:00:00 2001 From: AbhirupaGhosh Date: Thu, 4 Jun 2026 19:54:54 +0000 Subject: [PATCH 05/17] Style code (GHA) --- R/amRdataPlots.R | 225 +++++++++++--------- R/data_curation.R | 419 ++++++++++++++++++++++--------------- R/data_processing.R | 490 ++++++++++++++++++++++++-------------------- 3 files changed, 636 insertions(+), 498 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 9f4ba7a..44ae8a7 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -12,77 +12,78 @@ #' @import kableExtra #' #' @examples -#' generateSummary(metadata_parquet = "results/metadata.parquet", -#' out_path = "results/") +#' generateSummary( +#' metadata_parquet = "results/metadata.parquet", +#' out_path = "results/" +#' ) #' #' @export generateSummary <- function(metadata_parquet, out_path) { - # Little helper to apply distinct + non-empty + sorted vector clean_distinct <- function(df, col) { df |> - dplyr::distinct({{col}}) |> - dplyr::filter(!is.na({{col}}), {{col}} != "") |> - dplyr::arrange({{col}}) |> - dplyr::pull({{col}}) + dplyr::distinct({{ col }}) |> + dplyr::filter(!is.na({{ col }}), {{ col }} != "") |> + dplyr::arrange({{ col }}) |> + dplyr::pull({{ col }}) } - + # Format for Markdown md_tbl <- function(df) { knitr::kable(df, format = "pipe") } - + # Create a file write_new <- function(path, lines) { con <- file(path, open = "w", encoding = "UTF-8") on.exit(close(con), add = TRUE) writeLines(lines, con = con, sep = "\n", useBytes = TRUE) } - + # Add lines to file append_lines <- function(path, lines) { con <- file(path, open = "a", encoding = "UTF-8") on.exit(close(con), add = TRUE) writeLines(lines, con = con, sep = "\n", useBytes = TRUE) } - + # Setting paths if (!dir.exists(out_path)) dir.create(out_path, recursive = TRUE, showWarnings = FALSE) metadata_parquet <- normalizePath(metadata_parquet) metadata <- arrow::read_parquet(metadata_parquet) - md_path <- file.path(out_path, "amr_metadata_summary.md") - + md_path <- file.path(out_path, "amr_metadata_summary.md") + # Validation (got any data?) if (nrow(metadata) == 0) { stop("The output table is empty. Please check your query or input data.") } - + # Core summaries TotalEntryCount <- metadata |> dplyr::count() CleanEntryCount <- metadata |> dplyr::distinct(genome.genome_id) |> dplyr::count() - - Antibiotics <- clean_distinct(metadata, genome_drug.antibiotic) + + Antibiotics <- clean_distinct(metadata, genome_drug.antibiotic) AntibioticClasses <- clean_distinct(metadata, drug_class) - + LabMethods <- metadata |> dplyr::group_by(genome_drug.laboratory_typing_method) |> dplyr::count() |> dplyr::ungroup() - + PubMed_ids <- clean_distinct(metadata, genome_drug.pmid) - + PhenotypeCount <- metadata |> dplyr::group_by(genome_drug.resistant_phenotype) |> dplyr::count() |> dplyr::ungroup() - + PhenotypebyDrugCount <- metadata |> dplyr::group_by(genome_drug.resistant_phenotype, genome_drug.antibiotic) |> dplyr::count() |> dplyr::ungroup() - + ResPropbyDrug <- metadata |> dplyr::group_by(genome_drug.antibiotic) |> dplyr::count(genome_drug.resistant_phenotype) |> @@ -90,11 +91,11 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::transmute(genome_drug.antibiotic, res_prop = round(prop, 3)) |> dplyr::ungroup() - + PhenotypebyDrugClassCount <- metadata |> dplyr::group_by(genome.genome_id, drug_class) |> dplyr::filter(!(any(genome_drug.resistant_phenotype == "Resistant") & - genome_drug.resistant_phenotype == "Susceptible")) |> + genome_drug.resistant_phenotype == "Susceptible")) |> dplyr::ungroup() |> dplyr::group_by(genome.genome_id, drug_class) |> dplyr::slice_head(n = 1) |> @@ -102,7 +103,7 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::group_by(genome_drug.resistant_phenotype, drug_class) |> dplyr::count() |> dplyr::ungroup() - + ResPropbyDrugClass <- metadata |> dplyr::group_by(drug_class) |> dplyr::count(genome_drug.resistant_phenotype) |> @@ -110,38 +111,38 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::transmute(drug_class, res_prop = round(prop, 3)) |> dplyr::ungroup() - + Year <- metadata |> dplyr::distinct(genome.collection_year) |> dplyr::filter(!is.na(genome.collection_year)) |> dplyr::pull() |> sort() - + YearCount <- metadata |> dplyr::group_by(genome.collection_year) |> dplyr::filter(!is.na(genome.collection_year)) |> dplyr::count() |> dplyr::ungroup() - + Country <- clean_distinct(metadata, genome.isolation_country) CountryCount <- metadata |> dplyr::group_by(genome.isolation_country) |> dplyr::filter(!is.na(genome.isolation_country), genome.isolation_country != "") |> dplyr::count() |> dplyr::ungroup() - + Source <- clean_distinct(metadata, genome.isolation_source) SourceCount <- metadata |> dplyr::group_by(genome.isolation_source) |> dplyr::filter(!is.na(genome.isolation_source), genome.isolation_source != "") |> dplyr::count() |> dplyr::ungroup() - + Host <- clean_distinct(metadata, genome.host_common_name) - + # Header write_new(md_path, "# AMR summary report\n") - + # Basic stats append_lines( md_path, @@ -157,7 +158,7 @@ generateSummary <- function(metadata_parquet, out_path) { "" ) ) - + # Lists append_lines( md_path, @@ -172,7 +173,7 @@ generateSummary <- function(metadata_parquet, out_path) { "" ) ) - + # Tables! append_lines(md_path, c("## Phenotype counts", "", md_tbl(PhenotypeCount), "", "")) append_lines(md_path, c("## Phenotype x antibiotic", "", md_tbl(PhenotypebyDrugCount), "", "")) @@ -183,7 +184,7 @@ generateSummary <- function(metadata_parquet, out_path) { append_lines(md_path, c("## Year counts", "", md_tbl(YearCount), "", "")) append_lines(md_path, c("## Country counts", "", md_tbl(CountryCount), "", "")) append_lines(md_path, c("## Isolation sources", "", md_tbl(SourceCount), "", "")) - + # Hosts as a simple list if (length(Host)) { append_lines(md_path, c("## Hosts", "", paste0("- ", Host), "", "")) @@ -195,17 +196,18 @@ generateSummary <- function(metadata_parquet, out_path) { #' #' @param metadata_parquet Character. Path to the Parquet metadata file. #' @param out_path Character. Output directory for plot files. -#' +#' #' @return Invisibly returns a vector of pdf file paths written. #' @export generatePlots <- function(metadata_parquet, out_path) { - device <- "pdf" - if(!dir.exists(out_path)) {dir.create(out_path, showWarnings = FALSE, recursive = TRUE)} - + if (!dir.exists(out_path)) { + dir.create(out_path, showWarnings = FALSE, recursive = TRUE) + } + metadata <- arrow::read_parquet(normalizePath(metadata_parquet)) - + # --------- Build plots (same visuals as your generatePlots) ---------- # 1) Phenotypes across antibiotics and time df_year <- metadata |> @@ -217,7 +219,7 @@ generatePlots <- function(metadata_parquet, genome.isolation_country, genome.collection_year ) - + summary_year <- df_year |> dplyr::group_by( drug_abbr, @@ -225,41 +227,45 @@ generatePlots <- function(metadata_parquet, genome.collection_year ) |> dplyr::summarise(count = dplyr::n(), .groups = "drop") - + p1 <- ggplot2::ggplot( summary_year, - ggplot2::aes(x = genome.collection_year, - y = count, - colour = genome_drug.resistant_phenotype) + ggplot2::aes( + x = genome.collection_year, + y = count, + colour = genome_drug.resistant_phenotype + ) ) + ggplot2::geom_line() + ggplot2::geom_point() + - ggplot2::facet_wrap(~ drug_abbr, scales = "free_y") + + ggplot2::facet_wrap(~drug_abbr, scales = "free_y") + ggplot2::labs( title = "Resistant phenotypes across antibiotics and time", x = "Year", y = "Number of isolates", colour = "Phenotype" ) + ggplot2::scale_color_brewer(palette = "Pastel1") + - ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "black"), - legend.position = "bottom", - axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, colour = "black"), - axis.text.y = ggplot2::element_text(colour = "black"), - axis.title = ggplot2::element_text(colour = "black"), - panel.grid.minor = element_blank()) - + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme( + text = ggplot2::element_text(colour = "black"), + legend.position = "bottom", + axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, colour = "black"), + axis.text.y = ggplot2::element_text(colour = "black"), + axis.title = ggplot2::element_text(colour = "black"), + panel.grid.minor = element_blank() + ) + p1 # 2) Resistance only over time - - + + # 1) Levels for antibiotics (from the same subset used in p2) abx_levels <- summary_year |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::distinct(drug_abbr) |> dplyr::arrange(drug_abbr) |> dplyr::pull(drug_abbr) - + # 2) Base Okabe–Ito (CVD-friendly) and pastelizer okabe_ito_base <- c( "#000000", # black @@ -269,9 +275,9 @@ generatePlots <- function(metadata_parquet, "#F0E442", # yellow "#0072B2", # blue "#D55E00", # vermillion - "#CC79A7" # reddish purple + "#CC79A7" # reddish purple ) - + okabe_ito_pastel <- function(n, lighten = 0.15) { # Interpolate if more than 8 needed cols <- if (n <= length(okabe_ito_base)) { @@ -287,11 +293,11 @@ generatePlots <- function(metadata_parquet, } vapply(cols, blend_with_white, character(1), a = lighten) } - + # 3) Build a NAMED palette aligned to factor levels pal_vals <- okabe_ito_pastel(length(abx_levels), lighten = 0.15) pal_named <- stats::setNames(pal_vals, abx_levels) - + # 4) Plot with factor levels + named palette (no warnings, distinct colors) p2 <- ggplot2::ggplot( summary_year |> @@ -299,8 +305,10 @@ generatePlots <- function(metadata_parquet, dplyr::mutate( antibiotic_fac = factor(drug_abbr, levels = abx_levels) ), - ggplot2::aes(x = genome.collection_year, y = count, - colour = antibiotic_fac) + ggplot2::aes( + x = genome.collection_year, y = count, + colour = antibiotic_fac + ) ) + ggplot2::geom_line() + ggplot2::geom_point() + @@ -310,13 +318,13 @@ generatePlots <- function(metadata_parquet, y = "Number of resistant isolates", colour = "Antibiotic" ) + - ggplot2::scale_color_manual(values = pal_named, drop = TRUE) + # <- named palette + ggplot2::scale_color_manual(values = pal_named, drop = TRUE) + # <- named palette ggplot2::theme_minimal(base_size = 12) + ggplot2::theme( text = ggplot2::element_text(colour = "#2D2D2D"), legend.position = "bottom" ) - + # 3) Time × geography × phenotype df_country <- metadata |> dplyr::filter(genome.isolation_country != "") |> @@ -327,7 +335,7 @@ generatePlots <- function(metadata_parquet, genome.isolation_country, genome.collection_year ) - + summary_country_year <- df_country |> dplyr::group_by( genome.collection_year, @@ -335,13 +343,15 @@ generatePlots <- function(metadata_parquet, genome.isolation_country ) |> dplyr::summarise(count = dplyr::n(), .groups = "drop") - + p3 <- ggplot2::ggplot( summary_country_year, - ggplot2::aes(x = genome.collection_year, - y = genome.isolation_country, - size = count, - color = genome_drug.resistant_phenotype) + ggplot2::aes( + x = genome.collection_year, + y = genome.isolation_country, + size = count, + color = genome_drug.resistant_phenotype + ) ) + ggplot2::geom_point(alpha = 0.75) + ggplot2::scale_size(range = c(3, 15)) + @@ -350,35 +360,42 @@ generatePlots <- function(metadata_parquet, title = "AMR isolates across time and geography", x = "Year", y = "Country", size = "Count", color = "Phenotype" - ) + - ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "black"), - legend.position = "right") - + ) + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme( + text = ggplot2::element_text(colour = "black"), + legend.position = "right" + ) + # 4) Phenotype proportion per antibiotic (stacked, normalized) p4 <- ggplot2::ggplot( metadata, - ggplot2::aes(x = drug_abbr, - fill = genome_drug.resistant_phenotype) + ggplot2::aes( + x = drug_abbr, + fill = genome_drug.resistant_phenotype + ) ) + ggplot2::geom_bar(position = "fill") + ggplot2::coord_flip() + ggplot2::labs( title = "Phenotype proportion per antibiotic", x = "Antibiotic", y = "Proportion", fill = "Phenotype" - ) + ggplot2::scale_fill_brewer(palette = "Pastel1") + # <- keep pastel - ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "black"), - legend.position = "bottom", - axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) - + ) + + ggplot2::scale_fill_brewer(palette = "Pastel1") + # <- keep pastel + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme( + text = ggplot2::element_text(colour = "black"), + legend.position = "bottom", + axis.text.x = ggplot2::element_text(angle = 45, hjust = 1) + ) + # 5) Treemap of isolation sources summary_isolation_source <- metadata |> dplyr::filter(genome.isolation_source != "") |> dplyr::group_by(genome.isolation_source, genome_drug.resistant_phenotype) |> dplyr::summarise(count = dplyr::n(), .groups = "drop") |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") - + p5 <- ggplot2::ggplot( summary_isolation_source, ggplot2::aes(area = count, fill = genome.isolation_source) @@ -395,33 +412,37 @@ generatePlots <- function(metadata_parquet, ggplot2::labs( title = "Distribution of Resistant isolates by isolation source", fill = "Isolation source" - ) + + ) + ggplot2::scale_fill_manual( values = colorRampPalette(RColorBrewer::brewer.pal(8, "Pastel2"))(n_distinct(summary_isolation_source$genome.isolation_source)) - ) + - ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "black"), - legend.position = "none") - + ) + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme( + text = ggplot2::element_text(colour = "black"), + legend.position = "none" + ) + # 6) Histogram of resistant classes per genome p6 <- ggplot2::ggplot(metadata, ggplot2::aes(num_resistant_classes)) + ggplot2::geom_histogram(binwidth = 1, fill = "steelblue") + ggplot2::labs( title = "Distribution of resistant classes per genome", x = "# Resistant Classes", y = "Count" - ) + - ggplot2::theme_minimal(base_size = 12) + - ggplot2::theme(text = ggplot2::element_text(colour = "black"), - legend.position = "bottom", - axis.text = ggplot2::element_text(colour = "black"), - axis.title = ggplot2::element_text(colour = "black"), - panel.grid.minor = element_blank()) - + ) + + ggplot2::theme_minimal(base_size = 12) + + ggplot2::theme( + text = ggplot2::element_text(colour = "black"), + legend.position = "bottom", + axis.text = ggplot2::element_text(colour = "black"), + axis.title = ggplot2::element_text(colour = "black"), + panel.grid.minor = element_blank() + ) + plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) - + # --------- Write to device ---------- paths <- character(0) - + pdf_path <- file.path(out_path, paste0("amRdata_exploratory_plots.pdf")) grDevices::pdf(pdf_path, onefile = TRUE) on.exit(grDevices::dev.off(), add = TRUE) @@ -429,7 +450,7 @@ generatePlots <- function(metadata_parquet, print(plots[[nm]]) } paths <- c(paths, pdf_path) - - + + invisible(paths) } diff --git a/R/data_curation.R b/R/data_curation.R index 5ca98bb..1694568 100644 --- a/R/data_curation.R +++ b/R/data_curation.R @@ -45,8 +45,10 @@ } # Parse - df <- utils::read.table(text = raw_data, sep = "\t", header = TRUE, fill = TRUE, - quote = "", check.names = FALSE, comment.char = "") + df <- utils::read.table( + text = raw_data, sep = "\t", header = TRUE, fill = TRUE, + quote = "", check.names = FALSE, comment.char = "" + ) df <- tibble::as_tibble(df) |> dplyr::mutate(dplyr::across(dplyr::everything(), ~ iconv(.x, from = "", to = "UTF-8", sub = ""))) @@ -61,7 +63,7 @@ # Make sure the BV-BRC metadata live where they're supposed to .ensure_bvbrc_cache <- function(base_dir = ".", verbose = TRUE, - cache_rel = file.path("data", "bvbrc", "bvbrcData.duckdb"), + cache_rel = file.path("data", "bvbrc", "bvbrcData.duckdb"), cache_table = "bvbrc_bac_data") { base_dir <- normalizePath(base_dir, mustWork = FALSE) cache_db <- file.path(base_dir, cache_rel) @@ -115,12 +117,12 @@ base_dir <- normalizePath(base_dir, mustWork = FALSE) data_dir <- file.path(base_dir, "data") bvbrc_dir <- file.path(data_dir, "bvbrc") - logs_dir <- file.path(data_dir, "logs") + logs_dir <- file.path(data_dir, "logs") dir.create(bvbrc_dir, recursive = TRUE, showWarnings = FALSE) - dir.create(logs_dir, recursive = TRUE, showWarnings = FALSE) + dir.create(logs_dir, recursive = TRUE, showWarnings = FALSE) - db_path <- file.path(bvbrc_dir, "bvbrcData.duckdb") + db_path <- file.path(bvbrc_dir, "bvbrcData.duckdb") table_name <- "bvbrc_bac_data" meta_table <- "__meta" @@ -128,10 +130,10 @@ DBI::dbExecute( con, - glue::glue('CREATE TABLE IF NOT EXISTS {meta_table} ( + glue::glue("CREATE TABLE IF NOT EXISTS {meta_table} ( table_name TEXT PRIMARY KEY, last_updated TIMESTAMP - )') + )") ) # Tiny update check helper @@ -139,12 +141,14 @@ res <- tryCatch( DBI::dbGetQuery( con, - glue::glue('SELECT last_updated FROM {meta_table} - WHERE table_name = {DBI::dbQuoteString(con, table_name)}') + glue::glue("SELECT last_updated FROM {meta_table} + WHERE table_name = {DBI::dbQuoteString(con, table_name)}") ), error = function(e) NULL ) - if (is.null(res) || nrow(res) == 0L) return(NA) + if (is.null(res) || nrow(res) == 0L) { + return(NA) + } as.POSIXct(res$last_updated[[1]], origin = "1970-01-01", tz = "UTC") } @@ -277,7 +281,9 @@ #' If nothing suitable is found, throw a fit and an error. #' @keywords internal .resolveQueryValue <- function(query_type, query_value, user_bacs) { - if (!is.null(query_value) && nzchar(query_value)) return(query_value) + if (!is.null(query_value) && nzchar(query_value)) { + return(query_value) + } if (missing(user_bacs) || length(user_bacs) == 0) { stop("Provide query_value or user_bacs for the selected query_type.") } @@ -359,7 +365,7 @@ stringr::str_replace_all("\\s+", "_") |> stringr::str_replace_all("[^A-Za-z0-9._-]", "") - db_dir <- file.path(data_dir, bug_dirname) + db_dir <- file.path(data_dir, bug_dirname) dir.create(db_dir, recursive = TRUE, showWarnings = FALSE) db_file <- paste0(.generateDBname(user_bacs), ".duckdb") @@ -397,14 +403,13 @@ overwrite = FALSE, image = "danylmb/bvbrc:5.3", verbose = TRUE) { - - query_type <- match.arg(query_type) + query_type <- match.arg(query_type) query_value <- .resolveQueryValue(query_type, query_value, user_bacs) - + if (isTRUE(verbose)) { message("Querying BV-BRC: ", query_type, " == ", query_value) } - + # Count count_cmd <- paste0( "docker run --rm ", image, @@ -414,10 +419,10 @@ " --in genome_status,WGS,Complete", " --count" ) - count_lines <- tryCatch(system(count_cmd, intern = TRUE), error = function(e) character()) + count_lines <- tryCatch(system(count_cmd, intern = TRUE), error = function(e) character()) count_result <- suppressWarnings(as.integer(if (length(count_lines) >= 2) count_lines[2] else NA_integer_)) if (isTRUE(verbose) && !is.na(count_result)) message("Count returned: ", count_result) - + # Details data_cmd <- paste0( "docker run --rm ", image, @@ -429,7 +434,7 @@ ) data_raw <- tryCatch(system(data_cmd, intern = TRUE), error = function(e) character()) if (length(data_raw) == 0L) stop("BV-BRC returned no data for: ", query_type, " = ", query_value) - + data_result <- tibble::as_tibble( utils::read.table( text = data_raw, sep = "\t", header = TRUE, fill = TRUE, @@ -443,16 +448,16 @@ `genome.species` = as.character(`genome.species`), `genome.strain` = as.character(`genome.strain`) ) - + # Per-bug DB path - paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs, overwrite = overwrite) + paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs, overwrite = overwrite) db_path <- paths$db_path - + con <- DBI::dbConnect(duckdb::duckdb(), dbdir = db_path) DBI::dbWriteTable(con, "bac_data", data_result, overwrite = TRUE) - + if (isTRUE(verbose)) message("Wrote table 'bac_data' to: ", db_path) - + list(count_result = count_result, duckdbConnection = con, table_name = "bac_data") } @@ -474,28 +479,28 @@ overwrite = FALSE, verbose = TRUE) { base_dir <- normalizePath(base_dir, mustWork = FALSE) - + if (isTRUE(verbose)) message("Resolving input taxa.") bac_input_data <- .retrieveCustomQuery(base_dir = base_dir, user_bacs = user_bacs) - + if (is.null(bac_input_data) || nrow(bac_input_data) == 0) { message("No valid input provided or no matches found.") return(NULL) } - + # Resolve per-bug DB path - paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs, overwrite = overwrite) + paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs, overwrite = overwrite) db_path <- paths$db_path - - bac_data <- tibble::tibble() + + bac_data <- tibble::tibble() taxon_ids <- unique(bac_input_data$genome.taxon_id) - + if (isTRUE(verbose)) message("Querying BV-BRC for ", length(taxon_ids), " taxon IDs.") - + for (i in seq_along(taxon_ids)) { tax <- taxon_ids[i] if (isTRUE(verbose)) message("Taxon ", i, "/", length(taxon_ids), ": ", tax) - + res <- .getGenomeIDs( base_dir = base_dir, query_type = "taxon_id", @@ -504,22 +509,22 @@ overwrite = TRUE, verbose = verbose ) - + con <- res$duckdbConnection tbl <- res$table_name each_bac_data <- tibble::as_tibble(DBI::dbReadTable(con, tbl)) bac_data <- dplyr::bind_rows(bac_data, each_bac_data) - + # Close per-iteration connection to avoid open handles piling up try(DBI::dbDisconnect(con, shutdown = TRUE), silent = TRUE) } - + if (nrow(bac_data) > 0) { genome_ids <- bac_data |> dplyr::distinct(`genome.genome_id`) |> dplyr::pull(`genome.genome_id`) genome_ids <- genome_ids[!is.na(genome_ids)] - + if (length(genome_ids) > 0) { if (isTRUE(verbose)) message("Collected ", length(genome_ids), " distinct genome IDs.") return(genome_ids) @@ -578,18 +583,18 @@ verbose = TRUE) { base_dir <- normalizePath(base_dir, mustWork = FALSE) data_dir <- file.path(base_dir, "data") - tmp_dir <- file.path(data_dir, "tmp") + tmp_dir <- file.path(data_dir, "tmp") dir.create(tmp_dir, recursive = TRUE, showWarnings = FALSE) - + if (isTRUE(verbose)) { message("Preparing AMR query input for ", length(batch_genome_IDs), " genomes.") } - + docker_path <- Sys.which("docker") if (!nzchar(docker_path)) { stop("Docker is not available on your PATH but is required.") } - + # Generate genome list with p3-echo echo_args <- c( "run", "--rm", @@ -599,12 +604,12 @@ genome_ids_output <- suppressWarnings( system2("docker", args = echo_args, stdout = TRUE, stderr = TRUE) ) - + # Write a temporary file in data/tmp/ tmp_in <- tempfile(tmpdir = tmp_dir, pattern = "genome_drug_ids_", fileext = ".tsv") writeLines(genome_ids_output, con = tmp_in) tmp_in_mounted <- file.path("/data", "tmp", basename(tmp_in)) - + # Allow abx_filter to be a single string with spaces OR a vector of args abx_args <- if (length(abx_filter) == 1L) { @@ -612,7 +617,7 @@ } else { abx_filter } - + # Query drug data drug_args <- c( "run", "--rm", @@ -622,15 +627,15 @@ abx_args, "--attr", drug_fields ) - + if (isTRUE(verbose)) { message("Running AMR query.") } drug_data <- suppressWarnings(system2("docker", args = drug_args, stdout = TRUE, stderr = TRUE)) - + # Clean up after yourself try(unlink(tmp_in), silent = TRUE) - + return(drug_data) } @@ -655,18 +660,18 @@ verbose = TRUE) { base_dir <- normalizePath(base_dir, mustWork = FALSE) data_dir <- file.path(base_dir, "data") - tmp_dir <- file.path(data_dir, "tmp") + tmp_dir <- file.path(data_dir, "tmp") dir.create(tmp_dir, recursive = TRUE, showWarnings = FALSE) - + if (isTRUE(verbose)) { message("Preparing genome metadata input for ", length(batch_genome_IDs), " genomes.") } - + docker_path <- Sys.which("docker") if (!nzchar(docker_path)) { stop("Docker is not available on your PATH but is required.") } - + # Generate genome list with p3-echo echo_args <- c( "run", "--rm", @@ -676,14 +681,14 @@ genome_ids_output <- suppressWarnings( system2("docker", args = echo_args, stdout = TRUE, stderr = TRUE) ) - + tmp_in <- tempfile(tmpdir = tmp_dir, pattern = "genome_ids_", fileext = ".tsv") writeLines(genome_ids_output, con = tmp_in) tmp_in_mounted <- file.path("/data", "tmp", basename(tmp_in)) - + # Choose attributes (AMR for this pipeline) chosen_fields <- if (identical(filter_type, "AMR")) amr_fields else microtrait_fields - + get_args <- c( "run", "--rm", "-v", paste0(data_dir, ":/data"), @@ -691,15 +696,15 @@ "--input", tmp_in_mounted, "--attr", chosen_fields ) - + if (isTRUE(verbose)) { message("Running genome metadata query.") } genome_data <- suppressWarnings(system2("docker", args = get_args, stdout = TRUE, stderr = TRUE)) - + # Cleaning up try(unlink(tmp_in), silent = TRUE) - + return(genome_data) } @@ -735,8 +740,10 @@ retrieveMetadata <- function(user_bacs, base_dir <- normalizePath(base_dir, mustWork = FALSE) if (isTRUE(verbose)) message("Resolving genome IDs for user inputs.") - genome_ids <- .retrieveQueryIDs(base_dir = base_dir, user_bacs = user_bacs, - overwrite = overwrite, verbose = verbose) + genome_ids <- .retrieveQueryIDs( + base_dir = base_dir, user_bacs = user_bacs, + overwrite = overwrite, verbose = verbose + ) if (length(genome_ids) == 0) { message("No genome IDs available for the specified inputs.") return(NULL) @@ -753,8 +760,11 @@ retrieveMetadata <- function(user_bacs, "pmid,resistant_phenotype,", "source,taxon_id,testing_standard" ) - abx_filter <- if (identical(abx, "All")) "--required antibiotic" - else paste0("--in antibiotic,", paste(abx, collapse = ",")) + abx_filter <- if (identical(abx, "All")) { + "--required antibiotic" + } else { + paste0("--in antibiotic,", paste(abx, collapse = ",")) + } amr_fields <- paste0( "assembly_accession,assembly_method,", "bioproject_accession,biosample_accession,", @@ -808,7 +818,10 @@ retrieveMetadata <- function(user_bacs, ), envir = environment() ) - parallel::clusterEvalQ(cluster, { library(tibble); library(dplyr) }) + parallel::clusterEvalQ(cluster, { + library(tibble) + library(dplyr) + }) if (isTRUE(verbose)) message("Retrieving AMR phenotype data in batches.") batch_drug_data <- parallel::parLapply(cluster, genome_batches, function(batch) { @@ -822,7 +835,10 @@ retrieveMetadata <- function(user_bacs, ) }) combined_drug_data <- unlist(batch_drug_data, use.names = FALSE) - if (length(combined_drug_data) == 0) { message("No drug data returned."); return(NULL) } + if (length(combined_drug_data) == 0) { + message("No drug data returned.") + return(NULL) + } combined_drug_data_tbl <- tibble::as_tibble( utils::read.table( @@ -847,7 +863,10 @@ retrieveMetadata <- function(user_bacs, ) }) combined_genome_data <- unlist(batch_genome_data, use.names = FALSE) - if (length(combined_genome_data) == 0) { message("No genome data returned."); return(NULL) } + if (length(combined_genome_data) == 0) { + message("No genome data returned.") + return(NULL) + } combined_genome_data_tbl <- tibble::as_tibble( utils::read.table( @@ -860,13 +879,14 @@ retrieveMetadata <- function(user_bacs, dplyr::mutate(`genome.genome_id` = as.character(`genome.genome_id`)) # Per-bug DB path - paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs, overwrite = overwrite) + paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs, overwrite = overwrite) db_path <- paths$db_path logs_dir <- file.path(base_dir, "data", "logs") dir.create(logs_dir, recursive = TRUE, showWarnings = FALSE) cat(sprintf("[%s] Writing metadata DuckDB: %s\n", Sys.time(), db_path), - file = file.path(logs_dir, "bvbrc.log"), append = TRUE) + file = file.path(logs_dir, "bvbrc.log"), append = TRUE + ) con <- DBI::dbConnect(duckdb::duckdb(), dbdir = db_path) DBI::dbWriteTable(con, "amr_phenotype", combined_drug_data_tbl, overwrite = TRUE) @@ -892,10 +912,14 @@ retrieveMetadata <- function(user_bacs, # FASTA sanitizer to ensure Panaroo compatibility with BV-BRC CLI downloads .strip_fasta_preamble <- function(fna_path) { - if (!file.exists(fna_path)) return(invisible(FALSE)) + if (!file.exists(fna_path)) { + return(invisible(FALSE)) + } txt <- readLines(fna_path, warn = FALSE) first <- which(grepl("^\\s*>", txt))[1] - if (is.na(first)) return(invisible(FALSE)) + if (is.na(first)) { + return(invisible(FALSE)) + } if (first > 1L) { txt <- txt[first:length(txt)] txt[1] <- sub("^\\ufeff", "", txt[1]) @@ -907,14 +931,20 @@ retrieveMetadata <- function(user_bacs, # GFF sanitizer to ensure Panaroo compatibility with BV-BRC CLI downloads .sanitize_gff <- function(gff_path) { - if (!file.exists(gff_path)) return(invisible(FALSE)) + if (!file.exists(gff_path)) { + return(invisible(FALSE)) + } lines <- readLines(gff_path, warn = FALSE) - if (length(lines) == 0L) return(invisible(FALSE)) + if (length(lines) == 0L) { + return(invisible(FALSE)) + } if (!grepl("^##gff-version\\s*3", lines[1])) { lines <- c("##gff-version 3", lines) } out <- vapply(lines, function(line) { - if (grepl("^#", line)) return(line) + if (grepl("^#", line)) { + return(line) + } parts <- strsplit(line, "[\t ]", perl = TRUE)[[1]] if (length(parts) >= 9) { paste(c(parts[1:8], paste(parts[9:length(parts)], collapse = " ")), collapse = "\t") @@ -943,16 +973,19 @@ retrieveMetadata <- function(user_bacs, verbose = TRUE, fallback_to_bvbrc_cache = TRUE) { base_dir <- normalizePath(base_dir, mustWork = FALSE) - paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs) - db_path <- paths$db_path - + paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs) + db_path <- paths$db_path + con <- DBI::dbConnect(duckdb::duckdb(), dbdir = db_path) - - on.exit({ - # Keep connection open for caller - NULL - }, add = TRUE) - + + on.exit( + { + # Keep connection open for caller + NULL + }, + add = TRUE + ) + # Happy path: metadata present -> apply AMR-aware filters if ("metadata" %in% DBI::dbListTables(con)) { if (isTRUE(verbose)) message("Loading metadata for filtering.") @@ -962,7 +995,7 @@ retrieveMetadata <- function(user_bacs, message("No data available in 'metadata'.") return(NULL) } - + # Normalize evidence labels initial_metadata <- tibble::as_tibble(initial_metadata) |> dplyr::mutate( @@ -973,13 +1006,13 @@ retrieveMetadata <- function(user_bacs, TRUE ~ `genome_drug.evidence` ) ) - + # AMR and quality filtering filtered_metadata <- initial_metadata |> dplyr::filter(`genome_drug.evidence` == "Laboratory Method") |> dplyr::filter(`genome.genome_quality` == "Good") |> dplyr::filter(`genome_drug.resistant_phenotype` %in% c("Resistant", "Susceptible", "Intermediate")) - + DBI::dbWriteTable(con, "filtered", filtered_metadata, overwrite = TRUE) if (isTRUE(verbose)) { message("Post-filter rows: ", nrow(filtered_metadata)) @@ -987,33 +1020,33 @@ retrieveMetadata <- function(user_bacs, } return(list(duckdbConnection = con, table_name = "filtered")) } - + # Attempt BV-BRC cache location if (!isTRUE(fallback_to_bvbrc_cache)) { DBI::dbDisconnect(con, shutdown = TRUE) stop("No 'metadata' table found in ", db_path, ". Run retrieveMetadata() first.") } - + if (isTRUE(verbose)) { message("No 'metadata' in per-selection DB. Falling back to BV-BRC cache at data/bvbrc/.") } - + cache_db <- file.path(base_dir, "data", "bvbrc", "bvbrcData.duckdb") if (!file.exists(cache_db)) { DBI::dbDisconnect(con, shutdown = TRUE) stop("BV-BRC cache not found at: ", cache_db, ". Run .updateBVBRCdata() first.") } - + con_cache <- DBI::dbConnect(duckdb::duckdb(), dbdir = cache_db) on.exit(try(DBI::dbDisconnect(con_cache, shutdown = TRUE), silent = TRUE), add = TRUE) - + if (!"bvbrc_bac_data" %in% DBI::dbListTables(con_cache)) { DBI::dbDisconnect(con, shutdown = TRUE) stop("Table 'bvbrc_bac_data' not found in BV-BRC cache: ", cache_db) } - + bv <- tibble::as_tibble(DBI::dbReadTable(con_cache, "bvbrc_bac_data")) - + # Derive matches from user_bacs (taxon IDs or species) sel <- tibble::tibble(`genome.genome_id` = character()) for (v in user_bacs) { @@ -1029,22 +1062,22 @@ retrieveMetadata <- function(user_bacs, } } sel <- dplyr::distinct(sel) - + if (nrow(sel) == 0L) { DBI::dbDisconnect(con, shutdown = TRUE) stop("No genomes matched user_bacs in BV-BRC cache. (Cache present but no hits.)") } - + # Minimal 'filtered' for downstream steps (downloaders & genomeList) minimal_filtered <- sel # Ensure expected column name used downstream: # retrieveGenomes() reads "filtered" and expects `genome.genome_id` to exist. DBI::dbWriteTable(con, "filtered", minimal_filtered, overwrite = TRUE) - + if (isTRUE(verbose)) { message("Wrote table 'filtered' to: ", db_path) } - + list(duckdbConnection = con, table_name = "filtered") } @@ -1056,9 +1089,12 @@ retrieveMetadata <- function(user_bacs, # Prefer bash .pick_shell <- function(image) { chk <- suppressWarnings(system2("docker", - c("run", "--rm", image, "sh", "-lc", - "command -v bash >/dev/null || echo NOBASH"), - stdout = TRUE, stderr = TRUE)) + c( + "run", "--rm", image, "sh", "-lc", + "command -v bash >/dev/null || echo NOBASH" + ), + stdout = TRUE, stderr = TRUE + )) if (length(chk) && any(grepl("NOBASH", chk))) "sh" else "bash" } @@ -1105,21 +1141,28 @@ retrieveMetadata <- function(user_bacs, .ftp_download_one <- function(genomeID, out_dir, tries = 3L, min_bytes = 100) { files <- c(".fna", ".PATRIC.faa", ".PATRIC.gff") dests <- file.path(out_dir, paste0(genomeID, files)) - if (.is_complete_set(out_dir, genomeID, min_bytes)) return(TRUE) + if (.is_complete_set(out_dir, genomeID, min_bytes)) { + return(TRUE) + } get_one <- function(url, dest) { if (nzchar(Sys.which("wget"))) { res <- suppressWarnings(system2("wget", - c("-q", "-O", shQuote(dest), shQuote(url)), - stdout = TRUE, stderr = TRUE)) - st <- attr(res, "status"); if (is.null(st)) st <- 0L + c("-q", "-O", shQuote(dest), shQuote(url)), + stdout = TRUE, stderr = TRUE + )) + st <- attr(res, "status") + if (is.null(st)) st <- 0L return(st == 0L && file.exists(dest) && file.info(dest)$size > min_bytes) } else if (nzchar(Sys.which("curl"))) { - curl_args <- if (startsWith(url, "ftps://")) - c("--ssl-reqd", "-L", "-o", shQuote(dest), shQuote(url)) else - c("-L", "-o", shQuote(dest), shQuote(url)) + curl_args <- if (startsWith(url, "ftps://")) { + c("--ssl-reqd", "-L", "-o", shQuote(dest), shQuote(url)) + } else { + c("-L", "-o", shQuote(dest), shQuote(url)) + } res <- suppressWarnings(system2("curl", curl_args, stdout = TRUE, stderr = TRUE)) - st <- attr(res, "status"); if (is.null(st)) st <- 0L + st <- attr(res, "status") + if (is.null(st)) st <- 0L return(st == 0L && file.exists(dest) && file.info(dest)$size > min_bytes) } FALSE @@ -1128,13 +1171,27 @@ retrieveMetadata <- function(user_bacs, for (ext_i in seq_along(files)) { dest <- dests[ext_i] if (file.exists(dest) && file.info(dest)$size > min_bytes) next - ftps <- sprintf("ftps://ftp.bv-brc.org/genomes/%s/%s%s", genomeID, genomeID, files[ext_i]) + ftps <- sprintf("ftps://ftp.bv-brc.org/genomes/%s/%s%s", genomeID, genomeID, files[ext_i]) https <- sprintf("https://ftp.bv-brc.org/genomes/%s/%s%s", genomeID, genomeID, files[ext_i]) ok <- FALSE - for (k in 1:tries) { if (get_one(ftps, dest)) { ok <- TRUE; break } } - if (!ok) for (k in 1:2) { if (get_one(https, dest)) { ok <- TRUE; break } } - if (!ok) return(FALSE) + for (k in 1:tries) { + if (get_one(ftps, dest)) { + ok <- TRUE + break + } + } + if (!ok) { + for (k in 1:2) { + if (get_one(https, dest)) { + ok <- TRUE + break + } + } + } + if (!ok) { + return(FALSE) + } } .is_complete_set(out_dir, genomeID, min_bytes) } @@ -1143,7 +1200,7 @@ retrieveMetadata <- function(user_bacs, # p3-dump-genomes to fetch FASTA and .gto files .cli_dump_fastas_gto_chunk <- function(image, out_dir, genome_ids, tag, tries = 3L) { - out_dir <- normalizePath(out_dir, mustWork = FALSE) + out_dir <- normalizePath(out_dir, mustWork = FALSE) dir.create(out_dir, recursive = TRUE, showWarnings = FALSE) ids_file <- file.path(out_dir, paste0("ids_", tag, ".txt")) writeLines(genome_ids, ids_file) @@ -1152,12 +1209,15 @@ retrieveMetadata <- function(user_bacs, mount <- .docker_path(out_dir) # Safety against Windows-specific CRLF lines before `p3-dump-genomes` - sh_cmd <- sprintf('tr -d "\\r" < /out/%s | p3-dump-genomes --outDir /out --fasta --prot --gto -', - basename(ids_file)) + sh_cmd <- sprintf( + 'tr -d "\\r" < /out/%s | p3-dump-genomes --outDir /out --fasta --prot --gto -', + basename(ids_file) + ) - args <- c("run", "--rm", "-v", paste0(mount, ":/out"), image, shell, "-lc", shQuote(sh_cmd)) + args <- c("run", "--rm", "-v", paste0(mount, ":/out"), image, shell, "-lc", shQuote(sh_cmd)) res <- suppressWarnings(system2("docker", args = args, stdout = TRUE, stderr = TRUE)) - st <- attr(res, "status"); if (is.null(st)) st <- 0L + st <- attr(res, "status") + if (is.null(st)) st <- 0L if (st != 0L && tries > 1L) { Sys.sleep(1) @@ -1185,57 +1245,57 @@ retrieveMetadata <- function(user_bacs, # Export GFF from GTO per genomes in each chunk .cli_export_gff_chunk <- function(image, out_dir, genome_ids, tag, tries = 3L) { - out_dir <- normalizePath(out_dir, mustWork = FALSE) + out_dir <- normalizePath(out_dir, mustWork = FALSE) ids_file <- file.path(out_dir, paste0("ids_", tag, ".txt")) writeLines(genome_ids, ids_file) - exporter <- "/usr/bin/rast-export-genome" - shell <- .pick_shell(image) - mount <- .docker_path(out_dir) + exporter <- "/usr/bin/rast-export-genome" + shell <- .pick_shell(image) + mount <- .docker_path(out_dir) stderr_file <- file.path(out_dir, paste0("gff_chunk_", tag, ".stderr.txt")) # GFFs from BV-BRC .gto export are not directly compatible in Panaroo # This block reformats the contig IDs and ensures .gff/.fna pairs work together sh_cmd <- paste0( - 'set -euo pipefail; ', - 'fail_n=0; : > /out/', basename(stderr_file), '; ', + "set -euo pipefail; ", + "fail_n=0; : > /out/", basename(stderr_file), "; ", 'while IFS= read -r b || [ -n "$b" ]; do ', ' b=${b%$\'\\r\'}; [ -n "$b" ] || continue; ', ' gto="/out/${b}.gto"; gff="/out/${b}.PATRIC.gff"; map="/out/${b}.orig2id.tsv"; ', - ' if [ ! -s "$gto" ]; then echo "MISSING_GTO $b" >>/out/', basename(stderr_file), '; continue; fi; ', + ' if [ ! -s "$gto" ]; then echo "MISSING_GTO $b" >>/out/', basename(stderr_file), "; continue; fi; ", # Export GFF ' [ -s "$gff" ] || ', exporter, ' -i "$gto" -o "$gff" gff ', - ' || { echo "EXPORT_FAIL_GFF $b" >>/out/', basename(stderr_file), '; fail_n=$((fail_n+1)); continue; }; ', + ' || { echo "EXPORT_FAIL_GFF $b" >>/out/', basename(stderr_file), "; fail_n=$((fail_n+1)); continue; }; ", # Build mapping original_id -> id, and default to id if original_id missing - ' if command -v jq >/dev/null 2>&1; then ', + " if command -v jq >/dev/null 2>&1; then ", ' jq -r \'.contigs[] | [(.original_id // .id), .id] | @tsv\' "$gto" > "$map"; ', - ' else ', + " else ", ' python3 - "$gto" > "$map" <<\'PY\'\n', - 'import sys, json\n', - 'g = json.load(open(sys.argv[1]))\n', + "import sys, json\n", + "g = json.load(open(sys.argv[1]))\n", 'for c in g.get("contigs", []):\n', ' o = c.get("original_id") or c.get("id")\n', ' i = c.get("id")\n', - ' if o and i:\n', + " if o and i:\n", ' print(f"{o}\\t{i}")\n', - 'PY\n', - ' fi; ', + "PY\n", + " fi; ", # Relabel GFF sequence IDs: original_id -> id - ' awk \'FNR==NR{m[$1]=$2; next} ', - ' /^##sequence-region/ { if ($2 in m) {$2=m[$2]} print; next } ', - ' /^#/ { print; next } ', + " awk 'FNR==NR{m[$1]=$2; next} ", + " /^##sequence-region/ { if ($2 in m) {$2=m[$2]} print; next } ", + " /^#/ { print; next } ", ' { if ($1 in m) $1=m[$1]; print }\' "$map" "$gff" > "${gff}.tmp" && mv "${gff}.tmp" "$gff"; ', - - 'done < /out/', basename(ids_file), '; ', - 'exit 0' + "done < /out/", basename(ids_file), "; ", + "exit 0" ) args <- c("run", "--rm", "-v", paste0(mount, ":/out"), image, shell, "-lc", shQuote(sh_cmd)) - res <- suppressWarnings(system2("docker", args = args, stdout = TRUE, stderr = TRUE)) - st <- attr(res, "status"); if (is.null(st)) st <- 0L + res <- suppressWarnings(system2("docker", args = args, stdout = TRUE, stderr = TRUE)) + st <- attr(res, "status") + if (is.null(st)) st <- 0L if (st != 0L && tries > 1L) { Sys.sleep(1) @@ -1278,14 +1338,15 @@ retrieveGenomes <- function(base_dir = ".", cli_gff_workers = 4L, chunk_size = 50L, verbose = TRUE) { - - method <- match.arg(method) + method <- match.arg(method) base_dir <- normalizePath(base_dir, mustWork = FALSE) # IDs from .filterGenomes() if (isTRUE(verbose)) message("Filtering genomes before download.") f_out <- .filterGenomes(base_dir = base_dir, user_bacs = user_bacs, verbose = verbose) - if (is.null(f_out)) return(character()) + if (is.null(f_out)) { + return(character()) + } con <- f_out$duckdbConnection tbl <- f_out$table_name @@ -1294,12 +1355,12 @@ retrieveGenomes <- function(base_dir = ".", dplyr::pull(`genome.genome_id`) # Set up the paths and such - paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs) - bug_dir <- dirname(paths$db_path) + paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs) + bug_dir <- dirname(paths$db_path) genome_path <- file.path(bug_dir, "genomes") - logs_dir <- file.path(base_dir, "data", "logs") + logs_dir <- file.path(base_dir, "data", "logs") dir.create(genome_path, recursive = TRUE, showWarnings = FALSE) - dir.create(logs_dir, recursive = TRUE, showWarnings = FALSE) + dir.create(logs_dir, recursive = TRUE, showWarnings = FALSE) # Adding support for resuming a download if (isTRUE(skip_existing)) { @@ -1310,10 +1371,12 @@ retrieveGenomes <- function(base_dir = ".", if (length(ids) == 0L) { if (isTRUE(verbose)) message("All genomes already complete.") - all_complete <- .list_complete(genome_path, - tibble::as_tibble(DBI::dbReadTable(con, tbl)) |> - dplyr::distinct(`genome.genome_id`) |> - dplyr::pull(`genome.genome_id`)) + all_complete <- .list_complete( + genome_path, + tibble::as_tibble(DBI::dbReadTable(con, tbl)) |> + dplyr::distinct(`genome.genome_id`) |> + dplyr::pull(`genome.genome_id`) + ) return(all_complete) } @@ -1322,18 +1385,23 @@ retrieveGenomes <- function(base_dir = ".", if (isTRUE(verbose)) message("Trying FTPS download. Workers=", ftp_workers) future::plan(future::multisession, workers = max(1, ftp_workers)) ft_ok <- future.apply::future_lapply(ids, function(gid) .ftp_download_one(gid, genome_path), - future.seed = TRUE) + future.seed = TRUE + ) ok_ids <- ids[unlist(ft_ok)] if (isTRUE(verbose)) message("Complete file sets for ", length(ok_ids), " genomes (FTP).") return(c(ok_ids, .list_complete(genome_path, setdiff(ids, ok_ids)))) } # CLI for FASTA, FAA, and GTO, then GFF from GTO - chunks <- split(ids, ceiling(seq_along(ids)/chunk_size)) + chunks <- split(ids, ceiling(seq_along(ids) / chunk_size)) # Parallel chunk containers - if (isTRUE(verbose)) message("CLI being run in parallel for ", length(chunks), - " data chunks.") + if (isTRUE(verbose)) { + message( + "CLI being run in parallel for ", length(chunks), + " data chunks." + ) + } future::plan(future::multisession, workers = max(1, cli_fasta_workers)) fa_res <- future.apply::future_mapply( FUN = function(vec, tag) .cli_dump_fastas_gto_chunk(image, genome_path, vec, tag), @@ -1343,8 +1411,12 @@ retrieveGenomes <- function(base_dir = ".", if (!all(fa_res) && isTRUE(verbose)) warning(sum(!fa_res), " data chunks failed.") # GFF extraction in parallel containers - if (isTRUE(verbose)) message("GFF extraction being run in parallel for ", - length(chunks), " data chunks.") + if (isTRUE(verbose)) { + message( + "GFF extraction being run in parallel for ", + length(chunks), " data chunks." + ) + } future::plan(future::multisession, workers = max(1, cli_gff_workers)) g_res <- future.apply::future_mapply( FUN = function(vec, tag) .cli_export_gff_chunk(image, genome_path, vec, tag), @@ -1355,8 +1427,12 @@ retrieveGenomes <- function(base_dir = ".", # Success set: .fna + .PATRIC.faa + .PATRIC.gff all present per isolate ok_ids <- ids[vapply(ids, .is_complete_set, logical(1), dir = genome_path)] - if (isTRUE(verbose)) message("Complete file sets downloaded for ", - length(ok_ids), " genomes.") + if (isTRUE(verbose)) { + message( + "Complete file sets downloaded for ", + length(ok_ids), " genomes." + ) + } ok_ids } @@ -1375,15 +1451,14 @@ retrieveGenomes <- function(base_dir = ".", genomeList <- function(base_dir = ".", user_bacs, verbose = TRUE) { - base_dir <- normalizePath(base_dir, mustWork = FALSE) - paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs) - db_path <- paths$db_path - bug_dir <- dirname(db_path) + paths <- .buildDBpath(base_dir = base_dir, user_bacs = user_bacs) + db_path <- paths$db_path + bug_dir <- dirname(db_path) genome_path <- file.path(bug_dir, "genomes") - files_all <- list.files(genome_path, full.names = TRUE) - files_all <- files_all[file.info(files_all)$size > 100] + files_all <- list.files(genome_path, full.names = TRUE) + files_all <- files_all[file.info(files_all)$size > 100] # Separate by type gff_files <- files_all[grepl("\\.PATRIC\\.gff$", files_all)] @@ -1402,14 +1477,18 @@ genomeList <- function(base_dir = ".", faa_path <- file.path(genome_path, paste0(genomeID, ".PATRIC.faa")) data.frame( - genome_id = genomeID, - gff_path = if (file.exists(gff_path) && file.info(gff_path)$size > 100) gff_path else NA, - fna_path = if (file.exists(fna_path) && file.info(fna_path)$size > 100) fna_path else NA, - faa_path = if (file.exists(faa_path) && file.info(faa_path)$size > 100) faa_path else NA, + genome_id = genomeID, + gff_path = if (file.exists(gff_path) && file.info(gff_path)$size > 100) gff_path else NA, + fna_path = if (file.exists(fna_path) && file.info(fna_path)$size > 100) fna_path else NA, + faa_path = if (file.exists(faa_path) && file.info(faa_path)$size > 100) faa_path else NA, panaroo_input = if ( file.exists(gff_path) && file.exists(fna_path) && file.exists(faa_path) && - file.info(gff_path)$size > 100 && file.info(fna_path)$size > 100 && file.info(faa_path)$size > 100 - ) paste(gff_path, fna_path) else NA, + file.info(gff_path)$size > 100 && file.info(fna_path)$size > 100 && file.info(faa_path)$size > 100 + ) { + paste(gff_path, fna_path) + } else { + NA + }, stringsAsFactors = FALSE ) }) @@ -1468,7 +1547,7 @@ prepareGenomes <- function(user_bacs, method = c("ftp", "cli"), overwrite = FALSE, verbose = TRUE) { - method <- match.arg(method) + method <- match.arg(method) base_dir <- normalizePath(base_dir, mustWork = FALSE) # Ensure the BV-BRC metadata cache exists, fetch if missing diff --git a/R/data_processing.R b/R/data_processing.R index 76b4dca..d9ff231 100644 --- a/R/data_processing.R +++ b/R/data_processing.R @@ -13,7 +13,7 @@ # Map host paths under mounted root to container path #' .to_container() #' -#' Used for OS-agnostic mapping of Docker directories and mount paths +#' Used for OS-agnostic mapping of Docker directories and mount paths #' #' @keywords internal #' @examples NULL @@ -26,7 +26,7 @@ # Launch Panaroo to build a pangenome (per batch) #' processPanaroo() -#' +#' #' See Panaroo's documentation for details on how the parameters affect your #' pangenome output: https://gthlab.au/panaroo/#/gettingstarted/params #' @@ -51,12 +51,12 @@ panaroo_threads_per_job) { output_path <- .docker_path(output_path) dir.create(output_path, recursive = TRUE, showWarnings = FALSE) - + # Fail fast if Docker is missing if (!nzchar(Sys.which("docker"))) { stop("Docker is not available on your PATH but is required to run Panaroo.") } - + # Host mount root = bug directory mount_host <- output_path mount_cont <- "/work" @@ -81,7 +81,7 @@ # Convert to container-visible paths genome_filepath_cont <- .to_container(genome_filepath_host, host_root = mount_host, container_root = mount_cont) - output_dir_cont <- .to_container(output_dir_host, host_root = mount_host, container_root = mount_cont) + output_dir_cont <- .to_container(output_dir_host, host_root = mount_host, container_root = mount_cont) # Run Panaroo in Docker cmd_args <- c( @@ -99,14 +99,17 @@ "--remove-invalid-genes", "--core_threshold", as.character(core_threshold), "--len_dif_percent", as.character(len_dif_percent), - "--threshold", as.character(cluster_threshold), - "-f", as.character(family_seq_identity), - "-t", as.character(panaroo_threads_per_job) + "--threshold", as.character(cluster_threshold), + "-f", as.character(family_seq_identity), + "-t", as.character(panaroo_threads_per_job) ) - res <- tryCatch({ - system2("docker", args = cmd_args, stdout = TRUE, stderr = TRUE) - }, error = function(e) e) + res <- tryCatch( + { + system2("docker", args = cmd_args, stdout = TRUE, stderr = TRUE) + }, + error = function(e) e + ) if (inherits(res, "error")) { stop(sprintf("Docker/Panaroo failed to launch: %s", res$message)) @@ -179,7 +182,6 @@ family_seq_identity = 0.5, threads = 8, split_jobs = FALSE) { - duckdb_path <- normalizePath(duckdb_path) con <- DBI::dbConnect(duckdb::duckdb(), duckdb_path) on.exit(try(DBI::dbDisconnect(con, shutdown = FALSE), silent = TRUE), add = TRUE) @@ -188,17 +190,17 @@ output_path <- dirname(duckdb_path) } output_path <- normalizePath(output_path) - + genome_query_output <- DBI::dbReadTable(con, "files") - + panaroo_input_files <- genome_query_output |> dplyr::pull(panaroo_input) - + # Drop true NAs panaroo_input_files <- panaroo_input_files[!is.na(panaroo_input_files)] - + split_files <- strsplit(panaroo_input_files, " ") - + # Plan for filtering .with_future_plan(workers = threads) valid_entries <- furrr::future_map(split_files, function(paths) { @@ -213,7 +215,7 @@ filtered_panaroo_input <- sapply(split_files[unlist(valid_entries)], paste, collapse = " ") total_lines <- length(filtered_panaroo_input) - batch_size <- if (isTRUE(split_jobs)) ceiling(total_lines / 5) else total_lines + batch_size <- if (isTRUE(split_jobs)) ceiling(total_lines / 5) else total_lines panaroo_batches <- split(filtered_panaroo_input, ceiling(seq_along(filtered_panaroo_input) / batch_size)) n_jobs <- length(panaroo_batches) @@ -256,39 +258,38 @@ #' @param cluster_threshold Numeric. Sequence identity threshold (`--threshold`). Default `0.95`. #' @param family_seq_identity Numeric. Gene family clustering identity (`-f`). Default `0.5`. #' @param threads Integer. Number of threads for Panaroo and parallel execution. Default `8`. -#' +#' #' @returns A a single combined pangenome. -#' +#' #' @keywords internal .mergePanaroo <- function(input_path, core_threshold = 0.90, len_dif_percent = 0.95, cluster_threshold = 0.95, family_seq_identity = 0.5, - threads = 8){ - + threads = 8) { input_path <- .docker_path(input_path) - + # Fail fast if Docker is missing if (!nzchar(Sys.which("docker"))) { stop("Docker is not available on your PATH but is required to run panaroo-merge.") } - + merge_dir <- file.path(input_path, "merge_output") dir.create(merge_dir, recursive = TRUE, showWarnings = FALSE) - + all_dirs <- list.dirs(input_path, recursive = FALSE, full.names = TRUE) all_dirs <- all_dirs[grepl("^panaroo_out_", basename(all_dirs))] - + valid_dirs <- all_dirs[file.exists(file.path(all_dirs, "final_graph.gml"))] - + if (length(valid_dirs) > 1) { mount_host <- input_path mount_cont <- "/work" - + # Provide each dir as a separate argv token after "-d" dir_args <- as.vector(t(.to_container(valid_dirs, host_root = mount_host, container_root = mount_cont))) - + cmd_args <- c( "run", "--platform", "linux/amd64", @@ -302,11 +303,11 @@ "--merge_paralogs", "--core_threshold", as.character(core_threshold), "--len_dif_percent", as.character(len_dif_percent), - "--threshold", as.character(cluster_threshold), - "-f", as.character(family_seq_identity), - "-t", as.character(threads) + "--threshold", as.character(cluster_threshold), + "-f", as.character(family_seq_identity), + "-t", as.character(threads) ) - + system2("docker", args = cmd_args, stdout = TRUE, stderr = TRUE) } else { stop("No valid Panaroo batch directories found (need >= 2 with final_graph.gml).") @@ -314,7 +315,6 @@ } - #' Load Panaroo gene presence/absence table into DuckDB #' #' Reads `gene_presence_absence.csv` and constructs a genome-by-gene count @@ -326,13 +326,13 @@ #' @return A tibble containing the gene count matrix. #' #' @keywords internal -.panaroo2geneTable <- function(panaroo_output_path, duckdb_path){ +.panaroo2geneTable <- function(panaroo_output_path, duckdb_path) { filepath <- file.path(normalizePath(panaroo_output_path), "gene_presence_absence.csv") duckdb_path <- normalizePath(duckdb_path) con <- DBI::dbConnect(duckdb::duckdb(), duckdb_path) on.exit(try(DBI::dbDisconnect(con, shutdown = FALSE), silent = TRUE), add = TRUE) - gene_count <- read.table(filepath, sep=",", header=TRUE, fill=TRUE, quote="") |> + gene_count <- read.table(filepath, sep = ",", header = TRUE, fill = TRUE, quote = "") |> tibble::as_tibble() |> dplyr::select(-c(Non.unique.Gene.name, Annotation)) |> tidyr::pivot_longer(cols = -1) |> @@ -356,13 +356,13 @@ #' @return A tibble with `Gene` and `Annotation` columns. #' #' @keywords internal -.panaroo2geneNames <- function(panaroo_output_path, duckdb_path){ +.panaroo2geneNames <- function(panaroo_output_path, duckdb_path) { filepath <- file.path(normalizePath(panaroo_output_path), "gene_presence_absence.csv") duckdb_path <- normalizePath(duckdb_path) con <- DBI::dbConnect(duckdb::duckdb(), duckdb_path) on.exit(try(DBI::dbDisconnect(con, shutdown = FALSE), silent = TRUE), add = TRUE) - gene_names <- read.table(filepath, sep=",", header=TRUE, fill=TRUE, quote="") |> + gene_names <- read.table(filepath, sep = ",", header = TRUE, fill = TRUE, quote = "") |> tibble::as_tibble() |> dplyr::select(c(Gene, Annotation)) @@ -381,15 +381,15 @@ #' @return A tibble containing the struct matrix. #' #' @keywords internal -.panaroo2StructTable <- function(panaroo_output_path, duckdb_path){ +.panaroo2StructTable <- function(panaroo_output_path, duckdb_path) { struct_filepath <- file.path(normalizePath(panaroo_output_path), "struct_presence_absence.Rtab") duckdb_path <- normalizePath(duckdb_path) con <- DBI::dbConnect(duckdb::duckdb(), duckdb_path) on.exit(try(DBI::dbDisconnect(con, shutdown = FALSE), silent = TRUE), add = TRUE) - gene_struct <- read.table(struct_filepath, sep="\t", header=TRUE, fill=TRUE, quote="") |> + gene_struct <- read.table(struct_filepath, sep = "\t", header = TRUE, fill = TRUE, quote = "") |> tibble::as_tibble() |> - tidyr::pivot_longer(cols= -1) |> + tidyr::pivot_longer(cols = -1) |> tidyr::pivot_wider(names_from = Gene, values_from = value) |> dplyr::rename("genome_id" = "name") |> dplyr::mutate(genome_id = stringr::str_replace_all(genome_id, c("^X" = "", "\\.PATRIC$" = ""))) @@ -409,7 +409,7 @@ #' @return Invisibly returns TRUE. #' #' @keywords internal -.panaroo2OtherTables <- function(panaroo_output_path, duckdb_path){ +.panaroo2OtherTables <- function(panaroo_output_path, duckdb_path) { panaroo_output_path <- normalizePath(panaroo_output_path) duckdb_path <- normalizePath(duckdb_path) fasta_filepath <- file.path(panaroo_output_path, "pan_genome_reference.fa") @@ -418,15 +418,19 @@ gene_fasta <- Biostrings::readDNAStringSet(filepath = fasta_filepath) DBI::dbWriteTable(con, "gene_ref_seq", - tibble::tibble(name = names(gene_fasta), - sequence = as.character(gene_fasta)), - overwrite = TRUE) + tibble::tibble( + name = names(gene_fasta), + sequence = as.character(gene_fasta) + ), + overwrite = TRUE + ) readr::read_csv(file.path(panaroo_output_path, "gene_presence_absence.csv")) |> dplyr::select(-`Non-unique Gene name`) |> tidyr::pivot_longer(-c("Gene", "Annotation"), - names_to = "genome_ids", - values_to = "protein_ids") |> + names_to = "genome_ids", + values_to = "protein_ids" + ) |> dplyr::mutate(genome_ids = gsub(".PATRIC", "", genome_ids)) |> dplyr::select(genome_ids, Gene, protein_ids) |> dplyr::distinct() |> @@ -447,9 +451,9 @@ #' @return Invisibly returns TRUE. #' #' @keywords internal -.panaroo2duckdb <- function(panaroo_output_path, duckdb_path){ +.panaroo2duckdb <- function(panaroo_output_path, duckdb_path) { panaroo_output_path <- normalizePath(panaroo_output_path) - duckdb_path <- normalizePath(duckdb_path) + duckdb_path <- normalizePath(duckdb_path) .panaroo2geneTable(panaroo_output_path, duckdb_path) .panaroo2geneNames(panaroo_output_path, duckdb_path) @@ -484,7 +488,6 @@ threads = 0, memory = 0, extra_args = c("-g", "1")) { - # Fail fast if Docker is missing if (!nzchar(Sys.which("docker"))) { stop("Docker is not available on your PATH but is required to run CD-HIT.") @@ -530,7 +533,7 @@ "weizhongli1987/cdhit:4.8.1", "cd-hit", "-i", .to_container(cdhit_input_faa, mount_host, mount_cont), - "-o", .to_container(clustered_faa, mount_host, mount_cont), + "-o", .to_container(clustered_faa, mount_host, mount_cont), "-c", as.character(identity), "-n", as.character(word_length), "-T", as.character(threads), @@ -540,21 +543,26 @@ ) message("Running cd-hit via Docker...") - output <- tryCatch({ - system2("docker", args = cmd_args, stdout = TRUE, stderr = TRUE) - }, error = function(e) { - stop("cd-hit execution failed: ", e$message) - }) - + output <- tryCatch( + { + system2("docker", args = cmd_args, stdout = TRUE, stderr = TRUE) + }, + error = function(e) { + stop("cd-hit execution failed: ", e$message) + } + ) + if (!file.exists(clustered_faa)) { stop("cd-hit failed: output file not found. Check stderr:\n", paste(output, collapse = "\n")) } # Ensure .clstr exists (used downstream) if (!file.exists(paste0(clustered_faa, ".clstr"))) { - stop("cd-hit did not produce the expected .clstr file at: ", paste0(clustered_faa, ".clstr"), - "\nFull output:\n", paste(output, collapse = "\n")) + stop( + "cd-hit did not produce the expected .clstr file at: ", paste0(clustered_faa, ".clstr"), + "\nFull output:\n", paste(output, collapse = "\n") + ) } - + message("cd-hit completed successfully.") list( cdhit_input_faa = cdhit_input_faa, @@ -568,7 +576,7 @@ #' `runPanaroo2Duckdb()` executes Panaroo on the genomes registered in a #' per-selection DuckDB (created earlier by `prepareGenomes()`), optionally in #' multiple batches, and imports all resulting pangenome tables into the same -#' DuckDB database. +#' DuckDB database. #' #' It acts as a high-level wrapper around: #' * **`.runPanaroo()`** — runs Panaroo (single or multi-batch) @@ -633,10 +641,10 @@ #' and modeling steps in `amRdata` and `amRml`. #' #' @seealso -#' * `.runPanaroo()` — core Panaroo execution -#' * `.mergePanaroo()` — merge multiple Panaroo batches -#' * `.panaroo2duckdb()` — import Panaroo results into DuckDB -#' * [runDataProcessing()] — full pipeline including CD-HIT & InterProScan +#' * `.runPanaroo()` — core Panaroo execution +#' * `.mergePanaroo()` — merge multiple Panaroo batches +#' * `.panaroo2duckdb()` — import Panaroo results into DuckDB +#' * [runDataProcessing()] — full pipeline including CD-HIT & InterProScan #' #' @examples #' \dontrun{ @@ -672,14 +680,14 @@ runPanaroo2Duckdb <- function(duckdb_path, if (isTRUE(verbose)) message("Launching Panaroo.") .runPanaroo( - duckdb_path = duckdb_path, - output_path = out_dir, - core_threshold = core_threshold, - len_dif_percent = len_dif_percent, + duckdb_path = duckdb_path, + output_path = out_dir, + core_threshold = core_threshold, + len_dif_percent = len_dif_percent, cluster_threshold = cluster_threshold, family_seq_identity = family_seq_identity, - threads = threads, - split_jobs = split_jobs + threads = threads, + split_jobs = split_jobs ) # Identify Panaroo outputs that contain a final_graph.gml file @@ -730,17 +738,19 @@ runPanaroo2Duckdb <- function(duckdb_path, .parseProteinClusters <- function(clustered_faa) { clstr <- paste0(clustered_faa, ".clstr") if (!file.exists(clstr)) { - stop("CD-HIT cluster file not found: ", clstr, - "\nEnsure .runCDHIT() completed successfully and produced the .clstr file.") + stop( + "CD-HIT cluster file not found: ", clstr, + "\nEnsure .runCDHIT() completed successfully and produced the .clstr file." + ) } - + lines <- data.table::fread(clstr, sep = "\n", header = FALSE)$V1 cluster_ids <- grep("^>Cluster", lines) cluster_map <- data.table::data.table() for (i in seq_along(cluster_ids)) { start <- cluster_ids[i] + 1 - end <- if (i < length(cluster_ids)) cluster_ids[i + 1] - 1 else length(lines) + end <- if (i < length(cluster_ids)) cluster_ids[i + 1] - 1 else length(lines) cluster_lines <- lines[start:end] # This finds the reference cluster ID and names the cluster with it @@ -752,8 +762,10 @@ runPanaroo2Duckdb <- function(duckdb_path, } # Pull genome IDs - genome_matches <- stringr::str_match(cluster_lines, - "fig\\|([0-9]+\\.[0-9]+)\\.peg\\.[0-9]+")[, 2] + genome_matches <- stringr::str_match( + cluster_lines, + "fig\\|([0-9]+\\.[0-9]+)\\.peg\\.[0-9]+" + )[, 2] genome_matches <- genome_matches[!is.na(genome_matches)] if (length(genome_matches) > 0) { @@ -810,9 +822,9 @@ buildMatrices <- function(cluster_map) .buildProtMatrices(cluster_map) names_faa <- names(cdhit_output_faa) |> tibble::as_tibble() |> dplyr::mutate( - proteinID = stringr::str_extract(value, "^fig\\|[0-9]+\\.[0-9]+\\.peg\\.[0-9]+"), - locus_tag = stringr::str_match(value, "peg\\.[0-9]+\\|([^\\s]+)")[, 2], - proteinName= stringr::str_trim(stringr::str_match(value, "\\|[^\\s]+\\s+(.*?)\\s+\\[")[, 2]) + proteinID = stringr::str_extract(value, "^fig\\|[0-9]+\\.[0-9]+\\.peg\\.[0-9]+"), + locus_tag = stringr::str_match(value, "peg\\.[0-9]+\\|([^\\s]+)")[, 2], + proteinName = stringr::str_trim(stringr::str_match(value, "\\|[^\\s]+\\s+(.*?)\\s+\\[")[, 2]) ) |> dplyr::select(-value) @@ -828,47 +840,47 @@ CDHIT2duckdb <- function(duckdb_path, word_length = 5, threads = 0, memory = 0, - extra_args = c("-g", "1")){ - + extra_args = c("-g", "1")) { duckdb_path <- normalizePath(duckdb_path) con <- DBI::dbConnect(duckdb::duckdb(), duckdb_path) on.exit(try(DBI::dbDisconnect(con, shutdown = FALSE), silent = TRUE), add = TRUE) if (missing(output_path) || output_path %in% c(".", "results", "results/")) { - output_path <- dirname(duckdb_path) # e.g., ./results/ + output_path <- dirname(duckdb_path) # e.g., ./results/ } output_path <- normalizePath(output_path) cdhit_outputs <- .runCDHIT(duckdb_path, - output_path, - output_prefix = output_prefix, - identity = identity, - word_length = word_length, - threads = threads, - memory = memory, - extra_args = extra_args) - - cluster_map <- .parseProteinClusters(cdhit_outputs$clustered_faa) + output_path, + output_prefix = output_prefix, + identity = identity, + word_length = word_length, + threads = threads, + memory = memory, + extra_args = extra_args + ) + + cluster_map <- .parseProteinClusters(cdhit_outputs$clustered_faa) cluster_count <- .buildProtMatrices(cluster_map) DBI::dbWriteTable(con, "protein_count", cluster_count, overwrite = TRUE) cluster_fasta <- cdhit_outputs$cdhit_input_faa - cluster_name <- .clusterNames(cluster_map, cluster_fasta) + cluster_name <- .clusterNames(cluster_map, cluster_fasta) DBI::dbWriteTable(con, "protein_names", cluster_name, overwrite = TRUE) clustered_faa <- Biostrings::readAAStringSet(cdhit_outputs$clustered_faa) DBI::dbWriteTable(con, "protein_cluster_seq", - tibble::tibble( - name = names(clustered_faa) |> stringr::str_extract("fig\\|[0-9]+\\.[0-9]+\\.peg\\.[0-9]+"), - sequence = as.character(clustered_faa) - ), - overwrite = TRUE) + tibble::tibble( + name = names(clustered_faa) |> stringr::str_extract("fig\\|[0-9]+\\.[0-9]+\\.peg\\.[0-9]+"), + sequence = as.character(clustered_faa) + ), + overwrite = TRUE + ) invisible(TRUE) } - #' Check or install InterProScan data bundle #' #' Ensures that the InterProScan data directory exists locally, downloading @@ -885,51 +897,55 @@ CDHIT2duckdb <- function(duckdb_path, #' #' @keywords internal .checkInterProData <- function( - version = "5.76-107.0", - dest_dir = "inst/extdata/interpro", - docker_image = sprintf("interpro/interproscan:%s", version), - platform = "linux/amd64", - curl_bin = "curl", - verbose = TRUE + version = "5.76-107.0", + dest_dir = "inst/extdata/interpro", + docker_image = sprintf("interpro/interproscan:%s", version), + platform = "linux/amd64", + curl_bin = "curl", + verbose = TRUE ) { msg <- function(...) if (verbose) message(sprintf(...)) - + if (!dir.exists(dest_dir)) dir.create(dest_dir, recursive = TRUE, showWarnings = FALSE) dest_dir <- normalizePath(dest_dir, mustWork = TRUE) - + root_dir <- file.path(dest_dir, sprintf("interproscan-%s", version)) data_dir <- file.path(root_dir, "data") - + # Simple existence check if (dir.exists(data_dir) && length(list.files(data_dir, recursive = TRUE)) > 0) { msg("InterProScan data already present at: %s", data_dir) return(list(data_dir = normalizePath(data_dir), ready = TRUE)) } - + # Download bundle if needed - tar_url <- sprintf("http://ftp.ebi.ac.uk/pub/software/unix/iprscan/5/%s/alt/interproscan-data-%s.tar.gz", - version, version) - md5_url <- paste0(tar_url, ".md5") + tar_url <- sprintf( + "http://ftp.ebi.ac.uk/pub/software/unix/iprscan/5/%s/alt/interproscan-data-%s.tar.gz", + version, version + ) + md5_url <- paste0(tar_url, ".md5") tar_path <- file.path(dest_dir, basename(tar_url)) md5_path <- paste0(tar_path, ".md5") - + if (!file.exists(tar_path)) { msg("Downloading InterProScan data bundle.") - status_tar <- system2(curl_bin, c("-L", "-o", tar_path, tar_url)) - status_md5 <- system2(curl_bin, c("-L", "-o", md5_path, md5_url)) - if (status_tar != 0 || status_md5 != 0) + status_tar <- system2(curl_bin, c("-L", "-o", tar_path, tar_url)) + status_md5 <- system2(curl_bin, c("-L", "-o", md5_path, md5_url)) + if (status_tar != 0 || status_md5 != 0) { stop("Failed to download InterProScan data bundle.") + } } - + msg("Verifying MD5 checksum.") md5_expected <- sub("\\s+.*$", "", readLines(md5_path)[1]) - md5_actual <- tools::md5sum(tar_path)[[1]] - if (!identical(tolower(md5_expected), tolower(md5_actual))) + md5_actual <- tools::md5sum(tar_path)[[1]] + if (!identical(tolower(md5_expected), tolower(md5_actual))) { stop("MD5 checksum mismatch for InterProScan data bundle.") - + } + msg("Extracting InterProScan data bundle.") utils::untar(tar_path, exdir = dest_dir, tar = "internal") - + msg("Data unpacked successfully.") return(list(data_dir = normalizePath(data_dir), ready = TRUE)) } @@ -946,9 +962,11 @@ CDHIT2duckdb <- function(duckdb_path, #' #' @keywords internal .getDfIPRColNames <- function() { - c("AccNum", "SeqMD5Digest", "SLength", "Analysis", + c( + "AccNum", "SeqMD5Digest", "SLength", "Analysis", "DB.ID", "SignDesc", "StartLoc", "StopLoc", "Score", - "Status", "RunDate", "IPRAcc", "IPRDesc", "placeholder") + "Status", "RunDate", "IPRAcc", "IPRDesc", "placeholder" + ) } #' Internal helpers for reading InterProScan TSV outputs @@ -993,8 +1011,9 @@ CDHIT2duckdb <- function(duckdb_path, #' @keywords internal .readIPRscanTsv <- function(filepath) { readr::read_tsv(filepath, - col_types = .getDfIPRColTypes(), - col_names = .getDfIPRColNames()) + col_types = .getDfIPRColTypes(), + col_names = .getDfIPRColNames() + ) } @@ -1026,7 +1045,7 @@ CDHIT2duckdb <- function(duckdb_path, file_format, docker_image = sprintf("interpro/interproscan:%s", "5.76-107.0")) { # Normalize and mount paths - path <- .docker_path(path) + path <- .docker_path(path) bind_data <- .docker_path(ipr_data_path) dir.create(file.path(path, "tmp", "iprscan"), recursive = TRUE, showWarnings = FALSE) @@ -1050,39 +1069,42 @@ CDHIT2duckdb <- function(duckdb_path, "-v", paste0(bind_data, ":/opt/interproscan/data"), "-w", "/work", docker_image, - "--input", .to_container(temp_fasta_file, path, "/work"), + "--input", .to_container(temp_fasta_file, path, "/work"), "--cpu", as.character(threads), "-f", file_format, "--appl", appl_str, "-b", chunk_out_file_base_cont ) - - status <- tryCatch({ - system2( - "docker", - args = c( - "run", - "--rm", - "--platform", "linux/amd64", # force amd64 for ARM hosts - "-v", paste0(path, ":", "/work"), - "-v", paste0(bind_data, ":/opt/interproscan/data"), - "-w", "/work", - docker_image, - "--input", .to_container(temp_fasta_file, path, "/work"), - "--cpu", as.character(threads), - "-f", file_format, - "--appl", appl_str, - "-b", chunk_out_file_base_cont - ), - stdout = TRUE, - stderr = TRUE - ) - }, error = function(e) { - stop(sprintf("InterProScan execution failed for chunk %d: %s", chunk_id, e$message)) - }) - out_tsv <- paste0(chunk_out_file_base_host, ".tsv") + status <- tryCatch( + { + system2( + "docker", + args = c( + "run", + "--rm", + "--platform", "linux/amd64", # force amd64 for ARM hosts + "-v", paste0(path, ":", "/work"), + "-v", paste0(bind_data, ":/opt/interproscan/data"), + "-w", "/work", + docker_image, + "--input", .to_container(temp_fasta_file, path, "/work"), + "--cpu", as.character(threads), + "-f", file_format, + "--appl", appl_str, + "-b", chunk_out_file_base_cont + ), + stdout = TRUE, + stderr = TRUE + ) + }, + error = function(e) { + stop(sprintf("InterProScan execution failed for chunk %d: %s", chunk_id, e$message)) + } + ) + + out_tsv <- paste0(chunk_out_file_base_host, ".tsv") out_tsvgz <- paste0(chunk_out_file_base_host, ".tsv.gz") if (file.exists(out_tsv)) { @@ -1090,8 +1112,10 @@ CDHIT2duckdb <- function(duckdb_path, } else if (file.exists(out_tsvgz)) { return(out_tsvgz) } else { - stop(sprintf("InterProScan produced no output for chunk %d. Checked: %s and %s.\nLast message:\n%s", - chunk_id, out_tsv, out_tsvgz, paste(status, collapse = "\n"))) + stop(sprintf( + "InterProScan produced no output for chunk %d. Checked: %s and %s.\nLast message:\n%s", + chunk_id, out_tsv, out_tsvgz, paste(status, collapse = "\n") + )) } } @@ -1100,22 +1124,21 @@ domainFromIPR <- function(duckdb_path, path, out_file_base = "iprscan", appl = c("Pfam"), - ipr_version = "5.76-107.0", + ipr_version = "5.76-107.0", ipr_dest_dir = "inst/extdata/interpro", ipr_platform = "linux/amd64", auto_prepare_data = TRUE, threads = 8, file_format = "TSV", docker_repo = "interpro/interproscan") { - duckdb_path <- normalizePath(duckdb_path) if (missing(path) || path %in% c(".", "results", "results/")) { path <- dirname(duckdb_path) } path <- normalizePath(path) - + ipr_image <- sprintf("%s:%s", docker_repo, ipr_version) - + # Prepare data if needed ipr_info <- if (isTRUE(auto_prepare_data)) { .checkInterProData( @@ -1126,35 +1149,38 @@ domainFromIPR <- function(duckdb_path, verbose = TRUE ) } else { - list(data_dir = file.path(ipr_dest_dir, sprintf("interproscan-%s", ipr_version), "data"), - ready = NA) + list( + data_dir = file.path(ipr_dest_dir, sprintf("interproscan-%s", ipr_version), "data"), + ready = NA + ) } ipr_data_path <- ipr_info$data_dir - + # Pull image once try(suppressWarnings(system2("docker", args = c("pull", ipr_image))), silent = TRUE) - + con <- DBI::dbConnect(duckdb::duckdb(), duckdb_path) on.exit(try(DBI::dbDisconnect(con, shutdown = FALSE), silent = TRUE), add = TRUE) - + sequences_df <- dplyr::tbl(con, "protein_cluster_seq") |> tibble::as_tibble() - if (nrow(sequences_df) == 0L) + if (nrow(sequences_df) == 0L) { stop("No sequences found in 'protein_cluster_seq'. Please run CDHIT2duckdb() first.") - + } + # Chunking for parallel (not currently implemented due to memory limits) - chunks <- list(sequences_df) # Force 1 chunk for RAM limits - + chunks <- list(sequences_df) # Force 1 chunk for RAM limits + # Forcing 1 container operation for RAM limits workers <- 1 cpu_per_container <- threads - + message(sprintf( "InterPro: running in single-container mode with %d CPU(s).", cpu_per_container )) - + .with_future_plan(workers = 1) - + results <- future.apply::future_lapply(seq_along(chunks), function(i) { res <- try( .process_chunk( @@ -1176,55 +1202,60 @@ domainFromIPR <- function(duckdb_path, } res }) - + # Combine results tsvs <- Filter(function(x) !is.null(x) && file.exists(x), results) - if (length(tsvs) == 0L) + if (length(tsvs) == 0L) { stop("InterProScan produced no usable outputs. Check Docker logs above.") - + } + df_iprscan <- do.call(rbind, lapply(tsvs, .readIPRscanTsv)) - + # Load processed tables (unchanged) DBI::dbWriteTable(con, "domain_names", - df_iprscan |> - dplyr::select(AccNum, DB.ID, SignDesc, IPRAcc, IPRDesc, StartLoc, StopLoc), - overwrite = TRUE) - + df_iprscan |> + dplyr::select(AccNum, DB.ID, SignDesc, IPRAcc, IPRDesc, StartLoc, StopLoc), + overwrite = TRUE + ) + df_protein_domain_pa <- df_iprscan |> dplyr::select(AccNum, DB.ID, IPRAcc, placeholder) |> dplyr::mutate(domain_ID = stringr::str_glue("{DB.ID}_{IPRAcc}")) |> dplyr::distinct() |> dplyr::mutate(placeholder = stringr::str_replace_all(placeholder, "-", "1")) |> - tidyr::pivot_wider(id_cols = AccNum, names_from = domain_ID, values_from = placeholder, - values_fill = "0") |> + tidyr::pivot_wider( + id_cols = AccNum, names_from = domain_ID, values_from = placeholder, + values_fill = "0" + ) |> dplyr::group_by(AccNum) |> dplyr::summarize(across(everything(), ~ ifelse(any(. == "1"), "1", "0")), .groups = "drop") |> dplyr::mutate(across(-AccNum, as.numeric)) - + protein_filter <- dplyr::tbl(con, "protein_count") |> tibble::as_tibble() accs <- unique(df_protein_domain_pa$AccNum) accs_in_matrix <- intersect(accs, colnames(protein_filter)) - if (length(accs_in_matrix) == 0L) + if (length(accs_in_matrix) == 0L) { stop("No InterPro accessions match protein_count columns.") - + } + protein_filter <- protein_filter |> dplyr::select(genome_id, dplyr::all_of(accs_in_matrix)) df_protein_domain_pa <- df_protein_domain_pa |> dplyr::filter(AccNum %in% accs_in_matrix) |> dplyr::arrange(match(AccNum, accs_in_matrix)) - + domain_count <- as.matrix(protein_filter |> dplyr::select(-genome_id)) %*% as.matrix(df_protein_domain_pa |> dplyr::select(-AccNum)) |> tibble::as_tibble() |> dplyr::mutate(genome_id = protein_filter |> dplyr::pull(genome_id)) |> dplyr::relocate(genome_id, .before = dplyr::everything()) - + DBI::dbWriteTable(conn = con, name = "domain_count", domain_count, overwrite = TRUE) invisible(TRUE) } # Clean BV-BRC metadata, then save as Parquet files -cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ - duckdb_path <- normalizePath(duckdb_path) +cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/") { + duckdb_path <- normalizePath(duckdb_path) # If no explicit path is provided (or a generic one), choose results// when # the DuckDB lives under data//, or else fall back to the DuckDB directory. if (missing(path) || path %in% c(".", "results", "results/")) { @@ -1247,20 +1278,22 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ clean_drug <- readr::read_tsv(file.path(ref_file_path, "clean_drug.tsv")) drug_class <- readr::read_tsv(file.path(ref_file_path, "drug_class.tsv")) - drug_abbr <- readr::read_tsv(file.path(ref_file_path, "drug_abbr.tsv")) + drug_abbr <- readr::read_tsv(file.path(ref_file_path, "drug_abbr.tsv")) class_abbr <- readr::read_tsv(file.path(ref_file_path, "class_abbr.tsv")) clean_countries <- readr::read_tsv(file.path(ref_file_path, "cleaned_bvbrc_countries.tsv")) |> - dplyr::select("raw_entry", "clean_name", "short_name")|> + dplyr::select("raw_entry", "clean_name", "short_name") |> dplyr::distinct() dplyr::tbl(con, "filtered") |> tibble::as_tibble() |> - dplyr::select("genome_drug.genome_id", "genome_drug.antibiotic", - "genome_drug.genome_name", "genome_drug.laboratory_typing_method", - "genome_drug.resistant_phenotype", "genome_drug.taxon_id", - "genome_drug.pmid", "genome.collection_year", - "genome.isolation_country", "genome.host_common_name", - "genome.isolation_source", "genome.species") |> + dplyr::select( + "genome_drug.genome_id", "genome_drug.antibiotic", + "genome_drug.genome_name", "genome_drug.laboratory_typing_method", + "genome_drug.resistant_phenotype", "genome_drug.taxon_id", + "genome_drug.pmid", "genome.collection_year", + "genome.isolation_country", "genome.host_common_name", + "genome.isolation_source", "genome.species" + ) |> dplyr::left_join(clean_drug, by = c("genome_drug.antibiotic" = "original_drug")) |> dplyr::filter(!is.na(cleaned_drug)) |> dplyr::left_join(drug_class, by = c("cleaned_drug" = "drug")) |> @@ -1269,7 +1302,7 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ DBI::dbWriteTable(conn = con, name = "filtered", overwrite = TRUE) resistance_summary <- dplyr::tbl(con, "filtered") |> - tibble::as_tibble() |> + tibble::as_tibble() |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::group_by(genome_drug.genome_id) |> dplyr::summarise( @@ -1283,7 +1316,7 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ dplyr::mutate(genome_drug.antibiotic = cleaned_drug) |> dplyr::select(-cleaned_drug) |> dplyr::left_join(clean_countries, by = c("genome.isolation_country" = "raw_entry")) |> - dplyr::rename("cleaned_country"="clean_name", "country_abbr"="short_name") |> + dplyr::rename("cleaned_country" = "clean_name", "country_abbr" = "short_name") |> dplyr::mutate(genome.isolation_country = cleaned_country) |> dplyr::select(-cleaned_country) |> dplyr::left_join(resistance_summary, by = "genome_drug.genome_id") |> @@ -1291,38 +1324,42 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ is.na(resistant_classes) ~ genome_drug.resistant_phenotype, TRUE ~ resistant_classes )) |> - dplyr::mutate(num_resistant_classes= dplyr::case_when( + dplyr::mutate(num_resistant_classes = dplyr::case_when( is.na(num_resistant_classes) ~ 0, TRUE ~ num_resistant_classes )) |> dplyr::mutate(genome.collection_year = as.numeric(genome.collection_year)) |> - dplyr::mutate(year_bin = cut(genome.collection_year, breaks = year_breaks, - right = FALSE, include.lowest = TRUE, - labels = paste(year_breaks[-length(year_breaks)], - year_breaks[-1] - 1, sep = "-"))) |> + dplyr::mutate(year_bin = cut(genome.collection_year, + breaks = year_breaks, + right = FALSE, include.lowest = TRUE, + labels = paste(year_breaks[-length(year_breaks)], + year_breaks[-1] - 1, + sep = "-" + ) + )) |> DBI::dbWriteTable(conn = con, name = "cleaned_metadata", overwrite = TRUE) # Parquet output paths - genes_parquet <- file.path(path, "gene_count.parquet") - gene_names_parquet <- file.path(path, "gene_names.parquet") - gene_ref_seq_parquet <- file.path(path, "gene_seqs.parquet") - genome_gene_protein_parquet <- file.path(path, "genome_gene_protein.parquet") - struct_parquet <- file.path(path, "struct.parquet") + genes_parquet <- file.path(path, "gene_count.parquet") + gene_names_parquet <- file.path(path, "gene_names.parquet") + gene_ref_seq_parquet <- file.path(path, "gene_seqs.parquet") + genome_gene_protein_parquet <- file.path(path, "genome_gene_protein.parquet") + struct_parquet <- file.path(path, "struct.parquet") - proteins_parquet <- file.path(path, "protein_count.parquet") - domains_parquet <- file.path(path, "domain_count.parquet") + proteins_parquet <- file.path(path, "protein_count.parquet") + domains_parquet <- file.path(path, "domain_count.parquet") - metadata_parquet <- file.path(path, "metadata.parquet") # cleaned_metadata exported as 'metadata' + metadata_parquet <- file.path(path, "metadata.parquet") # cleaned_metadata exported as 'metadata' - domain_names_parquet <- file.path(path, "domain_names.parquet") - protein_names_parquet <- file.path(path, "protein_names.parquet") + domain_names_parquet <- file.path(path, "domain_names.parquet") + protein_names_parquet <- file.path(path, "protein_names.parquet") - protein_cluster_seq_parquet <- file.path(path, "protein_seqs.parquet") + protein_cluster_seq_parquet <- file.path(path, "protein_seqs.parquet") # Also export AMR/genome/original metadata - amr_phenotype_parquet <- file.path(path, "amr_phenotype.parquet") - genome_data_parquet <- file.path(path, "genome_data.parquet") - original_metadata_parquet <- file.path(path, "original_metadata.parquet") + amr_phenotype_parquet <- file.path(path, "amr_phenotype.parquet") + genome_data_parquet <- file.path(path, "genome_data.parquet") + original_metadata_parquet <- file.path(path, "original_metadata.parquet") writeCompressedParquet <- function(df, path) { arrow::write_parquet( @@ -1334,7 +1371,9 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ ) } - db_name <- duckdb_path |> stringr::str_split_i(".duckdb", i = 1) |> paste0("_parquet.duckdb") + db_name <- duckdb_path |> + stringr::str_split_i(".duckdb", i = 1) |> + paste0("_parquet.duckdb") con_new <- DBI::dbConnect(duckdb::duckdb(), db_name) on.exit(try(DBI::dbDisconnect(con_new, shutdown = FALSE), silent = TRUE), add = TRUE) @@ -1399,8 +1438,8 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ # debug/complete views: amr_phenotype, genome_data, original_metadata DBI::dbReadTable(con, "amr_phenotype") |> writeCompressedParquet(amr_phenotype_parquet) - DBI::dbReadTable(con, "genome_data") |> writeCompressedParquet(genome_data_parquet) - DBI::dbReadTable(con, "metadata") |> writeCompressedParquet(original_metadata_parquet) + DBI::dbReadTable(con, "genome_data") |> writeCompressedParquet(genome_data_parquet) + DBI::dbReadTable(con, "metadata") |> writeCompressedParquet(original_metadata_parquet) DBI::dbExecute(con_new, sprintf("CREATE OR REPLACE VIEW amr_phenotype AS SELECT * FROM read_parquet('%s')", amr_phenotype_parquet)) DBI::dbExecute(con_new, sprintf("CREATE OR REPLACE VIEW genome_data AS SELECT * FROM read_parquet('%s')", genome_data_parquet)) @@ -1515,16 +1554,16 @@ cleanData <- function(duckdb_path, path, ref_file_path = "data_raw/"){ #' #' **Input Requirements** #' * The `duckdb_path` must reference a per-selection DuckDB that contains: -#' `files` (paths to `.gff`, `.fna`, `.PATRIC.faa`), -#' `filtered` (genomes selected for download/filtering), and +#' `files` (paths to `.gff`, `.fna`, `.PATRIC.faa`), +#' `filtered` (genomes selected for download/filtering), and #' BV-BRC metadata tables written by earlier steps. #' #' **Outputs & Side Effects** #' * Writes tool-specific intermediate outputs under `output_path` (e.g., `panaroo_out_*`, CD-HIT files). #' * Writes Parquet files to `output_path`: -#' `gene_count.parquet`, `protein_count.parquet`, `domain_count.parquet`, `struct.parquet`, -#' `gene_names.parquet`, `protein_names.parquet`, `domain_names.parquet`, -#' `gene_seqs.parquet`, `protein_seqs.parquet`, `genome_gene_protein.parquet`, +#' `gene_count.parquet`, `protein_count.parquet`, `domain_count.parquet`, `struct.parquet`, +#' `gene_names.parquet`, `protein_names.parquet`, `domain_names.parquet`, +#' `gene_seqs.parquet`, `protein_seqs.parquet`, `genome_gene_protein.parquet`, #' `metadata.parquet`, `amr_phenotype.parquet`, `genome_data.parquet`, `original_metadata.parquet`. #' * Creates a new Parquet-backed DuckDB (`*_parquet.duckdb`) with read-only views pointing to those Parquets. #' @@ -1565,7 +1604,7 @@ runDataProcessing <- function(duckdb_path, cdhit_identity = 0.9, cdhit_word_length = 5, cdhit_memory = 0, - cdhit_extra_args = c("-g","1"), + cdhit_extra_args = c("-g", "1"), cdhit_output_prefix = "cdhit_out", # InterPro ipr_appl = c("Pfam"), @@ -1651,4 +1690,3 @@ runDataProcessing <- function(duckdb_path, parquet_duckdb_path = normalizePath(parquet_duckdb_path) )) } - From 959e92ea56bf60397c0082e5104aee31ada1f0b1 Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:18:05 -0600 Subject: [PATCH 06/17] Update amRdataPlots.R --- R/amRdataPlots.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 44ae8a7..0ce691d 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -238,6 +238,7 @@ generatePlots <- function(metadata_parquet, ) + ggplot2::geom_line() + ggplot2::geom_point() + +<<<<<<< Updated upstream ggplot2::facet_wrap(~drug_abbr, scales = "free_y") + ggplot2::labs( title = "Resistant phenotypes across antibiotics and time", @@ -265,6 +266,7 @@ generatePlots <- function(metadata_parquet, dplyr::distinct(drug_abbr) |> dplyr::arrange(drug_abbr) |> dplyr::pull(drug_abbr) +<<<<<<< Updated upstream # 2) Base Okabe–Ito (CVD-friendly) and pastelizer okabe_ito_base <- c( From 48466d088dc6b15dd9e7700932979cc076a32e16 Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:18:09 -0600 Subject: [PATCH 07/17] Update amRdataPlots.R --- R/amRdataPlots.R | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 0ce691d..397da19 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -213,7 +213,11 @@ generatePlots <- function(metadata_parquet, df_year <- metadata |> dplyr::filter(!is.na(genome.collection_year)) |> dplyr::select( +<<<<<<< Updated upstream genome.genome_id, +======= + genome_drug.genome_id, +>>>>>>> Stashed changes drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, @@ -240,6 +244,9 @@ generatePlots <- function(metadata_parquet, ggplot2::geom_point() + <<<<<<< Updated upstream ggplot2::facet_wrap(~drug_abbr, scales = "free_y") + +======= + ggplot2::facet_wrap(~ drug_abbr, scales = "free_y") + +>>>>>>> Stashed changes ggplot2::labs( title = "Resistant phenotypes across antibiotics and time", x = "Year", y = "Number of isolates", @@ -268,6 +275,9 @@ generatePlots <- function(metadata_parquet, dplyr::pull(drug_abbr) <<<<<<< Updated upstream +======= + +>>>>>>> Stashed changes # 2) Base Okabe–Ito (CVD-friendly) and pastelizer okabe_ito_base <- c( "#000000", # black @@ -331,7 +341,11 @@ generatePlots <- function(metadata_parquet, df_country <- metadata |> dplyr::filter(genome.isolation_country != "") |> dplyr::select( +<<<<<<< Updated upstream genome.genome_id, +======= + genome_drug.genome_id, +>>>>>>> Stashed changes drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, @@ -372,10 +386,15 @@ generatePlots <- function(metadata_parquet, # 4) Phenotype proportion per antibiotic (stacked, normalized) p4 <- ggplot2::ggplot( metadata, +<<<<<<< Updated upstream ggplot2::aes( x = drug_abbr, fill = genome_drug.resistant_phenotype ) +======= + ggplot2::aes(x = drug_abbr, + fill = genome_drug.resistant_phenotype) +>>>>>>> Stashed changes ) + ggplot2::geom_bar(position = "fill") + ggplot2::coord_flip() + From 6e863966146bc3f52a39f831ce674be1c7fbab3d Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Wed, 1 Jul 2026 09:37:25 -0600 Subject: [PATCH 08/17] Update amRdataPlots.R --- R/amRdataPlots.R | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 397da19..5e38f9b 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -213,11 +213,7 @@ generatePlots <- function(metadata_parquet, df_year <- metadata |> dplyr::filter(!is.na(genome.collection_year)) |> dplyr::select( -<<<<<<< Updated upstream genome.genome_id, -======= - genome_drug.genome_id, ->>>>>>> Stashed changes drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, @@ -242,11 +238,7 @@ generatePlots <- function(metadata_parquet, ) + ggplot2::geom_line() + ggplot2::geom_point() + -<<<<<<< Updated upstream - ggplot2::facet_wrap(~drug_abbr, scales = "free_y") + -======= ggplot2::facet_wrap(~ drug_abbr, scales = "free_y") + ->>>>>>> Stashed changes ggplot2::labs( title = "Resistant phenotypes across antibiotics and time", x = "Year", y = "Number of isolates", @@ -260,7 +252,7 @@ generatePlots <- function(metadata_parquet, axis.text.x = ggplot2::element_text(angle = 45, hjust = 1, colour = "black"), axis.text.y = ggplot2::element_text(colour = "black"), axis.title = ggplot2::element_text(colour = "black"), - panel.grid.minor = element_blank() + panel.grid.minor = ggplot2::element_blank() ) p1 @@ -273,11 +265,7 @@ generatePlots <- function(metadata_parquet, dplyr::distinct(drug_abbr) |> dplyr::arrange(drug_abbr) |> dplyr::pull(drug_abbr) -<<<<<<< Updated upstream - -======= ->>>>>>> Stashed changes # 2) Base Okabe–Ito (CVD-friendly) and pastelizer okabe_ito_base <- c( "#000000", # black @@ -341,11 +329,7 @@ generatePlots <- function(metadata_parquet, df_country <- metadata |> dplyr::filter(genome.isolation_country != "") |> dplyr::select( -<<<<<<< Updated upstream genome.genome_id, -======= - genome_drug.genome_id, ->>>>>>> Stashed changes drug_abbr, genome_drug.resistant_phenotype, genome.isolation_country, @@ -386,15 +370,10 @@ generatePlots <- function(metadata_parquet, # 4) Phenotype proportion per antibiotic (stacked, normalized) p4 <- ggplot2::ggplot( metadata, -<<<<<<< Updated upstream ggplot2::aes( x = drug_abbr, fill = genome_drug.resistant_phenotype ) -======= - ggplot2::aes(x = drug_abbr, - fill = genome_drug.resistant_phenotype) ->>>>>>> Stashed changes ) + ggplot2::geom_bar(position = "fill") + ggplot2::coord_flip() + @@ -435,7 +414,7 @@ generatePlots <- function(metadata_parquet, fill = "Isolation source" ) + ggplot2::scale_fill_manual( - values = colorRampPalette(RColorBrewer::brewer.pal(8, "Pastel2"))(n_distinct(summary_isolation_source$genome.isolation_source)) + values = colorRampPalette(RColorBrewer::brewer.pal(8, "Pastel2"))(dplyr::n_distinct(summary_isolation_source$genome.isolation_source)) ) + ggplot2::theme_minimal(base_size = 12) + ggplot2::theme( @@ -456,7 +435,7 @@ generatePlots <- function(metadata_parquet, legend.position = "bottom", axis.text = ggplot2::element_text(colour = "black"), axis.title = ggplot2::element_text(colour = "black"), - panel.grid.minor = element_blank() + panel.grid.minor = ggplot2::element_blank() ) plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) From 1ec7b9fbdcfd8c1b8d5cf073497a1faaf36db493 Mon Sep 17 00:00:00 2001 From: AbhirupaGhosh Date: Wed, 1 Jul 2026 15:39:13 +0000 Subject: [PATCH 09/17] Style code (GHA) --- R/amRdataPlots.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 5e38f9b..b90e483 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -238,7 +238,7 @@ generatePlots <- function(metadata_parquet, ) + ggplot2::geom_line() + ggplot2::geom_point() + - ggplot2::facet_wrap(~ drug_abbr, scales = "free_y") + + ggplot2::facet_wrap(~drug_abbr, scales = "free_y") + ggplot2::labs( title = "Resistant phenotypes across antibiotics and time", x = "Year", y = "Number of isolates", @@ -265,7 +265,7 @@ generatePlots <- function(metadata_parquet, dplyr::distinct(drug_abbr) |> dplyr::arrange(drug_abbr) |> dplyr::pull(drug_abbr) - + # 2) Base Okabe–Ito (CVD-friendly) and pastelizer okabe_ito_base <- c( "#000000", # black From ae451e0b0695862c81d77bf31eba7a55e40600fb Mon Sep 17 00:00:00 2001 From: Evan Pierce Brenner <108823789+epbrenner@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:38:35 -0600 Subject: [PATCH 10/17] Adding some test data metadata.parquet is generated from S. argenteus, and this parquet is being added for testing functions in this PR. --- data/metadata.parquet | Bin 0 -> 12161 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/metadata.parquet diff --git a/data/metadata.parquet b/data/metadata.parquet new file mode 100644 index 0000000000000000000000000000000000000000..ff600a0c82fd7296422e54ae7baf561659a328b0 GIT binary patch literal 12161 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 Date: Mon, 27 Jul 2026 17:22:10 -0600 Subject: [PATCH 11/17] update summary/plot functions - add custom colors based on other 2 packages - cleanup and sort results from summarization fn - cleaned up titles/labels in both functions - generated example PDF, MD, HTML - rm oneoff dependencies like treemap + unused kableExtra - consistency check between 'resistant isolates' vs. 'AMR isolates' vs. genomes in some cases. still needs checking. --- DESCRIPTION | 6 +- R/amRdataPlots.R | 151 ++-- R/utils_colors.R | 41 ++ data/plots/amRdata_exploratory_plots.pdf | Bin 0 -> 16237 bytes data/plots/amr_metadata_summary.html | 894 +++++++++++++++++++++++ data/plots/amr_metadata_summary.md | 149 ++++ 6 files changed, 1159 insertions(+), 82 deletions(-) create mode 100644 R/utils_colors.R create mode 100644 data/plots/amRdata_exploratory_plots.pdf create mode 100644 data/plots/amr_metadata_summary.html create mode 100644 data/plots/amr_metadata_summary.md diff --git a/DESCRIPTION b/DESCRIPTION index 71d2958..808ecb0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -34,18 +34,18 @@ Imports: data.table, dplyr, duckdb, + ggplot2, glue, + knitr, purrr, readr, reshape2, stringr, tibble, - tidyr, - treemapify + tidyr biocViews: GenomeAssembly, Annotation, Sequencing VignetteBuilder: knitr Suggests: - knitr, rmarkdown, testthat (>= 3.0.0) Config/roxygen2/version: 8.0.0 diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index b90e483..026789c 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -9,7 +9,6 @@ #' #' @import dplyr #' @import arrow -#' @import kableExtra #' #' @examples #' generateSummary( @@ -77,11 +76,14 @@ generateSummary <- function(metadata_parquet, out_path) { PhenotypeCount <- metadata |> dplyr::group_by(genome_drug.resistant_phenotype) |> dplyr::count() |> + dplyr::arrange(-n) |> dplyr::ungroup() + ## Stats by drugs PhenotypebyDrugCount <- metadata |> dplyr::group_by(genome_drug.resistant_phenotype, genome_drug.antibiotic) |> dplyr::count() |> + dplyr::arrange(-n) |> dplyr::ungroup() ResPropbyDrug <- metadata |> @@ -90,8 +92,10 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::mutate(prop = n / sum(n)) |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::transmute(genome_drug.antibiotic, res_prop = round(prop, 3)) |> + dplyr::arrange(-res_prop) |> dplyr::ungroup() + ## Stats by drug class PhenotypebyDrugClassCount <- metadata |> dplyr::group_by(genome.genome_id, drug_class) |> dplyr::filter(!(any(genome_drug.resistant_phenotype == "Resistant") & @@ -102,6 +106,7 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::ungroup() |> dplyr::group_by(genome_drug.resistant_phenotype, drug_class) |> dplyr::count() |> + dplyr::arrange(-n) |> dplyr::ungroup() ResPropbyDrugClass <- metadata |> @@ -110,8 +115,10 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::mutate(prop = n / sum(n)) |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::transmute(drug_class, res_prop = round(prop, 3)) |> + dplyr::arrange(-res_prop) |> dplyr::ungroup() + ## Collection years Year <- metadata |> dplyr::distinct(genome.collection_year) |> dplyr::filter(!is.na(genome.collection_year)) |> @@ -122,20 +129,25 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::group_by(genome.collection_year) |> dplyr::filter(!is.na(genome.collection_year)) |> dplyr::count() |> + dplyr::arrange(-n) |> dplyr::ungroup() + ## Geographical regions (countries) Country <- clean_distinct(metadata, genome.isolation_country) CountryCount <- metadata |> dplyr::group_by(genome.isolation_country) |> dplyr::filter(!is.na(genome.isolation_country), genome.isolation_country != "") |> dplyr::count() |> + dplyr::arrange(-n) |> dplyr::ungroup() + ## Isolation sources Source <- clean_distinct(metadata, genome.isolation_source) SourceCount <- metadata |> dplyr::group_by(genome.isolation_source) |> dplyr::filter(!is.na(genome.isolation_source), genome.isolation_source != "") |> dplyr::count() |> + dplyr::arrange(-n) |> dplyr::ungroup() Host <- clean_distinct(metadata, genome.host_common_name) @@ -147,11 +159,11 @@ generateSummary <- function(metadata_parquet, out_path) { append_lines( md_path, c( - sprintf("- **Entries**: %s", TotalEntryCount[[1]]), + sprintf("- **Total no. of observations**: %s", TotalEntryCount[[1]]), sprintf("- **Unique genome IDs**: %s", CleanEntryCount[[1]]), "", sprintf( - "- **Publications** (%d): %s", + "- **Associated publications** (%d): %s", length(PubMed_ids), if (length(PubMed_ids)) paste(PubMed_ids, collapse = ", ") else "None" ), @@ -176,13 +188,13 @@ generateSummary <- function(metadata_parquet, out_path) { # Tables! append_lines(md_path, c("## Phenotype counts", "", md_tbl(PhenotypeCount), "", "")) - append_lines(md_path, c("## Phenotype x antibiotic", "", md_tbl(PhenotypebyDrugCount), "", "")) - append_lines(md_path, c("## Resistant proportion per antibiotic", "", md_tbl(ResPropbyDrug), "", "")) - append_lines(md_path, c("## Phenotype x antibiotic class", "", md_tbl(PhenotypebyDrugClassCount), "", "")) + append_lines(md_path, c("## Phenotypes x antibiotic(s)", "", md_tbl(PhenotypebyDrugCount), "", "")) + append_lines(md_path, c("## Resistant proportions per antibiotic", "", md_tbl(ResPropbyDrug), "", "")) + append_lines(md_path, c("## Phenotypes x antibiotic class(es)", "", md_tbl(PhenotypebyDrugClassCount), "", "")) append_lines(md_path, c("## Resistant proportion per antibiotic class", "", md_tbl(ResPropbyDrugClass), "", "")) append_lines(md_path, c("## Laboratory methods", "", md_tbl(LabMethods), "", "")) - append_lines(md_path, c("## Year counts", "", md_tbl(YearCount), "", "")) - append_lines(md_path, c("## Country counts", "", md_tbl(CountryCount), "", "")) + append_lines(md_path, c("## Collection years", "", md_tbl(YearCount), "", "")) + append_lines(md_path, c("## Isolation countries", "", md_tbl(CountryCount), "", "")) append_lines(md_path, c("## Isolation sources", "", md_tbl(SourceCount), "", "")) # Hosts as a simple list @@ -194,21 +206,30 @@ generateSummary <- function(metadata_parquet, out_path) { #' Write all summary plots to file(s) #' +#' Expects `metadata_parquet` to be the output of `runDataProcessing()`'s +#' `cleanData()` step (or an export of the resulting `metadata` table), since +#' `drug_abbr`, `drug_class`, and `num_resistant_classes` are only populated +#' after that step joins in the reference drug tables. +#' #' @param metadata_parquet Character. Path to the Parquet metadata file. #' @param out_path Character. Output directory for plot files. #' -#' @return Invisibly returns a vector of pdf file paths written. +#' @return Invisibly returns the path to the written PDF (all plots as +#' separate pages of one multi-page file). #' @export generatePlots <- function(metadata_parquet, out_path) { - device <- "pdf" if (!dir.exists(out_path)) { dir.create(out_path, showWarnings = FALSE, recursive = TRUE) } metadata <- arrow::read_parquet(normalizePath(metadata_parquet)) - # --------- Build plots (same visuals as your generatePlots) ---------- + if (nrow(metadata) == 0) { + stop("The input metadata table is empty. Please check your query or input data.") + } + + ## Generate plots # 1) Phenotypes across antibiotics and time df_year <- metadata |> dplyr::filter(!is.na(genome.collection_year)) |> @@ -244,7 +265,7 @@ generatePlots <- function(metadata_parquet, x = "Year", y = "Number of isolates", colour = "Phenotype" ) + - ggplot2::scale_color_brewer(palette = "Pastel1") + + ggplot2::scale_color_manual(values = PHENOTYPE_COLORS, na.value = "gray70") + ggplot2::theme_minimal(base_size = 12) + ggplot2::theme( text = ggplot2::element_text(colour = "black"), @@ -255,50 +276,16 @@ generatePlots <- function(metadata_parquet, panel.grid.minor = ggplot2::element_blank() ) - p1 - # 2) Resistance only over time - - - # 1) Levels for antibiotics (from the same subset used in p2) + # 2) Resistance only over time, colored by antibiotic (named palette aligned + # to factor levels via meta_palette() so colors stay consistent across runs) abx_levels <- summary_year |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> dplyr::distinct(drug_abbr) |> dplyr::arrange(drug_abbr) |> dplyr::pull(drug_abbr) - # 2) Base Okabe–Ito (CVD-friendly) and pastelizer - okabe_ito_base <- c( - "#000000", # black - "#E69F00", # orange - "#56B4E9", # sky blue - "#009E73", # bluish green - "#F0E442", # yellow - "#0072B2", # blue - "#D55E00", # vermillion - "#CC79A7" # reddish purple - ) - - okabe_ito_pastel <- function(n, lighten = 0.15) { - # Interpolate if more than 8 needed - cols <- if (n <= length(okabe_ito_base)) { - okabe_ito_base[seq_len(n)] - } else { - grDevices::colorRampPalette(okabe_ito_base)(n) - } - to_rgb <- function(hex) grDevices::col2rgb(hex) / 255 - blend_with_white <- function(hex, a = lighten) { - rgb <- to_rgb(hex) - out <- (1 - a) * rgb + a * c(1, 1, 1) - grDevices::rgb(out[1], out[2], out[3]) - } - vapply(cols, blend_with_white, character(1), a = lighten) - } - - # 3) Build a NAMED palette aligned to factor levels - pal_vals <- okabe_ito_pastel(length(abx_levels), lighten = 0.15) - pal_named <- stats::setNames(pal_vals, abx_levels) + pal_named <- stats::setNames(meta_palette(length(abx_levels)), abx_levels) - # 4) Plot with factor levels + named palette (no warnings, distinct colors) p2 <- ggplot2::ggplot( summary_year |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> @@ -313,9 +300,9 @@ generatePlots <- function(metadata_parquet, ggplot2::geom_line() + ggplot2::geom_point() + ggplot2::labs( - title = "Distribution of resistance data over time", + title = "Distribution of AMR isolates over time", x = "Year", - y = "Number of resistant isolates", + y = "Number of AMR isolates", colour = "Antibiotic" ) + ggplot2::scale_color_manual(values = pal_named, drop = TRUE) + # <- named palette @@ -355,7 +342,7 @@ generatePlots <- function(metadata_parquet, ) + ggplot2::geom_point(alpha = 0.75) + ggplot2::scale_size(range = c(3, 15)) + - ggplot2::scale_color_viridis_d(option = "C", begin = 0.25, end = 0.95) + + ggplot2::scale_color_manual(values = PHENOTYPE_COLORS, na.value = "gray70") + ggplot2::labs( title = "AMR isolates across time and geography", x = "Year", y = "Country", @@ -378,10 +365,10 @@ generatePlots <- function(metadata_parquet, ggplot2::geom_bar(position = "fill") + ggplot2::coord_flip() + ggplot2::labs( - title = "Phenotype proportion per antibiotic", + title = "AMR proportion per antibiotic", x = "Antibiotic", y = "Proportion", fill = "Phenotype" ) + - ggplot2::scale_fill_brewer(palette = "Pastel1") + # <- keep pastel + ggplot2::scale_fill_manual(values = PHENOTYPE_COLORS, na.value = "gray70") + ggplot2::theme_minimal(base_size = 12) + ggplot2::theme( text = ggplot2::element_text(colour = "black"), @@ -389,33 +376,43 @@ generatePlots <- function(metadata_parquet, axis.text.x = ggplot2::element_text(angle = 45, hjust = 1) ) - # 5) Treemap of isolation sources + # 5) Resistant isolates by isolation source (top 10 sources + Other, same + # convention as amRviz's makeIsolationSourcesPlot) summary_isolation_source <- metadata |> dplyr::filter(genome.isolation_source != "") |> dplyr::group_by(genome.isolation_source, genome_drug.resistant_phenotype) |> dplyr::summarise(count = dplyr::n(), .groups = "drop") |> dplyr::filter(genome_drug.resistant_phenotype == "Resistant") + top_isolation_sources <- summary_isolation_source |> + dplyr::slice_max(order_by = count, n = 10) |> + dplyr::pull(genome.isolation_source) + + summary_isolation_source <- summary_isolation_source |> + dplyr::mutate( + isolation_source_grp = ifelse( + genome.isolation_source %in% top_isolation_sources, + genome.isolation_source, + "Other" + ) + ) + + n_sources <- dplyr::n_distinct(summary_isolation_source$isolation_source_grp) + p5 <- ggplot2::ggplot( summary_isolation_source, - ggplot2::aes(area = count, fill = genome.isolation_source) + ggplot2::aes( + x = stats::reorder(isolation_source_grp, count), + y = count, fill = isolation_source_grp + ) ) + - treemapify::geom_treemap() + - # treemapify::geom_treemap_text( - # ggplot2::aes(label = genome_drug.resistant_phenotype), - # color = "grey15", grow = FALSE - # ) + - treemapify::geom_treemap_text( - ggplot2::aes(label = genome.isolation_source), - color = "grey15", grow = FALSE - ) + + ggplot2::geom_col() + + ggplot2::coord_flip() + ggplot2::labs( - title = "Distribution of Resistant isolates by isolation source", - fill = "Isolation source" - ) + - ggplot2::scale_fill_manual( - values = colorRampPalette(RColorBrewer::brewer.pal(8, "Pastel2"))(dplyr::n_distinct(summary_isolation_source$genome.isolation_source)) + title = "Distribution of AMR isolates by isolation source", + x = "Isolation source", y = "Number of AMR isolates" ) + + ggplot2::scale_fill_manual(values = meta_palette(n_sources)) + ggplot2::theme_minimal(base_size = 12) + ggplot2::theme( text = ggplot2::element_text(colour = "black"), @@ -424,9 +421,9 @@ generatePlots <- function(metadata_parquet, # 6) Histogram of resistant classes per genome p6 <- ggplot2::ggplot(metadata, ggplot2::aes(num_resistant_classes)) + - ggplot2::geom_histogram(binwidth = 1, fill = "steelblue") + + ggplot2::geom_histogram(binwidth = 1, fill = META_COLORS[[1]]) + ggplot2::labs( - title = "Distribution of resistant classes per genome", + title = "Distribution of AMR classes per isolate", x = "# Resistant Classes", y = "Count" ) + ggplot2::theme_minimal(base_size = 12) + @@ -440,17 +437,13 @@ generatePlots <- function(metadata_parquet, plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) - # --------- Write to device ---------- - paths <- character(0) - - pdf_path <- file.path(out_path, paste0("amRdata_exploratory_plots.pdf")) + ## Write to device + pdf_path <- file.path(out_path, "amRdata_exploratory_plots.pdf") grDevices::pdf(pdf_path, onefile = TRUE) on.exit(grDevices::dev.off(), add = TRUE) for (nm in names(plots)) { print(plots[[nm]]) } - paths <- c(paths, pdf_path) - - invisible(paths) + invisible(pdf_path) } diff --git a/R/utils_colors.R b/R/utils_colors.R new file mode 100644 index 0000000..aa6a917 --- /dev/null +++ b/R/utils_colors.R @@ -0,0 +1,41 @@ +# Colour palettes shared across the amR suite's plots (kept in sync with +# amRviz's R/utils_colors.R so reports and the dashboard read as one system). + +# AMR phenotype palette (R/S/I plus full-word + lowercase variants so it works +# regardless of how the column is encoded). Susceptible is intentionally +# neutral grey so Resistant amber stands out as the signal of interest. +PHENOTYPE_COLORS <- c( + R = "#d4872a", Resistant = "#d4872a", resistant = "#d4872a", + S = "#8a8a8a", Susceptible = "#8a8a8a", susceptible = "#8a8a8a", + I = "#e6ab80", Intermediate = "#e6ab80", intermediate = "#e6ab80" +) + +# Categorical palette for metadata plots (hosts, isolation sources, etc.). +# Use `meta_palette(n)` at call sites rather than referencing META_COLORS +# directly, so plots handle more categories than length(META_COLORS) without +# falling back to NA / gray70. +META_COLORS <- c( + "#4a6b8a", "#87ceeb", "#e6ab80", "#8b6b7a", + "#4e9a9a", "#c4a35a", "#9b7fba", "#5b8db8", + "#d4735e", "#d4872a", "#b5b5b5" +) + +#' Categorical palette of `n` muted colors +#' +#' For `n <= length(META_COLORS)` returns the first `n` curated colors verbatim; +#' above that, interpolates via `grDevices::colorRampPalette()` so callers get a +#' full palette regardless of how many unique values their data has. +#' +#' @param n Number of colors to return. +#' @return A character vector of `n` hex colors (empty when `n <= 0`). +#' @keywords internal +#' @noRd +meta_palette <- function(n = length(META_COLORS)) { + if (n <= 0) { + return(character(0)) + } + if (n <= length(META_COLORS)) { + return(META_COLORS[seq_len(n)]) + } + grDevices::colorRampPalette(META_COLORS)(n) +} diff --git a/data/plots/amRdata_exploratory_plots.pdf b/data/plots/amRdata_exploratory_plots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a862fb9ec468baac82ed091ea75b3d262e889606 GIT binary patch literal 16237 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 + + + + + + + + + + + + +amr_metadata_summary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+

AMR summary report

+
    +
  • Total no. of observations: 723

  • +
  • Unique genome IDs: 73

  • +
  • Associated publications (2): 26806258, +34100643

  • +
+
+

Antibiotics (16)

+

cefoxitin, chloramphenicol, ciprofloxacin, clindamycin, daptomycin, +erythromycin, fosfomycin, fusidic_acid, gentamicin, levofloxacin, +linezolid, oxacillin, penicillin, tetracycline, +trimethoprim-sulfamethoxazole, vancomycin

+
+
+

Antibiotic classes (14)

+

aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, +fusidane, glycopeptides, lincosamides, lipopeptides, macrolides, +oxazolidinones, penicillins, phosphonics, tetracyclines, +trimethoprim-sulfonamides

+
+
+

Phenotype counts

+ + + + + + + + + + + + + + + + + +
genome_drug.resistant_phenotypen
Susceptible633
Resistant90
+
+
+

Phenotypes x antibiotic(s)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
genome_drug.resistant_phenotypegenome_drug.antibioticn
Susceptiblegentamicin73
Susceptiblecefoxitin72
Susceptibleoxacillin72
Susceptibletrimethoprim-sulfamethoxazole72
Resistantpenicillin69
Susceptibleclindamycin68
Susceptiblefusidic_acid68
Susceptibleerythromycin67
Susceptiblefosfomycin58
Susceptiblelevofloxacin58
Resistanterythromycin5
Susceptibledaptomycin5
Susceptiblelinezolid5
Susceptiblevancomycin5
Resistantclindamycin4
Susceptiblepenicillin4
Resistantchloramphenicol3
Resistantciprofloxacin3
Resistanttetracycline3
Susceptiblechloramphenicol2
Susceptibleciprofloxacin2
Susceptibletetracycline2
Resistantcefoxitin1
Resistantoxacillin1
Resistanttrimethoprim-sulfamethoxazole1
+
+
+

Resistant proportions per antibiotic

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
genome_drug.antibioticres_prop
penicillin0.945
chloramphenicol0.600
ciprofloxacin0.600
tetracycline0.600
erythromycin0.069
clindamycin0.056
cefoxitin0.014
oxacillin0.014
trimethoprim-sulfamethoxazole0.014
+
+
+

Phenotypes x antibiotic class(es)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
genome_drug.resistant_phenotypedrug_classn
Susceptibleaminoglycosides73
Susceptiblecephalosporins72
Susceptibletrimethoprim-sulfonamides72
Resistantpenicillins69
Susceptiblefusidane68
Susceptiblelincosamides68
Susceptiblemacrolides67
Susceptiblefluoroquinolones60
Susceptiblephosphonics58
Resistantmacrolides5
Susceptibleglycopeptides5
Susceptiblelipopeptides5
Susceptibleoxazolidinones5
Resistantlincosamides4
Susceptiblepenicillins4
Resistantamphenicols3
Resistantfluoroquinolones3
Resistanttetracyclines3
Susceptibleamphenicols2
Susceptibletetracyclines2
Resistantcephalosporins1
Resistanttrimethoprim-sulfonamides1
+
+
+

Resistant proportion per antibiotic class

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
drug_classres_prop
amphenicols0.600
tetracyclines0.600
penicillins0.479
macrolides0.069
lincosamides0.056
fluoroquinolones0.048
cephalosporins0.014
trimethoprim-sulfonamides0.014
+
+
+

Laboratory methods

+ + + + + + + + + + + + + + + + + +
genome_drug.laboratory_typing_methodn
658
Biofosun Gram-positive panels broth dilution65
+
+
+

Collection years

+ + + + + + + + + + + + + + + + + + + + + +
genome.collection_yearn
2015671
201739
201813
+
+
+

Isolation countries

+ + + + + + + + + + + + + +
genome.isolation_countryn
Thailand658
+
+
+

Isolation sources

+ + + + + + + + + + + + + + + + + +
genome.isolation_sourcen
pork39
duck26
+
+
+

Hosts

+
    +
  • Duck
  • +
  • Human
  • +
+
+
+ + + + +
+ + + + + + + + + + + + + + + diff --git a/data/plots/amr_metadata_summary.md b/data/plots/amr_metadata_summary.md new file mode 100644 index 0000000..11ef028 --- /dev/null +++ b/data/plots/amr_metadata_summary.md @@ -0,0 +1,149 @@ +# AMR summary report + +- **Total no. of observations**: 723 +- **Unique genome IDs**: 73 + +- **Associated publications** (2): 26806258, 34100643 + +## Antibiotics (16) + +cefoxitin, chloramphenicol, ciprofloxacin, clindamycin, daptomycin, erythromycin, fosfomycin, fusidic_acid, gentamicin, levofloxacin, linezolid, oxacillin, penicillin, tetracycline, trimethoprim-sulfamethoxazole, vancomycin + +## Antibiotic classes (14) + +aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, fusidane, glycopeptides, lincosamides, lipopeptides, macrolides, oxazolidinones, penicillins, phosphonics, tetracyclines, trimethoprim-sulfonamides + +## Phenotype counts + +|genome_drug.resistant_phenotype | n| +|:-------------------------------|---:| +|Susceptible | 633| +|Resistant | 90| + + +## Phenotypes x antibiotic(s) + +|genome_drug.resistant_phenotype |genome_drug.antibiotic | n| +|:-------------------------------|:-----------------------------|--:| +|Susceptible |gentamicin | 73| +|Susceptible |cefoxitin | 72| +|Susceptible |oxacillin | 72| +|Susceptible |trimethoprim-sulfamethoxazole | 72| +|Resistant |penicillin | 69| +|Susceptible |clindamycin | 68| +|Susceptible |fusidic_acid | 68| +|Susceptible |erythromycin | 67| +|Susceptible |fosfomycin | 58| +|Susceptible |levofloxacin | 58| +|Resistant |erythromycin | 5| +|Susceptible |daptomycin | 5| +|Susceptible |linezolid | 5| +|Susceptible |vancomycin | 5| +|Resistant |clindamycin | 4| +|Susceptible |penicillin | 4| +|Resistant |chloramphenicol | 3| +|Resistant |ciprofloxacin | 3| +|Resistant |tetracycline | 3| +|Susceptible |chloramphenicol | 2| +|Susceptible |ciprofloxacin | 2| +|Susceptible |tetracycline | 2| +|Resistant |cefoxitin | 1| +|Resistant |oxacillin | 1| +|Resistant |trimethoprim-sulfamethoxazole | 1| + + +## Resistant proportions per antibiotic + +|genome_drug.antibiotic | res_prop| +|:-----------------------------|--------:| +|penicillin | 0.945| +|chloramphenicol | 0.600| +|ciprofloxacin | 0.600| +|tetracycline | 0.600| +|erythromycin | 0.069| +|clindamycin | 0.056| +|cefoxitin | 0.014| +|oxacillin | 0.014| +|trimethoprim-sulfamethoxazole | 0.014| + + +## Phenotypes x antibiotic class(es) + +|genome_drug.resistant_phenotype |drug_class | n| +|:-------------------------------|:-------------------------|--:| +|Susceptible |aminoglycosides | 73| +|Susceptible |cephalosporins | 72| +|Susceptible |trimethoprim-sulfonamides | 72| +|Resistant |penicillins | 69| +|Susceptible |fusidane | 68| +|Susceptible |lincosamides | 68| +|Susceptible |macrolides | 67| +|Susceptible |fluoroquinolones | 60| +|Susceptible |phosphonics | 58| +|Resistant |macrolides | 5| +|Susceptible |glycopeptides | 5| +|Susceptible |lipopeptides | 5| +|Susceptible |oxazolidinones | 5| +|Resistant |lincosamides | 4| +|Susceptible |penicillins | 4| +|Resistant |amphenicols | 3| +|Resistant |fluoroquinolones | 3| +|Resistant |tetracyclines | 3| +|Susceptible |amphenicols | 2| +|Susceptible |tetracyclines | 2| +|Resistant |cephalosporins | 1| +|Resistant |trimethoprim-sulfonamides | 1| + + +## Resistant proportion per antibiotic class + +|drug_class | res_prop| +|:-------------------------|--------:| +|amphenicols | 0.600| +|tetracyclines | 0.600| +|penicillins | 0.479| +|macrolides | 0.069| +|lincosamides | 0.056| +|fluoroquinolones | 0.048| +|cephalosporins | 0.014| +|trimethoprim-sulfonamides | 0.014| + + +## Laboratory methods + +|genome_drug.laboratory_typing_method | n| +|:--------------------------------------------|---:| +| | 658| +|Biofosun Gram-positive panels broth dilution | 65| + + +## Collection years + +| genome.collection_year| n| +|----------------------:|---:| +| 2015| 671| +| 2017| 39| +| 2018| 13| + + +## Isolation countries + +|genome.isolation_country | n| +|:------------------------|---:| +|Thailand | 658| + + +## Isolation sources + +|genome.isolation_source | n| +|:-----------------------|--:| +|pork | 39| +|duck | 26| + + +## Hosts + +- Duck +- Human + + From f7cd204c18b177ca0706dbdfa9b9037242c5fca4 Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:12:29 -0600 Subject: [PATCH 12/17] Add AMR summary report for Staphylococcus argenteus - Include total observations, unique genome IDs, and associated publications. - Detail antibiotics and antibiotic classes with counts. - Present phenotype counts and resistant proportions per antibiotic and class. - Summarize laboratory methods, collection years, isolation countries, sources, and hosts. --- R/amRdataPlots.R | 45 ++++++++- R/utils_colors.R | 2 +- data/amRdata_exploratory_plots.pdf | Bin 0 -> 17875 bytes data/amr_metadata_summary.md | 150 +++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 data/amRdata_exploratory_plots.pdf create mode 100644 data/amr_metadata_summary.md diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 026789c..69664b4 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -57,6 +57,11 @@ generateSummary <- function(metadata_parquet, out_path) { stop("The output table is empty. Please check your query or input data.") } + # get Species name + Species_name <- metadata |> + dplyr::distinct(genome.species) |> + dplyr::pull() + # Core summaries TotalEntryCount <- metadata |> dplyr::count() CleanEntryCount <- metadata |> @@ -151,9 +156,18 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::ungroup() Host <- clean_distinct(metadata, genome.host_common_name) + HostCount <- metadata |> + dplyr::group_by(genome.host_common_name) |> + dplyr::filter(!is.na(genome.host_common_name), genome.host_common_name != "") |> + dplyr::count() |> + dplyr::arrange(-n) |> + dplyr::ungroup() # Header - write_new(md_path, "# AMR summary report\n") + write_new( + md_path, + sprintf("# AMR summary report for *%s*", Species_name) +) # Basic stats append_lines( @@ -196,11 +210,13 @@ generateSummary <- function(metadata_parquet, out_path) { append_lines(md_path, c("## Collection years", "", md_tbl(YearCount), "", "")) append_lines(md_path, c("## Isolation countries", "", md_tbl(CountryCount), "", "")) append_lines(md_path, c("## Isolation sources", "", md_tbl(SourceCount), "", "")) + append_lines(md_path, c("## Hosts", "", md_tbl(HostCount), "", "")) + # Hosts as a simple list - if (length(Host)) { - append_lines(md_path, c("## Hosts", "", paste0("- ", Host), "", "")) - } + # if (length(Host)) { + # append_lines(md_path, c("## Hosts", "", paste0("- ", Host), "", "")) + # } } @@ -218,7 +234,8 @@ generateSummary <- function(metadata_parquet, out_path) { #' separate pages of one multi-page file). #' @export generatePlots <- function(metadata_parquet, - out_path) { + out_path + ) { if (!dir.exists(out_path)) { dir.create(out_path, showWarnings = FALSE, recursive = TRUE) } @@ -437,6 +454,13 @@ generatePlots <- function(metadata_parquet, plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) + # Table for drug name - abbreviation mapping +drug_table <- metadata |> + dplyr::distinct(genome_drug.antibiotic, drug_abbr) |> + dplyr::rename("Abbreviation" = "drug_abbr", + "Antibiotic" = "genome_drug.antibiotic") |> + dplyr::arrange(Antibiotic) + ## Write to device pdf_path <- file.path(out_path, "amRdata_exploratory_plots.pdf") grDevices::pdf(pdf_path, onefile = TRUE) @@ -444,6 +468,17 @@ generatePlots <- function(metadata_parquet, for (nm in names(plots)) { print(plots[[nm]]) } +# grid::grid.newpage() + +gridExtra::grid.arrange( + grid::textGrob( + "Antibiotic name abbreviations", + gp = grid::gpar(fontsize = 16, fontface = "bold") + ), + gridExtra::tableGrob(drug_table, rows = NULL), + ncol = 1, + heights = c(0.08, 0.92) +) invisible(pdf_path) } diff --git a/R/utils_colors.R b/R/utils_colors.R index aa6a917..7c4a25e 100644 --- a/R/utils_colors.R +++ b/R/utils_colors.R @@ -6,7 +6,7 @@ # neutral grey so Resistant amber stands out as the signal of interest. PHENOTYPE_COLORS <- c( R = "#d4872a", Resistant = "#d4872a", resistant = "#d4872a", - S = "#8a8a8a", Susceptible = "#8a8a8a", susceptible = "#8a8a8a", + S = "#5b8db8", Susceptible = "#5b8db8", susceptible = "#5b8db8", I = "#e6ab80", Intermediate = "#e6ab80", intermediate = "#e6ab80" ) diff --git a/data/amRdata_exploratory_plots.pdf b/data/amRdata_exploratory_plots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f54769b808ac222aa362712c4ec5a07722595a71 GIT binary patch literal 17875 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 literal 0 HcmV?d00001 diff --git a/data/amr_metadata_summary.md b/data/amr_metadata_summary.md new file mode 100644 index 0000000..544e105 --- /dev/null +++ b/data/amr_metadata_summary.md @@ -0,0 +1,150 @@ +# AMR summary report for *Staphylococcus argenteus* +- **Total no. of observations**: 723 +- **Unique genome IDs**: 73 + +- **Associated publications** (2): 26806258, 34100643 + +## Antibiotics (16) + +cefoxitin, chloramphenicol, ciprofloxacin, clindamycin, daptomycin, erythromycin, fosfomycin, fusidic_acid, gentamicin, levofloxacin, linezolid, oxacillin, penicillin, tetracycline, trimethoprim-sulfamethoxazole, vancomycin + +## Antibiotic classes (14) + +aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, fusidane, glycopeptides, lincosamides, lipopeptides, macrolides, oxazolidinones, penicillins, phosphonics, tetracyclines, trimethoprim-sulfonamides + +## Phenotype counts + +|genome_drug.resistant_phenotype | n| +|:-------------------------------|---:| +|Susceptible | 633| +|Resistant | 90| + + +## Phenotypes x antibiotic(s) + +|genome_drug.resistant_phenotype |genome_drug.antibiotic | n| +|:-------------------------------|:-----------------------------|--:| +|Susceptible |gentamicin | 73| +|Susceptible |cefoxitin | 72| +|Susceptible |oxacillin | 72| +|Susceptible |trimethoprim-sulfamethoxazole | 72| +|Resistant |penicillin | 69| +|Susceptible |clindamycin | 68| +|Susceptible |fusidic_acid | 68| +|Susceptible |erythromycin | 67| +|Susceptible |fosfomycin | 58| +|Susceptible |levofloxacin | 58| +|Resistant |erythromycin | 5| +|Susceptible |daptomycin | 5| +|Susceptible |linezolid | 5| +|Susceptible |vancomycin | 5| +|Resistant |clindamycin | 4| +|Susceptible |penicillin | 4| +|Resistant |chloramphenicol | 3| +|Resistant |ciprofloxacin | 3| +|Resistant |tetracycline | 3| +|Susceptible |chloramphenicol | 2| +|Susceptible |ciprofloxacin | 2| +|Susceptible |tetracycline | 2| +|Resistant |cefoxitin | 1| +|Resistant |oxacillin | 1| +|Resistant |trimethoprim-sulfamethoxazole | 1| + + +## Resistant proportions per antibiotic + +|genome_drug.antibiotic | res_prop| +|:-----------------------------|--------:| +|penicillin | 0.945| +|chloramphenicol | 0.600| +|ciprofloxacin | 0.600| +|tetracycline | 0.600| +|erythromycin | 0.069| +|clindamycin | 0.056| +|cefoxitin | 0.014| +|oxacillin | 0.014| +|trimethoprim-sulfamethoxazole | 0.014| + + +## Phenotypes x antibiotic class(es) + +|genome_drug.resistant_phenotype |drug_class | n| +|:-------------------------------|:-------------------------|--:| +|Susceptible |aminoglycosides | 73| +|Susceptible |cephalosporins | 72| +|Susceptible |trimethoprim-sulfonamides | 72| +|Resistant |penicillins | 69| +|Susceptible |fusidane | 68| +|Susceptible |lincosamides | 68| +|Susceptible |macrolides | 67| +|Susceptible |fluoroquinolones | 60| +|Susceptible |phosphonics | 58| +|Resistant |macrolides | 5| +|Susceptible |glycopeptides | 5| +|Susceptible |lipopeptides | 5| +|Susceptible |oxazolidinones | 5| +|Resistant |lincosamides | 4| +|Susceptible |penicillins | 4| +|Resistant |amphenicols | 3| +|Resistant |fluoroquinolones | 3| +|Resistant |tetracyclines | 3| +|Susceptible |amphenicols | 2| +|Susceptible |tetracyclines | 2| +|Resistant |cephalosporins | 1| +|Resistant |trimethoprim-sulfonamides | 1| + + +## Resistant proportion per antibiotic class + +|drug_class | res_prop| +|:-------------------------|--------:| +|amphenicols | 0.600| +|tetracyclines | 0.600| +|penicillins | 0.479| +|macrolides | 0.069| +|lincosamides | 0.056| +|fluoroquinolones | 0.048| +|cephalosporins | 0.014| +|trimethoprim-sulfonamides | 0.014| + + +## Laboratory methods + +|genome_drug.laboratory_typing_method | n| +|:--------------------------------------------|---:| +| | 658| +|Biofosun Gram-positive panels broth dilution | 65| + + +## Collection years + +| genome.collection_year| n| +|----------------------:|---:| +| 2015| 671| +| 2017| 39| +| 2018| 13| + + +## Isolation countries + +|genome.isolation_country | n| +|:------------------------|---:| +|Thailand | 658| + + +## Isolation sources + +|genome.isolation_source | n| +|:-----------------------|--:| +|pork | 39| +|duck | 26| + + +## Hosts + +|genome.host_common_name | n| +|:-----------------------|---:| +|Human | 658| +|Duck | 26| + + From 3f26a76a2edc52583266696d52dd2b898d144b5e Mon Sep 17 00:00:00 2001 From: AbhirupaGhosh Date: Tue, 28 Jul 2026 00:14:39 +0000 Subject: [PATCH 13/17] Style code (GHA) --- R/amRdataPlots.R | 49 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 69664b4..a96c544 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -58,8 +58,8 @@ generateSummary <- function(metadata_parquet, out_path) { } # get Species name - Species_name <- metadata |> - dplyr::distinct(genome.species) |> + Species_name <- metadata |> + dplyr::distinct(genome.species) |> dplyr::pull() # Core summaries @@ -165,9 +165,9 @@ generateSummary <- function(metadata_parquet, out_path) { # Header write_new( - md_path, - sprintf("# AMR summary report for *%s*", Species_name) -) + md_path, + sprintf("# AMR summary report for *%s*", Species_name) + ) # Basic stats append_lines( @@ -234,8 +234,7 @@ generateSummary <- function(metadata_parquet, out_path) { #' separate pages of one multi-page file). #' @export generatePlots <- function(metadata_parquet, - out_path - ) { + out_path) { if (!dir.exists(out_path)) { dir.create(out_path, showWarnings = FALSE, recursive = TRUE) } @@ -454,12 +453,14 @@ generatePlots <- function(metadata_parquet, plots <- list(p1 = p1, p2 = p2, p3 = p3, p4 = p4, p5 = p5, p6 = p6) - # Table for drug name - abbreviation mapping -drug_table <- metadata |> - dplyr::distinct(genome_drug.antibiotic, drug_abbr) |> - dplyr::rename("Abbreviation" = "drug_abbr", - "Antibiotic" = "genome_drug.antibiotic") |> - dplyr::arrange(Antibiotic) + # Table for drug name - abbreviation mapping + drug_table <- metadata |> + dplyr::distinct(genome_drug.antibiotic, drug_abbr) |> + dplyr::rename( + "Abbreviation" = "drug_abbr", + "Antibiotic" = "genome_drug.antibiotic" + ) |> + dplyr::arrange(Antibiotic) ## Write to device pdf_path <- file.path(out_path, "amRdata_exploratory_plots.pdf") @@ -468,17 +469,17 @@ drug_table <- metadata |> for (nm in names(plots)) { print(plots[[nm]]) } -# grid::grid.newpage() - -gridExtra::grid.arrange( - grid::textGrob( - "Antibiotic name abbreviations", - gp = grid::gpar(fontsize = 16, fontface = "bold") - ), - gridExtra::tableGrob(drug_table, rows = NULL), - ncol = 1, - heights = c(0.08, 0.92) -) + # grid::grid.newpage() + + gridExtra::grid.arrange( + grid::textGrob( + "Antibiotic name abbreviations", + gp = grid::gpar(fontsize = 16, fontface = "bold") + ), + gridExtra::tableGrob(drug_table, rows = NULL), + ncol = 1, + heights = c(0.08, 0.92) + ) invisible(pdf_path) } From a5b4c767b0cbda1101019c921564db61b296a2a5 Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:21:42 -0600 Subject: [PATCH 14/17] Update plot titles for clarity in AMR data visualizations --- R/amRdataPlots.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 69664b4..0d11717 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -278,7 +278,7 @@ generatePlots <- function(metadata_parquet, ggplot2::geom_point() + ggplot2::facet_wrap(~drug_abbr, scales = "free_y") + ggplot2::labs( - title = "Resistant phenotypes across antibiotics and time", + title = "Distribution of AMR phenotypes across antibiotics by year", x = "Year", y = "Number of isolates", colour = "Phenotype" ) + @@ -317,7 +317,7 @@ generatePlots <- function(metadata_parquet, ggplot2::geom_line() + ggplot2::geom_point() + ggplot2::labs( - title = "Distribution of AMR isolates over time", + title = "Distribution of resistant isolates by year", x = "Year", y = "Number of AMR isolates", colour = "Antibiotic" @@ -361,7 +361,7 @@ generatePlots <- function(metadata_parquet, ggplot2::scale_size(range = c(3, 15)) + ggplot2::scale_color_manual(values = PHENOTYPE_COLORS, na.value = "gray70") + ggplot2::labs( - title = "AMR isolates across time and geography", + title = "Distribution of AMR phenotype by year and geography", x = "Year", y = "Country", size = "Count", color = "Phenotype" ) + @@ -382,7 +382,7 @@ generatePlots <- function(metadata_parquet, ggplot2::geom_bar(position = "fill") + ggplot2::coord_flip() + ggplot2::labs( - title = "AMR proportion per antibiotic", + title = "Distribution of AMR phenotypes", x = "Antibiotic", y = "Proportion", fill = "Phenotype" ) + ggplot2::scale_fill_manual(values = PHENOTYPE_COLORS, na.value = "gray70") + From c758dce61d46812d3a5731cf1baf3a0018cfb00f Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Tue, 28 Jul 2026 09:29:31 -0600 Subject: [PATCH 15/17] Fix laboratory typing method categorization in summary report --- R/amRdataPlots.R | 11 ++++++++--- data/amr_metadata_summary.md | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index a2f378e..248e9ec 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -72,9 +72,14 @@ generateSummary <- function(metadata_parquet, out_path) { AntibioticClasses <- clean_distinct(metadata, drug_class) LabMethods <- metadata |> - dplyr::group_by(genome_drug.laboratory_typing_method) |> - dplyr::count() |> - dplyr::ungroup() + dplyr::mutate( + genome_drug.laboratory_typing_method = dplyr::case_when( + is.na(genome_drug.laboratory_typing_method) ~ "Not defined", + genome_drug.laboratory_typing_method == "" ~ "Not defined", + TRUE ~ genome_drug.laboratory_typing_method + ) + ) |> + dplyr::count(genome_drug.laboratory_typing_method) PubMed_ids <- clean_distinct(metadata, genome_drug.pmid) diff --git a/data/amr_metadata_summary.md b/data/amr_metadata_summary.md index 544e105..7473cab 100644 --- a/data/amr_metadata_summary.md +++ b/data/amr_metadata_summary.md @@ -112,8 +112,8 @@ aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, fusidane, glycop |genome_drug.laboratory_typing_method | n| |:--------------------------------------------|---:| -| | 658| |Biofosun Gram-positive panels broth dilution | 65| +|Not defined | 658| ## Collection years From e6130f7dbe5a5598a3fb741b03bb963668eabbab Mon Sep 17 00:00:00 2001 From: Emily Boyer Date: Tue, 28 Jul 2026 10:15:16 -0600 Subject: [PATCH 16/17] Add missing gridExtra/grid deps, regenerate NAMESPACE/man, fix indentation --- DESCRIPTION | 2 ++ NAMESPACE | 2 ++ R/amRdataPlots.R | 19 ++++++++----------- man/generatePlots.Rd | 23 +++++++++++++++++++++++ man/generateSummary.Rd | 28 ++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 man/generatePlots.Rd create mode 100644 man/generateSummary.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 808ecb0..f3ec63d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -36,6 +36,8 @@ Imports: duckdb, ggplot2, glue, + grid, + gridExtra, knitr, purrr, readr, diff --git a/NAMESPACE b/NAMESPACE index ff6d425..69af5ae 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,6 +6,8 @@ export(CDHIT2duckdb) export(buildClusterFeatureMap) export(cleanData) export(cleanMetaData) +export(generatePlots) +export(generateSummary) export(prepareGenomes) export(proteinAnnotations2Duckdb) export(retrieveGenomes) diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 248e9ec..950a3fb 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -7,9 +7,6 @@ #' @return Writes a structured, human‑readable summary report to #' "/amr_metadata_summary.md". #' -#' @import dplyr -#' @import arrow -#' #' @examples #' generateSummary( #' metadata_parquet = "results/metadata.parquet", @@ -72,14 +69,14 @@ generateSummary <- function(metadata_parquet, out_path) { AntibioticClasses <- clean_distinct(metadata, drug_class) LabMethods <- metadata |> - dplyr::mutate( - genome_drug.laboratory_typing_method = dplyr::case_when( - is.na(genome_drug.laboratory_typing_method) ~ "Not defined", - genome_drug.laboratory_typing_method == "" ~ "Not defined", - TRUE ~ genome_drug.laboratory_typing_method - ) - ) |> - dplyr::count(genome_drug.laboratory_typing_method) + dplyr::mutate( + genome_drug.laboratory_typing_method = dplyr::case_when( + is.na(genome_drug.laboratory_typing_method) ~ "Not defined", + genome_drug.laboratory_typing_method == "" ~ "Not defined", + TRUE ~ genome_drug.laboratory_typing_method + ) + ) |> + dplyr::count(genome_drug.laboratory_typing_method) PubMed_ids <- clean_distinct(metadata, genome_drug.pmid) diff --git a/man/generatePlots.Rd b/man/generatePlots.Rd new file mode 100644 index 0000000..82845a2 --- /dev/null +++ b/man/generatePlots.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/amRdataPlots.R +\name{generatePlots} +\alias{generatePlots} +\title{Write all summary plots to file(s)} +\usage{ +generatePlots(metadata_parquet, out_path) +} +\arguments{ +\item{metadata_parquet}{Character. Path to the Parquet metadata file.} + +\item{out_path}{Character. Output directory for plot files.} +} +\value{ +Invisibly returns the path to the written PDF (all plots as +separate pages of one multi-page file). +} +\description{ +Expects \code{metadata_parquet} to be the output of \code{runDataProcessing()}'s +\code{cleanData()} step (or an export of the resulting \code{metadata} table), since +\code{drug_abbr}, \code{drug_class}, and \code{num_resistant_classes} are only populated +after that step joins in the reference drug tables. +} diff --git a/man/generateSummary.Rd b/man/generateSummary.Rd new file mode 100644 index 0000000..bbe49ef --- /dev/null +++ b/man/generateSummary.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/amRdataPlots.R +\name{generateSummary} +\alias{generateSummary} +\title{Generate a summary report for AMR metadata} +\usage{ +generateSummary(metadata_parquet, out_path) +} +\arguments{ +\item{metadata_parquet}{Character string. Path to a Parquet file containing +standardized AMR metadata.} + +\item{out_path}{Character string. Directory where the Markdown report is written.} +} +\value{ +Writes a structured, human‑readable summary report to +"/amr_metadata_summary.md". +} +\description{ +Generate a summary report for AMR metadata +} +\examples{ +generateSummary( + metadata_parquet = "results/metadata.parquet", + out_path = "results/" +) + +} From 1a28565fccd001b5d8a99b4f984fdabff9caadbb Mon Sep 17 00:00:00 2001 From: Abhirupa Ghosh <100681585+AbhirupaGhosh@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:49:42 -0600 Subject: [PATCH 17/17] Update drug class phenotype count and Remove AMR summary report markdown file due to redundancy in documentation. --- R/amRdataPlots.R | 48 +- data/amr_metadata_summary.md | 2 +- data/plots/amRdata_exploratory_plots.pdf | Bin 16237 -> 0 bytes data/plots/amr_metadata_summary.html | 894 ----------------------- data/plots/amr_metadata_summary.md | 149 ---- 5 files changed, 27 insertions(+), 1066 deletions(-) delete mode 100644 data/plots/amRdata_exploratory_plots.pdf delete mode 100644 data/plots/amr_metadata_summary.html delete mode 100644 data/plots/amr_metadata_summary.md diff --git a/R/amRdataPlots.R b/R/amRdataPlots.R index 950a3fb..7cf3f63 100644 --- a/R/amRdataPlots.R +++ b/R/amRdataPlots.R @@ -102,29 +102,33 @@ generateSummary <- function(metadata_parquet, out_path) { dplyr::arrange(-res_prop) |> dplyr::ungroup() - ## Stats by drug class - PhenotypebyDrugClassCount <- metadata |> - dplyr::group_by(genome.genome_id, drug_class) |> - dplyr::filter(!(any(genome_drug.resistant_phenotype == "Resistant") & - genome_drug.resistant_phenotype == "Susceptible")) |> - dplyr::ungroup() |> - dplyr::group_by(genome.genome_id, drug_class) |> - dplyr::slice_head(n = 1) |> - dplyr::ungroup() |> - dplyr::group_by(genome_drug.resistant_phenotype, drug_class) |> - dplyr::count() |> - dplyr::arrange(-n) |> - dplyr::ungroup() - - ResPropbyDrugClass <- metadata |> - dplyr::group_by(drug_class) |> - dplyr::count(genome_drug.resistant_phenotype) |> - dplyr::mutate(prop = n / sum(n)) |> - dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> - dplyr::transmute(drug_class, res_prop = round(prop, 3)) |> - dplyr::arrange(-res_prop) |> - dplyr::ungroup() + # Collapse to one phenotype per genome x drug_class +drug_class_calls <- metadata |> + dplyr::group_by(genome.genome_id, drug_class) |> + dplyr::summarise( + genome_drug.resistant_phenotype = dplyr::case_when( + any(genome_drug.resistant_phenotype == "Resistant", na.rm = TRUE) ~ "Resistant", + any(genome_drug.resistant_phenotype == "Intermediate", na.rm = TRUE) ~ "Intermediate", + any(genome_drug.resistant_phenotype == "Susceptible", na.rm = TRUE) ~ "Susceptible", + TRUE ~ NA_character_ + ), + .groups = "drop" + ) +# Stats by drug class +PhenotypebyDrugClassCount <- drug_class_calls |> + dplyr::count(genome_drug.resistant_phenotype, drug_class) |> + dplyr::arrange(dplyr::desc(n)) + +ResPropbyDrugClass <- drug_class_calls |> + dplyr::group_by(drug_class) |> + dplyr::count(genome_drug.resistant_phenotype) |> + dplyr::mutate(prop = n / sum(n)) |> + dplyr::filter(genome_drug.resistant_phenotype == "Resistant") |> + dplyr::transmute(drug_class, res_prop = round(prop, 3)) |> + dplyr::arrange(dplyr::desc(res_prop)) |> + dplyr::ungroup() + ## Collection years Year <- metadata |> dplyr::distinct(genome.collection_year) |> diff --git a/data/amr_metadata_summary.md b/data/amr_metadata_summary.md index 7473cab..b280f57 100644 --- a/data/amr_metadata_summary.md +++ b/data/amr_metadata_summary.md @@ -98,9 +98,9 @@ aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, fusidane, glycop |drug_class | res_prop| |:-------------------------|--------:| +|penicillins | 0.945| |amphenicols | 0.600| |tetracyclines | 0.600| -|penicillins | 0.479| |macrolides | 0.069| |lincosamides | 0.056| |fluoroquinolones | 0.048| diff --git a/data/plots/amRdata_exploratory_plots.pdf b/data/plots/amRdata_exploratory_plots.pdf deleted file mode 100644 index a862fb9ec468baac82ed091ea75b3d262e889606..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16237 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 - - - - - - - - - - - - -amr_metadata_summary - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - -
-

AMR summary report

-
    -
  • Total no. of observations: 723

  • -
  • Unique genome IDs: 73

  • -
  • Associated publications (2): 26806258, -34100643

  • -
-
-

Antibiotics (16)

-

cefoxitin, chloramphenicol, ciprofloxacin, clindamycin, daptomycin, -erythromycin, fosfomycin, fusidic_acid, gentamicin, levofloxacin, -linezolid, oxacillin, penicillin, tetracycline, -trimethoprim-sulfamethoxazole, vancomycin

-
-
-

Antibiotic classes (14)

-

aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, -fusidane, glycopeptides, lincosamides, lipopeptides, macrolides, -oxazolidinones, penicillins, phosphonics, tetracyclines, -trimethoprim-sulfonamides

-
-
-

Phenotype counts

- - - - - - - - - - - - - - - - - -
genome_drug.resistant_phenotypen
Susceptible633
Resistant90
-
-
-

Phenotypes x antibiotic(s)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
genome_drug.resistant_phenotypegenome_drug.antibioticn
Susceptiblegentamicin73
Susceptiblecefoxitin72
Susceptibleoxacillin72
Susceptibletrimethoprim-sulfamethoxazole72
Resistantpenicillin69
Susceptibleclindamycin68
Susceptiblefusidic_acid68
Susceptibleerythromycin67
Susceptiblefosfomycin58
Susceptiblelevofloxacin58
Resistanterythromycin5
Susceptibledaptomycin5
Susceptiblelinezolid5
Susceptiblevancomycin5
Resistantclindamycin4
Susceptiblepenicillin4
Resistantchloramphenicol3
Resistantciprofloxacin3
Resistanttetracycline3
Susceptiblechloramphenicol2
Susceptibleciprofloxacin2
Susceptibletetracycline2
Resistantcefoxitin1
Resistantoxacillin1
Resistanttrimethoprim-sulfamethoxazole1
-
-
-

Resistant proportions per antibiotic

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
genome_drug.antibioticres_prop
penicillin0.945
chloramphenicol0.600
ciprofloxacin0.600
tetracycline0.600
erythromycin0.069
clindamycin0.056
cefoxitin0.014
oxacillin0.014
trimethoprim-sulfamethoxazole0.014
-
-
-

Phenotypes x antibiotic class(es)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
genome_drug.resistant_phenotypedrug_classn
Susceptibleaminoglycosides73
Susceptiblecephalosporins72
Susceptibletrimethoprim-sulfonamides72
Resistantpenicillins69
Susceptiblefusidane68
Susceptiblelincosamides68
Susceptiblemacrolides67
Susceptiblefluoroquinolones60
Susceptiblephosphonics58
Resistantmacrolides5
Susceptibleglycopeptides5
Susceptiblelipopeptides5
Susceptibleoxazolidinones5
Resistantlincosamides4
Susceptiblepenicillins4
Resistantamphenicols3
Resistantfluoroquinolones3
Resistanttetracyclines3
Susceptibleamphenicols2
Susceptibletetracyclines2
Resistantcephalosporins1
Resistanttrimethoprim-sulfonamides1
-
-
-

Resistant proportion per antibiotic class

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
drug_classres_prop
amphenicols0.600
tetracyclines0.600
penicillins0.479
macrolides0.069
lincosamides0.056
fluoroquinolones0.048
cephalosporins0.014
trimethoprim-sulfonamides0.014
-
-
-

Laboratory methods

- - - - - - - - - - - - - - - - - -
genome_drug.laboratory_typing_methodn
658
Biofosun Gram-positive panels broth dilution65
-
-
-

Collection years

- - - - - - - - - - - - - - - - - - - - - -
genome.collection_yearn
2015671
201739
201813
-
-
-

Isolation countries

- - - - - - - - - - - - - -
genome.isolation_countryn
Thailand658
-
-
-

Isolation sources

- - - - - - - - - - - - - - - - - -
genome.isolation_sourcen
pork39
duck26
-
-
-

Hosts

-
    -
  • Duck
  • -
  • Human
  • -
-
-
- - - - -
- - - - - - - - - - - - - - - diff --git a/data/plots/amr_metadata_summary.md b/data/plots/amr_metadata_summary.md deleted file mode 100644 index 11ef028..0000000 --- a/data/plots/amr_metadata_summary.md +++ /dev/null @@ -1,149 +0,0 @@ -# AMR summary report - -- **Total no. of observations**: 723 -- **Unique genome IDs**: 73 - -- **Associated publications** (2): 26806258, 34100643 - -## Antibiotics (16) - -cefoxitin, chloramphenicol, ciprofloxacin, clindamycin, daptomycin, erythromycin, fosfomycin, fusidic_acid, gentamicin, levofloxacin, linezolid, oxacillin, penicillin, tetracycline, trimethoprim-sulfamethoxazole, vancomycin - -## Antibiotic classes (14) - -aminoglycosides, amphenicols, cephalosporins, fluoroquinolones, fusidane, glycopeptides, lincosamides, lipopeptides, macrolides, oxazolidinones, penicillins, phosphonics, tetracyclines, trimethoprim-sulfonamides - -## Phenotype counts - -|genome_drug.resistant_phenotype | n| -|:-------------------------------|---:| -|Susceptible | 633| -|Resistant | 90| - - -## Phenotypes x antibiotic(s) - -|genome_drug.resistant_phenotype |genome_drug.antibiotic | n| -|:-------------------------------|:-----------------------------|--:| -|Susceptible |gentamicin | 73| -|Susceptible |cefoxitin | 72| -|Susceptible |oxacillin | 72| -|Susceptible |trimethoprim-sulfamethoxazole | 72| -|Resistant |penicillin | 69| -|Susceptible |clindamycin | 68| -|Susceptible |fusidic_acid | 68| -|Susceptible |erythromycin | 67| -|Susceptible |fosfomycin | 58| -|Susceptible |levofloxacin | 58| -|Resistant |erythromycin | 5| -|Susceptible |daptomycin | 5| -|Susceptible |linezolid | 5| -|Susceptible |vancomycin | 5| -|Resistant |clindamycin | 4| -|Susceptible |penicillin | 4| -|Resistant |chloramphenicol | 3| -|Resistant |ciprofloxacin | 3| -|Resistant |tetracycline | 3| -|Susceptible |chloramphenicol | 2| -|Susceptible |ciprofloxacin | 2| -|Susceptible |tetracycline | 2| -|Resistant |cefoxitin | 1| -|Resistant |oxacillin | 1| -|Resistant |trimethoprim-sulfamethoxazole | 1| - - -## Resistant proportions per antibiotic - -|genome_drug.antibiotic | res_prop| -|:-----------------------------|--------:| -|penicillin | 0.945| -|chloramphenicol | 0.600| -|ciprofloxacin | 0.600| -|tetracycline | 0.600| -|erythromycin | 0.069| -|clindamycin | 0.056| -|cefoxitin | 0.014| -|oxacillin | 0.014| -|trimethoprim-sulfamethoxazole | 0.014| - - -## Phenotypes x antibiotic class(es) - -|genome_drug.resistant_phenotype |drug_class | n| -|:-------------------------------|:-------------------------|--:| -|Susceptible |aminoglycosides | 73| -|Susceptible |cephalosporins | 72| -|Susceptible |trimethoprim-sulfonamides | 72| -|Resistant |penicillins | 69| -|Susceptible |fusidane | 68| -|Susceptible |lincosamides | 68| -|Susceptible |macrolides | 67| -|Susceptible |fluoroquinolones | 60| -|Susceptible |phosphonics | 58| -|Resistant |macrolides | 5| -|Susceptible |glycopeptides | 5| -|Susceptible |lipopeptides | 5| -|Susceptible |oxazolidinones | 5| -|Resistant |lincosamides | 4| -|Susceptible |penicillins | 4| -|Resistant |amphenicols | 3| -|Resistant |fluoroquinolones | 3| -|Resistant |tetracyclines | 3| -|Susceptible |amphenicols | 2| -|Susceptible |tetracyclines | 2| -|Resistant |cephalosporins | 1| -|Resistant |trimethoprim-sulfonamides | 1| - - -## Resistant proportion per antibiotic class - -|drug_class | res_prop| -|:-------------------------|--------:| -|amphenicols | 0.600| -|tetracyclines | 0.600| -|penicillins | 0.479| -|macrolides | 0.069| -|lincosamides | 0.056| -|fluoroquinolones | 0.048| -|cephalosporins | 0.014| -|trimethoprim-sulfonamides | 0.014| - - -## Laboratory methods - -|genome_drug.laboratory_typing_method | n| -|:--------------------------------------------|---:| -| | 658| -|Biofosun Gram-positive panels broth dilution | 65| - - -## Collection years - -| genome.collection_year| n| -|----------------------:|---:| -| 2015| 671| -| 2017| 39| -| 2018| 13| - - -## Isolation countries - -|genome.isolation_country | n| -|:------------------------|---:| -|Thailand | 658| - - -## Isolation sources - -|genome.isolation_source | n| -|:-----------------------|--:| -|pork | 39| -|duck | 26| - - -## Hosts - -- Duck -- Human - -