Custom components
GPSEA aims to stay useful in the long run. Therefore, we took a great care to adhere to “good software development practices” and we framed GPSEA functionalities as a set of loosely coupled components. As a rule of thumb, each component corresponds to a Python abstract base class which is then extended by the builtin components and can also be extended by the future components, to serve both common or exotic use cases.
The abstract base classes define the component API.
Per guidelines in Python’s abc
module, the abstract classes use abc.ABCMeta
as a metaclass
and the class API consists of methods annotated with the abc.abstractmethod()
decorator.
These decorated methods must be overridden in the subclasses.
The following sections provide guidance for customizing the most commonly used GPSEA components.
Phenotype scorer
PhenotypeScorer
computes a phenotype score for an individual.
The phenotype score is a float with range \((-\infty, \infty)\) where NaN indicates
that a score cannot be computed (e.g. the lab measurement value was not ascertained for the individual).
Here we show an example of a toy phenotype scorer for using body mass index (BMI) as a phenotype score.
>>> import typing
>>> from gpsea.model import Patient
>>> from gpsea.analysis.pscore import PhenotypeScorer
>>> class BmiScorer(PhenotypeScorer): # ❶
...
... def __init__( # ❷
... self,
... id2bmi: typing.Mapping[str, float],
... ):
... self._id2bmi = id2bmi
...
... @property
... def name(self) -> str: # ❸
... return "BMI phenotype scorer"
...
... @property
... def description(self) -> str: # ❹
... return "Body mass index used as a phenotype score"
...
... @property
... def variable_name(self) -> str: # ❺
... return "BMI"
...
... def score(self, patient: Patient) -> float: # ❻
... try:
... return self._id2bmi[patient.labels.label]
... except KeyError:
... return float('nan')
❶ The BmiScorer
must extend PhenotypeScorer
to be used as a phenotype scorer.
❷ The scorer needs a dict
with label → BMI for the analyzed individuals.
We assume the user will pre-compute the corresponding dict
.
Then, the scorer must expose several properties, including ❸ name
, ❹ description
,
and the ❺ variable_name
it operates on.
The properties provide bookkeeping metadata to use in e.g. visualizations.
Try to choose short and concise names.
The most important part of the scorer is the ❻ score method which retrieves the BMI for an individual or returns NaN if the value is not available and the individual should be omitted from the analysis.
Variant predicate
The purpose of a VariantPredicate
is to test
if a variant meets a certain criterion and GPSEA ships with an array
of builtin predicates (see gpsea.analysis.predicate
module).
However, chances are a custom predicate will be needed in future,
so we show how to how to extend
the VariantPredicate
class
to create one’s own predicate.
Specifically, we show how to create a predicate to test if the variant affects a glycine residue of the transcript of interest.
>>> from gpsea.model import Variant, VariantEffect
>>> from gpsea.analysis.predicate import VariantPredicate
>>> class AffectsGlycinePredicate(VariantPredicate): # ❶
... def __init__( # ❷
... self,
... tx_id: str,
... ):
... self._tx_id = tx_id
... self._aa_code = "Gly"
...
... @property
... def name(self) -> str: # ❸
... return "Affects Glycine"
...
... @property
... def description(self) -> str: # ❹
... return "affects a glycine residue"
...
... @property
... def variable_name(self) -> str: # ❺
... return "affected aminoacid residue"
...
... def test(self, variant: Variant) -> bool: # ❻
... tx_ann = variant.get_tx_anno_by_tx_id(self._tx_id)
... if tx_ann is not None:
... hgvsp = tx_ann.hgvsp
... if hgvsp is not None:
... return hgvsp.startswith(f"p.{self._aa_code}")
... return False
...
... def __eq__(self, value: object) -> bool: # ➐
... return isinstance(value, AffectsGlycinePredicate) and self._tx_id == value._tx_id
...
... def __hash__(self) -> int: # ❽
... return hash((self._tx_id,))
...
... def __repr__(self) -> str: # ❾
... return str(self)
...
... def __str__(self) -> str: # ➓
... return f"AffectsGlycinePredicate(tx_id={self._tx_id})"
❶ The AffectsGlycinePredicate
must extend VariantPredicate
.
❷ We ask the user to provide the transcript accession str and we set the target aminoacid code to glycine Gly
.
Like in the Phenotype scorer above, ❸❹❺ provide metadata required for the bookkeeping.
The ❻ test
method includes the most interesting part - we retrieve the TranscriptAnnotation
with the functional annotation data for the transcript of interest, and we test if the HGVS protein indicates
that the reference aminoacid is glycine.
Last, we override ➐ __eq__()
and ❽ __hash__()
(required) as well as ❾ __repr__()
and ➓ __str__()
(recommended).