Improve annotation: custom ortholog species support - #1891
Conversation
ESCRI11
left a comment
There was a problem hiding this comment.
Nice cleanup — the rename is mechanically complete (no human_ortholog left anywhere in R code) and the two new params are threaded correctly through bin/pgxcreate_op.R. But the generalisation from "human" to "any species" is only half-done, and I think three things block the merge.
I checked each one against origin/edgy in both repos to be sure it's introduced here rather than pre-existing, and each has a runnable reprex below so you can confirm and re-check the fix.
1. The backward-compat claim doesn't hold (bigomics/playbase#520, R/pgx-init.R)
Both PR descriptions say old PGX objects are safe because pgx.initialize() normalizes human_ortholog → ortholog on load. The rename is real, but it runs at :234 — 42 lines after the column is read at :192 and :205. On edgy those reads named human_ortholog, which legacy objects actually have, so this is new.
Effect on every load of every existing dataset:
genescomes out length 0, sopgx$familiesis built from nothing → every family falls belowfamsize >= 10and is dropped, leaving only<all>. FeatureMap's family filter andpgx.getFeatureSets()go empty.all(is.na(...))isTRUE, sogetHumanOrtholog()fires a full network ortholog lookup and overwrites the column that was correct all along.
## playbase#520, R/pgx-init.R -- pgx.initialize() reads $ortholog at :192/:205
## but only renames the legacy column at :234, 42 lines later.
## a LEGACY pgx exactly as it sits in an existing .pgx file
genes_df <- data.frame(
symbol = c("Trp53", "Myc", "Jun"),
gene_name = c("Trp53", "Myc", "Jun"),
human_ortholog = c("TP53", "MYC", "JUN")
)
## R/pgx-init.R:192 -- $ortholog does not exist yet, so this is length 0
genes <- ifelse(!is.na(genes_df$ortholog), genes_df$ortholog, genes_df$gene_name)
length(genes) # expected 3
#> [1] 0
## R/pgx-init.R:205 -- so this is TRUE, and getHumanOrtholog() fires on every
## load of every legacy dataset, overwriting the column that was there all along
all(is.na(genes_df$ortholog)) || all(genes_df$ortholog == "")
#> [1] TRUE
## R/pgx-init.R:221 -- families are built from the empty `genes`
intersect(c("TP53", "MYC", "JUN"), genes) # -> every family empty, dropped by famsize >= 10
#> character(0)
## R/pgx-init.R:234 -- the compat rename, too late to help any of the above
colnames(genes_df) <- gsub("^human_orth", "orth", colnames(genes_df))
colnames(genes_df)
#> [1] "symbol" "gene_name" "ortholog"Fix: move the colnames(pgx$genes) <- gsub("^human_orth","orth",colnames(pgx$genes)) line to the top of pgx.initialize(), before anything reads the column. (Also worth fixing the comment above it — it currently reads "Rename 'ortholog' to 'ortholog'".)
I also ran the same block extracted verbatim from origin/edgy vs this branch against a 300-gene mouse table seeded from real playdata::FAMILIES: before → length(genes)=300, 1 family kept; after → length(genes)=0, 0 families kept.
2. Two of the six dropdown values can't be resolved, and the failure throws
ORTHOLOG_SPECIES (upload_module_computepgx.R:233) goes straight to getOrtholog(target_species=), which resolves the string against the live g:Profiler organism list via .map_gprofiler_id(). Two entries don't match anything:
"Drosphila melanogaster"— typo, should be Drosophila"Escherichia coli"— not in g:Profiler's core organism list at all (and HomoloGene/babelgene are eukaryote-only, so there's no fallback backend for it either)
The NULL isn't caught. .convert_orthologs() resolves target_species a second time, so .map_gprofiler_id(NULL) runs and throws; nothing between there and getGeneAnnotation() wraps it in try(), so the whole PGX computation aborts.
This is only reachable now: on edgy there is no getOrtholog(), only getHumanOrtholog(), with target_species = "hsapiens" hardcoded and no caller overriding it.
## opg#1891 upload_module_computepgx.R:233 + playbase#520 R/pgx-annot-utils.R:407
## The new "Ortholog species" dropdown feeds getOrtholog(target_species=), which
## resolves the string with .map_gprofiler_id() -- pasted verbatim below.
.map_gprofiler_id <- function(species) {
orgs <- jsonlite::fromJSON("https://biit.cs.ut.ee/gprofiler/api/util/organisms_list")
## exact match
exact.species <- paste0("^", species, "$")
i <- which(
grepl(exact.species, orgs$id, ignore.case = TRUE) |
grepl(exact.species, orgs$scientific_name, ignore.case = TRUE) |
grepl(exact.species, orgs$display_name, ignore.case = TRUE)
)
## internal match
if (length(i) == 0) {
i <- which(grepl(species, orgs$scientific_name, ignore.case = TRUE) |
grepl(species, orgs$display_name, ignore.case = TRUE))
}
if (length(i) == 0) return(NULL)
orgs[i[1], "id"]
}
## the dropdown, verbatim from upload_module_computepgx.R:233
ORTHOLOG_SPECIES <- c(
"Human", "Drosphila melanogaster", "Arabidopsis thaliana",
"Caenorhabditis elegans", "Saccharomyces cerevisiae", "Escherichia coli"
)
sapply(ORTHOLOG_SPECIES, function(s) {
id <- .map_gprofiler_id(s)
if (is.null(id)) "NULL -- unresolved" else id
})
#> Human Drosphila melanogaster Arabidopsis thaliana
#> "hsapiens" "NULL -- unresolved" "athaliana"
#> Caenorhabditis elegans Saccharomyces cerevisiae Escherichia coli
#> "caelegprjna13758" "scerevisiae" "NULL -- unresolved"
## getOrtholog() passes that NULL on to .convert_orthologs(), which resolves it
## a second time -- .map_gprofiler_id(NULL). Nothing wraps this in try().
.map_gprofiler_id(NULL)
#> Error in `grepl()`:
#> ! invalid 'pattern' argumentAlso visible above: "Caenorhabditis elegans" resolves to caelegprjna13758 (WormBase ParaSite) rather than celegans — there are two exact matches and i[1] picks the wrong one.
Fix: correct the typo; drop E. coli (or wire up a backend that covers it); pin C. elegans to celegans. Ideally build the dropdown from resolvable ids rather than free-text species names, and make getOrtholog() degrade gracefully instead of throwing on an unknown target.
3. Seven call sites still require the ortholog to be human
These were only renamed, but each one depends on the column holding human symbols. On edgy that was guaranteed by construction; with a user-chosen species it isn't, and nothing errors — the boards just come back empty.
pathway_plot_reactome_graph.R:94,pathway_plot_wikipathway_graph.R:105— Reactome/WikiPathways node ids are human symbols (the wikipathway comment at:102still says "Rename to human orthologs")dataview_plot_tissue.R:69— the variable is literallyhgnc.gene; GTEx tissue lookup needs HGNCfeaturemap_server.R:300,singlecell_plot_markersplot.R:126,129—playdata::FAMILIESare human symbols matched against the ortholog columnsignature_server.R:183—map_probes(column = "ortholog")against human signature gene listscompare_server.R:224— cross-organism compare usesorthologas the bridge; this now only works if both datasets chose the sameortholog_species, where human used to be a guaranteed common bridge
## opg#1891 -- these call sites were only renamed, but they still need the
## ortholog to be HUMAN. On edgy that held by construction (getHumanOrtholog
## only, target_species="hsapiens" hardcoded). With the new dropdown it doesn't.
human_family <- c("TP53", "MYC", "JUN") # e.g. playdata::FAMILIES[["Kinases (KEA)"]]
genes_human <- data.frame(symbol = c("Trp53", "Myc", "Jun"),
ortholog = c("TP53", "MYC", "JUN"))
genes_yeast <- data.frame(symbol = c("Trp53", "Myc", "Jun"),
ortholog = c("YBR112C", "YGL025C", "YOR028C"))
## featuremap_server.R:300, verbatim
genes_human$symbol[match(human_family, genes_human$ortholog, nomatch = 0)]
#> [1] "Trp53" "Myc" "Jun"
genes_yeast$symbol[match(human_family, genes_yeast$ortholog, nomatch = 0)]
#> character(0)
## pathway_plot_reactome_graph.R:94 / pathway_plot_wikipathway_graph.R:105
## -- fc names must be human symbols for the Reactome/WikiPathways node ids
sum(genes_human$ortholog %in% human_family)
#> [1] 3
sum(genes_yeast$ortholog %in% human_family)
#> [1] 0The inverse shows up too: enrichment_table_genes_in_geneset.R:42, signature_table_genes_in_signature.R:39 and expression_table_genetable.R:107 still drop the column with if (organism == "Human") df$ortholog <- NULL — for a human dataset mapped to, say, yeast orthologs that now hides real information.
This one needs a design call rather than a patch: does the platform still need a guaranteed-human bridge column for pathways / tissue / families / cross-dataset compare? Two options I can see — keep human_ortholog alongside a new species-specific ortholog, or guard all seven sites on pgx$ortholog_species (it is stored on the object) and fall back to the old behaviour when it isn't human.
Non-blocking
upload_module_computepgx.R:601—create_ai_infographicsdefault flippedFALSE→TRUE. Not in the PR description, and it contradicts both the tooltip ("adds extra compute time and cost") and the now-stale comment at:1240("defaults FALSE — image generation has real per-slot provider cost/time"). Every upload now opts into paid image generation. Intentional?include_default_gmthas no NULL guard.bin/pgxcreate_op.Rpassesparams$include_default_gmt; anyparams.RDatawritten before this change (a job queued across a deploy) or by an external caller yieldsNULL→pgx.add_GMT'sif (has.px && include_default_gmt)→ argument is of length zero.ortholog_speciesis guarded (if (is.null(...)) "Human"); worth doing the same here.- Unchecking "Include default genesets" without uploading a GMT: playbase#520 also changed
species_goto!(organism %in% c("Human","Mouse","Rat")), so on a human dataset that combination yields a completely emptypgx$GMT. A UI guard or validation message would help. dataview_module_geneinfo.R:70—"ortholog", "ortholog"duplicated (you flagged this yourself).dataview_table_rawdata.R:119-120— the block is now a no-op rename oforthologtoortholog; can go.components/app/R/global.R:229anddev/board.launch/global.R:171—ENABLE_ANNOTis dead now that the UI it gated is removed.upload_module_computepgx.R:604-608, 1630-1638— commented-outdownload_gmtlink and handler; git has the history, so these can be deleted rather than kept commented.correlation_table_corr.R:72-75— theif (pgx$organism %in% c("Human","human"))branch is now identical to the line above it (pre-existing, but touched here).- No tooltip on the new "Ortholog species" select, unlike its neighbours.
Happy to help with any of these — 1 and 2 are small, 3 is the one I'd want your read on first.
🤖 Generated with Claude Code
|
Loading a pre-PR dataset — three defects, one root cause. The shim at pgx-init.R:234 sits 42 lines below the three things that need it, so on all 48 versioned objects: genes collapses to logical(0) → every gene family is dropped (mouse 64→1, arabidopsis 4→1, measured); the :205 branch fires and pays a full remote lookup that reports total ratio mapped = 99.5718% and then discards it, because ortho$human is NULL after getHumanOrtholog renamed its columns; and :209 appends a duplicate ortholog column holding toupper(symbol) (Pzp;A2m → PZP;A2M). The fix is ~5 lines of reordering. Move the gsub to just after the obj.needed check, drop a pre-existing ortholog column first so no duplicate can form, and fix ortho$human/ortho$humans → ortho$ortholog/ortho$orthologs. With the rename first, ortholog is already populated on every legacy object, so :205 never fires — no lookup, no clobber, families intact. Three bugs, one reordering. Worth stressing to the CTO: toupper(symbol) reproduces the real human mapping in 94% of mouse rows but 0% of C. elegans, 3.1% of arabidopsis, 8.3% of fly. Testing on mouse would have hidden it almost perfectly. On recording the model organism — three answers:
|
|
guys. about the ortholog above, it seems the "best" solution for now is to keep both $human_ortholog and the new $ortholog in the gene annotation? In a later PR we can think better how to really handle non-human-ortholog cases. What do you think? |
|
Thanks both for the thorough review — this took a few passes to get right. Summary of what changed, mapped to your findings, plus a request for a critical second look before merge. Issue 1 — init-order bug (ESCRI11 + phisanti's live-tested repro)Fixed in
Issue 2 — dropdown typo / unresolvable species / crash on unknown speciesFixed in Non-blocking listAll addressed in Issue 3 — the "which sites need which column" questionWe went with Option A (keep a guaranteed-human
Turns out the affected-site list is meaningfully bigger than either of your reviews scoped (phisanti's 9-site list plus TileDB, The species-configurable Bug found during manual verificationWhile browser-testing the fix (installed the local playbase build, drove the app through Cluster Features / Test Signatures / Pathway Analysis / Drug Connectivity / Cell Profiling / PCSF / Standard WGCNA against the example dataset), hit a real crash: Request: critical second checkI'd like a skeptical second pass specifically on:
🤖 Generated with Claude Code |
|
@ivokwee last comment sounds good; can you actually push the commit? |
|
also please target devel branch; edgy is a thing of the past now |
Addresses review feedback on bigomics/omicsplayground#1891 (paired PR): - pgx.initialize(): move the human_ortholog->ortholog rename to the top of the function, before pgx$genes$ortholog is read. Previously the rename ran 40+ lines after the reads, so every legacy dataset looked like it had no ortholog column: gene families collapsed to empty and an unnecessary network ortholog lookup fired on every load. Also guard against a duplicate ortholog column when both names coexist, and fix stale ortho$human/ortho$humans references (now ortho$ortholog/ortho$orthologs) left over from the getHumanOrtholog refactor. - getOrtholog(): degrade gracefully (return an empty mapping) instead of throwing when a species name can't be resolved. - .map_gprofiler_id(): guard against NULL/empty input instead of crashing grepl(); when a species matches multiple organisms, prefer the core (digit-free) organism id over strain/assembly imports -- fixes C. elegans resolving to a WormBase ParaSite strain instead of the core "celegans" organism. - pgx.add_GMT(): guard against an explicit NULL include_default_gmt, matching the existing ortholog_species guard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NdKjbG6mq44txcD2jbmgid
Addresses review feedback on PR #1891: - Fix "Drosphila melanogaster" typo and drop unresolvable "Escherichia coli" from the ortholog species dropdown (not in g:Profiler's organism list; the fallback backends are eukaryote-only, so nothing can resolve it). - Revert an accidental create_ai_infographics default flip (FALSE->TRUE) introduced in the same commit as the ortholog rename; it contradicted its own tooltip and cost implications. - Remove dead ENABLE_ANNOT flag (its gated UI was already removed), a duplicate "ortholog" list entry, a no-op ortholog->ortholog rename block, a commented-out download_gmt link/handler, and a redundant organism branch that duplicated the line above it. - Add a tooltip to the ortholog species select and a warning message when neither default genesets nor a custom GMT would be included. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NdKjbG6mq44txcD2jbmgid
Addresses review feedback on PR #1891: - Fix "Drosphila melanogaster" typo and drop unresolvable "Escherichia coli" from the ortholog species dropdown (not in g:Profiler's organism list; the fallback backends are eukaryote-only, so nothing can resolve it). - Revert an accidental create_ai_infographics default flip (FALSE->TRUE) introduced in the same commit as the ortholog rename; it contradicted its own tooltip and cost implications. - Remove dead ENABLE_ANNOT flag (its gated UI was already removed), a duplicate "ortholog" list entry, a no-op ortholog->ortholog rename block, a commented-out download_gmt link/handler, and a redundant organism branch that duplicated the line above it. - Add a tooltip to the ortholog species select and a warning message when neither default genesets nor a custom GMT would be included. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NdKjbG6mq44txcD2jbmgid
Companion to the playbase#520 change of the same name. These board modules were mechanically renamed from human_ortholog to ortholog in "add ortholog input; replace human_ortholog to ortholog", but each one matches against a resource that is itself human-keyed regardless of the dataset's now-configurable ortholog_species: - pathway_plot_reactome_graph.R, pathway_plot_wikipathway_graph.R -- Reactome/WikiPathways node ids are human gene symbols - dataview_plot_tissue.R -- GTEx tissue database is human-keyed (matches the existing UI copy in dataview_ui.R, which already says "the human ortholog is used to query the GTEX database") - featuremap_server.R, singlecell_plot_markersplot.R -- playdata::FAMILIES is human-curated - signature_server.R -- custom gene lists are typed as human-style symbols - compare_server.R -- cross-organism compare needs a bridge namespace guaranteed common between two arbitrarily-configured datasets (matches the existing UI copy in compare_ui.R: "datasets from diff species is done using the human ortholog") - expression_server.R -- default genesets (pgx$GMT) are human-keyed; add human_ortholog as a match candidate alongside symbol The species-configurable ortholog column is left as-is for display purposes elsewhere (raw-data table, gene-info panel), where showing "this gene's ortholog in the species you picked" is the intended behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NdKjbG6mq44txcD2jbmgid
76338ae to
5050de0
Compare
Addresses review feedback on bigomics/omicsplayground#1891 (paired PR): - pgx.initialize(): move the human_ortholog->ortholog rename to the top of the function, before pgx$genes$ortholog is read. Previously the rename ran 40+ lines after the reads, so every legacy dataset looked like it had no ortholog column: gene families collapsed to empty and an unnecessary network ortholog lookup fired on every load. Also guard against a duplicate ortholog column when both names coexist, and fix stale ortho$human/ortho$humans references (now ortho$ortholog/ortho$orthologs) left over from the getHumanOrtholog refactor. - getOrtholog(): degrade gracefully (return an empty mapping) instead of throwing when a species name can't be resolved. - .map_gprofiler_id(): guard against NULL/empty input instead of crashing grepl(); when a species matches multiple organisms, prefer the core (digit-free) organism id over strain/assembly imports -- fixes C. elegans resolving to a WormBase ParaSite strain instead of the core "celegans" organism. - pgx.add_GMT(): guard against an explicit NULL include_default_gmt, matching the existing ortholog_species guard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NdKjbG6mq44txcD2jbmgid
Summary
playbase::pgx.createPGX()asortholog_species(bin/pgxcreate_op.R,upload_module_computepgx.R).include_default_gmt) so users can choose whether the built-in gene sets are merged in alongside an uploaded custom GMT file.human_ortholog→orthologacross ~13 board modules (compare, correlation, dataview, enrichment, expression, featuremap, pathway, signature, singlecell), matching the corresponding column rename in playbase now that the ortholog target isn't hardcoded to human.ENABLE_ANNOT/upload_annot_table_ui) — dead code.Compatibility:
human_ortholog→orthologcolumn renameThe
pgx$genes$human_orthologcolumn is renamed topgx$genes$ortholog(it's no longer necessarily human — it tracks whateverortholog_specieswas used). Any downstream code (scripts, exports, other repos) that still readshuman_orthologdirectly needs to be updated toortholog.Old PGX objects are not broken:
pgx.initialize()in playbase normalizes legacy columns on load viacolnames(pgx$genes) <- gsub("^human_orth","orth", colnames(pgx$genes))(R/pgx-init.R), so existing datasets get their column renamed automatically the next time they're loaded through the platform. Anything that readspgx$genesoutsidepgx.initialize()(raw.pgxfiles, external scripts, caches) will still see the old name until re-normalized.Dependency
Requires the matching playbase PR: bigomics/playbase#520 (same branch name
improve-annot, baseedgy) — this PR callspgx.createPGX(..., ortholog_species =, include_default_gmt =), which only exist on that playbase branch. Please merge/land both together.Note
dataview_module_geneinfo.R's candidate-column list now lists"ortholog"twice (a leftover of the mechanicalhuman_ortholog→orthologrename) — harmless but worth a small follow-up cleanup.🤖 Generated with Claude Code
https://claude.ai/code/session_01CpXxxrjbEhE4n4bpXbJxhK