Skip to content

Commit 5e0f443

Browse files
authored
Add parents for implicitly declared classes (#488)
Motivated by biopragmatics/bioregistry#1811, follow-up to #486. This PR adds a function that can be used to search all terms in an ontology, identify external references that weren't explicitly declared as a class in the ontology, and add a declaration. All terms declared this way get put under an ontology-wide _ad hoc_ grouping term, which in turn has children corresponding to each prefix for which undeclared classes were found. For example, in HGNC, the ontology-wide grouping term is http://purl.obolibrary.org/obo/hgnc#aux, under which are terms like http://purl.obolibrary.org/obo/hgnc#aux-ec (fo Here's how it looks for HGNC: <img width="1019" height="961" alt="Screenshot 2026-02-22 at 17 30 02" src="https://github.com/user-attachments/assets/77592273-eb81-45ca-bd73-951ddb471d1c" /> There are a few caveats to this approach: 1. Axiom injection (🤷) 2. Doesn't get nice metadata like labels 3. Weird looking labels because there are no labels (will be improved with protegeproject/protege#1322)
1 parent ae6d541 commit 5e0f443

4 files changed

Lines changed: 76 additions & 9 deletions

File tree

src/pyobo/sources/hgnc/hgnc.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616
from pyobo.resources.so import get_so_name
1717
from pyobo.sources.hgnc.hgncgenefamily import GENE_GROUP_PREFIX, get_gene_family_terms
1818
from pyobo.struct import Annotation, Obo, OBOLiteral, Reference, Term
19-
from pyobo.struct.struct import gene_symbol_synonym, previous_gene_symbol, previous_name
19+
from pyobo.struct import vocabulary as v
20+
from pyobo.struct.struct import (
21+
cleanup_terms,
22+
gene_symbol_synonym,
23+
previous_gene_symbol,
24+
previous_name,
25+
)
2026
from pyobo.struct.typedef import (
2127
comment,
2228
enables,
@@ -222,7 +228,22 @@ class HGNCGetter(Obo):
222228

223229
def iter_terms(self, force: bool = False) -> Iterable[Term]:
224230
"""Iterate over terms in the ontology."""
225-
return get_terms(force=force, version=self.data_version)
231+
yield from cleanup_terms(
232+
get_terms(force=force, version=self.data_version),
233+
prefix=PREFIX,
234+
prefix_allowlist={
235+
"chr": v.chromosomal_region,
236+
"uniprot": v.protein,
237+
"mgi": v.gene,
238+
"rgd": v.gene,
239+
"go": v.molecular_function,
240+
"ec": v.molecular_function,
241+
"ncbigene": v.gene,
242+
"rnacentral": v.rna,
243+
"mirbase": v.rna,
244+
"snornabase": v.rna,
245+
},
246+
)
226247

227248

228249
def _get_location_to_chr() -> dict[str, Reference]:

src/pyobo/sources/hgnc/hgncgenefamily.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
from ...struct.struct import Obo, Reference, Term
1010
from ...struct.struct import abbreviation as symbol_type
1111
from ...struct.typedef import enables, exact_match, from_species, is_mentioned_by
12+
from ...struct.vocabulary import GENE_GROUP
1213
from ...utils.path import ensure_df
1314

1415
__all__ = [
15-
"GENE_GROUP_REFERENCE",
16-
"GENE_GROUP_TERM",
1716
"HGNCGroupGetter",
1817
"get_gene_family_terms",
1918
]
@@ -30,6 +29,7 @@ class HGNCGroupGetter(Obo):
3029
ontology = GENE_GROUP_PREFIX
3130
bioversions_key = "hgnc"
3231
synonym_typedefs = [symbol_type]
32+
root_terms = [GENE_GROUP]
3333
typedefs = [from_species, enables, exact_match, is_mentioned_by]
3434

3535
def iter_terms(self, force: bool = False) -> Iterable[Term]:
@@ -50,9 +50,6 @@ def get_hierarchy(*, version: str | None = None, force: bool = False) -> Mapping
5050

5151
COLUMNS = ["id", "abbreviation", "name", "pubmed_ids", "desc_comment", "desc_go"]
5252

53-
GENE_GROUP_REFERENCE = Reference(prefix="SO", identifier="0005855", name="gene group")
54-
GENE_GROUP_TERM = Term(reference=GENE_GROUP_REFERENCE)
55-
5653

5754
def get_gene_family_terms(*, version: str | None = None, force: bool = False) -> Iterable[Term]:
5855
"""Get the HGNC Gene Group terms."""
@@ -67,10 +64,10 @@ def get_gene_family_terms(*, version: str | None = None, force: bool = False) ->
6764
child: Term = id_to_term[child_id]
6865
for parent_id in parent_ids:
6966
child.append_parent(id_to_term[parent_id])
70-
yield GENE_GROUP_TERM
67+
yield Term(reference=GENE_GROUP)
7168
for term in terms:
7269
if not term.parents:
73-
term.append_parent(GENE_GROUP_REFERENCE)
70+
term.append_parent(GENE_GROUP)
7471
yield from terms
7572

7673

src/pyobo/struct/struct.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2527,3 +2527,43 @@ def iter_terms(self, force: bool = False) -> Iterable[Term]:
25272527
HUMAN_TERM = Term(reference=v.HUMAN)
25282528
CHARLIE_TERM = Term(reference=v.CHARLIE, type="Instance").append_parent(HUMAN_TERM)
25292529
PYOBO_INJECTED = "Injected by PyOBO"
2530+
2531+
2532+
def cleanup_terms(
2533+
terms: Iterable[Term], *, prefix: str, prefix_allowlist: dict[str, Reference]
2534+
) -> set[Term]:
2535+
"""Define missing references as classes."""
2536+
aux_term = Term(reference=default_reference(prefix, "aux", "auxiliary terms"))
2537+
2538+
terms = set(terms)
2539+
term_references: dict[Reference, Term] = {term.reference: term for term in terms}
2540+
2541+
prefix_to_parent_term: dict[str, Term] = {}
2542+
for allowlist_prefix, allowlist_reference in prefix_allowlist.items():
2543+
if allowlist_reference not in term_references:
2544+
term = Term(reference=allowlist_reference)
2545+
term_references[allowlist_reference] = term
2546+
prefix_to_parent_term[allowlist_prefix] = term
2547+
terms.add(term)
2548+
2549+
prefix_to_parent_term[allowlist_prefix] = (
2550+
Term.default(
2551+
allowlist_prefix,
2552+
f"aux-{allowlist_prefix}",
2553+
name=f"auxiliary terms from {allowlist_prefix}",
2554+
)
2555+
.append_parent(aux_term)
2556+
.append_parent(allowlist_reference)
2557+
)
2558+
2559+
undefined: dict[Reference, Term] = {}
2560+
for term in terms:
2561+
for references in term._get_references().values():
2562+
for reference in references:
2563+
if reference in term_references or reference in undefined:
2564+
continue
2565+
if parent := prefix_to_parent_term.get(reference.prefix):
2566+
undefined[reference] = Term(reference=reference).append_parent(parent)
2567+
2568+
rv = terms | {aux_term} | set(prefix_to_parent_term.values()) | set(undefined.values())
2569+
return rv

src/pyobo/struct/vocabulary.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,12 @@ def _c(c: curies.Reference) -> Reference:
154154
comment, # maps to "comment:" line with strings
155155
obo_creation_date,
156156
]
157+
158+
mouse = Reference(prefix="NCBITaxon", identifier="10090", name="Mus musculus")
159+
rat = Reference(prefix="NCBITaxon", identifier="10116", name="Rattus norvegicus")
160+
gene = Reference(prefix="SO", identifier="0000704", name="gene")
161+
GENE_GROUP = Reference(prefix="SO", identifier="0005855", name="gene group")
162+
protein = Reference(prefix="PR", identifier="000000001", name="protein")
163+
molecular_function = Reference(prefix="GO", identifier="0003674", name="molecular function")
164+
rna = Reference(prefix="uniprot.core", identifier="RNA", name="RNA")
165+
chromosomal_region = Reference(prefix="GO", identifier="0098687", name="chromosomal region")

0 commit comments

Comments
 (0)