Skip to content

Commit 21d4d89

Browse files
committed
feat: add custom goterm set for the 3 supported species
1 parent 89c9963 commit 21d4d89

2 files changed

Lines changed: 95 additions & 73 deletions

File tree

grassp/tools/cluster_merging.py

Lines changed: 22 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22
from itertools import combinations
3-
from pathlib import Path
4-
from typing import TYPE_CHECKING, Optional
3+
from typing import TYPE_CHECKING, Literal, Optional
54

65
import numpy as np
76
import scanpy as sc
@@ -12,58 +11,21 @@
1211
from scipy.spatial.distance import squareform
1312
from scipy.stats import fisher_exact
1413

15-
from .enrichment import calculate_cluster_enrichment
14+
from .enrichment import _load_gmt, calculate_cluster_enrichment
15+
16+
__all__ = [ # re-export private helper for callers/tests that imported it here
17+
"_load_gmt",
18+
"calculate_cluster_enrichment",
19+
"dendrogram_cherry_pairs",
20+
"merge_clusters_go",
21+
"merge_small_clusters",
22+
"paga_dendrogram",
23+
]
1624

1725
if TYPE_CHECKING:
1826
from anndata import AnnData
1927

2028

21-
# ── Gene-set loading ──────────────────────────────────────────────────────────
22-
23-
24-
def _load_gmt(path: str | dict[str, list[str]] | None) -> dict[str, list[str]]:
25-
"""Parse a GMT file or gseapy library name into a gene-set dict.
26-
27-
Parameters
28-
----------
29-
path
30-
A ``dict`` (returned as-is), a path to a ``.gmt`` file, a gseapy
31-
library name (fetched via ``gseapy.get_library``), or ``None``
32-
(returns the default UniProt subcellular compartment gene sets
33-
bundled with grassp).
34-
35-
Returns
36-
-------
37-
dict[str, list[str]]
38-
Mapping of term name to list of gene symbols.
39-
"""
40-
if path is None:
41-
path = str(
42-
Path(__file__).parent.parent
43-
/ 'datasets'
44-
/ 'external'
45-
/ 'custom_goterms_genes_reviewed.gmt'
46-
)
47-
if isinstance(path, dict):
48-
return path
49-
import os
50-
51-
if not os.path.exists(path):
52-
import gseapy as gp
53-
54-
return gp.get_library(name=path)
55-
gene_sets: dict[str, list[str]] = {}
56-
with open(path) as f:
57-
for line in f:
58-
parts = line.strip().split('\t')
59-
if len(parts) < 3:
60-
continue
61-
term = parts[0]
62-
genes = [g for g in parts[2:] if g]
63-
gene_sets[term] = genes
64-
return gene_sets
65-
66-
6729
# ── Pair-testing helpers ──────────────────────────────────────────────────────
6830

6931

@@ -907,6 +869,7 @@ def merge_clusters_go(
907869
connectivity_lower: float = 0.5,
908870
cluster_col: str = 'leiden',
909871
gene_sets_path: Optional[str | dict] = None,
872+
species: Literal['hsap', 'mmus', 'scer'] = 'hsap',
910873
gene_name_key: str = 'Gene_name_canonical',
911874
compartment_col: str = 'Cell_compartment',
912875
key_added: str = 'leiden_merged',
@@ -951,8 +914,15 @@ def merge_clusters_go(
951914
``adata.obs`` column with initial cluster labels.
952915
gene_sets_path
953916
Path to a GMT file, a gseapy library name, a pre-loaded
954-
``dict[str, list[str]]``, or ``None`` (uses the default UniProt
955-
subcellular compartment gene sets bundled with grassp).
917+
``dict[str, list[str]]``, or ``None`` (uses the consolidated UniProt
918+
subcellular compartment gene sets for the chosen ``species``).
919+
species
920+
Species code used to pick the default gene-set file when
921+
``gene_sets_path`` is ``None``. One of ``"hsap"`` (human,
922+
``consolidated_goterms_human.gmt``), ``"mmus"`` (mouse,
923+
``consolidated_goterms_mouse.gmt``), or ``"scer"`` (yeast,
924+
``consolidated_goterms_yeast.gmt``). Default ``"hsap"``. Ignored when
925+
an explicit ``gene_sets_path`` is provided.
956926
gene_name_key
957927
``adata.obs`` column with gene/protein names used for enrichment.
958928
compartment_col
@@ -971,7 +941,7 @@ def merge_clusters_go(
971941
If True, plot the initial PAGA dendrogram after convergence with leaf
972942
lines colored by compartment term and merge nodes colored by p-value.
973943
"""
974-
gene_sets = _load_gmt(gene_sets_path)
944+
gene_sets = _load_gmt(gene_sets_path, species=species)
975945

976946
# Initialise working column from the original clustering
977947
adata.obs[key_added] = adata.obs[cluster_col].astype(str).astype('category')

grassp/tools/enrichment.py

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import annotations
2+
from pathlib import Path
23
from typing import TYPE_CHECKING, Literal, Optional, Union
34

45
if TYPE_CHECKING:
@@ -13,6 +14,74 @@
1314
rank_proteins_groups = scanpy.tl.rank_genes_groups
1415

1516

17+
# Map from species code → filename of the bundled consolidated GMT.
18+
# Shared between `calculate_cluster_enrichment` and `merge_clusters_go`.
19+
_SPECIES_TO_GMT_FILENAME: dict[str, str] = {
20+
"hsap": "consolidated_goterms_human.gmt",
21+
"mmus": "consolidated_goterms_mouse.gmt",
22+
"scer": "consolidated_goterms_yeast.gmt",
23+
}
24+
25+
26+
def _load_gmt(
27+
path: str | dict[str, list[str]] | None = None,
28+
species: Literal["hsap", "mmus", "scer"] = "hsap",
29+
) -> dict[str, list[str]]:
30+
"""Resolve a gene-set source into a ``{term: [gene, ...]}`` dict.
31+
32+
Parameters
33+
----------
34+
path
35+
One of:
36+
- ``dict`` — returned as-is.
37+
- existing file path — parsed as GMT.
38+
- non-existing string — treated as a ``gseapy`` library name and
39+
fetched via :func:`gseapy.get_library`.
40+
- ``None`` — uses the consolidated UniProt subcellular-location gene
41+
sets bundled with grassp, picked according to ``species``.
42+
species
43+
Used only when ``path is None``. One of ``"hsap"``, ``"mmus"``,
44+
``"scer"``; selects the matching ``consolidated_goterms_*.gmt`` file
45+
in ``grassp/datasets/external/``.
46+
47+
Returns
48+
-------
49+
dict[str, list[str]]
50+
Mapping of term name → list of gene symbols.
51+
"""
52+
if isinstance(path, dict):
53+
return path
54+
if path is None:
55+
if species not in _SPECIES_TO_GMT_FILENAME:
56+
raise ValueError(
57+
f"species must be one of {sorted(_SPECIES_TO_GMT_FILENAME)}, "
58+
f"got {species!r}"
59+
)
60+
path = str(
61+
Path(__file__).parent.parent
62+
/ "datasets"
63+
/ "external"
64+
/ _SPECIES_TO_GMT_FILENAME[species]
65+
)
66+
67+
import os
68+
69+
if not os.path.exists(path):
70+
import gseapy as gp
71+
72+
return gp.get_library(name=path)
73+
gene_sets: dict[str, list[str]] = {}
74+
with open(path) as f:
75+
for line in f:
76+
parts = line.strip().split("\t")
77+
if len(parts) < 3:
78+
continue
79+
term = parts[0]
80+
genes = [g for g in parts[2:] if g]
81+
gene_sets[term] = genes
82+
return gene_sets
83+
84+
1685
def calculate_cluster_enrichment(
1786
data: AnnData,
1887
cluster_key: str = "leiden",
@@ -101,27 +170,10 @@ def calculate_cluster_enrichment(
101170
)
102171
# print(f"Sorting {enrichment_ranking_metric} in {'ascending' if sort_ascending else 'descending'} order")
103172

104-
if gene_sets is None:
105-
# Pick the consolidated UniProt subcellular gene-set file for the
106-
# requested species. Mapping matches the files produced by
107-
# marker_curation/fetch_custom_goterms.py.
108-
from pathlib import Path
109-
110-
species_to_filename = {
111-
"hsap": "consolidated_goterms_human.gmt",
112-
"mmus": "consolidated_goterms_mouse.gmt",
113-
"scer": "consolidated_goterms_yeast.gmt",
114-
}
115-
if species not in species_to_filename:
116-
raise ValueError(
117-
f"species must be one of {sorted(species_to_filename)}, got {species!r}"
118-
)
119-
gene_sets = str(
120-
Path(__file__).parent.parent
121-
/ "datasets"
122-
/ "external"
123-
/ species_to_filename[species]
124-
)
173+
# Resolve the gene-set source to a {term: [gene, ...]} dict. Handles dict
174+
# passthrough, file paths, gseapy library names, and the species-specific
175+
# default when `gene_sets is None`.
176+
gene_sets = _load_gmt(gene_sets, species=species)
125177

126178
for n, group in groups:
127179
gene_list = group[gene_name_key].tolist()

0 commit comments

Comments
 (0)