gpsea 0.9.2__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 +10 -0
- gpsea/analysis/__init__.py +22 -0
- gpsea/analysis/_base.py +507 -0
- gpsea/analysis/_partition.py +69 -0
- gpsea/analysis/_util.py +29 -0
- gpsea/analysis/clf/__init__.py +40 -0
- gpsea/analysis/clf/_api.py +350 -0
- gpsea/analysis/clf/_counter.py +67 -0
- gpsea/analysis/clf/_gt_classifiers.py +702 -0
- gpsea/analysis/clf/_pheno.py +185 -0
- gpsea/analysis/clf/_test__gt_classifiers.py +145 -0
- gpsea/analysis/clf/_util.py +84 -0
- gpsea/analysis/mtc_filter/__init__.py +14 -0
- gpsea/analysis/mtc_filter/_impl.py +625 -0
- gpsea/analysis/pcats/__init__.py +38 -0
- gpsea/analysis/pcats/_config.py +29 -0
- gpsea/analysis/pcats/_impl.py +457 -0
- gpsea/analysis/pcats/stats/__init__.py +5 -0
- gpsea/analysis/pcats/stats/_stats.py +231 -0
- gpsea/analysis/pcats/stats/_test__stats.py +32 -0
- gpsea/analysis/predicate/__init__.py +43 -0
- gpsea/analysis/predicate/_api.py +223 -0
- gpsea/analysis/predicate/_predicates.py +750 -0
- gpsea/analysis/predicate/_variant.py +442 -0
- gpsea/analysis/pscore/__init__.py +9 -0
- gpsea/analysis/pscore/_api.py +288 -0
- gpsea/analysis/pscore/_hpo.py +328 -0
- gpsea/analysis/pscore/_measurement.py +112 -0
- gpsea/analysis/pscore/stats/__init__.py +7 -0
- gpsea/analysis/pscore/stats/_stats.py +122 -0
- gpsea/analysis/temporal/__init__.py +16 -0
- gpsea/analysis/temporal/_api.py +217 -0
- gpsea/analysis/temporal/_base.py +27 -0
- gpsea/analysis/temporal/_util.py +21 -0
- gpsea/analysis/temporal/endpoint/__init__.py +12 -0
- gpsea/analysis/temporal/endpoint/_impl.py +347 -0
- gpsea/analysis/temporal/stats/__init__.py +11 -0
- gpsea/analysis/temporal/stats/_api.py +35 -0
- gpsea/analysis/temporal/stats/_impl.py +54 -0
- gpsea/config.py +51 -0
- gpsea/io.py +407 -0
- gpsea/model/__init__.py +28 -0
- gpsea/model/_base.py +110 -0
- gpsea/model/_cohort.py +709 -0
- gpsea/model/_gt.py +176 -0
- gpsea/model/_phenotype.py +295 -0
- gpsea/model/_protein.py +551 -0
- gpsea/model/_temporal.py +300 -0
- gpsea/model/_test_gt.py +37 -0
- gpsea/model/_test_temporal.py +51 -0
- gpsea/model/_test_tx.py +64 -0
- gpsea/model/_tx.py +229 -0
- gpsea/model/_variant.py +910 -0
- gpsea/model/_variant_effects.py +170 -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 +37 -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 +29 -0
- gpsea/preprocessing/_api.py +235 -0
- gpsea/preprocessing/_caching.py +223 -0
- gpsea/preprocessing/_config.py +530 -0
- gpsea/preprocessing/_generic.py +56 -0
- gpsea/preprocessing/_patient.py +80 -0
- gpsea/preprocessing/_phenopacket.py +953 -0
- gpsea/preprocessing/_test__caching.py +72 -0
- gpsea/preprocessing/_test__phenopacket.py +44 -0
- gpsea/preprocessing/_uniprot.py +167 -0
- gpsea/preprocessing/_vep.py +197 -0
- gpsea/preprocessing/_vv.py +407 -0
- gpsea/py.typed +0 -0
- gpsea/util.py +78 -0
- gpsea/view/__init__.py +29 -0
- gpsea/view/_base.py +89 -0
- gpsea/view/_draw_variants.py +405 -0
- gpsea/view/_formatter.py +49 -0
- gpsea/view/_phenotype_analysis.py +78 -0
- gpsea/view/_protein_visualizable.py +167 -0
- gpsea/view/_protein_visualizer.py +687 -0
- gpsea/view/_txp.py +106 -0
- gpsea/view/_viewers.py +683 -0
- gpsea/view/templates/all_variants.html +30 -0
- gpsea/view/templates/cohort.html +216 -0
- gpsea/view/templates/disease.html +106 -0
- gpsea/view/templates/minibase.html +136 -0
- gpsea/view/templates/protein.html +32 -0
- gpsea/view/templates/stats.html +33 -0
- gpsea-0.9.2.dist-info/LICENSE +21 -0
- gpsea-0.9.2.dist-info/METADATA +79 -0
- gpsea-0.9.2.dist-info/RECORD +95 -0
- gpsea-0.9.2.dist-info/WHEEL +5 -0
- gpsea-0.9.2.dist-info/top_level.txt +1 -0
gpsea/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ._base import (
|
|
2
|
+
AnalysisException,
|
|
3
|
+
AnalysisResult,
|
|
4
|
+
MonoPhenotypeAnalysisResult,
|
|
5
|
+
MultiPhenotypeAnalysisResult,
|
|
6
|
+
Statistic,
|
|
7
|
+
StatisticResult,
|
|
8
|
+
)
|
|
9
|
+
from ._partition import Partitioning, ContinuousPartitioning
|
|
10
|
+
from ._util import Summarizable
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AnalysisException",
|
|
14
|
+
"AnalysisResult",
|
|
15
|
+
"MonoPhenotypeAnalysisResult",
|
|
16
|
+
"MultiPhenotypeAnalysisResult",
|
|
17
|
+
"Statistic",
|
|
18
|
+
"StatisticResult",
|
|
19
|
+
"Partitioning",
|
|
20
|
+
"ContinuousPartitioning",
|
|
21
|
+
"Summarizable",
|
|
22
|
+
]
|
gpsea/analysis/_base.py
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import math
|
|
3
|
+
import os
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from .clf import GenotypeClassifier, PhenotypeClassifier, P
|
|
10
|
+
from ._partition import Partitioning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StatisticResult:
|
|
14
|
+
"""
|
|
15
|
+
`StatisticResult` reports result of a :class:`~gpsea.analysis.Statistic`.
|
|
16
|
+
|
|
17
|
+
It includes a statistic (optional) and a corresponding p value.
|
|
18
|
+
The p value can be `NaN` if it is impossible to compute for a given dataset.
|
|
19
|
+
|
|
20
|
+
Raises an :class:`AssertionError` for an invalid input.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
statistic: typing.Optional[typing.Union[int, float]],
|
|
26
|
+
pval: float,
|
|
27
|
+
):
|
|
28
|
+
if statistic is not None:
|
|
29
|
+
assert isinstance(statistic, (float, int))
|
|
30
|
+
self._statistic = float(statistic)
|
|
31
|
+
else:
|
|
32
|
+
self._statistic = None
|
|
33
|
+
|
|
34
|
+
assert isinstance(pval, float) and (math.isnan(pval) or 0.0 <= pval <= 1.0)
|
|
35
|
+
self._pval = float(pval)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def statistic(self) -> typing.Optional[float]:
|
|
39
|
+
"""
|
|
40
|
+
Get a `float` with the test statistic or `None` if not available.
|
|
41
|
+
"""
|
|
42
|
+
return self._statistic
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def pval(self) -> float:
|
|
46
|
+
"""
|
|
47
|
+
Get a p value (a value or a `NaN`).
|
|
48
|
+
"""
|
|
49
|
+
return self._pval
|
|
50
|
+
|
|
51
|
+
def __eq__(self, value: object) -> bool:
|
|
52
|
+
return (
|
|
53
|
+
isinstance(value, StatisticResult)
|
|
54
|
+
and self._statistic == value._statistic
|
|
55
|
+
and self._pval == value._pval
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def __hash__(self) -> int:
|
|
59
|
+
return hash(
|
|
60
|
+
(
|
|
61
|
+
self._statistic,
|
|
62
|
+
self._pval,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str:
|
|
67
|
+
return repr(self)
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
return f"StatisticResult(statistic={self._statistic}, pval={self._pval})"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AnalysisException(Exception):
|
|
74
|
+
"""
|
|
75
|
+
Reports analysis issues that need user's attention.
|
|
76
|
+
|
|
77
|
+
To aid troubleshooting, the exception includes :attr:`~gpsea.analysis.AnalysisException.data` -
|
|
78
|
+
a mapping with any data that has been computed prior encountering the issues.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
data: typing.Mapping[str, typing.Any],
|
|
84
|
+
*args,
|
|
85
|
+
):
|
|
86
|
+
super().__init__(*args)
|
|
87
|
+
self._data = data
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def data(self) -> typing.Mapping[str, typing.Any]:
|
|
91
|
+
"""
|
|
92
|
+
Get a mapping with (partial) data to aid troubleshooting.
|
|
93
|
+
"""
|
|
94
|
+
return self._data
|
|
95
|
+
|
|
96
|
+
def __repr__(self) -> str:
|
|
97
|
+
return f"AnalysisException(args={self.args}, data={self._data})"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Statistic(metaclass=abc.ABCMeta):
|
|
101
|
+
"""
|
|
102
|
+
Mixin for classes that are used to compute a nominal p value for a genotype-phenotype association.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
name: str,
|
|
108
|
+
):
|
|
109
|
+
self._name = name
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def name(self) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Get the name of the statistic (e.g. `Fisher Exact Test`, `Logrank test`).
|
|
115
|
+
"""
|
|
116
|
+
return self._name
|
|
117
|
+
|
|
118
|
+
def __eq__(self, value: object) -> bool:
|
|
119
|
+
if isinstance(value, Statistic):
|
|
120
|
+
return self._name == value._name
|
|
121
|
+
return NotImplemented
|
|
122
|
+
|
|
123
|
+
def __hash__(self) -> int:
|
|
124
|
+
return hash((self._name,))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class AnalysisResult(metaclass=abc.ABCMeta):
|
|
128
|
+
"""
|
|
129
|
+
`AnalysisResult` includes the common parts of results of all analyses.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
gt_clf: GenotypeClassifier,
|
|
135
|
+
statistic: Statistic,
|
|
136
|
+
):
|
|
137
|
+
assert isinstance(gt_clf, GenotypeClassifier)
|
|
138
|
+
self._gt_clf = gt_clf
|
|
139
|
+
|
|
140
|
+
assert isinstance(statistic, Statistic)
|
|
141
|
+
self._statistic = statistic
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def gt_clf(self) -> GenotypeClassifier:
|
|
145
|
+
"""
|
|
146
|
+
Get the genotype classifier used in the survival analysis that produced this result.
|
|
147
|
+
"""
|
|
148
|
+
return self._gt_clf
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def statistic(self) -> Statistic:
|
|
152
|
+
"""
|
|
153
|
+
Get the statistic which computed the (nominal) p values for this result.
|
|
154
|
+
"""
|
|
155
|
+
return self._statistic
|
|
156
|
+
|
|
157
|
+
def __eq__(self, value: object) -> bool:
|
|
158
|
+
return (
|
|
159
|
+
isinstance(value, AnalysisResult)
|
|
160
|
+
and self._gt_clf == value._gt_clf
|
|
161
|
+
and self._statistic == value._statistic
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def __hash__(self) -> int:
|
|
165
|
+
return hash(
|
|
166
|
+
(
|
|
167
|
+
self._gt_clf,
|
|
168
|
+
self._statistic,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class MultiPhenotypeAnalysisResult(typing.Generic[P], AnalysisResult):
|
|
174
|
+
"""
|
|
175
|
+
`MultiPhenotypeAnalysisResult` reports the outcome of an analysis
|
|
176
|
+
that tested the association of genotype with two or more phenotypes.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
gt_clf: GenotypeClassifier,
|
|
182
|
+
pheno_clfs: typing.Iterable[PhenotypeClassifier[P]],
|
|
183
|
+
statistic: Statistic,
|
|
184
|
+
n_usable: typing.Sequence[int],
|
|
185
|
+
all_counts: typing.Sequence[pd.DataFrame],
|
|
186
|
+
statistic_results: typing.Sequence[typing.Optional[StatisticResult]],
|
|
187
|
+
corrected_pvals: typing.Optional[typing.Sequence[float]],
|
|
188
|
+
mtc_correction: typing.Optional[str],
|
|
189
|
+
):
|
|
190
|
+
super().__init__(
|
|
191
|
+
gt_clf=gt_clf,
|
|
192
|
+
statistic=statistic,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
self._pheno_clfs = tuple(pheno_clfs)
|
|
196
|
+
|
|
197
|
+
self._n_usable = tuple(n_usable)
|
|
198
|
+
self._all_counts = tuple(all_counts)
|
|
199
|
+
|
|
200
|
+
self._statistic_results = tuple(statistic_results)
|
|
201
|
+
self._corrected_pvals = (
|
|
202
|
+
None if corrected_pvals is None else tuple(corrected_pvals)
|
|
203
|
+
)
|
|
204
|
+
errors = self._check_sanity()
|
|
205
|
+
if errors:
|
|
206
|
+
raise ValueError(os.linesep.join(errors))
|
|
207
|
+
|
|
208
|
+
if mtc_correction is not None:
|
|
209
|
+
assert isinstance(mtc_correction, str)
|
|
210
|
+
self._mtc_correction = mtc_correction
|
|
211
|
+
|
|
212
|
+
def _check_sanity(self) -> typing.Sequence[str]:
|
|
213
|
+
errors = []
|
|
214
|
+
# All sequences must have the same lengths ...
|
|
215
|
+
for seq, name in (
|
|
216
|
+
(self._n_usable, "n_usable"),
|
|
217
|
+
(self._all_counts, "all_counts"),
|
|
218
|
+
(self._statistic_results, "statistic_results"),
|
|
219
|
+
):
|
|
220
|
+
if len(self._pheno_clfs) != len(seq):
|
|
221
|
+
errors.append(
|
|
222
|
+
f"`len(pheno_clfs)` must be the same as `len({name})` but "
|
|
223
|
+
f"{len(self._pheno_clfs)}!={len(seq)}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# ... including the optional corrected p values
|
|
227
|
+
if self._corrected_pvals is not None and len(self._pheno_clfs) != len(
|
|
228
|
+
self._corrected_pvals
|
|
229
|
+
):
|
|
230
|
+
errors.append(
|
|
231
|
+
f"`len(pheno_predicates)` must be the same as `len(corrected_pvals)` but "
|
|
232
|
+
f"{len(self._pheno_clfs)}!={len(self._corrected_pvals)}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not isinstance(self._gt_clf, GenotypeClassifier):
|
|
236
|
+
errors.append("`gt_clf` must be an instance of `GenotypeClassifier`")
|
|
237
|
+
return errors
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def pheno_clfs(
|
|
241
|
+
self,
|
|
242
|
+
) -> typing.Sequence[PhenotypeClassifier[P]]:
|
|
243
|
+
"""
|
|
244
|
+
Get the phenotype classifiers used in the analysis.
|
|
245
|
+
"""
|
|
246
|
+
return self._pheno_clfs
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def phenotypes(self) -> typing.Sequence[P]:
|
|
250
|
+
"""
|
|
251
|
+
Get the phenotypes that were tested for association with genotype in the analysis.
|
|
252
|
+
"""
|
|
253
|
+
return tuple(p.phenotype for p in self._pheno_clfs)
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def n_usable(self) -> typing.Sequence[int]:
|
|
257
|
+
"""
|
|
258
|
+
Get a sequence of numbers of patients where the phenotype was assessable,
|
|
259
|
+
and are, thus, usable for genotype-phenotype correlation analysis.
|
|
260
|
+
"""
|
|
261
|
+
return self._n_usable
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def all_counts(self) -> typing.Sequence[pd.DataFrame]:
|
|
265
|
+
"""
|
|
266
|
+
Get a :class:`~pandas.DataFrame` sequence where each `DataFrame` includes the counts of patients
|
|
267
|
+
in genotype and phenotype groups.
|
|
268
|
+
|
|
269
|
+
An example for a genotype predicate that bins into two categories (`Yes` and `No`) based on presence
|
|
270
|
+
of a missense variant in transcript `NM_123456.7`, and phenotype predicate that checks
|
|
271
|
+
presence/absence of `HP:0001166` (a phenotype term)::
|
|
272
|
+
|
|
273
|
+
Has MISSENSE_VARIANT in NM_123456.7
|
|
274
|
+
No Yes
|
|
275
|
+
Present
|
|
276
|
+
Yes 1 13
|
|
277
|
+
No 7 5
|
|
278
|
+
|
|
279
|
+
The rows correspond to the phenotype categories, and the columns represent the genotype categories.
|
|
280
|
+
"""
|
|
281
|
+
return self._all_counts
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def statistic_results(self) -> typing.Sequence[typing.Optional[StatisticResult]]:
|
|
285
|
+
"""
|
|
286
|
+
Get a sequence of :class:`~gpsea.analysis.StatisticResult` items with nominal p values and the associated statistic values
|
|
287
|
+
for each tested phenotype or `None` for the untested phenotypes.
|
|
288
|
+
"""
|
|
289
|
+
return self._statistic_results
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def pvals(self) -> typing.Sequence[float]:
|
|
293
|
+
"""
|
|
294
|
+
Get a sequence of nominal p values for each tested phenotype.
|
|
295
|
+
The sequence includes a `NaN` value for each phenotype that was *not* tested.
|
|
296
|
+
"""
|
|
297
|
+
return tuple(
|
|
298
|
+
float("nan") if r is None else r.pval for r in self._statistic_results
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def corrected_pvals(self) -> typing.Optional[typing.Sequence[float]]:
|
|
303
|
+
"""
|
|
304
|
+
Get a sequence with p values for each tested phenotype after multiple testing correction
|
|
305
|
+
or `None` if the correction was not applied.
|
|
306
|
+
The sequence includes a `NaN` value for each phenotype that was *not* tested.
|
|
307
|
+
"""
|
|
308
|
+
return self._corrected_pvals
|
|
309
|
+
|
|
310
|
+
def n_significant_for_alpha(
|
|
311
|
+
self,
|
|
312
|
+
alpha: float = 0.05,
|
|
313
|
+
) -> typing.Optional[int]:
|
|
314
|
+
"""
|
|
315
|
+
Get the count of the corrected p values with the value being less than or equal to `alpha`.
|
|
316
|
+
|
|
317
|
+
:param alpha: a `float` with significance level.
|
|
318
|
+
"""
|
|
319
|
+
if self.corrected_pvals is None:
|
|
320
|
+
return None
|
|
321
|
+
else:
|
|
322
|
+
return sum(p_val <= alpha for p_val in self.corrected_pvals)
|
|
323
|
+
|
|
324
|
+
def significant_phenotype_indices(
|
|
325
|
+
self,
|
|
326
|
+
alpha: float = 0.05,
|
|
327
|
+
pval_kind: typing.Literal["corrected", "nominal"] = "corrected",
|
|
328
|
+
) -> typing.Optional[typing.Sequence[int]]:
|
|
329
|
+
"""
|
|
330
|
+
Get the indices of phenotypes that attain significance for provided `alpha`.
|
|
331
|
+
"""
|
|
332
|
+
if pval_kind == "corrected":
|
|
333
|
+
if self.corrected_pvals is None:
|
|
334
|
+
vals = None
|
|
335
|
+
else:
|
|
336
|
+
vals = np.array(self.corrected_pvals)
|
|
337
|
+
elif pval_kind == "nominal":
|
|
338
|
+
vals = np.array(self.pvals)
|
|
339
|
+
else:
|
|
340
|
+
raise ValueError(f"Unsupported `pval_kind` value {pval_kind}")
|
|
341
|
+
|
|
342
|
+
if vals is None:
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
not_na = ~np.isnan(vals)
|
|
346
|
+
significant = vals <= alpha
|
|
347
|
+
selected = not_na & significant
|
|
348
|
+
|
|
349
|
+
return tuple(int(idx) for idx in np.argsort(vals) if selected[idx])
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def total_tests(self) -> int:
|
|
353
|
+
"""
|
|
354
|
+
Get total count of genotype-phenotype associations that were tested in this analysis.
|
|
355
|
+
"""
|
|
356
|
+
return sum(1 for result in self._statistic_results if result is not None)
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def mtc_correction(self) -> typing.Optional[str]:
|
|
360
|
+
"""
|
|
361
|
+
Get name/code of the used multiple testing correction
|
|
362
|
+
(e.g. `fdr_bh` for Benjamini-Hochberg) or `None` if no correction was applied.
|
|
363
|
+
"""
|
|
364
|
+
return self._mtc_correction
|
|
365
|
+
|
|
366
|
+
def __eq__(self, value: object) -> bool:
|
|
367
|
+
return (
|
|
368
|
+
isinstance(value, MultiPhenotypeAnalysisResult)
|
|
369
|
+
and super(AnalysisResult, self).__eq__(value)
|
|
370
|
+
and self._pheno_clfs == value._pheno_clfs
|
|
371
|
+
and self._n_usable == value._n_usable
|
|
372
|
+
and self._all_counts == value._all_counts
|
|
373
|
+
and self._statistic_results == value._statistic_results
|
|
374
|
+
and self._corrected_pvals == value._corrected_pvals
|
|
375
|
+
and self._mtc_correction == value._mtc_correction
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def __hash__(self) -> int:
|
|
379
|
+
return hash(
|
|
380
|
+
(
|
|
381
|
+
super(AnalysisResult, self).__hash__(),
|
|
382
|
+
self._pheno_clfs,
|
|
383
|
+
self._n_usable,
|
|
384
|
+
self._all_counts,
|
|
385
|
+
self._statistic_results,
|
|
386
|
+
self._corrected_pvals,
|
|
387
|
+
self._mtc_correction,
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class MonoPhenotypeAnalysisResult(AnalysisResult, metaclass=abc.ABCMeta):
|
|
393
|
+
"""
|
|
394
|
+
`MonoPhenotypeAnalysisResult` reports the outcome of an analysis
|
|
395
|
+
that tested a single genotype-phenotype association.
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
SAMPLE_ID = "patient_id"
|
|
399
|
+
"""
|
|
400
|
+
Name of the data index.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
GT_COL = "genotype"
|
|
404
|
+
"""
|
|
405
|
+
Name of column for storing genotype data.
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
PH_COL = "phenotype"
|
|
409
|
+
"""
|
|
410
|
+
Name of column for storing phenotype data.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
DATA_COLUMNS = (GT_COL, PH_COL)
|
|
414
|
+
"""
|
|
415
|
+
The required columns of the `data` data frame.
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
def __init__(
|
|
419
|
+
self,
|
|
420
|
+
gt_clf: GenotypeClassifier,
|
|
421
|
+
phenotype: Partitioning,
|
|
422
|
+
statistic: Statistic,
|
|
423
|
+
data: pd.DataFrame,
|
|
424
|
+
statistic_result: StatisticResult,
|
|
425
|
+
):
|
|
426
|
+
super().__init__(gt_clf, statistic)
|
|
427
|
+
|
|
428
|
+
assert isinstance(phenotype, Partitioning)
|
|
429
|
+
self._phenotype = phenotype
|
|
430
|
+
|
|
431
|
+
assert isinstance(data, pd.DataFrame) and all(
|
|
432
|
+
col in data.columns for col in MonoPhenotypeAnalysisResult.DATA_COLUMNS
|
|
433
|
+
)
|
|
434
|
+
self._data = data
|
|
435
|
+
|
|
436
|
+
assert isinstance(statistic_result, StatisticResult)
|
|
437
|
+
self._statistic_result = statistic_result
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def phenotype(self) -> Partitioning:
|
|
441
|
+
"""
|
|
442
|
+
Get the :class:`~gpsea.analysis.Partitioning` that produced the phenotype.
|
|
443
|
+
"""
|
|
444
|
+
return self._phenotype
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
def data(self) -> pd.DataFrame:
|
|
448
|
+
"""
|
|
449
|
+
Get the data frame with genotype and phenotype values for each tested individual.
|
|
450
|
+
|
|
451
|
+
The index of the data frame contains the identifiers of the tested individuals,
|
|
452
|
+
and the values are stored in `genotype` and `phenotype` columns.
|
|
453
|
+
|
|
454
|
+
The `genotype` column includes the genotype category ID
|
|
455
|
+
(:attr:`~gpsea.analysis.clf.PatientCategory.cat_id`)
|
|
456
|
+
or `None` if the individual could not be assigned into a genotype group.
|
|
457
|
+
The `phenotype` contains the phenotype values, and the data type depends on the analysis.
|
|
458
|
+
|
|
459
|
+
Here are some common phenotype data types:
|
|
460
|
+
|
|
461
|
+
* a phenotype score computed in :class:`~gpsea.analysis.pscore.PhenotypeScoreAnalysis` is a `float`
|
|
462
|
+
* survival computed in :class:`~gpsea.analysis.temporal.SurvivalAnalysis`
|
|
463
|
+
is of type :class:`~gpsea.analysis.temporal.Survival`
|
|
464
|
+
"""
|
|
465
|
+
return self._data
|
|
466
|
+
|
|
467
|
+
def complete_records(self) -> pd.DataFrame:
|
|
468
|
+
"""
|
|
469
|
+
Get the :attr:`~gpsea.analysis.MonoPhenotypeAnalysisResult.data` rows
|
|
470
|
+
where both `genotype` and `phenotype` columns are available (i.e. not `None` or `NaN`).
|
|
471
|
+
"""
|
|
472
|
+
return self._data.loc[
|
|
473
|
+
self._data[MonoPhenotypeAnalysisResult.GT_COL].notna()
|
|
474
|
+
& self._data[MonoPhenotypeAnalysisResult.PH_COL].notna()
|
|
475
|
+
]
|
|
476
|
+
|
|
477
|
+
def statistic_result(self) -> StatisticResult:
|
|
478
|
+
"""
|
|
479
|
+
Get statistic result with the nominal p value and the associated statistics.
|
|
480
|
+
"""
|
|
481
|
+
return self._statistic_result
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def pval(self) -> float:
|
|
485
|
+
"""
|
|
486
|
+
Get the p value of the test.
|
|
487
|
+
"""
|
|
488
|
+
return self._statistic_result.pval
|
|
489
|
+
|
|
490
|
+
def __eq__(self, value: object) -> bool:
|
|
491
|
+
return (
|
|
492
|
+
isinstance(value, MonoPhenotypeAnalysisResult)
|
|
493
|
+
and super(AnalysisResult, self).__eq__(value)
|
|
494
|
+
and self._phenotype == value._phenotype
|
|
495
|
+
and self._statistic_result == value._statistic_result
|
|
496
|
+
and self._data.equals(value._data)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
def __hash__(self) -> int:
|
|
500
|
+
return hash(
|
|
501
|
+
(
|
|
502
|
+
super(AnalysisResult, self).__hash__(),
|
|
503
|
+
self._phenotype,
|
|
504
|
+
self._statistic_result,
|
|
505
|
+
self._data,
|
|
506
|
+
)
|
|
507
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import os
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from gpsea.model import Patient
|
|
6
|
+
|
|
7
|
+
from ._util import Summarizable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Partitioning(Summarizable, metaclass=abc.ABCMeta):
|
|
11
|
+
"""
|
|
12
|
+
`Partitioning` is a superclass of all classes that assign a group,
|
|
13
|
+
compute a score or survival for an individual.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
@abc.abstractmethod
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Get the name of the partitioning.
|
|
21
|
+
"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
@abc.abstractmethod
|
|
26
|
+
def description(self) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Get a description of the partitioning.
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def variable_name(self) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Get a `str` with the name of the variable investigated by the partitioning.
|
|
37
|
+
|
|
38
|
+
For instance `Sex`, `Allele groups`, `HP:0001250`, `OMIM:256000`
|
|
39
|
+
"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def summarize(
|
|
43
|
+
self,
|
|
44
|
+
out: typing.TextIO,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Summarize the item while also considering `other` (default `None`).
|
|
48
|
+
"""
|
|
49
|
+
out.write(self.name)
|
|
50
|
+
out.write(os.linesep)
|
|
51
|
+
out.write(self.description)
|
|
52
|
+
out.write(os.linesep)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _check_patient(patient: Patient):
|
|
56
|
+
"""
|
|
57
|
+
Check if the `patient` meets the partitioning requirements.
|
|
58
|
+
"""
|
|
59
|
+
if not isinstance(patient, Patient):
|
|
60
|
+
raise ValueError(f"patient must be type Patient but was type {type(patient)}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ContinuousPartitioning(Partitioning, metaclass=abc.ABCMeta):
|
|
64
|
+
"""
|
|
65
|
+
`ContinuousPartitioning` computes a score that is a real number.
|
|
66
|
+
|
|
67
|
+
The class is just a marker class at this time.
|
|
68
|
+
"""
|
|
69
|
+
pass
|
gpsea/analysis/_util.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import io
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Summarizable(metaclass=abc.ABCMeta):
|
|
7
|
+
"""
|
|
8
|
+
A mixin for entities that can summarize themselves into a provided IO handle.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@abc.abstractmethod
|
|
12
|
+
def summarize(
|
|
13
|
+
self,
|
|
14
|
+
out: typing.TextIO,
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Summarize the item into the provided IO handle.
|
|
18
|
+
|
|
19
|
+
:param out: an IO handle to write into.
|
|
20
|
+
"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def summary(self) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Get the summary.
|
|
26
|
+
"""
|
|
27
|
+
buf = io.StringIO()
|
|
28
|
+
self.summarize(buf)
|
|
29
|
+
return buf.getvalue()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from ._api import Classifier, PatientCategory, Categorization
|
|
2
|
+
from ._api import C, GenotypeClassifier
|
|
3
|
+
from ._api import P, PhenotypeClassifier, PhenotypeCategorization
|
|
4
|
+
from ._counter import AlleleCounter
|
|
5
|
+
from ._pheno import HpoClassifier, DiseasePresenceClassifier
|
|
6
|
+
from ._gt_classifiers import (
|
|
7
|
+
sex_classifier,
|
|
8
|
+
diagnosis_classifier,
|
|
9
|
+
monoallelic_classifier,
|
|
10
|
+
biallelic_classifier,
|
|
11
|
+
allele_count,
|
|
12
|
+
random_classifier,
|
|
13
|
+
)
|
|
14
|
+
from ._util import (
|
|
15
|
+
prepare_classifiers_for_terms_of_interest,
|
|
16
|
+
prepare_hpo_terms_of_interest,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Classifier",
|
|
22
|
+
"PatientCategory",
|
|
23
|
+
"Categorization",
|
|
24
|
+
"GenotypeClassifier",
|
|
25
|
+
"C",
|
|
26
|
+
"AlleleCounter",
|
|
27
|
+
"sex_classifier",
|
|
28
|
+
"diagnosis_classifier",
|
|
29
|
+
"monoallelic_classifier",
|
|
30
|
+
"biallelic_classifier",
|
|
31
|
+
"allele_count",
|
|
32
|
+
"random_classifier",
|
|
33
|
+
"PhenotypeClassifier",
|
|
34
|
+
"PhenotypeCategorization",
|
|
35
|
+
"P",
|
|
36
|
+
"HpoClassifier",
|
|
37
|
+
"DiseasePresenceClassifier",
|
|
38
|
+
"prepare_classifiers_for_terms_of_interest",
|
|
39
|
+
"prepare_hpo_terms_of_interest",
|
|
40
|
+
]
|