diff --git a/docs/api/tasks.rst b/docs/api/tasks.rst index 706e6a3a7..3ed1e1c97 100644 --- a/docs/api/tasks.rst +++ b/docs/api/tasks.rst @@ -21,9 +21,9 @@ New to PyHealth tasks? Start here: - **Tutorial**: `Introduction to pyhealth.tasks `_ - Learn the basics of defining and using tasks - **Code Examples**: Browse all examples online at https://github.com/sunlabuiuc/PyHealth/tree/master/examples - **Pipeline Examples**: Check out our :doc:`../tutorials` page for complete end-to-end examples including: - + - Mortality Prediction Pipeline - - Readmission Prediction Pipeline + - Readmission Prediction Pipeline - Medical Coding Pipeline - Chest X-ray Classification Pipeline @@ -47,14 +47,14 @@ After you define a task: task = MortalityPredictionMIMIC4() # input_schema = {"conditions": "sequence", "procedures": "sequence"} # output_schema = {"mortality": "binary"} - + # 2. Apply task to dataset sample_dataset = base_dataset.set_task(task) - + # 3. Processors automatically transform samples: # - "sequence" -> SequenceProcessor (converts codes to indices) # - "binary" -> BinaryLabelProcessor (converts labels to tensors) - + # 4. Get model-ready tensors sample = sample_dataset[0] # sample["conditions"] is now a tensor of token indices @@ -74,7 +74,6 @@ Available Tasks :maxdepth: 3 Base Task - Readmission (30 Days, MIMIC-IV) In-Hospital Mortality (MIMIC-IV) MIMIC-III ICD-9 Coding Cardiology Detection diff --git a/docs/api/tasks/pyhealth.tasks.Readmission30DaysMIMIC4.rst b/docs/api/tasks/pyhealth.tasks.Readmission30DaysMIMIC4.rst deleted file mode 100644 index dd858473f..000000000 --- a/docs/api/tasks/pyhealth.tasks.Readmission30DaysMIMIC4.rst +++ /dev/null @@ -1,7 +0,0 @@ -pyhealth.tasks.Readmission30DaysMIMIC4 -======================================= - -.. autoclass:: pyhealth.tasks.readmission_30days_mimic4.Readmission30DaysMIMIC4 - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/api/tasks/pyhealth.tasks.readmission_prediction.rst b/docs/api/tasks/pyhealth.tasks.readmission_prediction.rst index 6db28a7c0..89af26f92 100644 --- a/docs/api/tasks/pyhealth.tasks.readmission_prediction.rst +++ b/docs/api/tasks/pyhealth.tasks.readmission_prediction.rst @@ -6,7 +6,11 @@ :undoc-members: :show-inheritance: -.. autofunction:: pyhealth.tasks.readmission_prediction.readmission_prediction_mimic4_fn +.. autoclass:: pyhealth.tasks.readmission_prediction.ReadmissionPredictionMIMIC4 + :members: + :undoc-members: + :show-inheritance: + .. autofunction:: pyhealth.tasks.readmission_prediction.readmission_prediction_eicu_fn .. autofunction:: pyhealth.tasks.readmission_prediction.readmission_prediction_eicu_fn2 diff --git a/examples/readmission/readmission_mimic4_rnn.py b/examples/readmission/readmission_mimic4_rnn.py new file mode 100644 index 000000000..bdc5f7de8 --- /dev/null +++ b/examples/readmission/readmission_mimic4_rnn.py @@ -0,0 +1,44 @@ +import tempfile + +from pyhealth.datasets import MIMIC4Dataset +from pyhealth.datasets import split_by_patient, get_dataloader +from pyhealth.models import RNN +from pyhealth.tasks import ReadmissionPredictionMIMIC4 +from pyhealth.trainer import Trainer + +# Since PyHealth uses multiprocessing, it is best practice to use a main guard. +if __name__ == '__main__': + # Use tempfile to automate cleanup + cache_dir = tempfile.TemporaryDirectory() + + base_dataset = MIMIC4Dataset( + ehr_root="https://physionet.org/files/mimic-iv-demo/2.2/", + ehr_tables=["diagnoses_icd", "procedures_icd", "prescriptions"], + cache_dir=cache_dir.name + ) + base_dataset.stats() + + sample_dataset = base_dataset.set_task(ReadmissionPredictionMIMIC4()) + + train_dataset, val_dataset, test_dataset = split_by_patient( + sample_dataset, [0.8, 0.1, 0.1] + ) + train_dataloader = get_dataloader(train_dataset, batch_size=32, shuffle=True) + val_dataloader = get_dataloader(val_dataset, batch_size=32, shuffle=False) + test_dataloader = get_dataloader(test_dataset, batch_size=32, shuffle=False) + + model = RNN( + dataset=sample_dataset, + ) + + trainer = Trainer(model=model) + trainer.train( + train_dataloader=train_dataloader, + val_dataloader=val_dataloader, + epochs=1, + monitor="roc_auc", + ) + + trainer.evaluate(test_dataloader) + + sample_dataset.close() diff --git a/pyhealth/tasks/__init__.py b/pyhealth/tasks/__init__.py index f0f6201a6..9a505f4b0 100644 --- a/pyhealth/tasks/__init__.py +++ b/pyhealth/tasks/__init__.py @@ -50,12 +50,11 @@ MortalityPredictionStageNetMIMIC4, ) from .patient_linkage import patient_linkage_mimic3_fn -from .readmission_30days_mimic4 import Readmission30DaysMIMIC4 from .readmission_prediction import ( ReadmissionPredictionMIMIC3, + ReadmissionPredictionMIMIC4, readmission_prediction_eicu_fn, readmission_prediction_eicu_fn2, - readmission_prediction_mimic4_fn, ReadmissionPredictionOMOP, ) from .sleep_staging import ( diff --git a/pyhealth/tasks/readmission_30days_mimic4.py b/pyhealth/tasks/readmission_30days_mimic4.py deleted file mode 100644 index e00a98cea..000000000 --- a/pyhealth/tasks/readmission_30days_mimic4.py +++ /dev/null @@ -1,113 +0,0 @@ -from datetime import datetime -from typing import Any, Dict, List - -import polars as pl - -from .base_task import BaseTask - - -class Readmission30DaysMIMIC4(BaseTask): - """ - Task for predicting 30-day readmission using MIMIC-IV data. - - This task processes patient data to predict whether a patient will be - readmitted within 30 days after discharge. It uses sequences of - conditions, procedures, and drugs as input features. - - Attributes: - task_name (str): The name of the task. - input_schema (Dict[str, str]): The schema for input data, which includes: - - conditions: A sequence of condition codes. - - procedures: A sequence of procedure codes. - - drugs: A sequence of drug codes. - output_schema (Dict[str, str]): The schema for output data, which includes: - - readmission: A binary indicator of readmission within 30 days. - """ - - task_name: str = "Readmission30DaysMIMIC4" - input_schema: Dict[str, str] = {"conditions": "sequence", "procedures": "sequence", "drugs": "sequence"} - output_schema: Dict[str, str] = {"readmission": "binary"} - - def __call__(self, patient: Any) -> List[Dict[str, Any]]: - samples = [] - - demographics = patient.get_events(event_type="patients") - assert len(demographics) == 1 - demographics = demographics[0] - anchor_age = int(demographics["anchor_age"]) - - # exclude: patients under 18 years old - if anchor_age < 18: - return samples - - admissions = patient.get_events(event_type="admissions") - - for i in range(len(admissions)): - admission = admissions[i] - next_admission = admissions[i + 1] if i < len(admissions) - 1 else None - - # get time difference between current visit and next visit - admission_dischtime = datetime.strptime( - admission.dischtime, "%Y-%m-%d %H:%M:%S" - ) - duration_hour = ( - (admission_dischtime - admission.timestamp).total_seconds() / 3600 - ) - if duration_hour <= 12: - continue - if next_admission is not None: - time_diff_hour = ( - (next_admission.timestamp - admission_dischtime).total_seconds() / 3600 - ) - if time_diff_hour <= 3: - continue - readmission = 1 if time_diff_hour < 30 * 24 else 0 - else: - readmission = 0 - - # returning polars dataframe is much faster than returning list of events - diagnoses_icd = patient.get_events( - event_type="diagnoses_icd", - start=admission.timestamp, - end=admission_dischtime, - return_df=True - ) - procedures_icd = patient.get_events( - event_type="procedures_icd", - start=admission.timestamp, - end=admission_dischtime, - return_df=True - ) - prescriptions = patient.get_events( - event_type="prescriptions", - start=admission.timestamp, - end=admission_dischtime, - return_df=True - ) - # convert to list of codes - conditions = diagnoses_icd.select( - pl.concat_str(["diagnoses_icd/icd_version", "diagnoses_icd/icd_code"], separator="_") - ).to_series().to_list() - procedures = procedures_icd.select( - pl.concat_str(["procedures_icd/icd_version", "procedures_icd/icd_code"], separator="_") - ).to_series().to_list() - drugs = prescriptions.select( - pl.concat_str(["prescriptions/drug"], separator="_") - ).to_series().to_list() - - # exclude: visits without condition, procedure, or drug code - if len(conditions) * len(procedures) * len(drugs) == 0: - continue - - samples.append( - { - "patient_id": patient.patient_id, - "admission_id": admission.hadm_id, - "conditions": conditions, - "procedures": procedures, - "drugs": drugs, - "readmission": readmission, - } - ) - - return samples diff --git a/pyhealth/tasks/readmission_prediction.py b/pyhealth/tasks/readmission_prediction.py index 2e78b3353..478963bac 100644 --- a/pyhealth/tasks/readmission_prediction.py +++ b/pyhealth/tasks/readmission_prediction.py @@ -112,66 +112,107 @@ def __call__(self, patient: Patient) -> List[Dict]: return samples -def readmission_prediction_mimic4_fn(patient: Patient, time_window=15): - """Processes a single patient for the readmission prediction task. +class ReadmissionPredictionMIMIC4(BaseTask): + """ + Readmission prediction on the MIMIC4 dataset. - Readmission prediction aims at predicting whether the patient will be readmitted - into hospital within time_window days based on the clinical information from - current visit (e.g., conditions and procedures). + This task aims at predicting whether the patient will be readmitted into hospital within + a specified number of days based on clinical information from the current visit. - Args: - patient: a Patient object - time_window: the time window threshold (gap < time_window means label=1 for - the task) + Attributes: + task_name (str): The name of the task. + input_schema (Dict[str, str]): The schema for the task input. + output_schema (Dict[str, str]): The schema for the task output. + """ + task_name: str = "ReadmissionPredictionMIMIC4" + input_schema: Dict[str, str] = {"conditions": "sequence", "procedures": "sequence", "drugs": "sequence"} + output_schema: Dict[str, str] = {"readmission": "binary"} - Returns: - samples: a list of samples, each sample is a dict with patient_id, visit_id, - and other task-specific attributes as key + def __init__(self, window: timedelta=timedelta(days=15), exclude_minors: bool=True) -> None: + """ + Initializes the task object. - Note that we define the task as a binary classification task. + Args: + window (timedelta): If two admissions are closer than this window, it is considered a readmission. Defaults to 15 days. + exclude_minors (bool): Whether to exclude patients whose "anchor_age" is less than 18. Defaults to True. + """ + self.window = window + self.exclude_minors = exclude_minors - Examples: - >>> from pyhealth.datasets import MIMIC4Dataset - >>> mimic4_base = MIMIC4Dataset( - ... root="/srv/local/data/physionet.org/files/mimiciv/2.0/hosp", - ... tables=["diagnoses_icd", "procedures_icd"], - ... code_mapping={"ICD10PROC": "CCSPROC"}, - ... ) - >>> from pyhealth.tasks import readmission_prediction_mimic4_fn - >>> mimic4_sample = mimic4_base.set_task(readmission_prediction_mimic4_fn) - >>> mimic4_sample.samples[0] - [{'visit_id': '130744', 'patient_id': '103', 'conditions': [['42', '109', '19', '122', '98', '663', '58', '51']], 'procedures': [['1']], 'label': 0}] - """ - samples = [] + def __call__(self, patient: Patient) -> List[Dict]: + """ + Generates binary classification data samples for a single patient. - # we will drop the last visit - for i in range(len(patient) - 1): - visit: Visit = patient[i] - next_visit: Visit = patient[i + 1] + Visits with no conditions OR no procedures OR no drugs are excluded from the output but are still used to calculate readmission for prior visits. - # get time difference between current visit and next visit - time_diff = (next_visit.encounter_time - visit.encounter_time).days - readmission_label = 1 if time_diff < time_window else 0 + Args: + patient (Patient): A patient object. - conditions = visit.get_code_list(table="diagnoses_icd") - procedures = visit.get_code_list(table="procedures_icd") - drugs = visit.get_code_list(table="prescriptions") - # exclude: visits without condition, procedure, or drug code - if len(conditions) * len(procedures) * len(drugs) == 0: - continue - # TODO: should also exclude visit with age < 18 - samples.append( - { - "visit_id": visit.visit_id, - "patient_id": patient.patient_id, - "conditions": [conditions], - "procedures": [procedures], - "drugs": [drugs], - "label": readmission_label, - } - ) - # no cohort selection - return samples + Returns: + List[Dict]: A list containing a dictionary for each patient visit with: + - 'visit_id': MIMIC4 hadm_id. + - 'patient_id': MIMIC4 subject_id. + - 'conditions': MIMIC4 diagnoses_icd table ICD-9 or ICD-10 codes. + - 'procedures': MIMIC4 procedures_icd table ICD-9 or ICD-10 codes. + - 'drugs': MIMIC4 prescriptions table drug column entries. + - 'readmission': binary label. + + Raises: + ValueError: If any `str` to `datetime` conversions fail. + AssertionError: If any icd_version value in the diagnoses_icd or procedures_icd tables is not "9" or "10" + """ + patients: List[Event] = patient.get_events(event_type="patients") + assert len(patients) == 1 + + if self.exclude_minors and int(patients[0]["anchor_age"]) < 18: + return [] + + admissions: List[Event] = patient.get_events(event_type="admissions") + if len(admissions) < 2: + return [] + + samples = [] + for i in range(len(admissions) - 1): # Skip the last admission since we need a "next" admission + filter = ("hadm_id", "==", admissions[i].hadm_id) + + diagnoses = [] + for event in patient.get_events(event_type="diagnoses_icd", filters=[filter]): + assert event.icd_version in ("9", "10") + diagnoses.append(f"{event.icd_version}_{event.icd_code}") + if len(diagnoses) == 0: + continue + + procedures = [] + for event in patient.get_events(event_type="procedures_icd", filters=[filter]): + assert event.icd_version in ("9", "10") + procedures.append(f"{event.icd_version}_{event.icd_code}") + if len(procedures) == 0: + continue + + prescriptions = patient.get_events(event_type="prescriptions", filters=[filter]) + prescriptions = [event.drug for event in prescriptions] + if len(prescriptions) == 0: + continue + + try: + discharge_time = datetime.strptime(admissions[i].dischtime, "%Y-%m-%d %H:%M:%S") + except ValueError: + discharge_time = datetime.strptime(admissions[i].dischtime, "%Y-%m-%d") + + readmission = int((admissions[i + 1].timestamp - discharge_time) < self.window) + + samples.append( + { + "visit_id": admissions[i].hadm_id, + "patient_id": patient.patient_id, + "conditions": diagnoses, + "procedures": procedures, + "drugs": prescriptions, + "readmission": readmission, + } + ) + + return samples def readmission_prediction_eicu_fn(patient: Patient, time_window=5): @@ -415,19 +456,6 @@ def __call__(self, patient: Patient) -> List[Dict]: if __name__ == "__main__": - from pyhealth.datasets import MIMIC4Dataset - - base_dataset = MIMIC4Dataset( - root="/srv/local/data/physionet.org/files/mimiciv/2.0/hosp", - tables=["diagnoses_icd", "procedures_icd", "prescriptions"], - dev=True, - code_mapping={"NDC": "ATC"}, - refresh_cache=False, - ) - sample_dataset = base_dataset.set_task(task_fn=readmission_prediction_mimic4_fn) - sample_dataset.stat() - print(sample_dataset.available_keys) - from pyhealth.datasets import eICUDataset base_dataset = eICUDataset( diff --git a/test-resources/core/mimic4demo/hosp/admissions.csv b/test-resources/core/mimic4demo/hosp/admissions.csv index 5a178adca..834171b36 100644 --- a/test-resources/core/mimic4demo/hosp/admissions.csv +++ b/test-resources/core/mimic4demo/hosp/admissions.csv @@ -16,4 +16,10 @@ subject_id,hadm_id,admittime,dischtime,admission_type,admission_location,insuran 10008,20013,2151-12-15 06:00:00,2151-12-20 09:00:00,EMERGENCY,EMERGENCY ROOM,Medicare,ENGLISH,WIDOWED,WHITE,DIED,1 10009,20014,2152-03-20 07:30:00,2152-03-23 14:00:00,URGENT,TRANSFER FROM HOSPITAL,Medicaid,ENGLISH,SINGLE,BLACK,HOME,0 10010,20015,2150-08-01 10:00:00,2150-08-05 15:00:00,EMERGENCY,EMERGENCY ROOM,Private,ENGLISH,MARRIED,WHITE,HOME,0 - +1,1,2150-01-01 00:00:00,2150-01-01 01:00:00,,,,,,,, +1,2,2150-01-16 00:00:00,2150-01-16 01:00:00,,,,,,,, +2,3,2150-01-01 00:00:00,2150-01-01 01:00:00,,,,,,,, +2,4,2150-01-16 00:00:00,2150-01-16 01:00:00,,,,,,,, +2,5,2150-01-21 00:00:00,2150-01-21 01:00:00,,,,,,,, +2,6,2150-01-22 00:00:00,2150-01-22 01:00:00,,,,,,,, +3,7,2150-01-01 00:00:00,2150-01-01 01:00:00,,,,,,,, diff --git a/test-resources/core/mimic4demo/hosp/diagnoses_icd.csv b/test-resources/core/mimic4demo/hosp/diagnoses_icd.csv index e4692fa2c..804e2ae40 100644 --- a/test-resources/core/mimic4demo/hosp/diagnoses_icd.csv +++ b/test-resources/core/mimic4demo/hosp/diagnoses_icd.csv @@ -45,4 +45,10 @@ subject_id,hadm_id,seq_num,icd_code,icd_version 10010,20015,1,E1010,10 10010,20015,2,E1065,10 10010,20015,3,I10,10 - +1,1,1,,10 +1,2,1,,10 +2,3,1,,10 +2,4,1,,10 +2,5,1,,10 +2,6,1,,10 +3,7,1,,10 diff --git a/test-resources/core/mimic4demo/hosp/patients.csv b/test-resources/core/mimic4demo/hosp/patients.csv index ce1a83a5e..fa520d5f0 100644 --- a/test-resources/core/mimic4demo/hosp/patients.csv +++ b/test-resources/core/mimic4demo/hosp/patients.csv @@ -9,4 +9,6 @@ subject_id,gender,anchor_age,anchor_year,anchor_year_group,dod 10008,F,73,2151,2017 - 2019,2151-12-20 10009,M,35,2152,2017 - 2019, 10010,F,50,2150,2017 - 2019, - +1,,17,,, +2,,18,,, +3,,18,,, diff --git a/test-resources/core/mimic4demo/hosp/prescriptions.csv b/test-resources/core/mimic4demo/hosp/prescriptions.csv index d14ecf7a1..3cd4672fa 100644 --- a/test-resources/core/mimic4demo/hosp/prescriptions.csv +++ b/test-resources/core/mimic4demo/hosp/prescriptions.csv @@ -23,4 +23,9 @@ subject_id,hadm_id,starttime,stoptime,drug,ndc,prod_strength,dose_val_rx,dose_un 10009,20014,2152-03-20 08:00:00,2152-03-23 13:00:00,Atorvastatin,00378377710,20 mg,20,MG,PO 10010,20015,2150-08-01 11:00:00,2150-08-05 14:00:00,Insulin Lispro,00002751001,100 unit/mL,8,UNIT,SC 10010,20015,2150-08-01 11:00:00,2150-08-05 14:00:00,Potassium Chloride,00338001404,20 mEq/100mL,40,MEQ,IV - +1,1,,,,,,,, +1,2,,,,,,,, +2,3,,,,,,,, +2,4,,,,,,,, +2,6,,,,,,,, +3,7,,,,,,,, diff --git a/test-resources/core/mimic4demo/hosp/procedures_icd.csv b/test-resources/core/mimic4demo/hosp/procedures_icd.csv index 001fbf26e..20df5b5c7 100644 --- a/test-resources/core/mimic4demo/hosp/procedures_icd.csv +++ b/test-resources/core/mimic4demo/hosp/procedures_icd.csv @@ -20,4 +20,10 @@ subject_id,hadm_id,seq_num,icd_code,icd_version 10009,20014,1,3E0G76Z,10 10010,20015,1,5A1955Z,10 10010,20015,2,3E0G76Z,10 - +1,1,1,,10 +1,2,1,,10 +2,3,1,,10 +2,4,1,,10 +2,5,1,,10 +2,6,1,,10 +3,7,1,,10 diff --git a/tests/core/test_mimic4_readmission_prediction.py b/tests/core/test_mimic4_readmission_prediction.py new file mode 100644 index 000000000..6bcb4712d --- /dev/null +++ b/tests/core/test_mimic4_readmission_prediction.py @@ -0,0 +1,107 @@ +from datetime import timedelta +from pathlib import Path +import tempfile +import unittest + +from pyhealth.datasets import MIMIC4Dataset +from pyhealth.tasks import ReadmissionPredictionMIMIC4 + + +class TestReadmissionPredictionMIMIC4(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.cache_dir = tempfile.TemporaryDirectory() + + dataset = MIMIC4Dataset( + ehr_root=str(Path(__file__).parent.parent.parent / "test-resources" / "core" / "mimic4demo"), + ehr_tables=["diagnoses_icd", "procedures_icd", "prescriptions"], + cache_dir=cls.cache_dir.name, + ) + + cls.samples15days = dataset.set_task(ReadmissionPredictionMIMIC4(window=timedelta(days=15))) + cls.samples5days = dataset.set_task(ReadmissionPredictionMIMIC4(window=timedelta(days=5))) + cls.sampleswithminors = dataset.set_task(ReadmissionPredictionMIMIC4(exclude_minors=False)) + + @classmethod + def tearDownClass(cls): + cls.samples15days.close() + cls.samples5days.close() + cls.sampleswithminors.close() + + def test_task_schema(self): + self.assertIn("task_name", vars(ReadmissionPredictionMIMIC4)) + self.assertIn("input_schema", vars(ReadmissionPredictionMIMIC4)) + self.assertIn("output_schema", vars(ReadmissionPredictionMIMIC4)) + + self.assertEqual("ReadmissionPredictionMIMIC4", ReadmissionPredictionMIMIC4.task_name) + self.assertIn("conditions", ReadmissionPredictionMIMIC4.input_schema) + self.assertIn("procedures", ReadmissionPredictionMIMIC4.input_schema) + self.assertIn("drugs", ReadmissionPredictionMIMIC4.input_schema) + self.assertIn("readmission", ReadmissionPredictionMIMIC4.output_schema) + + def test_task_defaults(self): + task = ReadmissionPredictionMIMIC4() + + self.assertEqual(task.window, timedelta(days=15)) + self.assertTrue(task.exclude_minors) + + def test_sample_schema(self): + for sample in self.samples15days: + self.assertIn("visit_id", sample) + self.assertIn("patient_id", sample) + self.assertIn("conditions", sample) + self.assertIn("procedures", sample) + self.assertIn("drugs", sample) + self.assertIn("readmission", sample) + + def test_time_window(self): + readmitted5days = [ s["readmission"] for s in self.samples5days if s["visit_id"] == "3" ] + readmitted15days = [ s["readmission"] for s in self.samples15days if s["visit_id"] == "3" ] + + self.assertEqual(len(readmitted5days), 1) + self.assertEqual(len(readmitted15days), 1) + + self.assertFalse(bool(readmitted5days[0].item())) + self.assertTrue (bool(readmitted15days[0].item())) + + readmitted5days = [ s["readmission"] for s in self.samples5days if s["visit_id"] == "4" ] + readmitted15days = [ s["readmission"] for s in self.samples15days if s["visit_id"] == "4" ] + + self.assertEqual(len(readmitted5days), 1) + self.assertEqual(len(readmitted15days), 1) + + self.assertTrue(bool(readmitted5days[0].item())) + self.assertTrue(bool(readmitted15days[0].item())) + + def test_last_admission_is_excluded(self): + self.assertNotIn("2", [ s["visit_id"] for s in self.samples15days ]) + self.assertNotIn("2", [ s["visit_id"] for s in self.samples5days ]) + self.assertNotIn("2", [ s["visit_id"] for s in self.sampleswithminors ]) + + self.assertNotIn("6", [ s["visit_id"] for s in self.samples15days ]) + self.assertNotIn("6", [ s["visit_id"] for s in self.samples5days ]) + self.assertNotIn("6", [ s["visit_id"] for s in self.sampleswithminors ]) + + def test_patient_with_only_one_visit_is_excluded(self): + self.assertNotIn("3", [ s["patient_id"] for s in self.samples15days ]) + self.assertNotIn("7", [ s["visit_id"] for s in self.samples15days ]) + + self.assertNotIn("3", [ s["patient_id"] for s in self.samples5days ]) + self.assertNotIn("7", [ s["visit_id"] for s in self.samples5days ]) + + self.assertNotIn("3", [ s["patient_id"] for s in self.sampleswithminors ]) + self.assertNotIn("7", [ s["visit_id"] for s in self.sampleswithminors ]) + + def test_admissions_without_drugs_are_excluded(self): + self.assertNotIn("5", [ s["visit_id"] for s in self.samples15days ]) + self.assertNotIn("5", [ s["visit_id"] for s in self.samples5days ]) + self.assertNotIn("5", [ s["visit_id"] for s in self.sampleswithminors ]) + + def test_admissions_of_minors_are_excluded(self): + self.assertNotIn("1", [ s["visit_id"] for s in self.samples15days ]) + self.assertNotIn("1", [ s["visit_id"] for s in self.samples5days ]) + self.assertIn ("1", [ s["visit_id"] for s in self.sampleswithminors ]) + + +if __name__ == "__main__": + unittest.main()