Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions kg_microbe/transform_utils/ontologies/ontologies_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,24 +365,14 @@ def _drop_metamodel_edges(self, df: pd.DataFrame) -> tuple:

def _add_kgx_metadata_to_edges(self, edges_file_path: Path):
"""
Drop metamodel edges and add knowledge_level/agent_type to an edge file.
Add knowledge_level and agent_type columns to ontology edge files.

Single read/filter/write pass over the (potentially very large — chebi,
ncbitaxon) edge file: removes ontology metamodel axiom edges
(:meth:`_drop_metamodel_edges`), then stamps every remaining edge with
knowledge_assertion + manual_agent since ontologies are manually curated
by domain expert curators (GO, ChEBI, ENVO, …).
All ontology edges use knowledge_assertion + manual_agent since ontologies
are manually curated by domain expert curators (GO, ChEBI, ENVO, etc.).
The relationships represent definitional assertions made by experts.
"""
df = pd.read_csv(edges_file_path, sep="\t", low_memory=False)

# Drop ontology metamodel axiom edges (non-biolink property/typing rows).
df, dropped = self._drop_metamodel_edges(df)
if dropped:
print(
f" Dropped {dropped} ontology metamodel edge(s) "
f"(rdfs:subPropertyOf/owl:inverseOf/rdf:type) from {edges_file_path.name}"
)

# Add columns if they don't exist
if KNOWLEDGE_LEVEL_COLUMN not in df.columns:
df[KNOWLEDGE_LEVEL_COLUMN] = KNOWLEDGE_ASSERTION
Expand Down Expand Up @@ -517,8 +507,9 @@ def post_process(self, name: str):
nodes_file = self.output_dir / f"{name}_nodes.tsv"
edges_file = self.output_dir / f"{name}_edges.tsv"

# Drop ontology metamodel axiom edges (rdfs:subPropertyOf / owl:inverseOf
# / rdf:type) and stamp knowledge_level/agent_type — single read/write.
# Add knowledge_level/agent_type columns. (Metamodel-axiom edges are
# dropped later in _normalize_schema, after KGX's biolink:->rdfs/owl/rdf
# predicate remap, so the CURIE filter actually matches.)
self._add_kgx_metadata_to_edges(edges_file)

# Fix node categories: specialized handlers for go/chebi/uberon/ncbitaxon,
Expand Down Expand Up @@ -924,13 +915,29 @@ def _normalize_schema(self, nodes_file: Path, edges_file: Path) -> None:
}
if "predicate" in df.columns:
df["predicate"] = df["predicate"].replace(owl_meta_predicate_map)
# Belt-and-braces: obograph can emit bare local names (e.g.
# ``subPropertyOf``) rather than the biolink:-prefixed form, so
# normalise those too before the metamodel drop below.
df["predicate"] = df["predicate"].replace(owl_meta_relation_map)
if "relation" in df.columns:
df["relation"] = df["relation"].replace(owl_meta_relation_map)
# Also catch cases where `relation` mistakenly got the biolink
# prefix too (belt-and-braces — same-row consistency).
df["relation"] = df["relation"].replace(owl_meta_predicate_map)

# Drop ontology metamodel-axiom edges now that predicates are in
# their final CURIE form (post the biolink:->rdfs/owl/rdf remap
# above). These property-level / typing statements are not biolink
# entity relationships. Nodes are left untouched.
df, dropped_metamodel = self._drop_metamodel_edges(df)

df.to_csv(edges_file, sep="\t", index=False)
if dropped_metamodel:
print(
f" [_normalize_schema] {edges_file.name}: dropped "
f"{dropped_metamodel} metamodel edge(s) "
"(rdfs:subPropertyOf/owl:inverseOf/rdf:type)"
)
if dropped_edge_cols or added_edge_cols or renamed:
rename_note = " rename(knowledge_source→primary_knowledge_source)" if renamed else ""
print(
Expand Down
61 changes: 46 additions & 15 deletions tests/test_ontologies_metamodel_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,55 @@ def test_drop_helper_missing_predicate_column(self):
self.assertEqual(dropped, 0)
self.assertEqual(len(out), 1)

# ---- Integration through the single read/write path ----
# ---- Integration through _normalize_schema (post biolink:->CURIE remap) ----

def test_metadata_pass_drops_and_preserves_columns(self):
"""_add_kgx_metadata_to_edges drops metamodel rows, adds metadata, keeps other cols."""
path = self._write_edges(_ROWS)
def test_normalize_schema_remaps_then_drops_metamodel(self):
"""
Remap KGX's biolink: meta-predicates in _normalize_schema, then drop them.

KGX emits these axioms as ``biolink:subPropertyOf`` / ``biolink:inverseOf``
/ ``biolink:type``; _normalize_schema remaps to rdfs/owl/rdf CURIEs and
only then can the drop match — so the drop must run there, not earlier.
"""
header = ["subject", "predicate", "object", "relation", "primary_knowledge_source"]
self.transform.edge_header = header
rows = [
# Entity edge — kept (biolink:subclass_of is not a meta-predicate).
["ENVO:00000015", "biolink:subclass_of", "ENVO:00000012", "rdfs:subClassOf", "envo.json"],
# Metamodel edges in KGX's biolink: serialization — remapped then dropped.
["METPO:2000002", "biolink:subPropertyOf", "METPO:2000001", "subPropertyOf", "metpo.json"],
["RO:0002327", "biolink:inverseOf", "RO:0002333", "inverseOf", "envo.json"],
["WD_Entity:Q715269", "biolink:type", "ENVO:00000015", "type", "envo.json"],
# Bare local-name predicate (belt-and-braces) — normalized then dropped.
["GO:1", "subPropertyOf", "GO:2", "subPropertyOf", "go.json"],
]
tmp = Path(tempfile.mkdtemp())
edges = tmp / "x_edges.tsv"
pd.DataFrame(rows, columns=header).to_csv(edges, sep="\t", index=False)
# nodes_file absent → node branch is skipped by its is_file() guard.
self.transform._normalize_schema(tmp / "x_nodes.tsv", edges)
df = pd.read_csv(edges, sep="\t")
self.assertEqual(set(df["predicate"]), {"biolink:subclass_of"})
self.assertEqual(len(df), 1)
# Column set/order preserved to edge_header.
self.assertEqual(list(df.columns), header)
# The surviving entity edge keeps its other columns intact.
self.assertEqual(df.iloc[0]["relation"], "rdfs:subClassOf")
self.assertEqual(df.iloc[0]["primary_knowledge_source"], "envo.json")

def test_add_kgx_metadata_only_adds_columns_no_drop(self):
"""The reverted _add_kgx_metadata_to_edges adds metadata and drops nothing."""
# It runs before the biolink:->CURIE remap, so metamodel rows are still
# biolink:-namespaced here and must NOT be dropped by this method.
rows = [
["ENVO:1", "biolink:subclass_of", "ENVO:2", "rdfs:subClassOf", "envo.json"],
["METPO:2", "biolink:subPropertyOf", "METPO:1", "subPropertyOf", "metpo.json"],
]
path = self._write_edges(rows)
self.transform._add_kgx_metadata_to_edges(path)
df = pd.read_csv(path, sep="\t")
# metamodel predicates gone
self.assertEqual(set(df["predicate"]), {"biolink:subclass_of", "biolink:related_to"})
# No rows dropped by this method.
self.assertEqual(len(df), 2)
# kgx metadata columns added
self.assertIn("knowledge_level", df.columns)
self.assertIn("agent_type", df.columns)
# Metadata columns added.
self.assertTrue((df["knowledge_level"] == "knowledge_assertion").all())
self.assertTrue((df["agent_type"] == "manual_agent").all())
# non-predicate columns survive intact on the kept rows (keyed by predicate,
# since both surviving rows share the same subject)
by_pred = df.set_index("predicate")
self.assertEqual(by_pred.loc["biolink:subclass_of", "relation"], "rdfs:subClassOf")
self.assertEqual(by_pred.loc["biolink:related_to", "relation"], "RO:0002131")
self.assertEqual(by_pred.loc["biolink:subclass_of", "primary_knowledge_source"], "envo.json")
Loading