mirror of
https://github.com/msberends/AMR.git
synced 2026-11-15 22:20:47 +01:00
(v3.0.1.9094) Taxonomy build: resolve cross-domain genus homonyms by status and source (#309)
Genus resolution now prefers LPSN/MycoBank over GBIF, then accepted over synonym, then source, then rank, and only then domain. Losing LPSN/MycoBank homonyms are removed instead of moved into another domain. Adds integrity checks that LPSN records stay prokaryotic and MycoBank records stay fungal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dab4c47e86
commit
b264f175f4
3 files changed
+83
-17
No files matched your search
+2
-2
@@ -1,6 +1,6 @@
|
||||
Package: AMR
|
||||
Version: 3.0.1.9092
|
||||
Date: 2026-10-01
|
||||
Version: 3.0.1.9094
|
||||
Date: 2026-10-03
|
||||
Title: Antimicrobial Resistance Data Analysis
|
||||
Description: Functions to simplify and standardise antimicrobial resistance (AMR)
|
||||
data analysis and to work with microbial and antimicrobial properties by
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# AMR 3.0.1.9092
|
||||
# AMR 3.0.1.9094
|
||||
|
||||
Planned as v3.1.0, mid October 2026.
|
||||
|
||||
|
||||
@@ -1242,8 +1242,19 @@ taxonomy <- taxonomy %>%
|
||||
taxonomy %>% count(rank, sort = TRUE)
|
||||
|
||||
# Resolve genera that appear in multiple domains (e.g. Giardia in Protozoa
|
||||
# and Animalia). Prefer Bacteria > Fungi > Protozoa > Archaea > Chromista >
|
||||
# Animalia, then overwrite domain-to-family for the entire genus.
|
||||
# and Animalia, or Trichophyton in Fungi and as a cyanobacterium in GBIF).
|
||||
# The winning lineage per genus is decided in this order:
|
||||
# 1. Authoritative sources (LPSN, MycoBank) always beat GBIF and other sources,
|
||||
# so GBIF can never overrule LPSN or MycoBank.
|
||||
# 2. Accepted beats non-accepted, based on the genus-rank record of that
|
||||
# source/domain (or its species if the genus record is missing). So a
|
||||
# synonymic bacterial genus in LPSN loses from an accepted fungal genus in
|
||||
# MycoBank with the same name (e.g. Pirella), and vice versa.
|
||||
# 3. Source: LPSN > MycoBank > GBIF > other.
|
||||
# 4. Genus-rank records over species/subspecies records.
|
||||
# 5. Domain: Bacteria > Fungi > Protozoa > Archaea > Chromista > Animalia >
|
||||
# Plantae, which only breaks ties within the same source (e.g. Giardia within GBIF).
|
||||
# The winning lineage then overwrites domain-to-family for the entire genus.
|
||||
taxonomy %>% count(domain, sort = TRUE)
|
||||
domain_priority <- c(
|
||||
"Bacteria" = 1L,
|
||||
@@ -1254,18 +1265,41 @@ domain_priority <- c(
|
||||
"Animalia" = 6L,
|
||||
"Plantae" = 7L
|
||||
)
|
||||
source_priority <- c("LPSN" = 1L, "MycoBank" = 2L, "GBIF" = 3L, "manually added" = 4L)
|
||||
|
||||
# find the best domain per genus from genus-rank records
|
||||
# find the best domain per genus, from all records of that genus (so that e.g. a
|
||||
# MycoBank species also counts when the genus-rank record only came from GBIF)
|
||||
best_domain <- taxonomy %>%
|
||||
filter(rank == "genus", genus != "", domain %in% names(domain_priority)) %>%
|
||||
filter(
|
||||
genus != "",
|
||||
rank %in% c("genus", "species", "subspecies"),
|
||||
domain %in% names(domain_priority),
|
||||
# a source only counts within its own scope, so that a lineage inherited
|
||||
# from an older (possibly flawed) version of `microorganisms` cannot vote
|
||||
!(source == "LPSN" & !domain %in% c("Bacteria", "Archaea")),
|
||||
!(source == "MycoBank" & domain != "Fungi")
|
||||
) %>%
|
||||
group_by(genus, source, domain) %>%
|
||||
mutate(
|
||||
dprio = domain_priority[domain],
|
||||
sprio = recode_values(source,
|
||||
"LPSN" ~ 1L,
|
||||
"MycoBank" ~ 2L,
|
||||
"GBIF" ~ 3L,
|
||||
default = 4L)) %>%
|
||||
arrange(genus, dprio, sprio) %>%
|
||||
# status of this genus in this source/domain: from the genus-rank record if
|
||||
# available, otherwise accepted if any of its species/subspecies is accepted
|
||||
genus_status = if (any(rank == "genus")) {
|
||||
status[rank == "genus"][1]
|
||||
} else if (any(status == "accepted")) {
|
||||
"accepted"
|
||||
} else {
|
||||
status[1]
|
||||
}
|
||||
) %>%
|
||||
ungroup() %>%
|
||||
mutate(
|
||||
tprio = if_else(source %in% c("LPSN", "MycoBank"), 1L, 2L),
|
||||
stprio = if_else(genus_status %in% "accepted", 1L, 2L),
|
||||
sprio = coalesce(unname(source_priority[source]), 5L),
|
||||
rprio = if_else(rank == "genus", 1L, 2L),
|
||||
dprio = unname(domain_priority[domain])
|
||||
) %>%
|
||||
arrange(genus, tprio, stprio, sprio, rprio, dprio) %>%
|
||||
distinct(genus, .keep_all = TRUE) %>%
|
||||
select(genus, best_domain = domain, best_kingdom = kingdom,
|
||||
best_phylum = phylum, best_class = class,
|
||||
@@ -1273,6 +1307,15 @@ best_domain <- taxonomy %>%
|
||||
|
||||
taxonomy <- taxonomy %>%
|
||||
left_join(best_domain, by = "genus") %>%
|
||||
# LPSN and MycoBank records of a losing homonym are different organisms under
|
||||
# another nomenclatural code, so remove them instead of moving them to the
|
||||
# winning domain (e.g. the bacterial Pirella species if the fungal Pirella wins)
|
||||
filter(
|
||||
!(genus != "" & !is.na(best_domain) & source == "LPSN" &
|
||||
!best_domain %in% c("Bacteria", "Archaea")),
|
||||
!(genus != "" & !is.na(best_domain) & source == "MycoBank" &
|
||||
best_domain != "Fungi")
|
||||
) %>%
|
||||
mutate(
|
||||
domain = if_else(genus != "" & !is.na(best_domain), best_domain, domain),
|
||||
kingdom = if_else(genus != "" & !is.na(best_kingdom), best_kingdom, kingdom),
|
||||
@@ -1286,6 +1329,20 @@ taxonomy <- taxonomy %>%
|
||||
c("genus", "species", "subspecies"), best_family, family)) %>%
|
||||
select(-starts_with("best_"))
|
||||
|
||||
# integrity tests: no LPSN record may have left the prokaryotes and no MycoBank
|
||||
# record may have left the fungi (see issue #309, Trichophyton as cyanobacterium).
|
||||
# Both must return 0 rows - otherwise check the genus resolution above.
|
||||
taxonomy %>%
|
||||
filter(source == "LPSN", !domain %in% c("Bacteria", "Archaea", "", NA)) %>%
|
||||
count(domain, genus, sort = TRUE)
|
||||
taxonomy %>%
|
||||
filter(source == "MycoBank", !domain %in% c("Fungi", "", NA)) %>%
|
||||
count(domain, genus, sort = TRUE)
|
||||
# and these known cross-domain homonyms must end up in the right domain
|
||||
taxonomy %>%
|
||||
filter(rank == "genus", genus %in% c("Trichophyton", "Pirella", "Tubercularia")) %>%
|
||||
select(fullname, domain, kingdom, phylum, family, source)
|
||||
|
||||
# check if some genera within kingdoms have multiple families / orders, etc:
|
||||
taxonomy %>%
|
||||
filter(genus != "") %>%
|
||||
@@ -1345,9 +1402,12 @@ taxonomy <- taxonomy %>%
|
||||
rank == "kingdom" ~ kingdom,
|
||||
rank == "domain" ~ domain,
|
||||
TRUE ~ paste(genus, species, subspecies) # already trimmed 7 lines up
|
||||
))) %>%
|
||||
arrange(fullname) %>%
|
||||
distinct(fullname, .keep_all = TRUE)
|
||||
))) %>%
|
||||
# keep the record of the most authoritative source: LPSN > MycoBank > GBIF > other
|
||||
mutate(sprio = coalesce(unname(source_priority[source]), 5L)) %>%
|
||||
arrange(fullname, sprio) %>%
|
||||
distinct(fullname, .keep_all = TRUE) %>%
|
||||
select(-sprio)
|
||||
|
||||
|
||||
# *** Save intermediate results (0) *** -----------------------------------------------------------
|
||||
@@ -3294,6 +3354,12 @@ microorganisms$fullname[
|
||||
any(duplicated(taxonomy$mo))
|
||||
any(duplicated(taxonomy$fullname))
|
||||
|
||||
# LPSN records must be prokaryotes and MycoBank records must be fungi, also in their MO code (issue #309)
|
||||
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_")
|
||||
|
||||
|
||||
# Update other data sets --------------------------------------------------------------------------
|
||||
|
||||
|
||||
Reference in new issue
Block a user