Skip to content

Commit ee1b590

Browse files
committed
continuous coverage now working
1 parent 8bf336f commit ee1b590

File tree

4 files changed

+115
-22
lines changed

4 files changed

+115
-22
lines changed

phenex/filters/categorical_filter.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from phenex.filters.filter import Filter
2-
from typing import List
3-
from typing import Optional
2+
from typing import List, Optional, Union
43
from ibis.expr.types.relations import Table
54

65
class CategoricalFilter(Filter):
@@ -20,9 +19,9 @@ class CategoricalFilter(Filter):
2019
"""
2120

2221
def __init__(
23-
self,
22+
self,
2423
column_name: str,
25-
allowed_values: List[str, int],
24+
allowed_values: List[Union[str, int]],
2625
domain: Optional[str] = None
2726
):
2827
self.column_name = column_name

phenex/filters/codelist_filter.py

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ def __init__(self, codelist: Codelist, name=None, use_code_type: bool = True):
2626

2727
def _convert_codelist_to_tuples(self) -> List[Tuple[str, str]]:
2828
if self.codelist is not None:
29+
if not isinstance(self.codelist, Codelist):
30+
raise ValueError("Codelist must be an instance of Codelist")
2931
return [
3032
(ct, c) for ct, codes in self.codelist.codelist.items() for c in codes
3133
]

phenex/mappers.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,53 @@ def rename(self, table: Table) -> Table:
176176
#
177177
# Domains
178178
#
179-
OMOPDomains = DomainsDictionary(**OMOPColumnMappers)
179+
OMOPDomains = DomainsDictionary(**OMOPColumnMappers)
180+
181+
182+
183+
184+
#
185+
# Vera Column Mappers
186+
#
187+
VeraPersonTableColumnMapper = PersonTableColumnMapper(
188+
NAME_TABLE="PERSON", PERSON_ID="PERSON_ID", DATE_OF_BIRTH="BIRTH_DATETIME", DATE_OF_DEATH="DEATH_DATETIME"
189+
)
190+
191+
VeraConditionOccurrenceColumnMapper = CodeTableColumnMapper(
192+
NAME_TABLE="CONDITION_OCCURRENCE",
193+
EVENT_DATE="CONDITION_START_DATE",
194+
CODE="CONDITION_CONCEPT_ID",
195+
)
196+
197+
VeraProcedureOccurrenceColumnMapper = CodeTableColumnMapper(
198+
NAME_TABLE="PROCEDURE_OCCURRENCE",
199+
EVENT_DATE="PROCEDURE_DATE",
200+
CODE="PROCEDURE_CONCEPT_ID",
201+
)
202+
203+
VeraDrugExposureColumnMapper = CodeTableColumnMapper(
204+
NAME_TABLE="DRUG_EXPOSURE",
205+
EVENT_DATE="DRUG_EXPOSURE_START_DATE",
206+
CODE="DRUG_CONCEPT_ID",
207+
)
208+
209+
VeraObservationPeriodColumnMapper = ObservationPeriodTableMapper(
210+
NAME_TABLE="OBSERVATION_PERIOD",
211+
PERSON_ID="PERSON_ID",
212+
OBSERVATION_PERIOD_START_DATE="OBSERVATION_PERIOD_START_DATE",
213+
OBSERVATION_PERIOD_END_DATE="OBSERVATION_PERIOD_END_DATE",
214+
)
215+
216+
VeraColumnMappers = {
217+
"PERSON": VeraPersonTableColumnMapper,
218+
"CONDITION_OCCURRENCE": VeraConditionOccurrenceColumnMapper,
219+
"PROCEDURE_OCCURRENCE": VeraProcedureOccurrenceColumnMapper,
220+
"DRUG_EXPOSURE": VeraDrugExposureColumnMapper,
221+
"OBSERVATION_PERIOD": VeraObservationPeriodColumnMapper,
222+
}
223+
224+
225+
#
226+
# Domains
227+
#
228+
VeraDomains = DomainsDictionary(**VeraColumnMappers)

phenex/phenotypes/continuous_coverage_phenotype.py

+60-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from typing import Union, List, Dict
1+
from typing import Union, List, Dict, Optional
22
from phenex.phenotypes.phenotype import Phenotype
3+
from phenex.filters.value import Value
34
from phenex.filters.codelist_filter import CodelistFilter
45
from phenex.filters.relative_time_range_filter import RelativeTimeRangeFilter
56
from phenex.filters.date_range_filter import DateRangeFilter
@@ -32,30 +33,72 @@ class ContinuousCoveragePhenotype(Phenotype):
3233
>>> phenotype = ContinuousCoveragePhenotype(coverage_period_min=coverage_min_filter)
3334
"""
3435

35-
def __init__(self, domain, start_date, end_date, gap_days=30, name=None):
36+
def __init__(self,
37+
name:Optional[str] = 'continuous_coverage',
38+
domain:Optional[str] = 'OBSERVATION_PERIOD',
39+
relative_time_range:Optional[RelativeTimeRangeFilter] = None,
40+
min_days : Optional[Value] = None,
41+
anchor_phenotype:Optional[Phenotype] = None,
42+
):
3643
super().__init__()
44+
self.name = name
3745
self.domain = domain
38-
self.start_date = start_date
39-
self.end_date = end_date
40-
self.gap_days = gap_days
41-
self.name = name or f"ContinuousCoverage_{domain}"
46+
self.relative_time_range = relative_time_range
47+
self.min_days = min_days
4248

4349
def _execute(self, tables: Dict[str, Table]) -> PhenotypeTable:
4450
coverage_table = tables[self.domain]
51+
# first perform time range filter on observation period start date
52+
coverage_table = coverage_table.mutate(EVENT_DATE = coverage_table.OBSERVATION_PERIOD_START_DATE)
53+
coverage_table = self._perform_time_filtering(coverage_table)
54+
# ensure that coverage end extends past the anchor date
55+
coverage_table = self._filter_observation_period_end(coverage_table)
4556
coverage_table = self._filter_coverage_period(coverage_table)
46-
coverage_table = self._check_continuous_coverage(coverage_table)
47-
return select_phenotype_columns(coverage_table)
57+
return coverage_table
58+
59+
def _perform_time_filtering(self, coverage_table):
60+
'''
61+
Filter the observation period start
62+
'''
63+
if self.relative_time_range is not None:
64+
coverage_table = self.relative_time_range.filter(coverage_table)
65+
return coverage_table
66+
67+
def _filter_observation_period_end(self, coverage_table):
68+
'''
69+
Get only rows where the observation period end date is after the anchor date
70+
'''
71+
if self.relative_time_range is not None:
72+
if self.relative_time_range.anchor_phenotype is not None:
73+
reference_column = self.relative_time_range.anchor_phenotype.table.EVENT_DATE
74+
else:
75+
reference_column = coverage_table.INDEX_DATE
76+
77+
coverage_table = coverage_table.filter(
78+
coverage_table.OBSERVATION_PERIOD_END_DATE >= reference_column
79+
)
80+
return coverage_table
81+
4882

4983
def _filter_coverage_period(self, coverage_table: Table) -> Table:
50-
return coverage_table.filter(
51-
(coverage_table['COVERAGE_START_DATE'] <= self.end_date) &
52-
(coverage_table['COVERAGE_END_DATE'] >= self.start_date)
53-
)
84+
if self.min_days.operator == '>':
85+
coverage_table = coverage_table.filter(
86+
(coverage_table['DAYS_FROM_ANCHOR'] > self.min_days.value)
87+
)
88+
elif self.min_days.operator == '>=':
89+
coverage_table = coverage_table.filter(
90+
(coverage_table['DAYS_FROM_ANCHOR'] >= self.min_days.value)
91+
)
92+
elif self.min_days.operator == '<':
93+
coverage_table = coverage_table.filter(
94+
(coverage_table['DAYS_FROM_ANCHOR'] < self.min_days.value)
95+
)
96+
elif self.min_days.operator == '<=':
97+
coverage_table = coverage_table.filter(
98+
(coverage_table['DAYS_FROM_ANCHOR'] <= self.min_days.value)
99+
)
100+
return coverage_table
54101

55-
def _check_continuous_coverage(self, coverage_table: Table) -> Table:
56-
# Logic to check for continuous coverage with allowed gap_days
57-
# This is a placeholder and should be replaced with actual implementation
58-
return coverage_table.mutate(BOOLEAN=True)
59102

60103
def get_codelists(self):
61-
return []
104+
return []

0 commit comments

Comments
 (0)