Skip to content

Commit ea26539

Browse files
committed
categorical phenotypes
1 parent 2c3c716 commit ea26539

File tree

4 files changed

+205
-0
lines changed

4 files changed

+205
-0
lines changed

phenex/filters/categorical_filter.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from phenex.filters.filter import Filter
2+
from typing import List
3+
from typing import Optional
4+
from ibis.expr.types.relations import Table
5+
6+
class CategoricalFilter(Filter):
7+
"""
8+
This class filters events in an EventTable based on specified categorical values
9+
10+
Attributes:
11+
category (Optional[str]): The category to filter events by.
12+
13+
Methods:
14+
_filter(table: MeasurementTable) -> MeasurementTable:
15+
Filters the given MeasurementTable based on the specified category.
16+
Parameters:
17+
table (Measurement): The table containing events to be filtered.
18+
Returns:
19+
MeasurementTable: The filtered MeasurementTable with events matching the category.
20+
"""
21+
22+
def __init__(
23+
self,
24+
column_name: str,
25+
allowed_values: List[str, int],
26+
domain: Optional[str] = None
27+
):
28+
self.column_name = column_name
29+
self.allowed_values = allowed_values
30+
self.domain = domain
31+
super(CategoricalFilter, self).__init__()
32+
33+
def _filter(self, table: Table):
34+
table = table.filter(table[self.column_name].isin(self.allowed_values))
35+
return table

phenex/phenotypes/death_phenotype.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from typing import Dict
2+
from ibis.expr.types.relations import Table
3+
from phenex.phenotypes.phenotype import Phenotype
4+
from phenex.tables import PhenotypeTable, is_phenex_person_table
5+
6+
class DeathPhenotype(Phenotype):
7+
"""
8+
DeathPhenotype is a class that represents a death-based phenotype. It filters individuals
9+
who have died and returns their date of death.
10+
11+
Attributes:
12+
name (str): Name of the phenotype, default is 'death'.
13+
domain (str): Domain of the phenotype, default is 'PERSON'.
14+
children (list): List of dependent phenotypes.
15+
16+
Methods:
17+
_execute(tables: Dict[str, Table]) -> PhenotypeTable:
18+
Executes the phenotype calculation and returns a table with the filtered individuals.
19+
"""
20+
21+
def __init__(self, name: str = "death", domain: str = "PERSON"):
22+
self.name = name
23+
self.domain = domain
24+
self.children = []
25+
super(DeathPhenotype, self).__init__()
26+
27+
def _execute(self, tables: Dict[str, Table]) -> PhenotypeTable:
28+
person_table = tables[self.domain]
29+
assert is_phenex_person_table(person_table)
30+
31+
death_table = person_table.filter(person_table.DEATH_DATE.notnull())
32+
return death_table.mutate(EVENT_DATE=death_table.DEATH_DATE)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import Union, List
2+
from phenex.phenotypes.phenotype import Phenotype
3+
from phenex.filters.relative_time_range_filter import RelativeTimeRangeFilter
4+
from phenex.filters.date_range_filter import DateRangeFilter
5+
from phenex.filters.aggregator import First, Last
6+
from phenex.filters.categorical_filter import CategoricalFilter
7+
from phenex.tables import is_phenex_code_table, PHENOTYPE_TABLE_COLUMNS, PhenotypeTable
8+
from phenex.phenotypes.functions import select_phenotype_columns
9+
from ibis import _
10+
11+
12+
class HospitalizationPhenotype(Phenotype):
13+
"""
14+
HospitalizationPhenotype filters an EncounterTable to identify inpatient events based on the encounter_type column.
15+
It uses a CategoricalFilter to filter for inpatient events and can apply additional date and time range filters.
16+
17+
Attributes:
18+
name (str): The name of the phenotype.
19+
domain (str): The domain of the phenotype, default is 'ENCOUNTER'.
20+
column_name (str): The name of the column to filter on, default is 'ENCOUNTER_TYPE'.
21+
allowed_values (List[str]): List of allowed values for the encounter_type column, default is ['inpatient'].
22+
date_range (DateRangeFilter, optional): A date range filter to apply.
23+
relative_time_range (Union[RelativeTimeRangeFilter, List[RelativeTimeRangeFilter]], optional): A relative time range filter or a list of filters to apply.
24+
return_date (str): Specifies whether to return the 'first', 'last', 'nearest', or 'all' event dates. Default is 'first'.
25+
table (PhenotypeTable): The resulting phenotype table after filtering.
26+
children (list): List of child phenotypes.
27+
28+
Methods:
29+
_execute(tables: Dict[str, Table]) -> PhenotypeTable:
30+
Executes the filtering process on the provided tables and returns the filtered phenotype table.
31+
"""
32+
def __init__(
33+
self,
34+
domain,
35+
column_name: str,
36+
allowed_values: List[str],
37+
name = None,
38+
date_range: DateRangeFilter = None,
39+
relative_time_range: Union[
40+
RelativeTimeRangeFilter, List[RelativeTimeRangeFilter]
41+
] = None,
42+
return_date="first",
43+
):
44+
super(HospitalizationPhenotype, self).__init__()
45+
46+
self.categorical_filter = CategoricalFilter(column_name=column_name, allowed_values=allowed_values)
47+
self.name = name
48+
self.date_range = date_range
49+
self.return_date = return_date
50+
assert self.return_date in [
51+
"first",
52+
"last",
53+
"nearest",
54+
"all",
55+
], f"Unknown return_date: {return_date}"
56+
self.table = None
57+
self.domain = domain
58+
if isinstance(relative_time_range, RelativeTimeRangeFilter):
59+
relative_time_range = [relative_time_range]
60+
61+
self.relative_time_range = relative_time_range
62+
if self.relative_time_range is not None:
63+
for rtr in self.relative_time_range:
64+
if rtr.anchor_phenotype is not None:
65+
self.children.append(rtr.anchor_phenotype)
66+
67+
def _execute(self, tables) -> PhenotypeTable:
68+
code_table = tables[self.domain]
69+
code_table = self._perform_categorical_filtering(code_table)
70+
code_table = self._perform_time_filtering(code_table)
71+
code_table = self._perform_date_selection(code_table)
72+
return select_phenotype_columns(code_table)
73+
74+
def _perform_categorical_filtering(self, code_table):
75+
assert is_phenex_code_table(code_table)
76+
code_table = self.categorical_filter.filter(code_table)
77+
return code_table
78+
79+
def _perform_time_filtering(self, code_table):
80+
if self.date_range is not None:
81+
code_table = self.date_range.filter(code_table)
82+
if self.relative_time_range is not None:
83+
for rtr in self.relative_time_range:
84+
code_table = rtr.filter(code_table)
85+
return code_table
86+
87+
def _perform_date_selection(self, code_table):
88+
if self.return_date is None or self.return_date == "all":
89+
return code_table
90+
if self.return_date == "first":
91+
aggregator = First()
92+
elif self.return_date == "last":
93+
aggregator = Last()
94+
else:
95+
raise ValueError(f"Unknown return_date: {self.return_date}")
96+
return aggregator.aggregate(code_table)

phenex/phenotypes/sex_phenotype.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from typing import Dict, List, Optional
2+
from ibis.expr.types.relations import Table
3+
from phenex.phenotypes.phenotype import Phenotype
4+
from phenex.filters.categorical_filter import CategoricalFilter
5+
from phenex.tables import PhenotypeTable, is_phenex_person_table
6+
7+
class SexPhenotype(Phenotype):
8+
"""
9+
SexPhenotype is a class that represents a sex-based phenotype. It filters individuals
10+
based on their sex (e.g., male, female) using the CategoricalFilter.
11+
12+
Attributes:
13+
name (str): Name of the phenotype, default is 'sex'.
14+
allowed_values (List[str]): List of allowed values for the sex column.
15+
domain (str): Domain of the phenotype, default is 'PERSON'.
16+
children (list): List of dependent phenotypes.
17+
18+
Methods:
19+
_execute(tables: Dict[str, Table]) -> PhenotypeTable:
20+
Executes the phenotype calculation and returns a table with the filtered individuals.
21+
"""
22+
23+
def __init__(
24+
self,
25+
name: str = "sex",
26+
allowed_values: List[str] = ["male", "female"],
27+
domain: str = "PERSON",
28+
):
29+
self.name = name
30+
self.allowed_values = allowed_values
31+
self.domain = domain
32+
self.children = []
33+
super(SexPhenotype, self).__init__()
34+
35+
def _execute(self, tables: Dict[str, Table]) -> PhenotypeTable:
36+
person_table = tables[self.domain]
37+
assert is_phenex_person_table(person_table)
38+
39+
sex_filter = CategoricalFilter(column_name="SEX", allowed_values=self.allowed_values)
40+
filtered_table = sex_filter._filter(person_table)
41+
42+
return filtered_table.mutate(VALUE=filtered_table.SEX)

0 commit comments

Comments
 (0)