diff --git a/docs/api/tasks/pyhealth.tasks.dka.rst b/docs/api/tasks/pyhealth.tasks.dka.rst index 814a2297a..d9a5f6423 100644 --- a/docs/api/tasks/pyhealth.tasks.dka.rst +++ b/docs/api/tasks/pyhealth.tasks.dka.rst @@ -6,3 +6,7 @@ pyhealth.tasks.dka :undoc-members: :show-inheritance: +.. autoclass:: pyhealth.tasks.dka.T1DDKAPredictionMIMIC4 + :members: + :undoc-members: + :show-inheritance: diff --git a/examples/clinical_tasks/t1dka_mimic4.py b/examples/clinical_tasks/t1dka_mimic4.py index 8bc56fc27..a2383e955 100644 --- a/examples/clinical_tasks/t1dka_mimic4.py +++ b/examples/clinical_tasks/t1dka_mimic4.py @@ -9,7 +9,7 @@ Target Population: - Patients with Type 1 Diabetes Mellitus (T1DM) ONLY - - Predicts DKA occurrence within 90 days of T1DM diagnosis + - Predicts DKA from the T1DM diagnosis through the next 90 days - Smaller, focused patient cohort """ @@ -179,4 +179,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/pyhealth/tasks/dka.py b/pyhealth/tasks/dka.py index d2d4944ad..200321e9d 100644 --- a/pyhealth/tasks/dka.py +++ b/pyhealth/tasks/dka.py @@ -282,6 +282,7 @@ class T1DDKAPredictionMIMIC4(BaseTask): Target Population: - Patients with Type 1 Diabetes (ICD-9 or ICD-10 codes) - Excludes patients without any T1DM diagnosis codes + - Excludes DKA events recorded before a separate T1DM diagnosis Label Definition: - Positive (1): Patient has DKA code within 90 days of T1DM diagnosis @@ -459,20 +460,34 @@ def __call__(self, patient: Any) -> List[Dict[str, Any]]: has_t1dm = False t1dm_times: List[datetime] = [] + t1dm_diagnosis_times: list[datetime] = [] + dka_times: list[datetime] = [] for diag in all_diagnoses: code = getattr(diag, "icd_code", None) version = getattr(diag, "icd_version", None) + diag_time = getattr(diag, "timestamp", None) + is_dka = self._is_dka_code(code, version) if self._is_t1dm_code(code, version): has_t1dm = True - diag_time = getattr(diag, "timestamp", None) if diag_time: t1dm_times.append(diag_time) + if not is_dka: + t1dm_diagnosis_times.append(diag_time) + if is_dka and diag_time: + dka_times.append(diag_time) # Skip patients without T1DM diagnosis (early exit before sorting) if not has_t1dm: return [] + if ( + t1dm_diagnosis_times + and dka_times + and min(dka_times) < min(t1dm_diagnosis_times) + ): + return [] + # Get admissions and sort by timestamp admissions = patient.get_events(event_type="admissions") if not admissions: @@ -609,9 +624,11 @@ def __call__(self, patient: Any) -> List[Dict[str, Any]]: # Determine label based on temporal relationship has_dka_within_window = False if has_dka and t1dm_times and dka_time: - for t1dm_time in t1dm_times: - delta = abs((dka_time - t1dm_time).days) - if delta <= self.dka_window_days: + diagnosis_times = t1dm_diagnosis_times or t1dm_times + for t1dm_time in diagnosis_times: + if t1dm_time <= dka_time <= t1dm_time + timedelta( + days=self.dka_window_days + ): has_dka_within_window = True break elif has_dka and not t1dm_times: diff --git a/tests/core/test_t1d_dka_window.py b/tests/core/test_t1d_dka_window.py index a7fc11852..f1c48807d 100644 --- a/tests/core/test_t1d_dka_window.py +++ b/tests/core/test_t1d_dka_window.py @@ -49,7 +49,7 @@ def get_events(self, event_type: str, filters=None, return_df: bool = False): def _build_patient(t0: datetime, admission_days: list[int], diag_specs: list[tuple[str, int | str, int, str]]) -> DummyPatient: admissions = [DummyAdmission(f"a{i+1}", t0 + timedelta(days=day)) for i, day in enumerate(admission_days)] - base_diag = DummyDiagnosis("E10.10", 10, t0, "t1dm") + base_diag = DummyDiagnosis("E10.9", 10, t0, "t1dm") diagnoses = [base_diag] + [ DummyDiagnosis(code, version, t0 + timedelta(days=day), hadm_id) for code, version, day, hadm_id in diag_specs @@ -101,6 +101,60 @@ def test_sequences_do_not_exceed_window(self): self._assert_within_window(sample_long, expected_visits=2) self.assertEqual(sample_long["label"], 0) + def test_dka_before_t1dm_diagnosis_is_excluded(self): + patient = _build_patient( + self.t0, + admission_days=[-60, -30], + diag_specs=[ + ("I10", 10, -60, "a1"), + ("E1011", 10, -30, "a2"), + ], + ) + + self.assertEqual(self.task(patient), []) + + def test_dka_at_t1dm_diagnosis_is_positive(self): + patient = _build_patient( + self.t0, + admission_days=[-10, 0], + diag_specs=[ + ("I10", 10, -10, "a1"), + ("E1011", 10, 0, "a2"), + ], + ) + + samples = self.task(patient) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0]["label"], 1) + + def test_dka_inside_window_is_positive(self): + patient = _build_patient( + self.t0, + admission_days=[10, 30], + diag_specs=[ + ("I10", 10, 10, "a1"), + ("E1011", 10, 30, "a2"), + ], + ) + + samples = self.task(patient) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0]["label"], 1) + + def test_dka_at_end_of_window_is_positive(self): + patient = _build_patient( + self.t0, + admission_days=[30, 90], + diag_specs=[ + ("I10", 10, 30, "a1"), + ("E1011", 10, 90, "a2"), + ], + ) + + samples = self.task(patient) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0]["label"], 1) + if __name__ == "__main__": unittest.main()