gpsea 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gpsea/__init__.py +5 -0
- gpsea/analysis/__init__.py +15 -0
- gpsea/analysis/_api.py +472 -0
- gpsea/analysis/_config.py +373 -0
- gpsea/analysis/_gp_analysis.py +202 -0
- gpsea/analysis/_gp_impl.py +195 -0
- gpsea/analysis/_stats.py +154 -0
- gpsea/analysis/_test_fisherExact.py +28 -0
- gpsea/analysis/_util.py +61 -0
- gpsea/analysis/mtc_filter/__init__.py +14 -0
- gpsea/analysis/mtc_filter/_impl.py +697 -0
- gpsea/analysis/pcats/__init__.py +29 -0
- gpsea/analysis/pcats/_impl.py +561 -0
- gpsea/analysis/pcats/stats/__init__.py +5 -0
- gpsea/analysis/pcats/stats/_stats.py +177 -0
- gpsea/analysis/pcats/stats/_test__stats.py +33 -0
- gpsea/analysis/predicate/__init__.py +7 -0
- gpsea/analysis/predicate/_api.py +212 -0
- gpsea/analysis/predicate/genotype/__init__.py +12 -0
- gpsea/analysis/predicate/genotype/_api.py +188 -0
- gpsea/analysis/predicate/genotype/_counter.py +65 -0
- gpsea/analysis/predicate/genotype/_gt_predicates.py +223 -0
- gpsea/analysis/predicate/genotype/_predicates.py +596 -0
- gpsea/analysis/predicate/genotype/_variant.py +373 -0
- gpsea/analysis/predicate/phenotype/__init__.py +19 -0
- gpsea/analysis/predicate/phenotype/_pheno.py +240 -0
- gpsea/analysis/predicate/phenotype/_util.py +88 -0
- gpsea/analysis/pscore/__init__.py +7 -0
- gpsea/analysis/pscore/_api.py +162 -0
- gpsea/analysis/pscore/_impl.py +135 -0
- gpsea/analysis/pscore/stats/__init__.py +7 -0
- gpsea/analysis/pscore/stats/_stats.py +44 -0
- gpsea/config.py +51 -0
- gpsea/data/__init__.py +3 -0
- gpsea/data/_toy.py +210 -0
- gpsea/io.py +317 -0
- gpsea/model/__init__.py +23 -0
- gpsea/model/_base.py +60 -0
- gpsea/model/_cohort.py +302 -0
- gpsea/model/_gt.py +176 -0
- gpsea/model/_phenotype.py +129 -0
- gpsea/model/_protein.py +284 -0
- gpsea/model/_test_gt.py +30 -0
- gpsea/model/_test_tx.py +64 -0
- gpsea/model/_tx.py +223 -0
- gpsea/model/_variant.py +909 -0
- gpsea/model/_variant_effects.py +77 -0
- gpsea/model/genome/GCF_000001405.25_GRCh37.p13_assembly_report.tsv +333 -0
- gpsea/model/genome/GCF_000001405.39_GRCh38.p13_assembly_report.tsv +703 -0
- gpsea/model/genome/__init__.py +22 -0
- gpsea/model/genome/_builds.py +49 -0
- gpsea/model/genome/_genome.py +553 -0
- gpsea/model/genome/_test_builds.py +42 -0
- gpsea/model/genome/_test_genome.py +248 -0
- gpsea/preprocessing/__init__.py +33 -0
- gpsea/preprocessing/_api.py +227 -0
- gpsea/preprocessing/_audit.py +372 -0
- gpsea/preprocessing/_config.py +410 -0
- gpsea/preprocessing/_generic.py +54 -0
- gpsea/preprocessing/_patient.py +54 -0
- gpsea/preprocessing/_phenopacket.py +450 -0
- gpsea/preprocessing/_phenotype.py +114 -0
- gpsea/preprocessing/_protein.py +97 -0
- gpsea/preprocessing/_uniprot.py +99 -0
- gpsea/preprocessing/_variant.py +118 -0
- gpsea/preprocessing/_vep.py +211 -0
- gpsea/preprocessing/_vv.py +385 -0
- gpsea/py.typed +0 -0
- gpsea/view/__init__.py +17 -0
- gpsea/view/_cohort.py +190 -0
- gpsea/view/_disease.py +50 -0
- gpsea/view/_draw_variants.py +430 -0
- gpsea/view/_formatter.py +47 -0
- gpsea/view/_protein_viewer.py +79 -0
- gpsea/view/_protein_visualizable.py +153 -0
- gpsea/view/_protein_visualizer.py +691 -0
- gpsea/view/_stats.py +65 -0
- gpsea/view/_txp.py +108 -0
- gpsea/view/templates/cohort.html +162 -0
- gpsea/view/templates/disease.html +106 -0
- gpsea/view/templates/protein.html +86 -0
- gpsea/view/templates/stats.html +71 -0
- gpsea-0.2.0.dist-info/LICENSE +21 -0
- gpsea-0.2.0.dist-info/METADATA +112 -0
- gpsea-0.2.0.dist-info/RECORD +87 -0
- gpsea-0.2.0.dist-info/WHEEL +5 -0
- gpsea-0.2.0.dist-info/top_level.txt +1 -0
gpsea/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from ._api import CohortAnalysis, GenotypePhenotypeAnalysisResult, HpoMtcReport
|
|
2
|
+
# TODO This should go away
|
|
3
|
+
from ._config import CohortAnalysisConfiguration, configure_cohort_analysis, configure_default_protein_metadata_service, MtcStrategy
|
|
4
|
+
from ._gp_analysis import apply_predicates_on_patients
|
|
5
|
+
from ._util import prepare_hpo_terms_of_interest
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'configure_cohort_analysis',
|
|
9
|
+
'CohortAnalysis', 'GenotypePhenotypeAnalysisResult',
|
|
10
|
+
'CohortAnalysisConfiguration', 'MtcStrategy',
|
|
11
|
+
'HpoMtcReport',
|
|
12
|
+
'apply_predicates_on_patients',
|
|
13
|
+
'configure_default_protein_metadata_service',
|
|
14
|
+
'prepare_hpo_terms_of_interest',
|
|
15
|
+
]
|
gpsea/analysis/_api.py
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
from collections import namedtuple, defaultdict
|
|
4
|
+
|
|
5
|
+
import hpotk
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from gpsea.model import Patient
|
|
9
|
+
from gpsea.preprocessing import ProteinMetadataService
|
|
10
|
+
from .predicate import PolyPredicate, PatientCategory
|
|
11
|
+
from .predicate.genotype import GenotypePolyPredicate, VariantPredicate, ProteinPredicates
|
|
12
|
+
from .predicate.phenotype import P, PhenotypePolyPredicate
|
|
13
|
+
from .pscore import PhenotypeScorer, CountingPhenotypeScorer
|
|
14
|
+
|
|
15
|
+
PatientsByHPO = namedtuple('PatientsByHPO', field_names=['all_with_hpo', 'all_without_hpo'])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HpoMtcReport:
|
|
19
|
+
"""
|
|
20
|
+
Class to simplify reporting results of multiple testing filtering by HpoMtcFilter subclasses.
|
|
21
|
+
"""
|
|
22
|
+
# TODO: delete with no replacement.
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
filter_name: str,
|
|
27
|
+
mtc_name: str,
|
|
28
|
+
filter_results_map: typing.Mapping[str, int],
|
|
29
|
+
n_terms_before_filtering: int,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Args:
|
|
33
|
+
filter_name: name of the MTC filter strategy (e.g. `heuristic sampler`)
|
|
34
|
+
mtc_name: name of the MTC function (e.g. `bonferroni`)
|
|
35
|
+
filter_results_map: mapping with reasons for filtering out a term as keys, and counts of filtered terms as values
|
|
36
|
+
n_terms_before_filtering: the number of HPO terms before filtering
|
|
37
|
+
"""
|
|
38
|
+
self._filter_name = filter_name
|
|
39
|
+
self._mtc_name = mtc_name
|
|
40
|
+
self._results_map = filter_results_map
|
|
41
|
+
self._n_terms_before_filtering = n_terms_before_filtering
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def filter_method(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Returns:
|
|
47
|
+
the name of the HpoMtcFilter method used.
|
|
48
|
+
"""
|
|
49
|
+
return self._filter_name
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def skipped_terms_dict(self) -> typing.Mapping[str, int]:
|
|
53
|
+
"""
|
|
54
|
+
Returns:
|
|
55
|
+
a mapping with reasons why an HPO term was skipped as keys and counts of the skipped terms as values.
|
|
56
|
+
"""
|
|
57
|
+
return self._results_map
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def mtc_method(self) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Returns:
|
|
63
|
+
the name of the multiple testing correction method used (e.g. `bonferroni`).
|
|
64
|
+
"""
|
|
65
|
+
return self._mtc_name
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def n_terms_before_filtering(self) -> int:
|
|
69
|
+
"""
|
|
70
|
+
Get the number of terms before filtering.
|
|
71
|
+
"""
|
|
72
|
+
return self._n_terms_before_filtering
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class GenotypePhenotypeAnalysisResult:
|
|
76
|
+
"""
|
|
77
|
+
`GenotypePhenotypeAnalysisResult` summarizes results of genotype-phenotype correlation analysis of a cohort.
|
|
78
|
+
"""
|
|
79
|
+
# TODO: delete and use `gpsea.analysis.pcats.MultiPhenotypeAnalysisResult`.
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
n_usable: typing.Mapping[P, int],
|
|
84
|
+
all_counts: typing.Mapping[P, pd.DataFrame],
|
|
85
|
+
pvals: pd.Series,
|
|
86
|
+
corrected_pvals: typing.Optional[pd.Series],
|
|
87
|
+
phenotype_categories: typing.Iterable[PatientCategory],
|
|
88
|
+
geno_predicate: PolyPredicate,
|
|
89
|
+
mtc_filter_report: typing.Optional[HpoMtcReport] = None
|
|
90
|
+
):
|
|
91
|
+
self._n_usable = n_usable
|
|
92
|
+
self._all_counts = all_counts
|
|
93
|
+
self._pvals = pvals
|
|
94
|
+
self._corrected_pvals = corrected_pvals
|
|
95
|
+
self._phenotype_categories = tuple(phenotype_categories)
|
|
96
|
+
self._geno_predicate = geno_predicate
|
|
97
|
+
self._mtc_filter_report = mtc_filter_report
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def n_usable(self) -> typing.Mapping[P, int]:
|
|
101
|
+
"""
|
|
102
|
+
Get a mapping from a phenotype `P` (either an HPO term or a disease ID)
|
|
103
|
+
to an `int` with the number of patients where the phenotype was assessable,
|
|
104
|
+
and are, thus, usable for genotype-phenotype correlation analysis.
|
|
105
|
+
"""
|
|
106
|
+
return self._n_usable
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def all_counts(self) -> typing.Mapping[P, pd.DataFrame]:
|
|
110
|
+
"""
|
|
111
|
+
Get a mapping from the phenotype item to :class:`pandas.DataFrame` with counts of patients
|
|
112
|
+
in genotype and phenotype groups.
|
|
113
|
+
|
|
114
|
+
An example for a genotype predicate that bins into two categories (`Yes` and `No`) based on presence
|
|
115
|
+
of a missense variant in transcript `NM_123456.7`, and phenotype predicate that checks
|
|
116
|
+
presence/absence of `HP:0001166` (a phenotype term)::
|
|
117
|
+
|
|
118
|
+
Has MISSENSE_VARIANT in NM_123456.7
|
|
119
|
+
No Yes
|
|
120
|
+
Present
|
|
121
|
+
Yes 1 13
|
|
122
|
+
No 7 5
|
|
123
|
+
|
|
124
|
+
The rows correspond to the phenotype categories, and the columns represent the genotype categories.
|
|
125
|
+
"""
|
|
126
|
+
return self._all_counts
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def pvals(self) -> pd.Series:
|
|
130
|
+
"""
|
|
131
|
+
Get a :class:`pandas.Series` with p values for each tested HPO term.
|
|
132
|
+
"""
|
|
133
|
+
return self._pvals
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def corrected_pvals(self) -> typing.Optional[pd.Series]:
|
|
137
|
+
"""
|
|
138
|
+
Get an optional :class:`pandas.Series` with p values for each tested HPO term after multiple testing correction.
|
|
139
|
+
"""
|
|
140
|
+
return self._corrected_pvals
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def phenotype_categories(self) -> typing.Sequence[PatientCategory]:
|
|
144
|
+
"""
|
|
145
|
+
Get a sequence of phenotype patient categories that can be investigated.
|
|
146
|
+
"""
|
|
147
|
+
return self._phenotype_categories
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def total_tests(self) -> int:
|
|
151
|
+
"""
|
|
152
|
+
Get total count of tests that were run for this analysis.
|
|
153
|
+
"""
|
|
154
|
+
return len(self._all_counts)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def mtc_filter_report(self) -> typing.Optional[HpoMtcReport]:
|
|
158
|
+
return self._mtc_filter_report
|
|
159
|
+
|
|
160
|
+
def summarize(
|
|
161
|
+
self, hpo: hpotk.MinimalOntology,
|
|
162
|
+
category: PatientCategory,
|
|
163
|
+
) -> pd.DataFrame:
|
|
164
|
+
"""
|
|
165
|
+
Create a data frame with summary of the genotype phenotype analysis.
|
|
166
|
+
|
|
167
|
+
The *rows* of the frame correspond to the analyzed HPO terms.
|
|
168
|
+
|
|
169
|
+
The columns of the data frame have `Count` and `Percentage` per used genotype predicate.
|
|
170
|
+
|
|
171
|
+
**Example**
|
|
172
|
+
|
|
173
|
+
If we use :class:`~gpsea.analysis.predicate.genotype.VariantEffectPredicate`
|
|
174
|
+
which can compare phenotype with and without a missense variant, we will have a data frame
|
|
175
|
+
that looks like this::
|
|
176
|
+
|
|
177
|
+
MISSENSE_VARIANT on `NM_1234.5` No Yes
|
|
178
|
+
Count Percent Count Percent p value Corrected p value
|
|
179
|
+
Arachnodactyly [HP:0001166] 1/10 10% 13/16 81% 0.000781 0.020299
|
|
180
|
+
Abnormality of the musculature [HP:0003011] 6/6 100% 11/11 100% 1.000000 1.000000
|
|
181
|
+
Abnormal nervous system physiology [HP:0012638] 9/9 100% 15/15 100% 1.000000 1.000000
|
|
182
|
+
... ... ... ... ... ... ...
|
|
183
|
+
"""
|
|
184
|
+
if category not in self._phenotype_categories:
|
|
185
|
+
raise ValueError(f'Unknown phenotype category: {category}. Use one of {self._phenotype_categories}')
|
|
186
|
+
|
|
187
|
+
# Row index: a list of tested HPO terms
|
|
188
|
+
pheno_idx = pd.Index(self._n_usable.keys())
|
|
189
|
+
# Column index: multiindex of counts and percentages for all genotype predicate groups
|
|
190
|
+
geno_idx = pd.MultiIndex.from_product(
|
|
191
|
+
iterables=(self._geno_predicate.get_categories(), ('Count', 'Percent')),
|
|
192
|
+
names=(self._geno_predicate.get_question(), None),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# We'll fill this frame with data
|
|
196
|
+
df = pd.DataFrame(index=pheno_idx, columns=geno_idx)
|
|
197
|
+
|
|
198
|
+
for pf, count in self._all_counts.items():
|
|
199
|
+
gt_totals = count.sum() # Sum across the phenotype categories (collapse the rows).
|
|
200
|
+
for gt_cat in count.columns:
|
|
201
|
+
cnt = count.loc[category, gt_cat]
|
|
202
|
+
total = gt_totals[gt_cat]
|
|
203
|
+
df.loc[pf, (gt_cat, 'Count')] = f'{cnt}/{total}'
|
|
204
|
+
pct = 0 if total == 0 else round(cnt * 100 / total)
|
|
205
|
+
df.loc[pf, (gt_cat, 'Percent')] = f'{pct}%'
|
|
206
|
+
|
|
207
|
+
# Add columns with p values and corrected p values (if present)
|
|
208
|
+
df.insert(df.shape[1], ('', self._pvals.name), self._pvals)
|
|
209
|
+
if self._corrected_pvals is not None:
|
|
210
|
+
df.insert(df.shape[1], ('', self._corrected_pvals.name), self._corrected_pvals)
|
|
211
|
+
|
|
212
|
+
# Format the index values: `HP:0001250` -> `Seizure [HP:0001250]` if the index members are HPO terms
|
|
213
|
+
# or just use the term ID CURIE otherwise (e.g. `OMIM:123000`).
|
|
214
|
+
labeled_idx = df.index.map(lambda term_id: GenotypePhenotypeAnalysisResult._format_term_id(hpo, term_id))
|
|
215
|
+
|
|
216
|
+
# Last, sort by corrected p value or just p value
|
|
217
|
+
df = df.set_index(labeled_idx)
|
|
218
|
+
if self._corrected_pvals is not None:
|
|
219
|
+
return df.sort_values(by=[('', self._corrected_pvals.name), ('', self._pvals.name)])
|
|
220
|
+
else:
|
|
221
|
+
return df.sort_values(by=('', self._pvals.name))
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def _format_term_id(
|
|
225
|
+
hpo: hpotk.MinimalOntology,
|
|
226
|
+
term_id: hpotk.TermId,
|
|
227
|
+
) -> str:
|
|
228
|
+
"""
|
|
229
|
+
Format a `term_id` as a `str`. HPO term ID is formatted as `<name> [<term_id>]` whereas other term IDs
|
|
230
|
+
are formatted as CURIEs (e.g. `OMIM:123000`).
|
|
231
|
+
"""
|
|
232
|
+
if term_id.prefix == 'HP':
|
|
233
|
+
min_onto = hpo.get_term(term_id)
|
|
234
|
+
return f'{min_onto.name} [{term_id.value}]'
|
|
235
|
+
else:
|
|
236
|
+
return term_id.value
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class PhenotypeScoreAnalysisResult:
|
|
240
|
+
"""
|
|
241
|
+
`PhenotypeScoreAnalysisResult` includes results of testing genotypes vs. phenotype scores.
|
|
242
|
+
|
|
243
|
+
See :ref:`Mann Whitney U Test for phenotype score <phenotype-score-stats>` for more background.
|
|
244
|
+
"""
|
|
245
|
+
# TODO: delete and use `gpsea.analysis.pscore.PhenotypeScoreAnalysisResult`
|
|
246
|
+
|
|
247
|
+
def __init__(
|
|
248
|
+
self,
|
|
249
|
+
genotype_phenotype_scores: pd.DataFrame,
|
|
250
|
+
p_value: float,
|
|
251
|
+
):
|
|
252
|
+
self._genotype_phenotype_scores = genotype_phenotype_scores
|
|
253
|
+
self._p_value = p_value
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def genotype_phenotype_scores(
|
|
257
|
+
self,
|
|
258
|
+
) -> pd.DataFrame:
|
|
259
|
+
"""
|
|
260
|
+
Get the DataFrame with the genotype group and the phenotype score for each patient.
|
|
261
|
+
|
|
262
|
+
The DataFrame has the following structure:
|
|
263
|
+
|
|
264
|
+
========== ======== =========
|
|
265
|
+
patient_id genotype phenotype
|
|
266
|
+
========== ======== =========
|
|
267
|
+
patient_1 0 1
|
|
268
|
+
patient_2 0 3
|
|
269
|
+
patient_3 1 2
|
|
270
|
+
... ... ...
|
|
271
|
+
========== ======== =========
|
|
272
|
+
|
|
273
|
+
The DataFrame index includes the patient IDs, and then there are 2 columns
|
|
274
|
+
with the `genotype` group id (:attr:`~gpsea.analysis.predicate.PatientCategory.cat_id`)
|
|
275
|
+
and the `phenotype` score.
|
|
276
|
+
"""
|
|
277
|
+
return self._genotype_phenotype_scores
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def p_value(self) -> float:
|
|
281
|
+
return self._p_value
|
|
282
|
+
|
|
283
|
+
def __str__(self) -> str:
|
|
284
|
+
return 'PhenotypeGroupAnalysisResult(' \
|
|
285
|
+
f'genotype_phenotype_scores={self._genotype_phenotype_scores}, ' \
|
|
286
|
+
f'p_value={self._p_value})'
|
|
287
|
+
|
|
288
|
+
def __repr__(self) -> str:
|
|
289
|
+
return str(self)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class CohortAnalysis(metaclass=abc.ABCMeta):
|
|
293
|
+
"""
|
|
294
|
+
`CohortAnalysis` is a driver class for running genotype-phenotype correlation analyses.
|
|
295
|
+
|
|
296
|
+
The class provides various methods to test genotype-phenotype correlations. All methods wrap results
|
|
297
|
+
into :class:`GenotypePhenotypeAnalysisResult`.
|
|
298
|
+
"""
|
|
299
|
+
# TODO: remove and use the analyses described in `User Guide > Statistical tests`.
|
|
300
|
+
|
|
301
|
+
def __init__(
|
|
302
|
+
self,
|
|
303
|
+
hpo: hpotk.MinimalOntology,
|
|
304
|
+
protein_service: ProteinMetadataService,
|
|
305
|
+
):
|
|
306
|
+
self._hpo = hpotk.util.validate_instance(hpo, hpotk.MinimalOntology, 'hpo')
|
|
307
|
+
self._protein_service = protein_service
|
|
308
|
+
self._protein_predicates = ProteinPredicates(self._protein_service)
|
|
309
|
+
|
|
310
|
+
@abc.abstractmethod
|
|
311
|
+
def compare_hpo_vs_genotype(
|
|
312
|
+
self,
|
|
313
|
+
predicate: VariantPredicate,
|
|
314
|
+
) -> GenotypePhenotypeAnalysisResult:
|
|
315
|
+
"""
|
|
316
|
+
Bin patients according to a presence of at least one allele that matches `predicate`
|
|
317
|
+
and test for genotype-phenotype correlations.
|
|
318
|
+
"""
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
@abc.abstractmethod
|
|
322
|
+
def compare_hpo_vs_recessive_genotype(
|
|
323
|
+
self,
|
|
324
|
+
predicate: VariantPredicate,
|
|
325
|
+
) -> GenotypePhenotypeAnalysisResult:
|
|
326
|
+
"""
|
|
327
|
+
Bin patients according to a presence of zero, one, or two alleles that matche the `predicate`
|
|
328
|
+
and test for genotype-phenotype correlations.
|
|
329
|
+
"""
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
@abc.abstractmethod
|
|
333
|
+
def compare_hpo_vs_genotype_groups(
|
|
334
|
+
self,
|
|
335
|
+
predicates: typing.Iterable[VariantPredicate],
|
|
336
|
+
group_names: typing.Iterable[str],
|
|
337
|
+
) -> GenotypePhenotypeAnalysisResult:
|
|
338
|
+
"""
|
|
339
|
+
Bin patients according to a presence of at least one allele that matches
|
|
340
|
+
any of the provided `predicates` and test for genotype-phenotype correlations
|
|
341
|
+
between the groups.
|
|
342
|
+
|
|
343
|
+
Note, the patients that pass testing by >1 genotype predicate are *OMITTED* from the analysis!
|
|
344
|
+
"""
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
@abc.abstractmethod
|
|
348
|
+
def compare_disease_vs_genotype(
|
|
349
|
+
self,
|
|
350
|
+
predicate: VariantPredicate,
|
|
351
|
+
disease_ids: typing.Optional[typing.Sequence[typing.Union[str, hpotk.TermId]]] = None,
|
|
352
|
+
) -> GenotypePhenotypeAnalysisResult:
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
def compare_genotype_vs_phenotype_group_count(
|
|
356
|
+
self,
|
|
357
|
+
gt_predicate: GenotypePolyPredicate,
|
|
358
|
+
phenotype_group_terms: typing.Iterable[typing.Union[str, hpotk.TermId]],
|
|
359
|
+
) -> PhenotypeScoreAnalysisResult:
|
|
360
|
+
# TODO: separate into pscore module
|
|
361
|
+
assert isinstance(gt_predicate, GenotypePolyPredicate)
|
|
362
|
+
assert gt_predicate.n_categorizations() == 2
|
|
363
|
+
|
|
364
|
+
counting_scorer = CountingPhenotypeScorer.from_query_curies(
|
|
365
|
+
hpo=self._hpo,
|
|
366
|
+
query=phenotype_group_terms,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
return self.compare_genotype_vs_phenotype_score(
|
|
370
|
+
gt_predicate=gt_predicate,
|
|
371
|
+
phenotype_scorer=counting_scorer,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
@abc.abstractmethod
|
|
375
|
+
def compare_genotype_vs_phenotype_score(
|
|
376
|
+
self,
|
|
377
|
+
gt_predicate: GenotypePolyPredicate,
|
|
378
|
+
phenotype_scorer: PhenotypeScorer,
|
|
379
|
+
) -> PhenotypeScoreAnalysisResult:
|
|
380
|
+
"""
|
|
381
|
+
Score the patients with a phenotype scoring method and test for correlation between the genotype group
|
|
382
|
+
and the phenotype score.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
gt_predicate: a genotype predicate for binning the patients along the genotype axis.
|
|
386
|
+
phenotype_scorer: a callable that computes a phenotype score for a given `Patient`.
|
|
387
|
+
"""
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
@abc.abstractmethod
|
|
391
|
+
def compare_genotype_vs_cohort_phenotypes(
|
|
392
|
+
self,
|
|
393
|
+
gt_predicate: GenotypePolyPredicate,
|
|
394
|
+
) -> GenotypePhenotypeAnalysisResult:
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
@abc.abstractmethod
|
|
398
|
+
def compare_genotype_vs_phenotypes(
|
|
399
|
+
self,
|
|
400
|
+
gt_predicate: GenotypePolyPredicate,
|
|
401
|
+
pheno_predicates: typing.Iterable[PhenotypePolyPredicate[P]],
|
|
402
|
+
):
|
|
403
|
+
"""
|
|
404
|
+
All analysis functions go through this function.
|
|
405
|
+
|
|
406
|
+
The genotype predicate will partition the individuals into non-overlapping groups
|
|
407
|
+
along the genotype axis.
|
|
408
|
+
The phenotype predicates represent the phenotypes we want to test.
|
|
409
|
+
Less phenotypes may actually be tested thanks to :class:`~gpsea.analysis.PhenotypeMtcFilter`.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
gt_predicate: a predicate for binning the individuals along the genotype axis
|
|
413
|
+
pheno_predicates: phenotype predicates for test the individuals along the phenotype axis
|
|
414
|
+
"""
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
@staticmethod
|
|
418
|
+
def _check_min_perc_patients_w_hpo(min_perc_patients_w_hpo: typing.Union[int, float],
|
|
419
|
+
cohort_size: int) -> float:
|
|
420
|
+
"""
|
|
421
|
+
Check if the input meets the requirements.
|
|
422
|
+
"""
|
|
423
|
+
if isinstance(min_perc_patients_w_hpo, int):
|
|
424
|
+
if min_perc_patients_w_hpo > 0:
|
|
425
|
+
return min_perc_patients_w_hpo / cohort_size
|
|
426
|
+
else:
|
|
427
|
+
raise ValueError(f'`min_perc_patients_w_hpo` must be a positive `int` '
|
|
428
|
+
f'but got {min_perc_patients_w_hpo}')
|
|
429
|
+
elif isinstance(min_perc_patients_w_hpo, float):
|
|
430
|
+
if 0 < min_perc_patients_w_hpo <= 1:
|
|
431
|
+
return min_perc_patients_w_hpo
|
|
432
|
+
else:
|
|
433
|
+
raise ValueError(f'`min_perc_patients_w_hpo` must be a `float` in range (0, 1] '
|
|
434
|
+
f'but got {min_perc_patients_w_hpo}')
|
|
435
|
+
else:
|
|
436
|
+
raise ValueError(f'`min_perc_patients_w_hpo` must be a positive `int` or a `float` in range (0, 1] '
|
|
437
|
+
f'but got {type(min_perc_patients_w_hpo)}')
|
|
438
|
+
|
|
439
|
+
@staticmethod
|
|
440
|
+
def _group_patients_by_hpo(phenotypic_features: typing.Iterable[hpotk.TermId],
|
|
441
|
+
patients: typing.Iterable[Patient],
|
|
442
|
+
hpo: hpotk.GraphAware,
|
|
443
|
+
missing_implies_excluded: bool) -> PatientsByHPO:
|
|
444
|
+
all_with_hpo = defaultdict(list)
|
|
445
|
+
all_without_hpo = defaultdict(list)
|
|
446
|
+
for hpo_term in phenotypic_features:
|
|
447
|
+
for patient in patients:
|
|
448
|
+
found = False
|
|
449
|
+
for pf in patient.present_phenotypes():
|
|
450
|
+
if hpo_term == pf.identifier or hpo.graph.is_ancestor_of(hpo_term, pf):
|
|
451
|
+
# Patient is annotated with `hpo_term` because `pf` is equal to `hpo_term`
|
|
452
|
+
# or it is a descendant of `hpo_term`.
|
|
453
|
+
all_with_hpo[hpo_term].append(patient)
|
|
454
|
+
|
|
455
|
+
# If one `pf` of the patient is found to be a descendant of `hpo`, we must break to prevent
|
|
456
|
+
# adding the patient to `present_hpo` more than once due to another descendant!
|
|
457
|
+
found = True
|
|
458
|
+
break
|
|
459
|
+
if not found:
|
|
460
|
+
# The patient is not annotated by the `hpo_term`.
|
|
461
|
+
|
|
462
|
+
if missing_implies_excluded:
|
|
463
|
+
# The `hpo_term` annotation is missing, hence implicitly excluded.
|
|
464
|
+
all_without_hpo[hpo_term].append(patient)
|
|
465
|
+
else:
|
|
466
|
+
# The `hpo_term` must be explicitly excluded patient to be accounted for.
|
|
467
|
+
for ef in patient.excluded_phenotypes():
|
|
468
|
+
if hpo_term == ef.identifier or hpo.graph.is_descendant_of(hpo_term, ef):
|
|
469
|
+
all_with_hpo[hpo_term].append(patient)
|
|
470
|
+
break
|
|
471
|
+
|
|
472
|
+
return PatientsByHPO(all_with_hpo, all_without_hpo)
|