From d683b9e7c632b176ae2105b1782467f89b1ae80b Mon Sep 17 00:00:00 2001 From: Matthijs Berends Date: Sat, 3 Oct 2026 11:45:25 +0200 Subject: [PATCH] (v3.0.1.9096) MO code registry: codes never change meaning, are never guessed, and are validated - data-raw/microorganisms_files/: registry of all MO codes of every release since v2.0.0 (78,992 codes, built from the git tags), approved renames, and README with the rules; decision by Matthijs S. Berends: MO codes from before v2.0.0 are not supported - as.mo(): codes of earlier releases are translated deterministically via the internal MO_RETIRED_CODES (generated from the registry); unknown codes are no longer guessed (previously F_TRCHP, Trichophyton in v3.0.1, became Talaromyces chlamydosporus) - synonym_mo_to_accepted_mo(): a missing target of one source no longer overwrites a valid target of another source (e.g. Chaetomium abuense), and no 'renamed to itself' - taxonomy build: codes are reused and reserved from the registry instead of the development data (fixes 40 codes reused in the 2026 development data), also for phylum to family; synonym cycles are broken; dangling 'renamed to' identifiers are removed; the build stops on any broken integrity rule before saving - run_microorganisms_build.R: runs the build in chunks at its checkpoints in one R process, with progress logging and resuming from any checkpoint; prompt updated accordingly - tests: shared integrity rules (helper-microorganisms.R) used by build and testthat, registry tests over all codes, sentinel organisms, regression tests; current defects of the data on main are listed in microorganisms_known_defects.csv until the next rebuild Co-Authored-By: Claude Opus 5.5 --- DESCRIPTION | 2 +- NEWS.md | 5 +- R/mo.R | 131 +- R/sysdata.rda | Bin 117360 -> 173764 bytes data-raw/_pre_commit_checks.R | 22 + .../reproduction_of_microorganisms.R | 357 +- .../reproduction_of_microorganisms_CLAUDE.md | 64 +- .../run_microorganisms_build.R | 200 + data-raw/microorganisms_files/README.md | 39 + .../build_mo_code_registry.R | 113 + .../microorganisms_files/mo_code_registry.csv | 78993 ++++++++++++++++ .../microorganisms_files/mo_code_renames.csv | 2 + tests/testthat/helper-microorganisms.R | 225 + .../testthat/microorganisms_known_defects.csv | 408 + tests/testthat/test-data-microorganisms.R | 197 + tests/testthat/test-mo.R | 4 +- 16 files changed, 80516 insertions(+), 246 deletions(-) create mode 100644 data-raw/_reproduction_scripts/run_microorganisms_build.R create mode 100644 data-raw/microorganisms_files/README.md create mode 100644 data-raw/microorganisms_files/build_mo_code_registry.R create mode 100644 data-raw/microorganisms_files/mo_code_registry.csv create mode 100644 data-raw/microorganisms_files/mo_code_renames.csv create mode 100644 tests/testthat/helper-microorganisms.R create mode 100644 tests/testthat/microorganisms_known_defects.csv create mode 100644 tests/testthat/test-data-microorganisms.R diff --git a/DESCRIPTION b/DESCRIPTION index 6e7156cce..9c872a4af 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: AMR -Version: 3.0.1.9095 +Version: 3.0.1.9096 Date: 2026-10-03 Title: Antimicrobial Resistance Data Analysis Description: Functions to simplify and standardise antimicrobial resistance (AMR) diff --git a/NEWS.md b/NEWS.md index 7791d0f90..7eb4521cc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 3.0.1.9095 +# AMR 3.0.1.9096 Planned as v3.1.0, mid October 2026. @@ -24,6 +24,7 @@ Planned as v3.1.0, mid October 2026. Thus, `mo_domain()` was previously an alias of `mo_kingdom()`; it is now a distinct function returning the domain. Output of `mo_domain()` is therefore unchanged, while `mo_kingdom()` now returns the formal, new kingdom. * Faster parallel computing via the `future` package for `as.sir()` and `wisca()`: a non-sequential plan (e.g. `future::plan(future::multisession)`) must be active before using `parallel = TRUE`. +* `as.mo()` no longer guesses unknown MO codes (these become `NA` with a warning), and MO codes from versions before v2.0.0 are no longer supported ### New * EUCAST 2026 and CLSI 2026 breakpoints: over 5,700 new breakpoints added to the `clinical_breakpoints` data set; EUCAST 2026 is now the default for all MIC and disk diffusion interpretations @@ -41,6 +42,7 @@ Planned as v3.1.0, mid October 2026. * New `amr_course()` to download and unpack course or webinar materials from GitHub in one call * Typed missing value constants `NA_ab_` and `NA_mo_`, for use in pipelines that need missing values of a specific class * New `wisca_plot()` to assess the susceptibility and incidence distributions from the Monte Carlo simulations +* MO codes of all releases since v2.0.0 are now reliably translated to their current code, also for taxa that moved to another domain; a registry guarantees that an MO code never gets another meaning ### Fixed * Streptomycin breakpoints imported from WHONET were coded as streptoduocin (`STR`) instead of streptomycin (`STR1`) @@ -62,6 +64,7 @@ Planned as v3.1.0, mid October 2026. * Translation fixes for Italian CoNS/CoPS names (#256), Dutch antimicrobials, and `sir_df()` foreign-language output (#272) * Fixed some EUCAST Expert Rules, mostly on *S. pneumoniae* * Logical comparison (`==`, `!=`) of `sir` class with a `factor` is now possible on R >= 4.3 +* `mo_current()` and `as.mo()` could not find the current name of some synonyms with multiple sources (e.g. *Chaetomium abuense*) ### Updated * `top_n_microorganisms()`: new `property_for_each` argument for sub-grouping within top *n* groups; rank ordering enforced (only lower taxonomic ranks allowed); fixed `property = NULL` not being accepted; inner filter now tracks original row indices to prevent cross-group contamination diff --git a/R/mo.R b/R/mo.R index f98e17c7a..59cbbf74f 100755 --- a/R/mo.R +++ b/R/mo.R @@ -1254,68 +1254,66 @@ parse_and_convert <- function(x) { } replace_old_mo_codes <- function(x, property) { - # this function transform old MO codes to current codes, such as: - # B_ESCH_COL (AMR v0.5.0) -> B_ESCHR_COLI - ind <- x %like_case% "^[A-Z]_[A-Z_]+$" & !x %in% AMR_env$MO_lookup$mo - if (any(ind, na.rm = TRUE)) { - add_MO_lookup_to_AMR_env() - # get the ones that match - affected <- x[ind] - affected_unique <- unique(affected) - all_direct_matches <- TRUE - # find their new codes, once per code - solved_unique <- unlist(lapply( - strsplit(affected_unique, ""), - function(m) { - domain <- paste0("^", m[1]) - name <- m[3:length(m)] - name[name == "_"] <- " " - name <- tolower(paste0(name, ".*", collapse = "")) - name <- gsub(" .*", " ", name, fixed = TRUE) - name <- paste0("^", name) - results <- AMR_env$MO_lookup$mo[AMR_env$MO_lookup$domain %like_case% domain & - AMR_env$MO_lookup$fullname_lower %like_case% name] - if (length(results) > 1) { - all_direct_matches <<- FALSE - } - results[1L] - } - ), use.names = FALSE) - solved <- solved_unique[match(affected, affected_unique)] - # assign on places where a match was found - x[ind] <- solved - n_matched <- length(affected[!is.na(affected)]) - n_solved <- length(affected[!is.na(solved)]) - n_unsolved <- length(affected[is.na(solved)]) - n_unique <- length(affected_unique[!is.na(affected_unique)]) - if (n_unique < n_matched) { - n_unique <- paste0(n_unique, " unique, ") - } else { - n_unique <- "" + # MO codes of earlier releases (since v2.0.0) that are not in the current data set anymore are translated + # with the MO code registry (internal data `MO_RETIRED_CODES`, see data-raw/microorganisms_files/README.md), + # e.g. a taxon that moved to another domain. + # MO codes are never guessed: a wrong organism is worse than NA. Codes from releases before v2.0.0 are + # not supported (decision by Matthijs S. Berends, 3 October 2026). + add_MO_lookup_to_AMR_env() + not_current <- !is.na(x) & !x %in% AMR_env$MO_lookup$mo + if (!any(not_current)) { + return(x) + } + # list at most 10 items, and escape curly brackets of names such as "Kapabacteria {class}" for the cli package + listing <- function(v) { + v <- gsub("}", "}}", gsub("{", "{{", v, fixed = TRUE), fixed = TRUE) + out <- vector_and(utils::head(v, 10), quotes = FALSE, sort = FALSE) + if (length(v) > 10) { + out <- paste0(out, ", and ", length(v) - 10, " more") } - if (property != "mo") { - warning_( - "in {.help [{.fun mo_", property, "}](AMR::mo_", property, ")}: the input contained ", n_matched, - " old MO code", ifelse(n_matched == 1, "", "s"), - " (", n_unique, "from another AMR package version). ", - "Please update your MO codes with {.help [{.fun as.mo}](AMR::as.mo)} to increase speed." - ) - } else { - warning_( - "in {.help [{.fun as.mo}](AMR::as.mo)}: the input contained ", n_matched, - " old MO code", ifelse(n_matched == 1, "", "s"), - " (", n_unique, "from another AMR package version). ", - n_solved, " old MO code", ifelse(n_solved == 1, "", "s"), - ifelse(n_solved == 1, " was", " were"), - ifelse(all_direct_matches, " updated ", font_bold(" guessed ")), - "to ", ifelse(n_solved == 1, "a ", ""), - "currently used MO code", ifelse(n_solved == 1, "", "s"), - ifelse(n_unsolved > 0, - paste0(" and ", n_unsolved, " old MO code", ifelse(n_unsolved == 1, "", "s"), " could not be updated."), - "." - ) + out + } + in_function <- ifelse(property == "mo", + "in {.help [{.fun as.mo}](AMR::as.mo)}: ", + paste0("in {.help [{.fun mo_", property, "}](AMR::mo_", property, ")}: ") + ) + + # codes from earlier releases + is_retired <- not_current & x %in% MO_RETIRED_CODES$old_mo + if (any(is_retired)) { + retired <- unique(x[is_retired]) + current <- MO_RETIRED_CODES$mo[match(retired, MO_RETIRED_CODES$old_mo)] + x[is_retired] <- current[match(x[is_retired], retired)] + msg <- paste0( + in_function, "the input contained ", length(retired), " MO code", ifelse(length(retired) == 1, "", "s"), + " from an earlier AMR package version. " + ) + if (any(!is.na(current))) { + msg <- paste0( + msg, sum(!is.na(current)), ifelse(sum(!is.na(current)) == 1, " was", " were"), + " translated to the current MO code of the same taxon (", listing(paste0(retired[!is.na(current)], " -> ", current[!is.na(current)])), "). " ) } + if (any(is.na(current))) { + gone <- MO_RETIRED_CODES$fullname[match(retired[is.na(current)], MO_RETIRED_CODES$old_mo)] + msg <- paste0( + msg, sum(is.na(current)), ifelse(sum(is.na(current)) == 1, " was", " were"), + " set to NA, as the taxon is not in the current data set anymore (", listing(paste0(retired[is.na(current)], " = ", gone)), "). " + ) + } + warning_(msg, "Please update your MO codes with {.help [{.fun as.mo}](AMR::as.mo)}.") + } + + # anything else that looks like an MO code, but was never part of a release since v2.0.0 + is_unknown_code <- not_current & !is_retired & x %like_case% "^[A-Z]{1,2}_[][A-Z0-9_-]+$" + if (any(is_unknown_code)) { + unknown <- unique(x[is_unknown_code]) + x[is_unknown_code] <- NA_character_ + warning_( + in_function, "the input contained ", length(unknown), " unknown MO code", ifelse(length(unknown) == 1, "", "s"), + " (", listing(unknown), "), which ", ifelse(length(unknown) == 1, "was", "were"), + " set to NA. MO codes of AMR package versions before v2.0.0 are not supported." + ) } x } @@ -1395,15 +1393,24 @@ synonym_mo_to_accepted_mo <- function(x, fill_in_accepted = FALSE, dataset = AMR x_mycobank <- dataset$mycobank_renamed_to[match(out, dataset$mo)] x_lpsn <- dataset$lpsn_renamed_to[match(out, dataset$mo)] - out[must_be_corrected & !is.na(x_gbif)] <- dataset$mo[match(x_gbif[must_be_corrected & !is.na(x_gbif)], dataset$gbif)] - out[must_be_corrected & !is.na(x_mycobank)] <- dataset$mo[match(x_mycobank[must_be_corrected & !is.na(x_mycobank)], dataset$mycobank)] - out[must_be_corrected & !is.na(x_lpsn)] <- dataset$mo[match(x_lpsn[must_be_corrected & !is.na(x_lpsn)], dataset$lpsn)] + # priority LPSN > MycoBank > GBIF, but a source may only overwrite the result if its target exists in the + # data set (before 3.1.0, e.g. a MycoBank target that was not included replaced a valid GBIF target by NA) + by_gbif <- dataset$mo[match(x_gbif, dataset$gbif, incomparables = NA)] + by_mycobank <- dataset$mo[match(x_mycobank, dataset$mycobank, incomparables = NA)] + by_lpsn <- dataset$mo[match(x_lpsn, dataset$lpsn, incomparables = NA)] + out[must_be_corrected & !is.na(by_gbif)] <- by_gbif[must_be_corrected & !is.na(by_gbif)] + out[must_be_corrected & !is.na(by_mycobank)] <- by_mycobank[must_be_corrected & !is.na(by_mycobank)] + out[must_be_corrected & !is.na(by_lpsn)] <- by_lpsn[must_be_corrected & !is.na(by_lpsn)] is_still_synonym <- dataset$status[match(out, dataset$mo)] == "synonym" } x_no_synonym <- dataset$status[match(x, dataset$mo)] != "synonym" out[x_no_synonym] <- NA_character_ + if (isFALSE(fill_in_accepted)) { + # a synonym without any known current name is not 'renamed' to itself + out[!is.na(out) & out == x] <- NA_character_ + } if (isTRUE(fill_in_accepted)) { out[!is.na(x_no_synonym) & x_no_synonym] <- x[!is.na(x_no_synonym) & x_no_synonym] } diff --git a/R/sysdata.rda b/R/sysdata.rda index 4bb94462526bbe079525a5e3a0b13ab30cc457c0..899b932402c588ce15faab372875e76ef857a544 100755 GIT binary patch delta 56905 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 delta 57 zcmX?dmg~b5_6_nMSlF{}>1|g2pm9spbKT64$ckbH2D3XyT^JZoPhs!+Y<=EMIN8pC Mx0{Ip2pA)y0K$kA`2YX_ diff --git a/data-raw/_pre_commit_checks.R b/data-raw/_pre_commit_checks.R index fccb6b7c2..547623dce 100644 --- a/data-raw/_pre_commit_checks.R +++ b/data-raw/_pre_commit_checks.R @@ -624,6 +624,28 @@ usethis::use_data(antimicrobials, overwrite = TRUE, version = 2, compress = "xz" pre_commit_lst$AB_LOOKUP <- create_AB_AV_lookup(antimicrobials) pre_commit_lst$AV_LOOKUP <- create_AB_AV_lookup(antivirals) +# MO codes of earlier releases that are not in the current data set anymore, with the current code of the same +# taxon (see data-raw/microorganisms_files/README.md). as.mo() uses this to translate them, instead of guessing. +source("tests/testthat/helper-microorganisms.R") +mo_registry <- read_mo_registry(".") +mo_renames <- read_mo_renames(".") +retired <- mo_registry[!mo_registry$mo %in% as.character(microorganisms$mo), c("mo", "fullname", "rank"), drop = FALSE] +renamed <- match(retired$mo, mo_renames$mo) +retired$fullname[!is.na(renamed)] <- mo_renames$new_name[renamed[!is.na(renamed)]] +# the same name, or else the same name without a rank suffix (such as "Kapabacteria {class}") and the same rank +current_mo <- as.character(microorganisms$mo)[match(retired$fullname, microorganisms$fullname)] +by_rank <- as.character(microorganisms$mo)[match( + paste(mo_name_without_suffix(retired$fullname), retired$rank), + paste(mo_name_without_suffix(microorganisms$fullname), microorganisms$rank) +)] +pre_commit_lst$MO_RETIRED_CODES <- data.frame( + old_mo = retired$mo, + mo = ifelse(is.na(current_mo), by_rank, current_mo), + fullname = retired$fullname, + stringsAsFactors = FALSE +) +rm(mo_registry, mo_renames, retired, renamed, current_mo, by_rank) + # Export to package as internal data ---- # usethis::use_data() must receive unquoted object names, which is not flexible at all. # we'll use good old base::save() instead diff --git a/data-raw/_reproduction_scripts/reproduction_of_microorganisms.R b/data-raw/_reproduction_scripts/reproduction_of_microorganisms.R index 79fa96e30..9a6dbbec7 100644 --- a/data-raw/_reproduction_scripts/reproduction_of_microorganisms.R +++ b/data-raw/_reproduction_scripts/reproduction_of_microorganisms.R @@ -115,6 +115,9 @@ devtools::load_all(".") # to load the AMR package # keep the previous data set at hand, `AMR::microorganisms` will be replaced at the end of this script microorganisms_old <- AMR::microorganisms +# the integrity rules of the data set and the MO code registry, shared with the unit tests +source("tests/testthat/helper-microorganisms.R") + # Helper functions -------------------------------------------------------------------------------- @@ -143,6 +146,11 @@ review <- function(x, title = deparse(substitute(x))) { invisible(x) } +# taxonomic names in ASCII and without quotes +ascii_names <- function(x) { + gsub("[\"'`]", "", stringi::stri_trans_general(x, "Latin-ASCII")) +} + # the priority of sources, used everywhere in this script source_priority <- c("LPSN" = 1L, "MycoBank" = 2L, "GBIF" = 3L, "manually added" = 4L, "inferred" = 5L) source_prio <- function(source) { @@ -1052,8 +1060,11 @@ saveRDS(taxonomy_mycobank, "data-raw/taxonomy_mycobank.rds", version = 2) # Read GBIF data ---------------------------------------------------------------------------------- +# @resume-block gbif_raw after=taxonomy_gbif +# (needed by `current_gbif` when resuming from a checkpoint with run_microorganisms_build.R) taxonomy_gbif.bak <- vroom(file_gbif, guess_max = 5e5) colnames(taxonomy_gbif.bak) <- gsub(".*:(.*)", "\\1", colnames(taxonomy_gbif.bak)) +# @end-resume-block # include all fungal orders from the mycobank db include_fungal_orders <- unique(taxonomy_mycobank$order[ @@ -1282,10 +1293,7 @@ taxonomy_gbif <- readRDS("data-raw/taxonomy_gbif.rds") # Add full names ---------------------------------------------------------------------------------- # names must be ASCII before they are made unique, otherwise e.g. 'Fusarium aloës' and 'Fusarium aloes' -# both end up as 'Fusarium aloes' in the final data set (which happened to 12 names in 2026) -ascii_names <- function(x) { - gsub("[\"'`]", "", stringi::stri_trans_general(x, "Latin-ASCII")) -} +# both end up as 'Fusarium aloes' in the final data set (which happened to 12 names in 2026), see ascii_names() taxonomy_gbif <- taxonomy_gbif %>% mutate(across(domain:subspecies, ascii_names)) taxonomy_lpsn <- taxonomy_lpsn %>% @@ -1639,6 +1647,8 @@ saveRDS(taxonomy, "data-raw/taxonomy0.rds") # Ensure every referenced rank has its own row (domain through species). # Where possible, enrich with GBIF identifiers. +# @resume-block current_gbif after=taxonomy1 +# (needed by add_missing_parents() after filtering, when resuming from a checkpoint) current_gbif <- taxonomy_gbif.bak %>% filter(is.na(acceptedNameUsageID)) %>% mutate( @@ -1663,6 +1673,7 @@ current_gbif <- taxonomy_gbif.bak %>% phylum = if_else(taxonRank == "phylum", canonical, phylum) ) %>% select(-canonical) +# @end-resume-block taxonomy <- add_missing_parents(taxonomy, current_gbif) @@ -2035,6 +2046,8 @@ taxonomy$rank[is.na(taxonomy$rank)] <- "domain" # this part is required here, because it's needed for filtering on 'relevant' species to keep later on # (`pathogens` was read at the start of this script) +# @resume-block prevalence after=taxonomy2 +# (compute_prevalence() is used again later on, when resuming from a checkpoint) # get all established, both old and current taxonomic names established <- pathogens %>% filter(status == "established") %>% @@ -2147,6 +2160,7 @@ compute_prevalence <- function(taxonomy) { mutate(prevalence = if_else(rank == "genus", pmin(prevalence, gen_prevalence, na.rm = TRUE), prevalence)) %>% select(-gen_prevalence) } +# @end-resume-block taxonomy <- compute_prevalence(taxonomy) table(taxonomy$prevalence, useNA = "always") @@ -2513,6 +2527,77 @@ taxonomy <- taxonomy %>% distinct(fullname, .keep_all = TRUE) message("Removed ", n_before - nrow(taxonomy), " nonsense names") +# Break cycles of synonyms ------------------------------------------------------------------------ + +# Sources can contradict each other, e.g. in 2026 GBIF listed Capillidium denaeosporus as synonym of +# Conidiobolus denaeosporum and also the other way around, so that the 'current name' depended on the starting +# point. In a cycle, the statement of the most authoritative source counts: that record stays a synonym, and its +# target becomes accepted. +next_synonym_step <- function(df) { + by_gbif <- df$fullname[match(df$gbif_renamed_to, df$gbif, incomparables = NA)] + by_mycobank <- df$fullname[match(df$mycobank_renamed_to, df$mycobank, incomparables = NA)] + by_lpsn <- df$fullname[match(df$lpsn_renamed_to, df$lpsn, incomparables = NA)] + out <- coalesce(by_lpsn, by_mycobank, by_gbif) + out[df$status != "synonym"] <- NA_character_ + setNames(out, df$fullname) +} +synonym_next <- next_synonym_step(taxonomy) +synonym_cycles <- list() +for (start in names(synonym_next)[!is.na(synonym_next)]) { + path <- start + repeat { + nxt <- unname(synonym_next[path[length(path)]]) + if (is.na(nxt)) { + break + } + if (nxt %in% path) { + cycle <- sort(path[match(nxt, path):length(path)]) + synonym_cycles[[paste(cycle, collapse = " | ")]] <- cycle + break + } + path <- c(path, nxt) + if (length(path) > 25) { + break + } + } +} +# If multiple records in the cycle come from the most authoritative source, the sources contradict themselves and +# the direction cannot be determined automatically (in 2026, even the authors in `ref` of these GBIF records were +# unreliable). Then ALL records in the cycle become accepted, so that no wrong current name is given, and they are +# shown for a human decision. +cycle_fixes <- bind_rows(lapply(synonym_cycles, function(cycle_members) { + # (not named `cycle`, since tibble() below would then use its own new column `cycle`) + members <- taxonomy[match(cycle_members, taxonomy$fullname), , drop = FALSE] + best <- members[source_prio(members$source) == min(source_prio(members$source)), , drop = FALSE] + if (nrow(best) == 1) { + tibble( + cycle = paste(cycle_members, collapse = " | "), + made_accepted = unname(synonym_next[best$fullname]), + reason = paste("statement of", best$fullname, "from", best$source, "(most authoritative source)") + ) + } else { + tibble( + cycle = paste(cycle_members, collapse = " | "), + made_accepted = cycle_members, + reason = paste("contradicting statements of", toString(unique(best$source)), "- all accepted, NEEDS A HUMAN DECISION") + ) + } +})) +review(cycle_fixes, "Cycles of synonyms") +if (nrow(cycle_fixes) > 0) { + taxonomy <- taxonomy %>% + mutate( + fix = fullname %in% cycle_fixes$made_accepted, + status = if_else(fix, "accepted", status), + lpsn_renamed_to = if_else(fix, NA_character_, lpsn_renamed_to), + mycobank_renamed_to = if_else(fix, NA_character_, mycobank_renamed_to), + gbif_renamed_to = if_else(fix, NA_character_, gbif_renamed_to) + ) %>% + select(-fix) +} +rm(synonym_next, synonym_cycles, cycle_fixes) + + # Fix genera that are synonyms while they contain accepted species -------------------------------- # e.g. in 2026, MycoBank listed the genus Blastomyces as a synonym, while Blastomyces dermatitidis was accepted @@ -2560,151 +2645,59 @@ mo_domain <- taxonomy %>% ) mo_domain -# `domain` exists in the data set since 2026, so codes are kept per domain -# (in the 2026 build, this was `AMR::microorganisms %>% rename(domain = kingdom)`, which would now fail) -existing_mo_tbl <- microorganisms_old %>% - mutate(mo = as.character(mo)) - +# @resume-block mo_registry after=taxonomy2c +# (the MO code registry is used by the final checks, when resuming from a checkpoint) # parts of existing MO codes, split on the underscore (genus codes can contain digits or hyphens, such as # B_RBCLM1 or B_CND-P, so a regex with only [A-Z] does not work and caused repeated elements like # B_SCLLM_CNNM_LNSM_LNSM_LNSM in earlier versions) mo_part <- function(mo, i) { vapply(strsplit(as.character(mo), "_", fixed = TRUE), function(x) if (length(x) >= i) x[i] else NA_character_, character(1)) } +# parts of a name, e.g. the species of 'Escherichia coli' +mo_part_of_name <- function(x, i) { + vapply(strsplit(x, " ", fixed = TRUE), function(x) if (length(x) >= i) x[i] else "", character(1)) +} + +# Existing codes come from the MO code registry (data-raw/microorganisms_files/), which contains every code of +# every release since v2.0.0, and NOT from the previous data set: codes that only existed in a development +# version carry no weight (decision by Matthijs S. Berends, 3 October 2026). A registered code is never given +# to another taxon, also not when its taxon is not in the new data set anymore, since users store these codes. +mo_registry <- read_mo_registry(".") +mo_renames <- read_mo_renames(".") +if (is.null(mo_registry)) { + stop("MO code registry not found, see data-raw/microorganisms_files/README.md", call. = FALSE) +} +release_order <- function(release) { + as.integer(factor(release, levels = unique(release[order(numeric_version(sub("^v", "", release)))]))) +} +existing_mo_tbl <- mo_registry %>% + # approved renames, e.g. a corrected spelling: the code continues with the new name + left_join(mo_renames %>% select(mo, new_name), by = "mo") %>% + mutate( + fullname = coalesce(new_name, fullname), + species = if_else(!is.na(new_name) & rank %in% c("species", "subspecies"), mo_part_of_name(new_name, 2), species), + subspecies = if_else(!is.na(new_name) & rank == "subspecies", mo_part_of_name(new_name, 3), subspecies) + ) %>% + select(-new_name) %>% + # if a taxon had multiple codes over time, the code of the most recent release is used + arrange(desc(release_order(last_release)), desc(release_order(first_release))) %>% + distinct(domain, rank, fullname, .keep_all = TRUE) + # all codes that have ever been used must never be given to another taxon, also not when their taxon # is not in the new data set anymore (users may have stored old codes) -reserved_genus_codes <- existing_mo_tbl %>% +reserved_genus_codes <- mo_registry %>% filter(rank %in% c("genus", "species", "subspecies")) %>% transmute(domain, code = mo_part(mo, 2)) %>% distinct() -reserved_species_codes <- existing_mo_tbl %>% +reserved_species_codes <- mo_registry %>% filter(rank %in% c("species", "subspecies")) %>% transmute(domain, genus_code = mo_part(mo, 2), code = mo_part(mo, 3)) %>% distinct() +# @end-resume-block # phylum until family are abbreviated with 8 characters and prefixed with their rank -# Phylum - keep old and fill up for new ones -mo_phylum <- taxonomy %>% - filter(rank == "phylum") %>% - distinct(domain, phylum) %>% - left_join( - existing_mo_tbl %>% - filter(rank == "phylum") %>% - transmute( - domain, - phylum = fullname, - mo_old = gsub("^[A-Z]{1,2}_", "", as.character(mo)) - ), - by = c("domain", "phylum") - ) %>% - group_by(domain) %>% - mutate( - mo_phylum8 = AMR:::abbreviate_mo(phylum, minlength = 8, prefix = "[PHL]_"), - mo_phylum9 = AMR:::abbreviate_mo(phylum, minlength = 9, prefix = "[PHL]_"), - mo_phylum = if_else(!is.na(mo_old), mo_old, mo_phylum8), - mo_duplicated = duplicated(mo_phylum), - mo_phylum = if_else(mo_duplicated, mo_phylum9, mo_phylum), - mo_duplicated = duplicated(mo_phylum) - ) %>% - ungroup() -if (any(mo_phylum$mo_duplicated, na.rm = TRUE)) { - stop("Duplicate MO codes for phylum!") -} -mo_phylum <- mo_phylum %>% - select(domain, phylum, mo_phylum) - -# Class - keep old and fill up for new ones -mo_class <- taxonomy %>% - filter(rank == "class") %>% - distinct(domain, class) %>% - left_join( - existing_mo_tbl %>% - filter(rank == "class") %>% - transmute( - domain, - class = fullname, - mo_old = gsub("^[A-Z]{1,2}_", "", as.character(mo)) - ), - by = c("domain", "class") - ) %>% - group_by(domain) %>% - mutate( - mo_class8 = AMR:::abbreviate_mo(class, minlength = 8, prefix = "[CLS]_"), - mo_class9 = AMR:::abbreviate_mo(class, minlength = 9, prefix = "[CLS]_"), - mo_class = if_else(!is.na(mo_old), mo_old, mo_class8), - mo_duplicated = duplicated(mo_class), - mo_class = if_else(mo_duplicated, mo_class9, mo_class), - mo_duplicated = duplicated(mo_class) - ) %>% - ungroup() -if (any(mo_class$mo_duplicated, na.rm = TRUE)) { - stop("Duplicate MO codes for class!") -} -mo_class <- mo_class %>% - select(domain, class, mo_class) - -# Order - keep old and fill up for new ones -mo_order <- taxonomy %>% - filter(rank == "order") %>% - distinct(domain, order) %>% - left_join( - existing_mo_tbl %>% - filter(rank == "order") %>% - transmute( - domain, - order = fullname, - mo_old = gsub("^[A-Z]{1,2}_", "", as.character(mo)) - ), - by = c("domain", "order") - ) %>% - group_by(domain) %>% - mutate( - mo_order8 = AMR:::abbreviate_mo(order, minlength = 8, prefix = "[ORD]_"), - mo_order9 = AMR:::abbreviate_mo(order, minlength = 9, prefix = "[ORD]_"), - mo_order = if_else(!is.na(mo_old), mo_old, mo_order8), - mo_duplicated = duplicated(mo_order), - mo_order = if_else(mo_duplicated, mo_order9, mo_order), - mo_duplicated = duplicated(mo_order) - ) %>% - ungroup() -if (any(mo_order$mo_duplicated, na.rm = TRUE)) { - stop("Duplicate MO codes for order!") -} -mo_order <- mo_order %>% - select(domain, order, mo_order) - -# Family - keep old and fill up for new ones -mo_family <- taxonomy %>% - filter(rank == "family") %>% - distinct(domain, family) %>% - left_join( - existing_mo_tbl %>% - filter(rank == "family") %>% - transmute( - domain, - family = fullname, - mo_old = gsub("^[A-Z]{1,2}_", "", as.character(mo)) - ), - by = c("domain", "family") - ) %>% - group_by(domain) %>% - mutate( - mo_family8 = AMR:::abbreviate_mo(family, minlength = 8, prefix = "[FAM]_"), - mo_family9 = AMR:::abbreviate_mo(family, minlength = 9, prefix = "[FAM]_"), - mo_family = if_else(!is.na(mo_old), mo_old, mo_family8), - mo_duplicated = duplicated(mo_family), - mo_family = if_else(mo_duplicated, mo_family9, mo_family), - mo_duplicated = duplicated(mo_family) - ) %>% - ungroup() -if (any(mo_family$mo_duplicated, na.rm = TRUE)) { - stop("Duplicate MO codes for family!") -} -mo_family <- mo_family %>% - select(domain, family, mo_family) - # keep the old code where available, and give every new taxon the first candidate code that is not in use # and was never used before (`reserved`) - if all candidates are taken, a number is added to the first one assign_codes <- function(old, candidates, reserved = character(0)) { @@ -2726,6 +2719,47 @@ assign_codes <- function(old, candidates, reserved = character(0)) { out } +# Phylum until family - keep old (from the registry) and fill up for new ones, never with a registered code +higher_rank_codes <- function(rank_name, tag) { + reserved <- mo_registry %>% + filter(rank == rank_name) %>% + transmute(domain, code = sub("^[A-Z]{1,2}_", "", mo)) + out <- taxonomy %>% + filter(rank == rank_name) %>% + distinct(domain, name = .data[[rank_name]]) %>% + left_join( + existing_mo_tbl %>% + filter(rank == rank_name) %>% + transmute(domain, name = mo_name_without_suffix(fullname), mo_old = sub("^[A-Z]{1,2}_", "", mo)) %>% + distinct(domain, name, .keep_all = TRUE), + by = c("domain", "name") + ) %>% + group_by(domain) %>% + mutate( + code = assign_codes( + old = mo_old, + candidates = Map( + c, + AMR:::abbreviate_mo(name, minlength = 8, prefix = paste0("[", tag, "]_")), + AMR:::abbreviate_mo(name, minlength = 9, prefix = paste0("[", tag, "]_")) + ), + reserved = reserved$code[reserved$domain == cur_group()$domain] + ) + ) %>% + ungroup() + if (anyDuplicated(paste(out$domain, out$code)) > 0 || anyNA(out$code)) { + stop("Duplicate MO codes for ", rank_name, "!", call. = FALSE) + } + out %>% + select(domain, name, code) %>% + setNames(c("domain", rank_name, paste0("mo_", rank_name))) +} +mo_phylum <- higher_rank_codes("phylum", "PHL") +mo_class <- higher_rank_codes("class", "CLS") +mo_order <- higher_rank_codes("order", "ORD") +mo_family <- higher_rank_codes("family", "FAM") + + # construct code part for genus - keep old code where available and generate new ones where needed mo_genus <- taxonomy %>% filter(rank == "genus") %>% @@ -2863,6 +2897,13 @@ mo_subspecies <- mo_subspecies %>% mo_unknown <- existing_mo_tbl %>% filter(fullname %like% "unknown") %>% transmute(fullname, mo_unknown = as.character(mo)) +# unknowns that were added after the last release keep their code too, as long as it is not registered +mo_unknown <- mo_unknown %>% + bind_rows( + microorganisms_old %>% + filter(fullname %like% "unknown", !fullname %in% mo_unknown$fullname, !mo %in% mo_registry$mo) %>% + transmute(fullname, mo_unknown = as.character(mo)) + ) # apply the new codes! taxonomy1 <- taxonomy @@ -3588,23 +3629,33 @@ microorganisms_old %>% select(mo, fullname, rank, status, prevalence) %>% review("Previously manually added taxa that are not in the new data set") -# LPSN records must be prokaryotes and MycoBank records must be fungi, also in their MO code (issue #309) -if (!all(taxonomy$domain[taxonomy$source == "LPSN"] %in% c("Bacteria", "Archaea")) || - !all(taxonomy$mo[taxonomy$source == "LPSN"] %like% "^(B|A)_") || - !all(taxonomy$domain[taxonomy$source == "MycoBank"] == "Fungi") || - !all(taxonomy$mo[taxonomy$source == "MycoBank"] %like% "^F_")) { - warning("LPSN records outside prokaryotes or MycoBank records outside fungi, check 'Combine the datasets'", call. = FALSE) -} - -# these must never fail, as the package relies on them -stopifnot( - "MO codes must be unique" = !anyDuplicated(taxonomy$mo), - "full names must be unique" = !anyDuplicated(taxonomy$fullname), - "MO codes must not be empty" = !any(taxonomy$mo %in% c("", NA)), - "prevalence must not be empty" = !anyNA(taxonomy$prevalence), - "status must be accepted, synonym or unknown" = all(taxonomy$status %in% c("accepted", "synonym", "unknown")), - "sources must be known" = all(taxonomy$source %in% c("LPSN", "MycoBank", "GBIF", "manually added")) +# 'renamed to' identifiers of records that were removed after the earlier clean-up (e.g. by deduplication) lead +# nowhere, so remove them again: these synonyms then have no current name, instead of a broken one +n_dangling <- sum( + (!is.na(taxonomy$lpsn_renamed_to) & !taxonomy$lpsn_renamed_to %in% taxonomy$lpsn) | + (!is.na(taxonomy$mycobank_renamed_to) & !taxonomy$mycobank_renamed_to %in% taxonomy$mycobank) | + (!is.na(taxonomy$gbif_renamed_to) & !taxonomy$gbif_renamed_to %in% taxonomy$gbif) ) +taxonomy <- taxonomy %>% + mutate( + lpsn_renamed_to = if_else(lpsn_renamed_to %in% lpsn, lpsn_renamed_to, NA_character_), + mycobank_renamed_to = if_else(mycobank_renamed_to %in% mycobank, mycobank_renamed_to, NA_character_), + gbif_renamed_to = if_else(gbif_renamed_to %in% gbif, gbif_renamed_to, NA_character_) + ) +message("Removed dangling 'renamed to' identifiers of ", n_dangling, " records") + +# synonyms without a current name are no error, but every one of them is a name that as.mo() cannot update +review(mo_synonyms_without_current_name(taxonomy), "Synonyms without a current name") + +# the integrity rules of the data set and the MO code registry (shared with the unit tests in +# tests/testthat/helper-microorganisms.R): these must never fail, as the package and its users rely on them +integrity_issues <- mo_integrity_issues(taxonomy, registry = mo_registry, renames = mo_renames) +if (!is.null(mo_integrity_report(integrity_issues))) { + stop("The new data set breaks integrity rules, fix the cause in this script:\n", + mo_integrity_report(integrity_issues), + call. = FALSE + ) +} # Update other data sets -------------------------------------------------------------------------- @@ -3676,11 +3727,13 @@ taxonomy <- taxonomy %>% AMR:::dataset_UTF8_to_ASCII() # check again after the conversion to ASCII -stopifnot( - "full names must be unique after conversion to ASCII" = !anyDuplicated(taxonomy$fullname), - "MO codes must be unique after conversion to ASCII" = !anyDuplicated(taxonomy$mo), - "MO codes may only contain A-Z, 0-9, _, -, [ and ]" = all(taxonomy$mo %like_case% "^[][A-Z0-9_-]+$") -) +integrity_issues <- mo_integrity_issues(taxonomy, registry = mo_registry, renames = mo_renames) +if (!is.null(mo_integrity_report(integrity_issues))) { + stop("The data set breaks integrity rules after the conversion to ASCII:\n", + mo_integrity_report(integrity_issues), + call. = FALSE + ) +} microorganisms <- taxonomy diff --git a/data-raw/_reproduction_scripts/reproduction_of_microorganisms_CLAUDE.md b/data-raw/_reproduction_scripts/reproduction_of_microorganisms_CLAUDE.md index 1bdbb2897..de7b6b47b 100644 --- a/data-raw/_reproduction_scripts/reproduction_of_microorganisms_CLAUDE.md +++ b/data-raw/_reproduction_scripts/reproduction_of_microorganisms_CLAUDE.md @@ -65,20 +65,22 @@ These are needed for `TAXONOMY_VERSION` in `R/aa_globals.R` later. Write them do ## Step 2: run the build The script takes several hours (most of it is LPSN scraping, which is cached in -`data-raw/lpsn_scrape_cache.rds`, so a restart is fast). Run it detached from your tool calls, so that -it survives time limits, and write everything to a log: +`data-raw/lpsn_scrape_cache.rds`, so a restart is fast). Never `source()` the whole script. Use the runner +`data-raw/_reproduction_scripts/run_microorganisms_build.R`: it cuts the script into chunks at its +checkpoints, runs them one by one in a single R process, logs each chunk with its duration in +`/logs/progress.tsv`, stops with the name of the failing chunk, and can resume from any checkpoint. +See the chunks first with `--list`. Run it detached from your tool calls, so that it survives time limits: ```bash mkdir -p /logs cd -nohup Rscript -e 'options(AMR_build_view = FALSE, AMR_build_print_rows = 200, AMR_build_review_dir = "", warn = 1, width = 200); source("data-raw/_reproduction_scripts/reproduction_of_microorganisms.R", echo = TRUE, max.deparse.length = Inf, keep.source = TRUE)' > /logs/build_1.log 2>&1 & +nohup Rscript data-raw/_reproduction_scripts/run_microorganisms_build.R --review-dir= --log-dir=/logs > /logs/build_1.log 2>&1 & ``` -Monitor the log at sensible intervals (every 10 to 20 minutes, not continuously). Read every -`>>> REVIEW:` block as soon as it appears; you do not have to wait until the end to start thinking. -With `AMR_build_review_dir` set, every review block is also saved in full as a CSV file in -``, named after its title. These files are part of the PR. Set the same option in every -resumed run. +Monitor the log and `progress.tsv` at sensible intervals (every 10 to 20 minutes, not continuously). Read +every `>>> REVIEW:` block as soon as it appears; you do not have to wait until the end to start thinking. +Every review block is also saved in full as a CSV file in ``, named after its title. These +files are part of the PR. ### Intermediate results @@ -105,24 +107,18 @@ and must not be committed. ### If the run fails -1. Read the error and the 100 lines before it. Find the root cause in the script, fix it, and - check the syntax with `Rscript -e 'invisible(parse("