diff --git a/.gitignore b/.gitignore index dfdafb3810..cc93144f7d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,10 @@ mimic-iv-1.0 scrap *.gzip *.csv.gz -*summary*.txt \ No newline at end of file +*summary*.txt +venv +__pycache__ +raw_data +preproc_data +data +*.csv \ No newline at end of file diff --git a/README copy.md b/README copy.md new file mode 100644 index 0000000000..1101cdf712 --- /dev/null +++ b/README copy.md @@ -0,0 +1,95 @@ +# MIMIC-IV +**MIMIC-IV data pipeline** is an end-to-end pipeline that offers a configurable framework to prepare MIMIC-IV data for the downstream tasks. +The pipeline cleans the raw data by removing outliers and allowing users to impute missing entries. +It also provides options for the clinical grouping of medical features using standard coding systems for dimensionality reduction. +All of these options are customizable for the users, allowing them to generate a personalized patient cohort. +The customization steps can be recorded for the reproducibility of the overall framework. +The pipeline produces a smooth time-series dataset by binning the sequential data into equal-length time intervals and allowing for filtering of the time-series length according to the user's preferences. +Besides the data processing modules, our pipeline also includes two additional modules for modeling and evaluation. +For modeling, the pipeline includes several commonly used sequential models for performing prediction tasks. +The evaluation module offers a series of standard methods for evaluating the performance of the created models. +This module also includes options for reporting individual and group fairness measures. + +##### Citing MIMIC-IV Data Pipeline: +MIMIC-IV Data Pipeline is available on [ML4H](https://proceedings.mlr.press/v193/gupta22a/gupta22a.pdf). +If you use MIMIC-IV Data Pipeline, we would appreciate citations to the following paper. + +``` +@InProceedings{gupta2022extensive, + title = {{An Extensive Data Processing Pipeline for MIMIC-IV}}, + author = {Gupta, Mehak and Gallamoza, Brennan and Cutrona, Nicolas and Dhakal, Pranjal and Poulain, Raphael and Beheshti, Rahmatollah}, + booktitle = {Proceedings of the 2nd Machine Learning for Health symposium}, + pages = {311--325}, + year = {2022}, + volume = {193}, + series = {Proceedings of Machine Learning Research}, + month = {28 Nov}, + publisher = {PMLR}, + url = {https://proceedings.mlr.press/v193/gupta22a.html} +} +``` + +## Table of Contents: +- [Steps to download MIMIC-IV dataset for the pipeline](#Steps-to-download-MIMIC-IV-dataset-for-the-pipeline) +- [Repository Structure](#Repository-Structure) +- [How to use the pipeline?](#How-to-use-the-pipeline) + +### Steps to download MIMIC-IV dataset for the pipeline + +Go to https://physionet.org/content/mimiciv/1.0/ + +Follow instructions to get access to MIMIC-IV dataset. + +Download the files using your terminal: wget -r -N -c -np --user mehakg --ask-password https://physionet.org/files/mimiciv/1.0/ + +### Repository Structure + +- **mainPipeline.ipynb** + is the main file to interact with the pipeline. It provides step-step by options to extract and pre-process cohorts. +- **./data** + consists of all data files stored during pre-processing + - **./cohort** + consists of files saved during cohort extraction + - **./features** + consist of files containing features data for all selected features. + - **./summary** + consists of summary files for all features. + It also consists of file with list of variables in all features and can be used for feature selection. + - **./dict** + consists of dictionary structured files for all features obtained after time-series representation + - **./output** + consists output files saved after training and testing of model. These files are used during evaluation. +- **./mimic-iv-1.0** + consist of files downloaded from MIMIC-IV website. +- **./saved_models** + consists of models saved during training. +- **./preprocessing** + - **./day_intervals_preproc** + - **day_intervals_cohort.py** file is used to extract samples, labels and demographic data for cohorts. + - **disease_cohort.py** is used to filter samples based on diagnoses codes at time of admission + - **./hosp_module_preproc** + - **feature_selection_hosp.py** is used to extract, clean and summarize selected features for non-ICU data. + - **feature_selection_icu.py** is used to extract, clean and summarize selected features for ICU data. +- **./model** + - **train.py** + consists of code to create batches of data according to batch_size and create, train and test different models. + - **Mimic_model.py** + consist of different model architectures. + - **evaluation.py** + consists of class to perform evaluation of results obtained from models. + This class can be instantiated separated for use as standalone module. + - **fairness.py** + consists of code to perform fairness evaluation. + It can also be used as standalone module. + - **parameters.py** + consists of list of hyperparameters to be defined for model training. + - **callibrate_output** + consists of code to calibrate model output. + It can also be used as standalone module. + +### How to use the pipeline? +- After downloading the repo, open **mainPipeline.ipynb**. +- **mainPipeline.ipynb**, contains sequential code blocks to extract, preprocess, model and train MIMIC-IV EHR data. +- Follow each code bloack and read intructions given just before each code block to run code block. +- Follow the exact file paths and filenames given in instructions for each code block to run the pipeline. +- For evaluation module, clear instructions are provided on how to use it as a standalone module. diff --git a/README.md b/README.md index 1101cdf712..c195fffbd9 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,25 @@ Download the files using your terminal: wget -r -N -c -np --user mehakg --ask-pa ### How to use the pipeline? - After downloading the repo, open **mainPipeline.ipynb**. - **mainPipeline.ipynb**, contains sequential code blocks to extract, preprocess, model and train MIMIC-IV EHR data. -- Follow each code bloack and read intructions given just before each code block to run code block. +- Follow each code block and read intructions given just before each code block to run code block. - Follow the exact file paths and filenames given in instructions for each code block to run the pipeline. - For evaluation module, clear instructions are provided on how to use it as a standalone module. + +### Pipeline details + +#### Cohort extraction +Options: +- use icu data + + +#### Feature extraction + +#### Feature preprocessing + +##### Preprocessing + +##### Summary + +##### Selection + +##### Event Cleaning \ No newline at end of file diff --git a/_old_requirements.txt b/_old_requirements.txt new file mode 100644 index 0000000000..58020deb8b --- /dev/null +++ b/_old_requirements.txt @@ -0,0 +1,9 @@ +import_ipynb==0.1.3 +ipywidgets==7.5.1 +Jinja2==2.11.2 +matplotlib==3.2.2 +numpy==1.18.5 +pandas==1.0.5 +scikit_learn==1.0.2 +torch==1.6.0 +tqdm==4.47.0 diff --git a/mainPipeline.ipynb b/mainPipeline.ipynb index 4573ff0821..8ceb7f0a98 100644 --- a/mainPipeline.ipynb +++ b/mainPipeline.ipynb @@ -2,8 +2,8 @@ "cells": [ { "cell_type": "code", - "execution_count": 157, - "id": "available-albany", + "execution_count": 1, + "id": "4b80a7cf-e155-48b2-840e-4cc5101d6984", "metadata": {}, "outputs": [], "source": [ @@ -11,9 +11,16 @@ "import sys\n", "from pathlib import Path\n", "import os\n", - "import importlib\n", - "\n", - "\n", + "import importlib" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "available-albany", + "metadata": {}, + "outputs": [], + "source": [ "module_path='preprocessing/day_intervals_preproc'\n", "if module_path not in sys.path:\n", " sys.path.append(module_path)\n", @@ -70,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 158, + "execution_count": 3, "id": "nutritional-chicago", "metadata": {}, "outputs": [], @@ -173,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "structured-dimension", "metadata": { "tags": [ @@ -196,7 +203,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "71ef24fe5a444ebb8191a30f09302791", + "model_id": "73670930477c4c639717c622d624b049", "version_major": 2, "version_minor": 0 }, @@ -217,7 +224,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5d7ae412aa5f4976a0c630efa469f694", + "model_id": "17de961b447c4005aac3dcbef993546a", "version_major": 2, "version_minor": 0 }, @@ -273,38 +280,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "broke-spirituality", "metadata": {}, "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "f3e3ebb6ac2b44919e40f658a661a986", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "RadioButtons(options=('Length of Stay ge 3', 'Length of Stay ge 7', 'Custom'), value='Length of Stay ge 3')" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0ef6884738014f2c840370f7b264656c", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Label(value='Length of stay ge (in days)', layout=Layout(width='180px')), IntSlider(value=3, co…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "name": "stdout", "output_type": "stream", @@ -316,7 +295,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5056acfb82184e948c1e3079a6ffd597", + "model_id": "c21fd5c8416f49c2822bce9081e011f2", "version_major": 2, "version_minor": 0 }, @@ -337,7 +316,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0bd8a7d96b9a41a5bdfe3c5506e3b058", + "model_id": "20eb962a8b80459bae218ffda503ea74", "version_major": 2, "version_minor": 0 }, @@ -395,7 +374,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "republican-freight", "metadata": {}, "outputs": [ @@ -403,16 +382,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "===========MIMIC-IV v1.0============\n", - "EXTRACTING FOR: | ICU | LENGTH OF STAY | 3 |\n", - "[ LOS LABELS FINISHED ]\n", + "===========MIMIC-IV v2.0============\n", + "EXTRACTING FOR: | ICU | MORTALITY | 0 |\n", + "[ MORTALITY LABELS FINISHED ]\n", "[ COHORT SUCCESSFULLY SAVED ]\n", "[ SUMMARY SUCCESSFULLY SAVED ]\n", - "Length of Stay FOR ICU DATA\n", - "# Admission Records: 76540\n", - "# Patients: 53150\n", - "# Positive cases: 24397\n", - "# Negative cases: 52143\n" + "Mortality FOR ICU DATA\n", + "# Admission Records: 140\n", + "# Patients: 100\n", + "# Positive cases: 10\n", + "# Negative cases: 130\n" ] } ], @@ -471,14 +450,41 @@ " version_path=\"mimiciv/1.0\"\n", " cohort_output = day_intervals_cohort.extract_data(radio_input1.value,label,time,icd_code, root_dir,disease_label)\n", "elif version.value=='Version 2':\n", - " version_path=\"mimiciv/2.0\"\n", + " version_path=\"mimiciv\"\n", " cohort_output = day_intervals_cohort_v2.extract_data(radio_input1.value,label,time,icd_code, root_dir,disease_label)" ] }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ICU',\n", + " 'Mortality',\n", + " 0,\n", + " 'No Disease Filter',\n", + " 'd:\\\\Work\\\\Repos\\\\MIMIC-IV-Data-Pipeline',\n", + " '')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "radio_input1.value,label,time,icd_code, root_dir,disease_label" + ] + }, { "cell_type": "markdown", "id": "interstate-stadium", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 2. FEATURE SELECTION\n", "Features available for ICU data -\n", @@ -501,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "raised-olympus", "metadata": {}, "outputs": [ @@ -516,7 +522,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c7a96185f866472ba7d6f415fc1c7e7f", + "model_id": "d91444a4a11144a4abb5fc2e9281ee42", "version_major": 2, "version_minor": 0 }, @@ -530,7 +536,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "116cd5d5738745c1a7c4c30a3affdf10", + "model_id": "f919bf191891416d94039f9076a4a7f4", "version_major": 2, "version_minor": 0 }, @@ -544,7 +550,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "11fe8d7e1ad84f23a61e81b0c0fb53e5", + "model_id": "97ff8f2f6fd04245980b5362c537b399", "version_major": 2, "version_minor": 0 }, @@ -558,7 +564,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "bce5452627164365b27b6ea2e2eab349", + "model_id": "6dff2879bcc94b58b646bf29af7b7ded", "version_major": 2, "version_minor": 0 }, @@ -572,7 +578,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "02b89f4a21684c70a40564454d2cce67", + "model_id": "f459aff3d4794b85bd95a7669e81fdf0", "version_major": 2, "version_minor": 0 }, @@ -620,7 +626,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "native-covering", "metadata": { "scrolled": true @@ -631,31 +637,17 @@ "output_type": "stream", "text": [ "[EXTRACTING DIAGNOSIS DATA]\n", - "# unique ICD-9 codes 6686\n", - "# unique ICD-10 codes 10120\n", - "# unique ICD-10 codes (After converting ICD-9 to ICD-10) 10414\n", - "# unique ICD-10 codes (After clinical gruping ICD-10 codes) 1522\n", - "# Admissions: 76504\n", - "Total rows 1362068\n", + "# unique ICD-9 codes 539\n", + "# unique ICD-10 codes 508\n", + "# unique ICD-10 codes (After converting ICD-9 to ICD-10) 689\n", + "# unique ICD-10 codes (After clinical gruping ICD-10 codes) 388\n", + "# Admissions: 140\n", + "Total rows 2647\n", "[SUCCESSFULLY SAVED DIAGNOSIS DATA]\n", "[EXTRACTING OUPTPUT EVENTS DATA]\n", - "# Unique Events: 71\n", - "# Admissions: 74364\n", - "Total rows 4457381\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r", - "0it [00:00, ?it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "# Unique Events: 39\n", + "# Admissions: 137\n", + "Total rows 9362\n", "[SUCCESSFULLY SAVED OUPTPUT EVENTS DATA]\n", "[EXTRACTING CHART EVENTS DATA]\n" ] @@ -664,26 +656,26 @@ "name": "stderr", "output_type": "stream", "text": [ - "33it [06:39, 12.12s/it]\n" + "1it [00:00, 1.11it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "# Unique Events: 454\n", - "# Admissions: 76529\n", - "Total rows 81030530\n", + "# Unique Events: 298\n", + "# Admissions: 140\n", + "Total rows 162571\n", "[SUCCESSFULLY SAVED CHART EVENTS DATA]\n", "[EXTRACTING PROCEDURES DATA]\n", - "# Unique Events: 157\n", - "# Admissions: 76041\n", - "Total rows 713377\n", + "# Unique Events: 82\n", + "# Admissions: 138\n", + "Total rows 1435\n", "[SUCCESSFULLY SAVED PROCEDURES DATA]\n", "[EXTRACTING MEDICATIONS DATA]\n", - "# of unique type of drug: 196\n", - "# Admissions: 72118\n", - "# Total rows 5078987\n", + "# of unique type of drug: 76\n", + "# Admissions: 136\n", + "# Total rows 11038\n", "[SUCCESSFULLY SAVED MEDICATIONS DATA]\n" ] } @@ -704,10 +696,21 @@ " feature_nonicu(cohort_output, version_path,diag_flag,lab_flag,proc_flag,med_flag)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cohort_output, version_path,diag_flag,out_flag,chart_flag,proc_flag,med_flag" + ] + }, { "cell_type": "markdown", "id": "aboriginal-upset", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 3. CLINICAL GROUPING\n", "Below you will have option to clinically group diagnosis and medications.\n", @@ -720,7 +723,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 14, "id": "partial-manhattan", "metadata": {}, "outputs": [ @@ -734,7 +737,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1c9195ae736744d7a79960cbfe87b95d", + "model_id": "f08060562dcd42f69f99300fc590dedc", "version_major": 2, "version_minor": 0 }, @@ -778,7 +781,34 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('cohort_icu_mortality_0_',\n", + " True,\n", + " 'Convert ICD-9 to ICD-10 and group ICD-10 codes',\n", + " False,\n", + " False,\n", + " False,\n", + " 0,\n", + " 0)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cohort_output, diag_flag, group_diag,False,False,False,0,0" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "descending-symphony", "metadata": {}, "outputs": [ @@ -787,7 +817,7 @@ "output_type": "stream", "text": [ "[PROCESSING DIAGNOSIS DATA]\n", - "Total number of rows 1289600\n", + "Total number of rows 2504\n", "[SUCCESSFULLY SAVED DIAGNOSIS DATA]\n" ] } @@ -830,7 +860,27 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, True, True, True, True)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(diag_flag,proc_flag,med_flag,out_flag,chart_flag)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, "id": "thick-residence", "metadata": {}, "outputs": [ @@ -839,19 +889,6 @@ "output_type": "stream", "text": [ "[GENERATING FEATURE SUMMARY]\n", - " subject_id hadm_id starttime stoptime drug_name \\\n", - "0 17868682 22726960 2160-01-07 10:00:00 2160-01-07 16:00:00 aspirin \n", - "1 17067646 20845642 2159-02-23 10:00:00 2159-02-26 23:00:00 aspirin \n", - "2 17067646 25358552 2159-08-08 10:00:00 2159-08-13 18:00:00 aspirin \n", - "3 13359788 27483342 2143-11-22 19:00:00 2143-11-23 19:00:00 aspirin \n", - "4 15346117 20604717 2195-01-21 10:00:00 2195-01-24 21:00:00 aspirin \n", - "\n", - " start_hours_from_admit stop_hours_from_admit dose_val_rx \n", - "0 -1 days +22:00:00.000000000 0 days 04:00:00.000000000 81 \n", - "1 0 days 12:49:00.000000000 4 days 01:49:00.000000000 81 \n", - "2 -1 days +13:54:00.000000000 4 days 21:54:00.000000000 81 \n", - "3 0 days 02:59:00.000000000 1 days 02:59:00.000000000 81 \n", - "4 -1 days +13:23:00.000000000 3 days 00:23:00.000000000 81 \n", "[SUCCESSFULLY SAVED FEATURE SUMMARY]\n" ] } @@ -866,7 +903,9 @@ { "cell_type": "markdown", "id": "northern-architecture", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 5. Feature Selection\n", "\n", @@ -881,7 +920,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 20, "id": "immediate-seafood", "metadata": {}, "outputs": [ @@ -896,7 +935,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3d5b89b25a7d41a38b994eecc9550f69", + "model_id": "831fcd09d5a24893b587cd2cc4947d56", "version_major": 2, "version_minor": 0 }, @@ -918,7 +957,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c97cc3c6313246d68cefd652eca49cfa", + "model_id": "8a6c9e9e2cfc4b63962ab1dab3662e88", "version_major": 2, "version_minor": 0 }, @@ -940,7 +979,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7c1d627a06e648ada8d44f90f7ae4f04", + "model_id": "85eed2e5a1834402a772379a7e388faa", "version_major": 2, "version_minor": 0 }, @@ -962,7 +1001,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2b5a02b857cd4266a63bdbab772d050e", + "model_id": "dc4cf06db0a649f5b928a754d85b661c", "version_major": 2, "version_minor": 0 }, @@ -984,7 +1023,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "09f56b608e144b88bd975b743a7cd075", + "model_id": "8af1bced7fc745409b7db3728ccc3c5e", "version_major": 2, "version_minor": 0 }, @@ -1047,10 +1086,32 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 21, "id": "perceived-python", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[FEATURE SELECTION DIAGNOSIS DATA]\n", + "Total number of rows 2504\n", + "[SUCCESSFULLY SAVED DIAGNOSIS DATA]\n", + "[FEATURE SELECTION MEDICATIONS DATA]\n", + "Total number of rows 11038\n", + "[SUCCESSFULLY SAVED MEDICATIONS DATA]\n", + "[FEATURE SELECTION PROCEDURES DATA]\n", + "Total number of rows 1435\n", + "[SUCCESSFULLY SAVED PROCEDURES DATA]\n", + "[FEATURE SELECTION OUTPUT EVENTS DATA]\n", + "Total number of rows 9362\n", + "[SUCCESSFULLY SAVED OUTPUT EVENTS DATA]\n", + "[FEATURE SELECTION CHART EVENTS DATA]\n", + "Total number of rows 162571\n", + "[SUCCESSFULLY SAVED CHART EVENTS DATA]\n" + ] + } + ], "source": [ "select_diag=False\n", "select_med=False\n", @@ -1083,10 +1144,42 @@ " features_selection_hosp(cohort_output, diag_flag,proc_flag,med_flag,lab_flag,select_diag,select_med,select_proc,select_lab)" ] }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('cohort_icu_mortality_0_',\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(cohort_output, diag_flag,proc_flag,med_flag,out_flag, chart_flag,select_diag,select_med,select_proc,select_out,select_chart)" + ] + }, { "cell_type": "markdown", "id": "comfortable-director", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 6. CLEANING OF FEATURES\n", "Below you will have option to to clean lab and chart events by performing outlier removal and unit conversion.\n", @@ -1098,7 +1191,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "moderate-forum", "metadata": {}, "outputs": [ @@ -1112,7 +1205,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "222126e0df6d44f88fe98d2909d23129", + "model_id": "7edefbfc026d4eae9c732f1d82a70ce3", "version_major": 2, "version_minor": 0 }, @@ -1126,7 +1219,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5fc77b72dfae4b9d9e9fb66e0a4a6234", + "model_id": "778ebe54223d4e0b8e0e0c60ceb02555", "version_major": 2, "version_minor": 0 }, @@ -1140,7 +1233,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "cbd8497ab8684c258d21dd3c5c965aed", + "model_id": "dd1977db5d754e78859692220234400d", "version_major": 2, "version_minor": 0 }, @@ -1214,7 +1307,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 24, "id": "impossible-mailman", "metadata": {}, "outputs": [ @@ -1223,7 +1316,7 @@ "output_type": "stream", "text": [ "[PROCESSING CHART EVENTS DATA]\n", - "Total number of rows 4892842\n", + "Total number of rows 162571\n", "[SUCCESSFULLY SAVED CHART EVENTS DATA]\n" ] } @@ -1246,10 +1339,32 @@ " preprocess_features_hosp(cohort_output, False,False,False,lab_flag,False,False,False,clean_lab,impute_outlier,thresh,left_thresh)" ] }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('cohort_icu_mortality_0_', False, False, True, True, True, 98, 0)" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(cohort_output, False, False,chart_flag,clean_chart,impute_outlier_chart,thresh,left_thresh)" + ] + }, { "cell_type": "markdown", "id": "independent-academy", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 7. Time-Series Representation\n", "In this section, please choose how you want to process and represent time-series data.\n", @@ -1274,7 +1389,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 26, "id": "mechanical-three", "metadata": {}, "outputs": [ @@ -1289,12 +1404,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0ff4a416c46e4b0e8c44438d7194b616", + "model_id": "cf03952437504aa6b000af62ca5542aa", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "RadioButtons(index=1, options=('First 12 hours', 'First 24 hours', 'Custom'), value='First 24 hours')" + "RadioButtons(options=('First 72 hours', 'First 48 hours', 'First 24 hours', 'Custom'), value='First 72 hours')" ] }, "metadata": {}, @@ -1303,7 +1418,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "24c44d3c094e4ee095172e3721728436", + "model_id": "49077fdbb6ce49f5a2f910dc856bbe03", "version_major": 2, "version_minor": 0 }, @@ -1324,7 +1439,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "40be412e210240c49a62c25414254b3e", + "model_id": "1be8ec6f9848424cae38b4fee674df65", "version_major": 2, "version_minor": 0 }, @@ -1338,7 +1453,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f190f26d47fc4cc7ae489e0677fb63f4", + "model_id": "07db34eba6c94bad96c31b65cfb2a516", "version_major": 2, "version_minor": 0 }, @@ -1359,7 +1474,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "466fa54671a3463c96a0d2405dc39bc6", + "model_id": "e6d9d12a47494e72852aff8ca363fc76", "version_major": 2, "version_minor": 0 }, @@ -1370,6 +1485,41 @@ "metadata": {}, "output_type": "display_data" }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "If you have choosen mortality prediction task, then what prediction window length you want to keep?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8f76d3ae14044a11a71d59a5839bed9c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('2 hours', '4 hours', '6 hours', '8 hours', 'Custom'), value='2 hours')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "235fdea0b4cb40cbb85feed9aca459e3", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Label(value='Prediction window (in hours)', layout=Layout(width='180px')), IntSlider(value=2, m…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stdout", "output_type": "stream", @@ -1454,7 +1604,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 27, "id": "indie-appendix", "metadata": {}, "outputs": [ @@ -1465,21 +1615,7 @@ "[ READ COHORT ]\n", "[ ======READING DIAGNOSIS ]\n", "[ ======READING PROCEDURES ]\n", - "[ ======READING OUT EVENTS ]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r", - "0it [00:00, ?it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "[ ======READING OUT EVENTS ]\n", "[ ======READING CHART EVENTS ]\n" ] }, @@ -1487,7 +1623,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "17it [08:07, 28.66s/it]\n" + "1it [00:01, 1.10s/it]\n" ] }, { @@ -1496,7 +1632,7 @@ "text": [ "[ ======READING MEDICATIONS ]\n", "[ READ ALL FEATURES ]\n", - "include_time 24\n", + "include_time 72\n", "[ PROCESSED TIME SERIES TO EQUAL LENGTH ]\n" ] }, @@ -1504,7 +1640,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "100%|██████████| 24/24 [00:20<00:00, 1.15it/s]\n" + "100%|██████████| 72/72 [00:00<00:00, 79.20it/s]\n" ] }, { @@ -1513,14 +1649,14 @@ "text": [ "bucket 1\n", "[ PROCESSED TIME SERIES TO EQUAL TIME INTERVAL ]\n", - "24\n" + "72\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "100%|██████████| 60494/60494 [10:16:42<00:00, 1.63it/s] \n" + "100%|██████████| 54/54 [00:09<00:00, 5.58it/s]" ] }, { @@ -1529,6 +1665,13 @@ "text": [ "[ SUCCESSFULLY SAVED DATA DICTIONARIES ]\n" ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] } ], "source": [ @@ -1560,10 +1703,44 @@ " gen=data_generation.Generator(cohort_output,data_mort,data_admn,data_los,diag_flag,lab_flag,proc_flag,med_flag,impute,include,bucket,predW)" ] }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('cohort_icu_mortality_0_',\n", + " True,\n", + " False,\n", + " False,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " True,\n", + " False,\n", + " 72,\n", + " 1,\n", + " 2)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(cohort_output,data_mort,data_admn,data_los,diag_flag,proc_flag,out_flag,chart_flag,med_flag,impute,include,bucket,predW)" + ] + }, { "cell_type": "markdown", "id": "lined-reset", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 8. Machine Learning Models\n", "\n", @@ -1577,7 +1754,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 25, "id": "consolidated-former", "metadata": {}, "outputs": [ @@ -1591,7 +1768,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "dc736b979c044a84a3348b8d17c936b3", + "model_id": "9fd49d0d536d4afea1b8635a59f5eac3", "version_major": 2, "version_minor": 0 }, @@ -1612,7 +1789,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f8f67b6fe390466cb8565f91909d9825", + "model_id": "41a7bab9c71d4b9fb888e44a09aabde2", "version_major": 2, "version_minor": 0 }, @@ -1633,7 +1810,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c9c36f7c64774ed6a0d0591056a39fc8", + "model_id": "3c0600abf96745beb66d2cd62f979afb", "version_major": 2, "version_minor": 0 }, @@ -1654,7 +1831,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6006248cbfe84600974598218b4dd64a", + "model_id": "983178aa7dc84480a1c622890bc24241", "version_major": 2, "version_minor": 0 }, @@ -1683,7 +1860,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 26, "id": "promising-miller", "metadata": { "scrolled": true @@ -1693,59 +1870,43 @@ "name": "stdout", "output_type": "stream", "text": [ - "Total Samples 500\n", - "Positive Samples 233\n", + "Total Samples 117\n", + "Positive Samples 20\n", "=============OVERSAMPLING===============\n", - "Total Samples 534\n", - "Positive Samples 267\n", + "Total Samples 194\n", + "Positive Samples 97\n", "=================== 0 FOLD=====================\n", - "train_hids 424\n", - "X_df (424, 21560)\n", - "y_df (424,)\n", - "(424, 21560)\n", - "(424,)\n", - "test_hids 106\n", - "X_df (106, 21560)\n", - "y_df (106,)\n", - "(106, 21560)\n", - "(106,)\n", - "===============MODEL TRAINING===============\n", - "BCE Loss: 1.38\n", - "AU-ROC: 0.79\n", - "AU-PRC: 0.85\n", - "AU-PRC Baaseline: 0.55\n", - "Accuracy: 0.75\n", - "Precision: 0.82\n", - "Recall: 0.71\n", - "Specificity: 0.81\n", - "NPV: 0.70\n", - "ECE: 0.17\n", - "MCE: 0.35\n" + "train_hids 152\n", + "X_df (152, 9824)\n", + "y_df (152,)\n", + "(152, 9824)\n", + "(152,)\n", + "test_hids 38\n", + "X_df (38, 9824)\n", + "y_df (38,)\n" ] }, { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" + "ename": "ValueError", + "evalue": "y contains previously unseen labels: 'BLACK/CAPE VERDEAN'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\_encode.py:225\u001b[0m, in \u001b[0;36m_encode\u001b[1;34m(values, uniques, check_unknown)\u001b[0m\n\u001b[0;32m 224\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[1;32m--> 225\u001b[0m \u001b[39mreturn\u001b[39;00m _map_to_integer(values, uniques)\n\u001b[0;32m 226\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m \u001b[39mas\u001b[39;00m e:\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\_encode.py:165\u001b[0m, in \u001b[0;36m_map_to_integer\u001b[1;34m(values, uniques)\u001b[0m\n\u001b[0;32m 164\u001b[0m table \u001b[39m=\u001b[39m _nandict({val: i \u001b[39mfor\u001b[39;00m i, val \u001b[39min\u001b[39;00m \u001b[39menumerate\u001b[39m(uniques)})\n\u001b[1;32m--> 165\u001b[0m \u001b[39mreturn\u001b[39;00m np\u001b[39m.\u001b[39marray([table[v] \u001b[39mfor\u001b[39;49;00m v \u001b[39min\u001b[39;49;00m values])\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\_encode.py:165\u001b[0m, in \u001b[0;36m\u001b[1;34m(.0)\u001b[0m\n\u001b[0;32m 164\u001b[0m table \u001b[39m=\u001b[39m _nandict({val: i \u001b[39mfor\u001b[39;00m i, val \u001b[39min\u001b[39;00m \u001b[39menumerate\u001b[39m(uniques)})\n\u001b[1;32m--> 165\u001b[0m \u001b[39mreturn\u001b[39;00m np\u001b[39m.\u001b[39marray([table[v] \u001b[39mfor\u001b[39;00m v \u001b[39min\u001b[39;00m values])\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\_encode.py:159\u001b[0m, in \u001b[0;36m_nandict.__missing__\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 158\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mnan_value\n\u001b[1;32m--> 159\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mKeyError\u001b[39;00m(key)\n", + "\u001b[1;31mKeyError\u001b[0m: 'BLACK/CAPE VERDEAN'", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\mainPipeline.ipynb Cell 33\u001b[0m line \u001b[0;36m7\n\u001b[0;32m 5\u001b[0m \u001b[39melif\u001b[39;00m radio_input7\u001b[39m.\u001b[39mvalue\u001b[39m==\u001b[39m\u001b[39m'\u001b[39m\u001b[39m10-fold CV\u001b[39m\u001b[39m'\u001b[39m:\n\u001b[0;32m 6\u001b[0m cv\u001b[39m=\u001b[39m\u001b[39mint\u001b[39m(\u001b[39m10\u001b[39m)\n\u001b[1;32m----> 7\u001b[0m ml\u001b[39m=\u001b[39mml_models\u001b[39m.\u001b[39;49mML_models(data_icu,cv,radio_input5\u001b[39m.\u001b[39;49mvalue,concat\u001b[39m=\u001b[39;49mradio_input6\u001b[39m.\u001b[39;49mvalue\u001b[39m==\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39mConactenate\u001b[39;49m\u001b[39m'\u001b[39;49m,oversampling\u001b[39m=\u001b[39;49mradio_input8\u001b[39m.\u001b[39;49mvalue\u001b[39m==\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39mTrue\u001b[39;49m\u001b[39m'\u001b[39;49m)\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\model\\ml_models.py:42\u001b[0m, in \u001b[0;36mML_models.__init__\u001b[1;34m(self, data_icu, k_fold, model_type, concat, oversampling)\u001b[0m\n\u001b[0;32m 40\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39moversampling\u001b[39m=\u001b[39moversampling\n\u001b[0;32m 41\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mloss\u001b[39m=\u001b[39mevaluation\u001b[39m.\u001b[39mLoss(\u001b[39m'\u001b[39m\u001b[39mcpu\u001b[39m\u001b[39m'\u001b[39m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m,\u001b[39mTrue\u001b[39;00m)\n\u001b[1;32m---> 42\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mml_train()\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\model\\ml_models.py:124\u001b[0m, in \u001b[0;36mML_models.ml_train\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 122\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mtest_data\u001b[39m=\u001b[39mX_test\u001b[39m.\u001b[39mcopy(deep\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[0;32m 123\u001b[0m X_test[\u001b[39m'\u001b[39m\u001b[39mgender\u001b[39m\u001b[39m'\u001b[39m]\u001b[39m=\u001b[39mgen_encoder\u001b[39m.\u001b[39mtransform(X_test[\u001b[39m'\u001b[39m\u001b[39mgender\u001b[39m\u001b[39m'\u001b[39m])\n\u001b[1;32m--> 124\u001b[0m X_test[\u001b[39m'\u001b[39m\u001b[39methnicity\u001b[39m\u001b[39m'\u001b[39m]\u001b[39m=\u001b[39meth_encoder\u001b[39m.\u001b[39;49mtransform(X_test[\u001b[39m'\u001b[39;49m\u001b[39methnicity\u001b[39;49m\u001b[39m'\u001b[39;49m])\n\u001b[0;32m 125\u001b[0m X_test[\u001b[39m'\u001b[39m\u001b[39minsurance\u001b[39m\u001b[39m'\u001b[39m]\u001b[39m=\u001b[39mins_encoder\u001b[39m.\u001b[39mtransform(X_test[\u001b[39m'\u001b[39m\u001b[39minsurance\u001b[39m\u001b[39m'\u001b[39m])\n\u001b[0;32m 126\u001b[0m \u001b[39m#X_test['Age']=age_encoder.transform(X_test['Age'])\u001b[39;00m\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\preprocessing\\_label.py:137\u001b[0m, in \u001b[0;36mLabelEncoder.transform\u001b[1;34m(self, y)\u001b[0m\n\u001b[0;32m 134\u001b[0m \u001b[39mif\u001b[39;00m _num_samples(y) \u001b[39m==\u001b[39m \u001b[39m0\u001b[39m:\n\u001b[0;32m 135\u001b[0m \u001b[39mreturn\u001b[39;00m np\u001b[39m.\u001b[39marray([])\n\u001b[1;32m--> 137\u001b[0m \u001b[39mreturn\u001b[39;00m _encode(y, uniques\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mclasses_)\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\_encode.py:227\u001b[0m, in \u001b[0;36m_encode\u001b[1;34m(values, uniques, check_unknown)\u001b[0m\n\u001b[0;32m 225\u001b[0m \u001b[39mreturn\u001b[39;00m _map_to_integer(values, uniques)\n\u001b[0;32m 226\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m \u001b[39mas\u001b[39;00m e:\n\u001b[1;32m--> 227\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mValueError\u001b[39;00m(\u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39my contains previously unseen labels: \u001b[39m\u001b[39m{\u001b[39;00m\u001b[39mstr\u001b[39m(e)\u001b[39m}\u001b[39;00m\u001b[39m\"\u001b[39m)\n\u001b[0;32m 228\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m 229\u001b[0m \u001b[39mif\u001b[39;00m check_unknown:\n", + "\u001b[1;31mValueError\u001b[0m: y contains previously unseen labels: 'BLACK/CAPE VERDEAN'" + ] } ], "source": [ @@ -1761,7 +1922,9 @@ { "cell_type": "markdown", "id": "ordinary-chancellor", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 9. Deep Learning Models\n", "- Time-series LSTM and Time-series CNN which will only use time-series events like medications, charts, labs, output events to train model.\n", @@ -1777,14 +1940,14 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 27, "id": "operational-pride", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d162f67f625a457c8a9801aa324f5be3", + "model_id": "4c5bf3139bfc4a888740b17daf69fc66", "version_major": 2, "version_minor": 0 }, @@ -1805,7 +1968,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0a23df702c704ed5a63944d897653f56", + "model_id": "71a6547b8f1d49e4b542d6af61e5498b", "version_major": 2, "version_minor": 0 }, @@ -1826,7 +1989,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f8f5b62f5a8c4641ab60a4cd150455c7", + "model_id": "f68b900bb6894d528a725a338cfb1807", "version_major": 2, "version_minor": 0 }, @@ -1851,7 +2014,7 @@ }, { "cell_type": "code", - "execution_count": 159, + "execution_count": 28, "id": "golden-stewart", "metadata": { "scrolled": true @@ -1862,33 +2025,36 @@ "output_type": "stream", "text": [ "===============MODEL TRAINING===============\n", - "Total Samples 500\n", - "Positive Samples 233\n", + "Total Samples 117\n", + "Positive Samples 20\n", + "=============OVERSAMPLING===============\n", + "Total Samples 194\n", + "Positive Samples 97\n", "[ MODEL CREATED ]\n", "LSTMBase(\n", " (med): ValEmbed(\n", - " (codeEmbed): BatchNorm1d(163, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (fc): Linear(in_features=163, out_features=152, bias=True)\n", + " (codeEmbed): BatchNorm1d(54, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (fc): Linear(in_features=54, out_features=152, bias=True)\n", " )\n", " (proc): CodeEmbed(\n", - " (codeEmbed): Embedding(157, 52)\n", - " (fc): Linear(in_features=8164, out_features=152, bias=True)\n", + " (codeEmbed): Embedding(61, 52)\n", + " (fc): Linear(in_features=3172, out_features=152, bias=True)\n", " )\n", " (out): CodeEmbed(\n", - " (codeEmbed): Embedding(70, 52)\n", - " (fc): Linear(in_features=3640, out_features=152, bias=True)\n", + " (codeEmbed): Embedding(29, 52)\n", + " (fc): Linear(in_features=1508, out_features=152, bias=True)\n", " )\n", " (chart): ValEmbed(\n", - " (codeEmbed): BatchNorm1d(446, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (fc): Linear(in_features=446, out_features=152, bias=True)\n", + " (codeEmbed): BatchNorm1d(250, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", + " (fc): Linear(in_features=250, out_features=152, bias=True)\n", " )\n", " (cond): StatEmbed(\n", - " (codeEmbed): Embedding(1492, 52)\n", - " (fc): Linear(in_features=77584, out_features=152, bias=True)\n", + " (codeEmbed): Embedding(364, 52)\n", + " (fc): Linear(in_features=18928, out_features=152, bias=True)\n", " )\n", - " (ethEmbed): Embedding(9, 152, padding_idx=0)\n", + " (ethEmbed): Embedding(13, 152, padding_idx=0)\n", " (genderEmbed): Embedding(3, 152, padding_idx=0)\n", - " (ageEmbed): Embedding(74, 152, padding_idx=0)\n", + " (ageEmbed): Embedding(51, 152, padding_idx=0)\n", " (insEmbed): Embedding(4, 152, padding_idx=0)\n", " (embedfc): Linear(in_features=1368, out_features=152, bias=True)\n", " (rnn): LSTM(152, 256, num_layers=2, batch_first=True)\n", @@ -1896,100 +2062,28 @@ " (fc2): Linear(in_features=128, out_features=1, bias=True)\n", ")\n", "=================== 0 FOLD=====================\n", - "======= EPOCH 0.0 ========\n", - "BCE Loss: 1.30\n", - "AU-ROC: 0.68\n", - "AU-PRC: 0.63\n", - "AU-PRC Baaseline: 0.47\n", - "Accuracy: 0.64\n", - "Precision: 0.60\n", - "Recall: 0.70\n", - "Specificity: 0.58\n", - "NPV: 0.69\n", - "ECE: 0.07\n", - "MCE: 0.17\n", - "======= VALIDATION ========\n", - "BCE Loss: 1.26\n", - "AU-ROC: 0.72\n", - "AU-PRC: 0.77\n", - "AU-PRC Baaseline: 0.53\n", - "Accuracy: 0.70\n", - "Precision: 0.74\n", - "Recall: 0.67\n", - "Specificity: 0.74\n", - "NPV: 0.67\n", - "ECE: 0.14\n", - "MCE: 0.44\n", - "Validation results improved\n", - "Updating Model\n", - "======= EPOCH 1.0 ========\n" + "======= EPOCH 0.0 ========\n" ] }, { - "ename": "KeyboardInterrupt", - "evalue": "", + "ename": "ValueError", + "evalue": "y_true takes value in {} and pos_label is not specified: either make y_true take value in {0, 1} or {-1, 1} or pass pos_label explicitly.", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mdata_icu\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 9\u001b[1;33m \u001b[0mmodel\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdl_train\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mDL_models\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata_icu\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mdiag_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mproc_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mout_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mchart_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mmed_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mradio_input6\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mcv\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0moversampling\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mradio_input8\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m==\u001b[0m\u001b[1;34m'True'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mmodel_name\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'attn_icu_read'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mtrain\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 10\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mmodel\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdl_train\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mDL_models\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata_icu\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mdiag_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mproc_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mmed_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mlab_flag\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mradio_input6\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mcv\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0moversampling\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mradio_input8\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m==\u001b[0m\u001b[1;34m'True'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mmodel_name\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'attn_icu_read'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mtrain\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Desktop\\MIMIC-IV-Data-Pipeline\\model\\dl_train.py\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, data_icu, diag_flag, proc_flag, out_flag, chart_flag, med_flag, lab_flag, model_type, k_fold, oversampling, model_name, train)\u001b[0m\n\u001b[0;32m 81\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mtrain\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 82\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"===============MODEL TRAINING===============\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 83\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdl_train\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 84\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 85\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Desktop\\MIMIC-IV-Data-Pipeline\\model\\dl_train.py\u001b[0m in \u001b[0;36mdl_train\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 162\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"======= EPOCH {:.1f} ========\"\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mepoch\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 163\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mnbatch\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtrain_hids\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbatch_size\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 164\u001b[1;33m \u001b[0mmeds\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mchart\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mout\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mproc\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mlab\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstat_train\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mdemo_train\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mY_train\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mgetXY\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtrain_hids\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mnbatch\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbatch_size\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnbatch\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbatch_size\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mlabels\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 165\u001b[0m \u001b[1;31m# print(chart.shape)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 166\u001b[0m \u001b[1;31m# print(meds.shape)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Desktop\\MIMIC-IV-Data-Pipeline\\model\\dl_train.py\u001b[0m in \u001b[0;36mgetXY\u001b[1;34m(self, ids, labels)\u001b[0m\n\u001b[0;32m 315\u001b[0m \u001b[1;31m# print(\"key\",key)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 316\u001b[0m \u001b[1;31m# print(\"keys[key]\",keys[key])\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 317\u001b[1;33m \u001b[0mdyn_temp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdyn\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mkeys\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 318\u001b[0m \u001b[0mdyn_temp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdyn_temp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mto_numpy\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 319\u001b[0m \u001b[0mdyn_temp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtensor\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdyn_temp\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Anaconda3\\envs\\DSRA\\lib\\site-packages\\pandas\\core\\frame.py\u001b[0m in \u001b[0;36m__getitem__\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 2773\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mis_unique\u001b[0m \u001b[1;32mand\u001b[0m \u001b[0mkey\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2774\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnlevels\u001b[0m \u001b[1;33m>\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 2775\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_getitem_multilevel\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2776\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_item_cache\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2777\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Anaconda3\\envs\\DSRA\\lib\\site-packages\\pandas\\core\\frame.py\u001b[0m in \u001b[0;36m_getitem_multilevel\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 2847\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_getitem_multilevel\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2848\u001b[0m \u001b[1;31m# self.columns is a MultiIndex\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 2849\u001b[1;33m \u001b[0mloc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2850\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mloc\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mslice\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mSeries\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mIndex\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2851\u001b[0m \u001b[0mnew_columns\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mloc\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Anaconda3\\envs\\DSRA\\lib\\site-packages\\pandas\\core\\indexes\\multi.py\u001b[0m in \u001b[0;36mget_loc\u001b[1;34m(self, key, method)\u001b[0m\n\u001b[0;32m 2651\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mtuple\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlist\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2652\u001b[0m \u001b[1;31m# not including list here breaks some indexing, xref #30892\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 2653\u001b[1;33m \u001b[0mloc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_level_indexer\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlevel\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2654\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0m_maybe_to_slice\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mloc\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2655\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Anaconda3\\envs\\DSRA\\lib\\site-packages\\pandas\\core\\indexes\\multi.py\u001b[0m in \u001b[0;36m_get_level_indexer\u001b[1;34m(self, key, level, indexer)\u001b[0m\n\u001b[0;32m 2922\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mlevel\u001b[0m \u001b[1;33m>\u001b[0m \u001b[1;36m0\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlexsort_depth\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2923\u001b[0m \u001b[1;31m# Desired level is not sorted\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 2924\u001b[1;33m \u001b[0mlocs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marray\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mlevel_codes\u001b[0m \u001b[1;33m==\u001b[0m \u001b[0mcode\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mbool\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcopy\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2925\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mlocs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0many\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2926\u001b[0m \u001b[1;31m# The label is present in self.levels[level] but unused:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mKeyboardInterrupt\u001b[0m: " + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\mainPipeline.ipynb Cell 36\u001b[0m line \u001b[0;36m9\n\u001b[0;32m 6\u001b[0m cv\u001b[39m=\u001b[39m\u001b[39mint\u001b[39m(\u001b[39m10\u001b[39m)\n\u001b[0;32m 8\u001b[0m \u001b[39mif\u001b[39;00m data_icu:\n\u001b[1;32m----> 9\u001b[0m model\u001b[39m=\u001b[39mdl_train\u001b[39m.\u001b[39;49mDL_models(data_icu,diag_flag,proc_flag,out_flag,chart_flag,med_flag,\u001b[39mFalse\u001b[39;49;00m,radio_input6\u001b[39m.\u001b[39;49mvalue,cv,oversampling\u001b[39m=\u001b[39;49mradio_input8\u001b[39m.\u001b[39;49mvalue\u001b[39m==\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39mTrue\u001b[39;49m\u001b[39m'\u001b[39;49m,model_name\u001b[39m=\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39mattn_icu_read\u001b[39;49m\u001b[39m'\u001b[39;49m,train\u001b[39m=\u001b[39;49m\u001b[39mTrue\u001b[39;49;00m)\n\u001b[0;32m 10\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m 11\u001b[0m model\u001b[39m=\u001b[39mdl_train\u001b[39m.\u001b[39mDL_models(data_icu,diag_flag,proc_flag,\u001b[39mFalse\u001b[39;00m,\u001b[39mFalse\u001b[39;00m,med_flag,lab_flag,radio_input6\u001b[39m.\u001b[39mvalue,cv,oversampling\u001b[39m=\u001b[39mradio_input8\u001b[39m.\u001b[39mvalue\u001b[39m==\u001b[39m\u001b[39m'\u001b[39m\u001b[39mTrue\u001b[39m\u001b[39m'\u001b[39m,model_name\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mattn_icu_read\u001b[39m\u001b[39m'\u001b[39m,train\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\model\\dl_train.py:83\u001b[0m, in \u001b[0;36mDL_models.__init__\u001b[1;34m(self, data_icu, diag_flag, proc_flag, out_flag, chart_flag, med_flag, lab_flag, model_type, k_fold, oversampling, model_name, train)\u001b[0m\n\u001b[0;32m 81\u001b[0m \u001b[39mif\u001b[39;00m train:\n\u001b[0;32m 82\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m===============MODEL TRAINING===============\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[1;32m---> 83\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mdl_train()\n\u001b[0;32m 85\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m 86\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mnet\u001b[39m=\u001b[39mtorch\u001b[39m.\u001b[39mload(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39msave_path)\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\model\\dl_train.py:180\u001b[0m, in \u001b[0;36mDL_models.dl_train\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 176\u001b[0m train_logits\u001b[39m.\u001b[39mextend(logits\u001b[39m.\u001b[39mdata\u001b[39m.\u001b[39mcpu()\u001b[39m.\u001b[39mnumpy())\n\u001b[0;32m 178\u001b[0m \u001b[39m#print(train_prob)\u001b[39;00m\n\u001b[0;32m 179\u001b[0m \u001b[39m#print(train_truth)\u001b[39;00m\n\u001b[1;32m--> 180\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mloss(torch\u001b[39m.\u001b[39;49mtensor(train_prob),torch\u001b[39m.\u001b[39;49mtensor(train_truth),torch\u001b[39m.\u001b[39;49mtensor(train_logits),\u001b[39mFalse\u001b[39;49;00m,\u001b[39mFalse\u001b[39;49;00m)\n\u001b[0;32m 181\u001b[0m val_loss\u001b[39m=\u001b[39m\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mmodel_val(val_hids)\n\u001b[0;32m 182\u001b[0m \u001b[39m#print(\"Updating Model\")\u001b[39;00m\n\u001b[0;32m 183\u001b[0m \u001b[39m#T.save(self.net,self.save_path)\u001b[39;00m\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1518\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 1516\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_compiled_call_impl(\u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs) \u001b[39m# type: ignore[misc]\u001b[39;00m\n\u001b[0;32m 1517\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m-> 1518\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_call_impl(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1527\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 1522\u001b[0m \u001b[39m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[0;32m 1523\u001b[0m \u001b[39m# this function, and just call forward.\u001b[39;00m\n\u001b[0;32m 1524\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m (\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_backward_hooks \u001b[39mor\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_backward_pre_hooks \u001b[39mor\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_forward_hooks \u001b[39mor\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_forward_pre_hooks\n\u001b[0;32m 1525\u001b[0m \u001b[39mor\u001b[39;00m _global_backward_pre_hooks \u001b[39mor\u001b[39;00m _global_backward_hooks\n\u001b[0;32m 1526\u001b[0m \u001b[39mor\u001b[39;00m _global_forward_hooks \u001b[39mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[1;32m-> 1527\u001b[0m \u001b[39mreturn\u001b[39;00m forward_call(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[0;32m 1529\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m 1530\u001b[0m result \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\model\\evaluation.py:98\u001b[0m, in \u001b[0;36mLoss.forward\u001b[1;34m(self, prob, labels, logits, train, standalone)\u001b[0m\n\u001b[0;32m 94\u001b[0m prob \u001b[39m=\u001b[39m prob\u001b[39m.\u001b[39mdata\u001b[39m.\u001b[39mcpu()\u001b[39m.\u001b[39mnumpy()\n\u001b[0;32m 95\u001b[0m \u001b[39mif\u001b[39;00m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mauroc):\n\u001b[0;32m 96\u001b[0m \u001b[39m# print(labels)\u001b[39;00m\n\u001b[0;32m 97\u001b[0m \u001b[39m# print(prob)\u001b[39;00m\n\u001b[1;32m---> 98\u001b[0m fpr, tpr, threshholds \u001b[39m=\u001b[39m metrics\u001b[39m.\u001b[39;49mroc_curve(labels, prob)\n\u001b[0;32m 99\u001b[0m auc \u001b[39m=\u001b[39m metrics\u001b[39m.\u001b[39mauc(fpr, tpr)\n\u001b[0;32m 100\u001b[0m \u001b[39mif\u001b[39;00m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39maurocPlot):\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\_param_validation.py:214\u001b[0m, in \u001b[0;36mvalidate_params..decorator..wrapper\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m 208\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m 209\u001b[0m \u001b[39mwith\u001b[39;00m config_context(\n\u001b[0;32m 210\u001b[0m skip_parameter_validation\u001b[39m=\u001b[39m(\n\u001b[0;32m 211\u001b[0m prefer_skip_nested_validation \u001b[39mor\u001b[39;00m global_skip_validation\n\u001b[0;32m 212\u001b[0m )\n\u001b[0;32m 213\u001b[0m ):\n\u001b[1;32m--> 214\u001b[0m \u001b[39mreturn\u001b[39;00m func(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[0;32m 215\u001b[0m \u001b[39mexcept\u001b[39;00m InvalidParameterError \u001b[39mas\u001b[39;00m e:\n\u001b[0;32m 216\u001b[0m \u001b[39m# When the function is just a wrapper around an estimator, we allow\u001b[39;00m\n\u001b[0;32m 217\u001b[0m \u001b[39m# the function to delegate validation to the estimator, but we replace\u001b[39;00m\n\u001b[0;32m 218\u001b[0m \u001b[39m# the name of the estimator by the name of the function in the error\u001b[39;00m\n\u001b[0;32m 219\u001b[0m \u001b[39m# message to avoid confusion.\u001b[39;00m\n\u001b[0;32m 220\u001b[0m msg \u001b[39m=\u001b[39m re\u001b[39m.\u001b[39msub(\n\u001b[0;32m 221\u001b[0m \u001b[39mr\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mparameter of \u001b[39m\u001b[39m\\\u001b[39m\u001b[39mw+ must be\u001b[39m\u001b[39m\"\u001b[39m,\n\u001b[0;32m 222\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mparameter of \u001b[39m\u001b[39m{\u001b[39;00mfunc\u001b[39m.\u001b[39m\u001b[39m__qualname__\u001b[39m\u001b[39m}\u001b[39;00m\u001b[39m must be\u001b[39m\u001b[39m\"\u001b[39m,\n\u001b[0;32m 223\u001b[0m \u001b[39mstr\u001b[39m(e),\n\u001b[0;32m 224\u001b[0m )\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\metrics\\_ranking.py:1095\u001b[0m, in \u001b[0;36mroc_curve\u001b[1;34m(y_true, y_score, pos_label, sample_weight, drop_intermediate)\u001b[0m\n\u001b[0;32m 993\u001b[0m \u001b[39m@validate_params\u001b[39m(\n\u001b[0;32m 994\u001b[0m {\n\u001b[0;32m 995\u001b[0m \u001b[39m\"\u001b[39m\u001b[39my_true\u001b[39m\u001b[39m\"\u001b[39m: [\u001b[39m\"\u001b[39m\u001b[39marray-like\u001b[39m\u001b[39m\"\u001b[39m],\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 1004\u001b[0m y_true, y_score, \u001b[39m*\u001b[39m, pos_label\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m, sample_weight\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m, drop_intermediate\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m\n\u001b[0;32m 1005\u001b[0m ):\n\u001b[0;32m 1006\u001b[0m \u001b[39m \u001b[39m\u001b[39m\"\"\"Compute Receiver operating characteristic (ROC).\u001b[39;00m\n\u001b[0;32m 1007\u001b[0m \n\u001b[0;32m 1008\u001b[0m \u001b[39m Note: this implementation is restricted to the binary classification task.\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 1093\u001b[0m \u001b[39m array([ inf, 0.8 , 0.4 , 0.35, 0.1 ])\u001b[39;00m\n\u001b[0;32m 1094\u001b[0m \u001b[39m \"\"\"\u001b[39;00m\n\u001b[1;32m-> 1095\u001b[0m fps, tps, thresholds \u001b[39m=\u001b[39m _binary_clf_curve(\n\u001b[0;32m 1096\u001b[0m y_true, y_score, pos_label\u001b[39m=\u001b[39;49mpos_label, sample_weight\u001b[39m=\u001b[39;49msample_weight\n\u001b[0;32m 1097\u001b[0m )\n\u001b[0;32m 1099\u001b[0m \u001b[39m# Attempt to drop thresholds corresponding to points in between and\u001b[39;00m\n\u001b[0;32m 1100\u001b[0m \u001b[39m# collinear with other points. These are always suboptimal and do not\u001b[39;00m\n\u001b[0;32m 1101\u001b[0m \u001b[39m# appear on a plotted ROC curve (and thus do not affect the AUC).\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 1106\u001b[0m \u001b[39m# but does not drop more complicated cases like fps = [1, 3, 7],\u001b[39;00m\n\u001b[0;32m 1107\u001b[0m \u001b[39m# tps = [1, 2, 4]; there is no harm in keeping too many thresholds.\u001b[39;00m\n\u001b[0;32m 1108\u001b[0m \u001b[39mif\u001b[39;00m drop_intermediate \u001b[39mand\u001b[39;00m \u001b[39mlen\u001b[39m(fps) \u001b[39m>\u001b[39m \u001b[39m2\u001b[39m:\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\metrics\\_ranking.py:821\u001b[0m, in \u001b[0;36m_binary_clf_curve\u001b[1;34m(y_true, y_score, pos_label, sample_weight)\u001b[0m\n\u001b[0;32m 818\u001b[0m y_score \u001b[39m=\u001b[39m y_score[nonzero_weight_mask]\n\u001b[0;32m 819\u001b[0m sample_weight \u001b[39m=\u001b[39m sample_weight[nonzero_weight_mask]\n\u001b[1;32m--> 821\u001b[0m pos_label \u001b[39m=\u001b[39m _check_pos_label_consistency(pos_label, y_true)\n\u001b[0;32m 823\u001b[0m \u001b[39m# make y_true a boolean vector\u001b[39;00m\n\u001b[0;32m 824\u001b[0m y_true \u001b[39m=\u001b[39m y_true \u001b[39m==\u001b[39m pos_label\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\venv\\Lib\\site-packages\\sklearn\\utils\\validation.py:2245\u001b[0m, in \u001b[0;36m_check_pos_label_consistency\u001b[1;34m(pos_label, y_true)\u001b[0m\n\u001b[0;32m 2234\u001b[0m \u001b[39mif\u001b[39;00m pos_label \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m \u001b[39mand\u001b[39;00m (\n\u001b[0;32m 2235\u001b[0m classes\u001b[39m.\u001b[39mdtype\u001b[39m.\u001b[39mkind \u001b[39min\u001b[39;00m \u001b[39m\"\u001b[39m\u001b[39mOUS\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 2236\u001b[0m \u001b[39mor\u001b[39;00m \u001b[39mnot\u001b[39;00m (\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 2242\u001b[0m )\n\u001b[0;32m 2243\u001b[0m ):\n\u001b[0;32m 2244\u001b[0m classes_repr \u001b[39m=\u001b[39m \u001b[39m\"\u001b[39m\u001b[39m, \u001b[39m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mjoin([\u001b[39mrepr\u001b[39m(c) \u001b[39mfor\u001b[39;00m c \u001b[39min\u001b[39;00m classes\u001b[39m.\u001b[39mtolist()])\n\u001b[1;32m-> 2245\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mValueError\u001b[39;00m(\n\u001b[0;32m 2246\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39my_true takes value in \u001b[39m\u001b[39m{{\u001b[39;00m\u001b[39m{\u001b[39;00mclasses_repr\u001b[39m}\u001b[39;00m\u001b[39m}}\u001b[39;00m\u001b[39m and pos_label is not \u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 2247\u001b[0m \u001b[39m\"\u001b[39m\u001b[39mspecified: either make y_true take value in \u001b[39m\u001b[39m{\u001b[39m\u001b[39m0, 1} or \u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 2248\u001b[0m \u001b[39m\"\u001b[39m\u001b[39m{\u001b[39m\u001b[39m-1, 1} or pass pos_label explicitly.\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 2249\u001b[0m )\n\u001b[0;32m 2250\u001b[0m \u001b[39melif\u001b[39;00m pos_label \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m 2251\u001b[0m pos_label \u001b[39m=\u001b[39m \u001b[39m1\u001b[39m\n", + "\u001b[1;31mValueError\u001b[0m: y_true takes value in {} and pos_label is not specified: either make y_true take value in {0, 1} or {-1, 1} or pass pos_label explicitly." ] - }, - { - "data": { - "image/png": "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smX/N9by4YcfpkWLFrRo0YJHHnlk7/H777+fpk2bcsYZZ7BixYq9x7/66iu6dOlC27ZtOemkk/bGHYuFCxfSvn172rRpQ/v27fdeN/pnBjjnnHOYNWsWAG+88QaZmZm0atWK008/PebPyo9zjnfeeYfzzz8fgH79+jFlypT9zlu+fDnZ2dn87ne/A/yfXaVKlQDfJP7zzz8DsHXrVurWrbv3nNzm8m3btqnpXCSFxHN0+nhgJPBMAa+fBTSJPNoBj0e+lsg9r3zG8m9/Lull9tG8blXuPveYYr3n8MMPJycnhw0bNjB16lQOOeQQFi1axM6dO+nQoQNnnnkmK1euZOXKlSxcuBDnHN26dWP27Nk0bNiQFStW8NRTT9GhQweuvPJKRo0axc0331zk57711lv5XvPkk09m3Lhx1KhRg+3bt3PcccfRq1cvatasybZt22jRogX33nvv3uvUqlWLjz76iFGjRvHQQw8xduzY/T7ru+++4/333+eLL76gW7dunH/++UyaNIm1a9fyySefsGHDBo4++miuvPLKfGNt0qQJU6dOZcuWLTz//PNccsklvP766wB8+OGH/POf/2TBggU452jXrh2nnHIKOTk5TJw4kSVLlpCdnU1mZiZt27YFYMCAAYwePZomTZqwYMECrrnmGt55552Y/ryaNWvG7NmzKVu2LDNnzuQPf/gDL7/8coHnb9y4kauvvprZs2fTuHFjNm/evN85K1asoHfv3vm+f9asWVSrVm3v802bNlGtWrW9v8TUr1+fb775Zr/3ffnll1SrVo2ePXuyZs0azjjjDIYPH05GRgZjx46la9euVKxYkapVqzJ//vy975s8eTK33347GzZs4LXXXovpnohI8otbEnfOzTazRoWc0h14xjnngPlmVs3MDnXOfRevmBLN/2g+sX788cd7K9KtW7eycuVK3nrrLd566y3atGkDwH//+19WrlxJw4YNadCgAR06dADgkksuYcSIETEn8fyuefLJJzNixAgmT54MwNdff83KlSupWbMmGRkZ9OrVa5/r9OzZE4C2bdsyadKkfD/rvPPOo0yZMjRv3pwffvgBgPfff58LLriAMmXK8Nvf/pZOnToVGm/Pnj2ZOHEiCxYs2KeF4v3336dHjx4cfPDBe8+bM2cOOTk59OjRY2/12a1bt70/59y5c7ngggv2XmPnzp1F3q9cW7dupV+/fqxcuRIzY/fu3YWeP3/+fE4++eS9U7Jq1Kix3zlNmzZl6dKlMX1+7t+VaPlVzNnZ2cyZM4clS5bQsGFDevfuzfjx47nqqqv4+9//zvTp02nXrh0PPvggN910095fvnr06EGPHj2YPXs2d955JzNnzowpLhEpnnumfAIZZYpd+B2oIOeJ1wO+jnq+PnJsvyRuZgOAAQANGzYs9KKJunFFWb16NRkZGdSpUwfnHI8++iidO3fe55w333yT22+/nYEDB+5zfO3atfv9Bx5rE6hzLt9rzpo1i5kzZzJv3jwqVarEqaeeundKUYUKFcjIyNjn/IMOOgjwA/ays7Pz/azcc3I/N/prrPr06UNmZib9+vWjTJn/9e4Udp387kVOTg7VqlWLOWnmdeedd9KpUycmT57M2rVrOfXUUwEoW7bsPgMUc++Zc67IP5PiVOK1atXip59+Ijs7m7Jly7J+/fq9zeHR6tevT5s2bTj88MMB/4vU/Pnz6datG8uWLaNdO9+Y1bt3b7p06bLf+08++WS++uorfvzxR2rVqlVo/CJSTJ99xvJX34VGjSANknh+/wPm+z+3c24MMAYgKyureFkiABs3bmTQoEEMGTIEM6Nz5848/vjjnHbaaZQrV44vv/ySevXq0blzZ+68804uvvhiKleuzDfffLN3xax169Yxb948TjzxRJ5//nk6duwY02cXdM2tW7dSvXp1KlWqxBdffLFPU2tp6tixI08//TT9+vVj48aNzJo1i759+xZ4fsOGDbn//vs544wz9jl+8sknc/nll3PbbbfhnGPy5Mn861//wjm393h2djavvPIKAwcOpGrVqjRu3JgXX3yRCy64AOccH3/8Ma1atdrnuiNHjgTYp58bfCWeO6hu/Pjxe483atSIUaNGkZOTwzfffMPChQsBOPHEE7n22mtZs2bN3ub0vNV4cSpxM6NTp0689NJL9OnTh6effpru3bvvd95xxx3Hli1b2LhxI7Vr1+add94hKyuL6tWrs3XrVr788kuOOuooZsyYwdFHHw3AqlWrOOKIIzAzPvroI3bt2kXNmjVjikskHU1YsI6pS/fvzirS9l9ZXrUuzRO48mGQSXw90CDqeX3g24BiKbHt27fTunVrdu/eTdmyZbn00ku56aabAOjfvz9r164lMzMT5xy1a9dmypQpnHnmmXz+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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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+OM52//+979Tp04dLrjgAlavXp2z/euvv+bCCy+kadOmtG3bNqfucCxYsIBWrVrRpEkTWrVqlXPc3D8zwMUXX8zMmTMBeO+998jIyCA9PZ3zzz8/7M/Kj3OOGTNm0KtXLwD69euX08IuyIQJE+jUqRPly5cHoEmTJuQ3JfHKlStz6qtbty4bNmxg8+bNR1WviMSGSI5OHweMAl4s4PVOQO3QowXwTOjrUbn/rRWs/O7noz3MIepVq8h9l9Qv1ntOO+00srOz2bJlC1OmTOGEE05g4cKF7N27l9atW9OhQwfWrFnDmjVrWLBgAc45unTpwqxZs6hZsyarV6/mueeeo3Xr1lx77bU8/fTT3HbbbUV+7gcffJDvMdu1a8fYsWM56aST2L17N2eddRY9e/akcuXK7Nq1iwYNGvDAAw/kHKdKlSp8/vnnPP300zz66KM8++yzh33W999/z6effsqqVavo0qULvXr1YuLEiWzYsIHly5ezZcsWzjzzTK699tp8a61duzZTpkxhx44dvPrqq1x55ZW8++67ACxevJjnn3+e+fPn45yjRYsWnHPOOWRnZzN+/HiWLFlCVlYWGRkZNG3aFIABAwYwevRoateuzfz58xk8eDAzZswI679X3bp1mTVrFqVLl2b69OncfffdvPnmmwXuv3XrVq6//npmzZpFWloa27dvP2yf1atX07t373zfP3PmTCpVqpTzfNu2bVSqVCnnj5gaNWrw7bffFlrz+PHjufXWW4v82dLT05k4cSJt2rRhwYIF/Oc//2HTpk387ne/K/K9IhLbIhbizrlZZpZayC5dgRedcw6YZ2aVzKyqc+77SNUUbf5H88G6bNmynBbpzp07WbNmDR988AEffPABTZo0AeDXX39lzZo11KxZk1NPPZXWrVsDcOWVVzJy5MiwQzy/Y7Zr146RI0cyadIkADZu3MiaNWuoXLkyKSkp9OzZ85Dj9OjRA4CmTZsyceLEfD+rW7dulCpVinr16uW07D799FMuvfRSSpUqxe9//3vat29faL09evRg/PjxzJ8//5Aeik8//ZTu3btz3HHH5ew3e/ZssrOz6d69e07rs0uXLjk/55w5c7j00ktzjrF3794iz9dBO3fupF+/fqxZswYzY//+/YXuP2/ePNq1a5dzS9ZJJ5102D516tQhMzMzrM8/+P9KboUNNvv+++9Z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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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d999d8jbifpCh45Mhn5hYaFUfUIsTms/dukP105F+5d5Jj4bQDXn/DTnvAvANgDLhnjd/wawA0BAi01fu3ZNnEMCALBp0ybVFoyCc+6uaWFhYVjbifrC6R2ZDP39+/dL1SfE4rT2Y5d+IO1UpH+ZIT4OwHmP7y8MPOaGMTYOwEMAnpTogyBsw+Vy4Y9//CO6PEaRh7udqC+c3JFFgj7hH93fX130YwR68maobZi891f8NYBvc857/e3axBjbAGADAGRmZuKpp54CAMyePRvZ2dl46aWXAADjx4/HwoULsXHjRgBAXFwc1q1bh507d7p3lVqxYgWqq6vdK2rNmTMHKSkp7tHEkyZNwrx589xnUklJSXjkkUewfft2NDQ0AABWrVqFyspKHD9+HAAwf/58xMTEuP8Cmzx5MoqLi7F161YAQGpqKlavXo1t27ahZeDNWrt2LSoqKnDy5EkAwIIFC9DT04M33ngDQP8ZXFFREZ5//nlYv/fKlSuxZcsW96p169atQ3l5OU6fPg0AWLRoEVpbW/H2228DAKZNm4a8vDzs2LEDAJCTk4Ply5dj06ZN7pBZv3492tra3DVdvHgx6urqcOjQIQDAzJkzkZubixdeeAEAMHbsWCxZsgQbN25EX18foqKisH79euzevds993nZsqEuuDibLz34IEZmZmJOSQkqDh9GR0cHAGB2cTFqzp51t59bpkxBb18fqqqqAABjx4zB2LFjcfjIESA6Gknjx4PFxqLb5cK31q4FAEyfMQPvf/AButrbERsbi6kFBejo7HS/b7m5ucgeORJH338fQP8to9unTcPb77yD3t5eAMDcuXNx4sQJfPT++4i97TYUFxejubkZJ0+eREtHB3ZduoR506ahaqBNZmZlYXZxMfbt2+d+nxYuXIhDFRVoqK8HANxRUoL6a9fcS8ROnjwZaWlpqKioAABkZ2dj+vTpaD53Dk8ePoyCsWPxmSVLcPDgQfeWwHPmzMHly5fdq6kVFBQgMTERp06dwp49ezB69GgUFha6Pxvx8fG499578dZbb6G1tRUAMPX227HjtdeQjv5NToqKihAdHe3+jI4bNw75+fm4fv069uzZg6SkJMybNw/l5eXuz8I999yDqqoq917s06ZNQ29vLyorK9HS0YEd587h/pISfOhR47lz5+LAgQOimhDhRXl5OebNm4euujo8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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" } ], "source": [ @@ -2009,7 +2103,9 @@ { "cell_type": "markdown", "id": "driven-factor", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## 10. Running BEHRT\n", "Below we integrate the implementation of BEHRT in our pipeline.\n", @@ -2028,10 +2124,85 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "id": "aggressive-break", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "STARTING READING FILES.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 117/117 [00:03<00:00, 38.60it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FINISHED READING FILES. \n", + "\n", + "STARTING TOKENIZATION.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 117/117 [00:05<00:00, 19.54it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FINISHED TOKENIZATION. \n", + "\n", + "FINAL COHORT STATISTICS: \n", + "7 Positive samples.\n", + "73 Negative samples.\n", + "\n", + "43 Female samples.\n", + "37 Male samples.\n", + "\n", + "56 WHITE samples.\n", + "5 UNKNOWN samples.\n", + "11 BLACK/AFRICAN AMERICAN samples.\n", + "0 BLACK/CAPE VERDEAN samples.\n", + "3 OTHER samples.\n", + "1 PORTUGUESE samples.\n", + "0 HISPANIC/LATINO - PUERTO RICAN samples.\n", + "1 WHITE - BRAZILIAN samples.\n", + "0 HISPANIC OR LATINO samples.\n", + "2 UNABLE TO OBTAIN samples.\n", + "0 WHITE - OTHER EUROPEAN samples.\n", + "1 HISPANIC/LATINO - SALVADORAN samples.\n", + "\n", + "\n", + "48 Other samples.\n", + "27 Medicare samples.\n", + "5 Medicaid samples.\n" + ] + }, + { + "ename": "UnboundLocalError", + "evalue": "cannot access local variable 'train' where it is not associated with a value", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mUnboundLocalError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\mainPipeline.ipynb Cell 38\u001b[0m line \u001b[0;36m8\n\u001b[0;32m 5\u001b[0m token\u001b[39m=\u001b[39mtokenization\u001b[39m.\u001b[39mBEHRT_models(data_icu,diag_flag,proc_flag,\u001b[39mFalse\u001b[39;00m,\u001b[39mFalse\u001b[39;00m,med_flag,lab_flag)\n\u001b[0;32m 6\u001b[0m tokenized_src, tokenized_age, tokenized_gender, tokenized_ethni, tokenized_ins, tokenized_labels\u001b[39m=\u001b[39mtoken\u001b[39m.\u001b[39mtokenize()\n\u001b[1;32m----> 8\u001b[0m behrt_train\u001b[39m.\u001b[39;49mtrain_behrt(tokenized_src, tokenized_age, tokenized_gender, tokenized_ethni, tokenized_ins, tokenized_labels)\n", + "File \u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\model\\behrt_train.py:133\u001b[0m, in \u001b[0;36mtrain_behrt.__init__\u001b[1;34m(self, src, age, sex, ethni, ins, target_data)\u001b[0m\n\u001b[0;32m 130\u001b[0m ValDset \u001b[39m=\u001b[39m DataLoader(val_data, max_len\u001b[39m=\u001b[39mtrain_params[\u001b[39m'\u001b[39m\u001b[39mmax_len_seq\u001b[39m\u001b[39m'\u001b[39m], code\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mcode\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[0;32m 131\u001b[0m valload \u001b[39m=\u001b[39m torch\u001b[39m.\u001b[39mutils\u001b[39m.\u001b[39mdata\u001b[39m.\u001b[39mDataLoader(dataset\u001b[39m=\u001b[39mValDset, batch_size\u001b[39m=\u001b[39mtrain_params[\u001b[39m'\u001b[39m\u001b[39mbatch_size\u001b[39m\u001b[39m'\u001b[39m], shuffle\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[1;32m--> 133\u001b[0m train_loss, val_loss \u001b[39m=\u001b[39m train(trainload, valload, train_params[\u001b[39m'\u001b[39m\u001b[39mdevice\u001b[39m\u001b[39m'\u001b[39m])\n\u001b[0;32m 135\u001b[0m behrt\u001b[39m.\u001b[39mload_state_dict(torch\u001b[39m.\u001b[39mload(\u001b[39m\"\u001b[39m\u001b[39m./saved_models/checkpoint/behrt\u001b[39m\u001b[39m\"\u001b[39m, map_location\u001b[39m=\u001b[39mtrain_params[\u001b[39m'\u001b[39m\u001b[39mdevice\u001b[39m\u001b[39m'\u001b[39m]))\n\u001b[0;32m 136\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39mLoading succesfull\u001b[39m\u001b[39m\"\u001b[39m)\n", + "\u001b[1;31mUnboundLocalError\u001b[0m: cannot access local variable 'train' where it is not associated with a value" + ] + } + ], "source": [ "if data_icu:\n", " token=tokenization.BEHRT_models(data_icu,diag_flag,proc_flag,out_flag,chart_flag,med_flag,False)\n", @@ -2066,10 +2237,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "id": "streaming-integration", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'device' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32md:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\mainPipeline.ipynb Cell 40\u001b[0m line \u001b[0;36m4\n\u001b[0;32m 2\u001b[0m device\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mcuda:0\u001b[39m\u001b[39m'\u001b[39m\n\u001b[0;32m 3\u001b[0m \u001b[39m#device='cpu'\u001b[39;00m\n\u001b[1;32m----> 4\u001b[0m loss\u001b[39m=\u001b[39mevaluation\u001b[39m.\u001b[39mLoss(device,acc\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,ppv\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,sensi\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,tnr\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,npv\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,auroc\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,aurocPlot\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,auprc\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,auprcPlot\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,callb\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m,callbPlot\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[0;32m 5\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mopen\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m./data/output/outputDict\u001b[39m\u001b[39m\"\u001b[39m, \u001b[39m'\u001b[39m\u001b[39mrb\u001b[39m\u001b[39m'\u001b[39m) \u001b[39mas\u001b[39;00m fp:\n\u001b[0;32m 6\u001b[0m outputDict\u001b[39m=\u001b[39mpickle\u001b[39m.\u001b[39mload(fp)\n", + "\u001b[1;31mNameError\u001b[0m: name 'device' is not defined" + ] + } + ], "source": [ "if torch.cuda.is_available():\n", " device='cuda:0'\n", @@ -2107,10 +2290,151 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "id": "civilian-direction", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 sensitive_attributegrouptptnfpfntprtnrfprfnrprnraccuracy
0ethnicityBLACK/AFRICAN AMERICAN0100nan1.0000000.000000nan0.0000001.0000001.000000
1ethnicityWHITE01030.0000001.0000000.0000001.0000000.0000001.0000000.250000
2genderF02030.0000001.0000000.0000001.0000000.0000001.0000000.400000
3age_binned40-5000030.000000nannan1.0000000.0000001.0000000.000000
4age_binned70-800100nan1.0000000.000000nan0.0000001.0000001.000000
5age_binned80-900100nan1.0000000.000000nan0.0000001.0000001.000000
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fairness.fairness_evaluation(inputFile='outputDict',outputFile='fairnessReport')" ] @@ -2134,10 +2458,81 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "id": "secure-flavor", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BEFORE CALLIBRATION\n", + "BCE Loss: 1.40\n", + "AU-ROC: 0.50\n", + "AU-PRC: 0.80\n", + "AU-PRC Baaseline: 0.60\n", + "Accuracy: 0.40\n", + "Precision: 0.00\n", + "Recall: 0.00\n", + "Specificity: 1.00\n", + "NPV: 0.40\n", + "ECE: 0.15\n", + "MCE: 0.15\n", + "AFTER CALLIBRATION\n", + "BCE Loss: 1.39\n", + "AU-ROC: 0.50\n", + "AU-PRC: 0.80\n", + "AU-PRC Baaseline: 0.60\n", + "Accuracy: 0.40\n", + "Precision: 0.00\n", + "Recall: 0.00\n", + "Specificity: 1.00\n", + "NPV: 0.40\n", + "ECE: 0.10\n", + "MCE: 0.10\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "callibrate_output.callibrate(inputFile='outputDict',outputFile='callibratedResults')" ] @@ -2168,7 +2563,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/mimic_pipeline.ipynb b/mimic_pipeline.ipynb new file mode 100644 index 0000000000..779ed2b896 --- /dev/null +++ b/mimic_pipeline.ipynb @@ -0,0 +1,1647 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as widgets\n", + "from pathlib import Path\n", + "\n", + "from pipeline.cohort_extractor import CohortExtractor\n", + "from pipeline.prediction_task import TargetType, PredictionTask, DiseaseCode\n", + "from pipeline.features_extractor import FeatureExtractor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Welcome to MIMIC-IV Project" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "Path(\"raw_data\").mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This repository explains the steps to download and clean MIMIC-IV dataset for analysis.\n", + "The repository is compatible with MIMIC-IV v2.0\n", + "\n", + "Please go to:\n", + "- https://physionet.org/content/mimiciv/2.0/ \n", + "\n", + "Follow instructions to get access to MIMIC-IV dataset.\n", + "\n", + "\n", + "Save downloaded files in the fikder raw_data\n", + "\n", + "The structure should look like below\n", + "- raw_data/mimiciv_2_0/hosp\n", + "- raw_data/mimiciv_2_0/icu" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. DATA EXTRACTION" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Please select what prediction task you want to perform ?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4380137ad15a41a7bb2c71dab9ebfae8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('Mortality', 'Length of Stay', 'Readmission', 'Phenotype'), value='Mortality')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(\"Please select what prediction task you want to perform ?\")\n", + "task_ratio = widgets.RadioButtons(options=['Mortality','Length of Stay','Readmission','Phenotype'],value='Mortality')\n", + "display(task_ratio)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refining Cohort and Prediction Task Definition\n", + "\n", + "Based on your current selection following block will provide option to further refine prediction task and cohort associated with it:\n", + "\n", + "- First you will refine the prediction task choosing from following options -\n", + " - **Length of Stay** - You can select from two predefined options or enter custom number of days to predict length os stay greater than number of days.\n", + "\n", + " - **Readmission** - You can select from two predefined options or enter custom number of days to predict readmission after \"number of days\" after previous admission.\n", + "\n", + " - **Phenotype Prediction** - You can select from four major chronic diseases to predict its future outcome\n", + "\n", + " - Heart failure\n", + " - CAD (Coronary Artery Disease)\n", + " - CKD (Chronic Kidney Disease)\n", + " - COPD (Chronic obstructive pulmonary disease)\n", + "\n", + "- Second, you will choode whether to perfom above task using ICU or non-ICU admissions data\n", + "\n", + "- Third, you can refine the refine the cohort selection for any of the above choosen prediction tasks by including the admission samples admitted with particular chronic disease - \n", + " - Heart failure\n", + " - CAD (Coronary Artery Disease)\n", + " - CKD (Chronic Kidney Disease)\n", + " - COPD (Chronic obstructive pulmonary disease)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Please select below if you want to work with ICU or Non-ICU data:\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2753dae2c46f49abb5c8623756c319b0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('ICU', 'Non-ICU'), value='ICU')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Please select if you want to perform the chosen prediction task for a specific disease.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f53d7dd969044115bedd712be0ba9a5f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('No Disease Filter', 'Heart Failure', 'CKD', 'CAD', 'COPD'), value='No Disease Filter')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def create_length_of_stay_widgets():\n", + " radio_options = ['Length of Stay ≥ 3', 'Length of Stay ≥ 7', 'Custom']\n", + " radio_input = widgets.RadioButtons(options=radio_options, value='Length of Stay ≥ 3')\n", + " slider = widgets.IntSlider(value=3, min=1, max=10, step=1, continuous_update=False)\n", + " display(radio_input, widgets.HBox([widgets.Label('Length of stay ≥ (days):', layout={'width': '180px'}), slider]))\n", + " return radio_input, slider\n", + "\n", + "def create_readmission_widgets():\n", + " radio_options = ['30 Day Readmission', '60 Day Readmission', '90 Day Readmission', '120 Day Readmission', 'Custom']\n", + " radio_input = widgets.RadioButtons(options=radio_options, value='30 Day Readmission')\n", + " slider = widgets.IntSlider(value=30, min=10, max=150, step=10)\n", + " display(radio_input, widgets.HBox([widgets.Label('Readmission after (days):', layout={'width': '180px'}), slider]))\n", + " return radio_input, slider\n", + "\n", + "def create_phenotype_widgets():\n", + " radio_options = ['Heart Failure in 30 days', 'CAD in 30 days', 'CKD in 30 days', 'COPD in 30 days']\n", + " radio_input = widgets.RadioButtons(options=radio_options, value='Heart Failure in 30 days')\n", + " display(radio_input)\n", + " return radio_input\n", + "\n", + "def create_mortality_widgets():\n", + " radio_input = widgets.RadioButtons(options=['Mortality'], value='Mortality')\n", + " return radio_input\n", + "\n", + "if task_ratio.value != 'Mortality':\n", + " print(\"Please select to precise the prediction task \")\n", + "if task_ratio.value == 'Length of Stay':\n", + " los_radio, los_slider = create_length_of_stay_widgets()\n", + "elif task_ratio.value == 'Readmission':\n", + " readmission_radio, readmission_slider = create_readmission_widgets()\n", + "elif task_ratio.value == 'Phenotype':\n", + " phenotype_radio = create_phenotype_widgets()\n", + "elif task_ratio.value == 'Mortality':\n", + " mortality_radio = create_mortality_widgets()\n", + "\n", + "print(\"Please select below if you want to work with ICU or Non-ICU data:\")\n", + "icu_type_input = widgets.RadioButtons(options=['ICU', 'Non-ICU'], value='ICU')\n", + "display(icu_type_input)\n", + "\n", + "print(\"Please select if you want to perform the chosen prediction task for a specific disease.\")\n", + "disease_filter_input = widgets.RadioButtons(options=['No Disease Filter', 'Heart Failure', 'CKD', 'CAD', 'COPD'], value='No Disease Filter')\n", + "display(disease_filter_input)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def get_time_from_input():\n", + " task_type = task_ratio.value\n", + " if task_type == 'Length of Stay' and los_radio.value == 'Custom':\n", + " return los_slider.value\n", + " elif task_type == 'Readmission' and readmission_radio.value == 'Custom':\n", + " return readmission_slider.value\n", + " elif task_type == 'Readmission':\n", + " return int(readmission_radio.value.split()[0])\n", + " elif task_type == 'Length of Stay':\n", + " return int(los_radio.value.split()[4])\n", + " elif task_type == 'Phenotype':\n", + " return 30\n", + " return 0\n", + "\n", + "def get_disease_label():\n", + " if task_ratio.value != 'Phenotype':\n", + " return None\n", + " task_type = phenotype_radio.value\n", + " disease_mapping = {\n", + " 'Heart Failure in 30 days': DiseaseCode.HEARTH_FAILURE,\n", + " 'CAD in 30 days': DiseaseCode.CAD,\n", + " 'CKD in 30 days': DiseaseCode.CKD,\n", + " 'COPD in 30 days': DiseaseCode.COPD\n", + " }\n", + " return disease_mapping.get(task_type, \"\")\n", + "\n", + "def convert_to_icd_code(disease):\n", + " if (disease==\"Heart Failure\"):\n", + " icd_code=DiseaseCode.HEARTH_FAILURE\n", + " elif (disease==\"CKD\"):\n", + " icd_code=DiseaseCode.CKD\n", + " elif (disease==\"COPD\"):\n", + " icd_code=DiseaseCode.COPD\n", + " elif (disease==\"CAD\"):\n", + " icd_code=DiseaseCode.CAD\n", + " else:\n", + " icd_code=None\n", + " return icd_code \n", + "\n", + "def convert_to_prediction_task(task_text):\n", + " if task_text == 'Length of Stay':\n", + " return TargetType.LOS\n", + " elif task_text == 'Mortality': \n", + " return TargetType.MORTALITY\n", + " else:\n", + " return TargetType.READMISSION" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(, None, None, 0, True)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# DEBUG\n", + "(convert_to_prediction_task(task_ratio.value), \n", + "get_disease_label() if task_ratio.value == 'Phenotype' else None, \n", + "convert_to_icd_code(disease_filter_input.value) ,\n", + "get_time_from_input(), \n", + "(icu_type_input.value==\"ICU\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:===========MIMIC-IV v2.0============\n", + "INFO:root:EXTRACTING FOR: ICU | MORTALITY | 0 |\n", + "INFO:root:[ MORTALITY LABELS FINISHED: 10 Mortality Cases ]\n", + "INFO:root:[SUCCESSFULLY SAVED COHORT DATA]\n" + ] + } + ], + "source": [ + "prediction_task = PredictionTask(\n", + " target_type = convert_to_prediction_task(task_ratio.value), \n", + " disease_readmission= get_disease_label() if task_ratio.value == 'Phenotype' else None, \n", + " disease_selection=convert_to_icd_code(disease_filter_input.value) ,\n", + " nb_days=get_time_from_input(), \n", + " use_icu=(icu_type_input.value==\"ICU\")\n", + ")\n", + "cohort_extractor = CohortExtractor(prediction_task=prediction_task)\n", + "cohort = cohort_extractor.extract()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. FEATURE EXTRACTION\n", + "Features available for ICU data -\n", + "- Diagnosis (https://mimic.mit.edu/docs/iv/modules/hosp/diagnoses_icd/)\n", + "- Procedures (https://mimic.mit.edu/docs/iv/modules/icu/procedureevents/)\n", + "- Medications (https://mimic.mit.edu/docs/iv/modules/icu/inputevents/)\n", + "- Output Events (https://mimic.mit.edu/docs/iv/modules/icu/outputevents/)\n", + "- Chart Events (https://mimic.mit.edu/docs/iv/modules/icu/chartevents/)\n", + "\n", + "Features available for ICU data -\n", + "- Diagnosis (https://mimic.mit.edu/docs/iv/modules/hosp/diagnoses_icd/)\n", + "- Procedures (https://mimic.mit.edu/docs/iv/modules/hosp/procedures_icd/)\n", + "- Medications (https://mimic.mit.edu/docs/iv/modules/hosp/prescriptions/)\n", + "- Lab Events (https://mimic.mit.edu/docs/iv/modules/hosp/labevents/)\n", + "\n", + "All features will be saved in **./preproc_data/features/**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feature Selection\n", + "Which Features you want to include for cohort?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "dafe8aa61f444b1aba9538b6a4553f20", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Checkbox(value=False, description='Diagnosis')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9fb3415c769f4211a8280fdb19fbfb18", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Checkbox(value=False, description='Output Events')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5393df8a9da34b7d8bfea88681bd3b74", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Checkbox(value=False, description='Chart Events(Labs and Vitals)')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4b99ad904e8a4650b8d19a237801bcaf", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Checkbox(value=False, description='Procedures')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c558b62a4d2f49ff9e6c9290c7f9c359", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Checkbox(value=False, description='Medications')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "**Please run below cell to extract selected features**\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:Comm:handle_msg[dafe8aa61f444b1aba9538b6a4553f20]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 25, 713000, tzinfo=tzutc()), 'msg_id': '0676aaaf-830a-4a7d-a54b-b74545f800bd', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '0676aaaf-830a-4a7d-a54b-b74545f800bd', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': 'dafe8aa61f444b1aba9538b6a4553f20', 'data': {'method': 'update', 'state': {'value': True}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[9fb3415c769f4211a8280fdb19fbfb18]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 26, 741000, tzinfo=tzutc()), 'msg_id': '731bbc8f-b3c6-4552-a23e-ecda79ee3a18', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '731bbc8f-b3c6-4552-a23e-ecda79ee3a18', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': '9fb3415c769f4211a8280fdb19fbfb18', 'data': {'method': 'update', 'state': {'value': True}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[5393df8a9da34b7d8bfea88681bd3b74]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 27, 168000, tzinfo=tzutc()), 'msg_id': '8d3a1d76-0688-4e55-b964-5bf7cbbb3a30', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '8d3a1d76-0688-4e55-b964-5bf7cbbb3a30', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': '5393df8a9da34b7d8bfea88681bd3b74', 'data': {'method': 'update', 'state': {'value': True}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[4b99ad904e8a4650b8d19a237801bcaf]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 27, 739000, tzinfo=tzutc()), 'msg_id': '0eea441a-3aaa-4672-98c2-9122d73b851e', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '0eea441a-3aaa-4672-98c2-9122d73b851e', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': '4b99ad904e8a4650b8d19a237801bcaf', 'data': {'method': 'update', 'state': {'value': True}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[c558b62a4d2f49ff9e6c9290c7f9c359]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 28, 631000, tzinfo=tzutc()), 'msg_id': 'bc2a646c-bb26-40dc-af86-87e2db4d4c79', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': 'bc2a646c-bb26-40dc-af86-87e2db4d4c79', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': 'c558b62a4d2f49ff9e6c9290c7f9c359', 'data': {'method': 'update', 'state': {'value': True}, 'buffer_paths': []}}, 'buffers': []})\n" + ] + } + ], + "source": [ + "print(\"Feature Selection\")\n", + "if cohort_extractor.prediction_task.use_icu:\n", + " print(\"Which Features you want to include for cohort?\")\n", + " dia_input = widgets.Checkbox(description='Diagnosis')\n", + " display(dia_input)\n", + " out_input = widgets.Checkbox(description='Output Events')\n", + " display(out_input)\n", + " chart_input = widgets.Checkbox(description='Chart Events(Labs and Vitals)')\n", + " display(chart_input)\n", + " proc_input = widgets.Checkbox(description='Procedures')\n", + " display(proc_input)\n", + " med_input = widgets.Checkbox(description='Medications')\n", + " display(med_input)\n", + "else:\n", + " print(\"Which Features you want to include for cohort?\")\n", + " dia_input = widgets.Checkbox(description='Diagnosis')\n", + " display(dia_input)\n", + " lab_input = widgets.Checkbox(description='Labs')\n", + " display(lab_input)\n", + " proc_input = widgets.Checkbox(description='Procedures')\n", + " display(proc_input)\n", + " med_input = widgets.Checkbox(description='Medications')\n", + " display(med_input)\n", + "print(\"**Please run below cell to extract selected features**\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('cohort_ICU_mortality_0_', True, True, False, True, True, True, True)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#DEBUG\n", + "(\n", + " cohort_extractor.cohort_output,\n", + " prediction_task.use_icu,\n", + " dia_input.value,\n", + " not prediction_task.use_icu and lab_input.value,\n", + " prediction_task.use_icu and chart_input.value,\n", + " med_input.value,\n", + "\n", + " prediction_task.use_icu and out_input.value,\n", + " proc_input.value,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:[EXTRACTING DIAGNOSIS DATA]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:[SUCCESSFULLY SAVED DIAGNOSES DATA]\n", + "INFO:root:[EXTRACTING PROCEDURES DATA]\n", + "INFO:root:# Unique Events: 82\n", + "INFO:root:# Admissions: 138\n", + "INFO:root:Total rows: 1435\n", + "INFO:root:[SUCCESSFULLY SAVED PROCEDURES DATA]\n", + "INFO:root:[EXTRACTING MEDICATIONS DATA]\n", + "INFO:root:Number of unique types of drugs: 76\n", + "INFO:root:Number of admissions: 136\n", + "INFO:root:Total number of rows: 11038\n", + "INFO:root:[SUCCESSFULLY SAVED MEDICATIONS DATA]\n", + "INFO:root:[EXTRACTING OUTPUT EVENTS DATA]\n", + "INFO:root:# Unique Events: 39\n", + "INFO:root:# Admissions: 137\n", + "INFO:root:Total rows: 9362\n", + "INFO:root:[SUCCESSFULLY SAVED OUTPUT EVENTS DATA]\n", + "INFO:root:[EXTRACTING CHART EVENTS DATA]\n", + "1it [00:00, 1.02it/s]\n", + "INFO:root:# Unique Events: 298\n", + "INFO:root:# Admissions: 140\n", + "INFO:root:Total rows: 162571\n", + "INFO:root:# Unique Events: 298\n", + "INFO:root:# Admissions: 140\n", + "INFO:root:Total rows: 162571\n", + "INFO:root:[SUCCESSFULLY SAVED CHART EVENTS DATA]\n" + ] + } + ], + "source": [ + "feature_extractor= FeatureExtractor(\n", + " cohort_output=cohort_extractor.cohort_output,\n", + " use_icu=prediction_task.use_icu,\n", + " for_diagnoses=dia_input.value,\n", + " for_labs= not prediction_task.use_icu and lab_input.value,\n", + " for_output_events= prediction_task.use_icu and out_input.value,\n", + " for_chart_events=prediction_task.use_icu and chart_input.value,\n", + " for_procedures=proc_input.value,\n", + " for_medications= med_input.value,\n", + ")\n", + "\n", + "features = feature_extractor.save_features()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. CLINICAL GROUPING\n", + "Grouping medical codes will reduce dimensional space of features.\n", + "\n", + "Default options selected below will group medical codes to reduce feature dimension space.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to group ICD 10 DIAG codes ?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2910dfea5a8a4e118722900171948a66", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=2, layout=Layout(width='100%'), options=('Keep both ICD-9 and ICD-10 codes', 'Convert ICD-9…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "**Please run below cell to perform feature preprocessing**\n" + ] + } + ], + "source": [ + "if feature_extractor.for_diagnoses:\n", + " print(\"Do you want to group ICD 10 DIAG codes ?\")\n", + " group_dia_icd_input = widgets.RadioButtons(options=['Keep both ICD-9 and ICD-10 codes','Convert ICD-9 to ICD-10 codes','Convert ICD-9 to ICD-10 and group ICD-10 codes'],value='Convert ICD-9 to ICD-10 and group ICD-10 codes',layout={'width': '100%'})\n", + " display(group_dia_icd_input) \n", + "\n", + "if not prediction_task.use_icu:\n", + " if feature_extractor.for_medications:\n", + " print(\"Do you want to group Medication codes to use Non propietary names?\")\n", + " group_med_code_input = widgets.RadioButtons(options=['Yes','No'],value='Yes',layout={'width': '100%'})\n", + " display(group_med_code_input)\n", + " if feature_extractor.for_procedures:\n", + " print(\"Which ICD codes for Procedures you want to keep in data?\")\n", + " group_proc_icd_input = widgets.RadioButtons(options=['ICD-9 and ICD-10','ICD-10'],value='ICD-10',layout={'width': '100%'})\n", + " display(group_proc_icd_input)\n", + "print(\"**Please run below cell to perform feature preprocessing**\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "from pipeline.feature.diagnoses import IcdGroupOption\n", + "\n", + "group_diag_icd = IcdGroupOption.KEEP\n", + "if feature_extractor.for_diagnoses:\n", + " if group_dia_icd_input.value == \"Keep both ICD-9 and ICD-10 codes\":\n", + " group_dia_icd_input = IcdGroupOption.KEEP\n", + " elif group_dia_icd_input.value == \"Convert ICD-9 to ICD-10 codes\":\n", + " group_dia_icd_input = IcdGroupOption.CONVERT\n", + " elif group_dia_icd_input.value == \"Convert ICD-9 to ICD-10 and group ICD-10 codes\":\n", + " group_dia_icd_input = IcdGroupOption.GROUP\n", + "\n", + "\n", + "group_med_code = feature_extractor.for_medications and (not prediction_task.use_icu) and (group_med_code_input.value==\"Yes\")\n", + "keep_proc_icd9 = prediction_task.use_icu or not(feature_extractor.for_procedures and (group_proc_icd_input.value==\"ICD-10\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(,\n", + " True,\n", + " True,\n", + " False,\n", + " False,\n", + " False,\n", + " False)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(group_diag_icd, \n", + "group_med_code,\n", + "keep_proc_icd9,\n", + "False,\n", + "False,\n", + "False,\n", + "False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:[PROCESSING DIAGNOSIS DATA]\n", + "INFO:root:Total number of rows: 2647\n", + "INFO:root:[SUCCESSFULLY SAVED DIAGNOSES DATA]\n" + ] + } + ], + "source": [ + "from pipeline.features_preprocessor import FeaturePreprocessor\n", + "feat_preproc = FeaturePreprocessor(feature_extractor=feature_extractor, \n", + " group_diag_icd=group_diag_icd, \n", + " group_med_code=group_med_code,\n", + " keep_proc_icd9=keep_proc_icd9,\n", + " clean_chart=False,\n", + " impute_outlier_chart=False,\n", + " clean_labs=False,\n", + " impute_labs=False,\n", + " )\n", + "preproc = feat_preproc.preprocess_no_event_features()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(,\n", + " True,\n", + " True,\n", + " False,\n", + " False,\n", + " False,\n", + " False)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(group_diag_icd, group_med_code,keep_proc_icd9,False,False,False,False,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. SUMMARY OF FEATURES\n", + "\n", + "This step will generate summary of all features extracted so far.
\n", + "It will save summary files in **./preproc_data/summary/**
\n", + "- These files provide summary about **mean frequency** of medical codes per admission.
\n", + "- It also provides **total occurrence count** of each medical code.
\n", + "- For labs and chart events it will also provide
**missing %** which tells how many rows for a certain medical code has missing value.\n", + "\n", + "Please use this information to further refine your cohort by selecting
which medical codes in each feature you want to keep and
which codes you would like to remove for downstream analysis tasks.\n", + "\n", + "**Please run below cell to generate summary files**" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "summaries = feat_preproc.save_summaries()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Feature Selection\n", + "\n", + "based on the files generated in previous step and other infromation gathered by you,
\n", + "Please select which medical codes you want to include in this study.\n", + "\n", + "Please run below cell to to select options for which features you want to perform feature selection.\n", + "\n", + "- Select **Yes** if you want to select a subset of medical codes for that feature and
**edit** the corresponding feature file for it.\n", + "- Select **No** if you want to keep all the codes in a feature." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to do Feature Selection for Diagnoses \n", + " (If yes, please edit list of codes in ./data/summary/diag_features.csv)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a7269594cc614e42a5419a18686a8cd5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=1, options=('Yes', 'No'), value='No')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to do Feature Selection for Medications \n", + " (If yes, please edit list of codes in ./data/summary/med_features.csv)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "99ec2392121145e199ee5514747f0e9a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=1, options=('Yes', 'No'), value='No')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to do Feature Selection for Procedures \n", + " (If yes, please edit list of codes in ./data/summary/proc_features.csv)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "97c98f43d03345958b51265d37173fea", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=1, options=('Yes', 'No'), value='No')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to do Feature Selection for Output event \n", + " (If yes, please edit list of codes in ./data/summary/out_features.csv)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "180e3da541a241e3bb680e2096173230", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=1, options=('Yes', 'No'), value='No')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to do Feature Selection for Chart events \n", + " (If yes, please edit list of codes in ./data/summary/chart_features.csv)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b88c35d877374ae6a7ed61d8c58fc55b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=1, options=('Yes', 'No'), value='No')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:Comm:handle_msg[a7269594cc614e42a5419a18686a8cd5]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 51, 698000, tzinfo=tzutc()), 'msg_id': '65b9c316-a36d-4bdf-b1c0-85ea90cb52dc', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '65b9c316-a36d-4bdf-b1c0-85ea90cb52dc', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': 'a7269594cc614e42a5419a18686a8cd5', 'data': {'method': 'update', 'state': {'index': 0}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[99ec2392121145e199ee5514747f0e9a]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 52, 759000, tzinfo=tzutc()), 'msg_id': '6cb9e6ed-a5cf-4c38-beaf-5caec1e950c2', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '6cb9e6ed-a5cf-4c38-beaf-5caec1e950c2', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': '99ec2392121145e199ee5514747f0e9a', 'data': {'method': 'update', 'state': {'index': 0}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[97c98f43d03345958b51265d37173fea]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 53, 682000, tzinfo=tzutc()), 'msg_id': '6bf5c215-49dc-470e-9596-6ac8c782350e', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '6bf5c215-49dc-470e-9596-6ac8c782350e', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': '97c98f43d03345958b51265d37173fea', 'data': {'method': 'update', 'state': {'index': 0}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[180e3da541a241e3bb680e2096173230]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 54, 860000, tzinfo=tzutc()), 'msg_id': '19a3da7e-0a9b-4599-8f01-4ff6464cecdb', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '19a3da7e-0a9b-4599-8f01-4ff6464cecdb', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': '180e3da541a241e3bb680e2096173230', 'data': {'method': 'update', 'state': {'index': 0}, 'buffer_paths': []}}, 'buffers': []})\n", + "DEBUG:Comm:handle_msg[b88c35d877374ae6a7ed61d8c58fc55b]({'header': {'date': datetime.datetime(2023, 12, 11, 15, 33, 55, 463000, tzinfo=tzutc()), 'msg_id': '27b76f35-1cd4-48f8-b45c-ba1c01464494', 'msg_type': 'comm_msg', 'session': 'e8845a06-cb7d-41e1-bf48-372172e275ed', 'username': '6b58c396-60d8-4f33-b913-49b4b6a7dd8b', 'version': '5.2'}, 'msg_id': '27b76f35-1cd4-48f8-b45c-ba1c01464494', 'msg_type': 'comm_msg', 'parent_header': {}, 'metadata': {}, 'content': {'comm_id': 'b88c35d877374ae6a7ed61d8c58fc55b', 'data': {'method': 'update', 'state': {'index': 0}, 'buffer_paths': []}}, 'buffers': []})\n" + ] + } + ], + "source": [ + "if feature_extractor.for_diagnoses:\n", + " print(\"Do you want to do Feature Selection for Diagnoses \\n (If yes, please edit list of codes in ./data/summary/diag_features.csv)\")\n", + " select_dia_input = widgets.RadioButtons(options=['Yes','No'],value='No')\n", + " display(select_dia_input) \n", + "if feature_extractor.for_medications:\n", + " print(\"Do you want to do Feature Selection for Medications \\n (If yes, please edit list of codes in ./data/summary/med_features.csv)\")\n", + " select_med_input = widgets.RadioButtons(options=['Yes','No'],value='No')\n", + " display(select_med_input) \n", + "if feature_extractor.for_procedures:\n", + " print(\"Do you want to do Feature Selection for Procedures \\n (If yes, please edit list of codes in ./data/summary/proc_features.csv)\")\n", + " select_proc_input = widgets.RadioButtons(options=['Yes','No'],value='No')\n", + " display(select_proc_input) \n", + "if prediction_task.use_icu and feature_extractor.for_output_events:\n", + " print(\"Do you want to do Feature Selection for Output event \\n (If yes, please edit list of codes in ./data/summary/out_features.csv)\")\n", + " select_out_input = widgets.RadioButtons(options=['Yes','No'],value='No')\n", + " display(select_out_input) \n", + "if prediction_task.use_icu and feature_extractor.for_chart_events:\n", + " print(\"Do you want to do Feature Selection for Chart events \\n (If yes, please edit list of codes in ./data/summary/chart_features.csv)\")\n", + " select_chart_input = widgets.RadioButtons(options=['Yes','No'],value='No')\n", + " display(select_chart_input) \n", + "if not(prediction_task.use_icu) and feature_extractor.for_labs:\n", + " print(\"Do you want to do Feature Selection for Labs \\n (If yes, please edit list of codes in ./data/summary/lab_features.csv)\")\n", + " select_lab_input = widgets.RadioButtons(options=['Yes','No'],value='No')\n", + " display(select_lab_input) " + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(False, True, True, True, True, False, False)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(prediction_task.use_icu, select_diag, select_med,select_proc, select_lab,select_chart, select_out)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "from pipeline.feature_selector import FeatureSelector\n", + "\n", + "\n", + "select_diag=select_dia_input.value == 'Yes' if feature_extractor.for_diagnoses else False\n", + "select_med=select_med_input.value == 'Yes' if feature_extractor.for_medications else False\n", + "select_proc=select_proc_input.value == 'Yes' if feature_extractor.for_procedures else False\n", + "select_out=select_out_input.value == 'Yes' if prediction_task.use_icu and feature_extractor.for_output_events else False\n", + "select_chart=select_chart_input.value == 'Yes' if prediction_task.use_icu and feature_extractor.for_chart_events else False\n", + "select_lab=select_lab_input.value == 'Yes' if not (prediction_task.use_icu) and feature_extractor.for_labs else False\n", + "\n", + "feature_selector = FeatureSelector(prediction_task.use_icu, select_diag, select_med,select_proc, select_lab,select_chart, select_out)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. CLEANING OF FEATURES\n", + "Below you will have option to to clean lab and chart events by performing outlier removal and unit conversion.\n", + "\n", + "Outlier removal is performed to remove values higher than selected **right threshold** percentile and lower than selected **left threshold** percentile among all values for each itemid. \n", + "\n", + "**Please run below cell to select preprocessing for diferent features**" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Outlier removal in values of chart events ?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "25076133c50e48afbfe25fc07ac0e8db", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(layout=Layout(height='40px', width='100%'), options=('No outlier detection', 'Impute Outlier (def…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6161acba75304c98be6eeda34e3934e2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Label(value='Right Outlier Threshold', layout=Layout(width='150px')), IntSlider(value=98, layou…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3e23f6ab80974a0ba8b8da7853818794", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Label(value='Left Outlier Threshold', layout=Layout(width='150px')), IntSlider(value=0, layout=…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if (prediction_task.use_icu and select_chart) or (not(prediction_task.use_icu) and select_lab):\n", + " event_name = \"chart\" if prediction_task.use_icu else \"lab\"\n", + " print(f\"Outlier removal in values of {event_name} events ?\")\n", + " layout = widgets.Layout(width='100%', height='40px') #set width and height\n", + "\n", + " outlier_input = widgets.RadioButtons(options=['No outlier detection','Impute Outlier (default:98)','Remove outliers (default:98)'],value='No outlier detection',layout=layout)\n", + " display(outlier_input)\n", + " right_outlier=widgets.IntSlider(\n", + " value=98,\n", + " min=90,\n", + " max=99,\n", + " step=1,\n", + " disabled=False,layout={'width': '100%'}\n", + " )\n", + " left_outlier=widgets.IntSlider(\n", + " value=0,\n", + " min=0,\n", + " max=10,\n", + " step=1,\n", + " disabled=False,layout={'width': '100%'}\n", + " )\n", + " display(widgets.HBox([widgets.Label('Right Outlier Threshold',layout={'width': '150px'}), right_outlier]))\n", + " display(widgets.HBox([widgets.Label('Left Outlier Threshold',layout={'width': '150px'}), left_outlier]))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "not(prediction_task.use_icu) and select_lab" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "98" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "right_outlier.value" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "right_thresh=100\n", + "left_thresh = 0\n", + "clean_chart = False\n", + "clean_lab = False\n", + "if (prediction_task.use_icu and select_chart):\n", + " clean_chart=outlier_input.value!='No outlier detection'\n", + " right_thresh = right_outlier.value\n", + " left_thresh = left_outlier.value\n", + "if (not(prediction_task.use_icu) and select_lab):\n", + " clean_lab=outlier_input.value!='No outlier detection'\n", + " right_thresh = right_outlier.value\n", + " left_thresh = left_outlier.value\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(None, False, False, False, False, True, True, 98, 0)" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(None,False,False,clean_chart,clean_chart,clean_lab, clean_lab,right_thresh,left_thresh)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "feat_preproc = FeaturePreprocessor(feature_extractor=feature_extractor, \n", + " group_diag_icd=None,\n", + " group_med_code=False,\n", + " keep_proc_icd9=False,\n", + " \n", + "\n", + " clean_chart=clean_chart,\n", + " impute_outlier_chart = clean_chart,\n", + " clean_labs=clean_lab,\n", + " impute_labs = clean_lab,\n", + "\n", + " thresh = right_thresh,\n", + " left_thresh=left_thresh\n", + " )\n", + "preproc = feat_preproc.preproc_events_features()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Time-Series Representation\n", + "In this section, please choose how you want to process and represent time-series data.\n", + "\n", + "- First option is to select the length of time-series data you want to include for this study. (Default is 72 hours)\n", + "\n", + "- Second option is to select bucket size which tells in what size time windows you want to divide your time-series.
\n", + "For example, if you select **2** bucket size, it wil aggregate data for every 2 hours and
a time-series of length 24 hours will be represented as time-series with 12 time-windows
where data for every 2 hours is agggregated from original raw time-series.\n", + "\n", + "During this step, we will also save the time-series data in data dictionaries in the format that can be directly used for following deep learning analysis.\n", + "\n", + "### Imputation\n", + "You can also choose if you want to impute lab/chart values. The imputation will be done by froward fill and mean or median imputation.
\n", + "Values will be forward fill first and if no value exists for that admission we will use mean or median value for the patient.\n", + "\n", + "The data dictionaries will be saved in **./data/dict/**\n", + "\n", + "Please refer the readme to know the structure of data dictionaries.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prediction_task.target_type" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=======Time-series Data Represenation=======\n", + "Length of data to be included for time-series prediction ?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cd267a86fa524e31a387d158d70173ae", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('First 72 hours', 'First 48 hours', 'First 24 hours', 'Custom'), value='First 72 hours')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6a93b3e471a74c40bd3733aa658f0bd5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Label(value='Fisrt (in hours):', layout=Layout(width='150px')), IntSlider(value=72, description…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "What time bucket size you want to choose ?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "592e8e287214438ba539ed045574b03b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('1 hour', '2 hour', '3 hour', '4 hour', '5 hour', 'Custom'), value='1 hour')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6ddba3d30a92453e9ac4989f4a35336c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Label(value='Bucket Size (in hours):', layout=Layout(width='150px')), IntSlider(value=1, max=6,…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to forward fill and mean or median impute lab/chart values to form continuous data signal?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0a303ac3c4514203bcd9107e717f57c0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('No Imputation', 'forward fill and mean', 'forward fill and median'), value='No Imputati…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "If you have choosen mortality prediction task, then what prediction window length you want to keep?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4ac0e3cd00184c0e8f51d2017695856b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('2 hours', '4 hours', '6 hours', '8 hours', 'Custom'), value='2 hours')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "58f7f0b018aa4a5bb32243376d6a21d7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Label(value='Prediction window (in hours)', layout=Layout(width='180px')), IntSlider(value=2, m…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "**Please run below cell to perform time-series represenation and save in data dictionaries**\n" + ] + } + ], + "source": [ + "print(\"=======Time-series Data Represenation=======\")\n", + "\n", + "print(\"Length of data to be included for time-series prediction ?\")\n", + "if(prediction_task.target_type== TargetType.MORTALITY):\n", + " radio_input8 = widgets.RadioButtons(options=['First 72 hours','First 48 hours','First 24 hours','Custom'],value='First 72 hours')\n", + " display(radio_input8)\n", + " text2=widgets.IntSlider(\n", + " value=72,\n", + " min=24,\n", + " max=72,\n", + " step=1,\n", + " description='Fisrt',\n", + " disabled=False\n", + " )\n", + " display(widgets.HBox([widgets.Label('Fisrt (in hours):',layout={'width': '150px'}), text2]))\n", + "elif(prediction_task.target_type== TargetType.READMISSION):\n", + " radio_input8 = widgets.RadioButtons(options=['Last 72 hours','Last 48 hours','Last 24 hours','Custom'],value='Last 72 hours')\n", + " display(radio_input8)\n", + " text2=widgets.IntSlider(\n", + " value=72,\n", + " min=24,\n", + " max=72,\n", + " step=1,\n", + " description='Last',\n", + " disabled=False\n", + " )\n", + " display(widgets.HBox([widgets.Label('Last (in hours):',layout={'width': '150px'}), text2]))\n", + "elif(prediction_task.target_type== TargetType.LOS):\n", + " radio_input8 = widgets.RadioButtons(options=['First 12 hours','First 24 hours','Custom'],value='First 24 hours')\n", + " display(radio_input8)\n", + " text2=widgets.IntSlider(\n", + " value=72,\n", + " min=12,\n", + " max=72,\n", + " step=1,\n", + " description='First',\n", + " disabled=False\n", + " )\n", + " display(widgets.HBox([widgets.Label('Fisrt (in hours):',layout={'width': '150px'}), text2]))\n", + " \n", + " \n", + "print(\"What time bucket size you want to choose ?\")\n", + "radio_input7 = widgets.RadioButtons(options=['1 hour','2 hour','3 hour','4 hour','5 hour','Custom'],value='1 hour')\n", + "display(radio_input7)\n", + "text1=widgets.IntSlider(\n", + " value=1,\n", + " min=1,\n", + " max=6,\n", + " step=1,\n", + " disabled=False\n", + " )\n", + "#display(text1)\n", + "display(widgets.HBox([widgets.Label('Bucket Size (in hours):',layout={'width': '150px'}), text1]))\n", + "print(\"Do you want to forward fill and mean or median impute lab/chart values to form continuous data signal?\")\n", + "radio_impute = widgets.RadioButtons(options=['No Imputation', 'forward fill and mean','forward fill and median'],value='No Imputation')\n", + "display(radio_impute) \n", + "\n", + "radio_input6 = widgets.RadioButtons(options=['0 hours','2 hours','4 hours','6 hours'],value='0 hours')\n", + "if(prediction_task.target_type== TargetType.MORTALITY):\n", + " print(\"If you have choosen mortality prediction task, then what prediction window length you want to keep?\")\n", + " radio_input6 = widgets.RadioButtons(options=['2 hours','4 hours','6 hours','8 hours','Custom'],value='2 hours')\n", + " display(radio_input6)\n", + " text3=widgets.IntSlider(\n", + " value=2,\n", + " min=2,\n", + " max=8,\n", + " step=1,\n", + " disabled=False\n", + " )\n", + " display(widgets.HBox([widgets.Label('Prediction window (in hours)',layout={'width': '180px'}), text3]))\n", + "print(\"**Please run below cell to perform time-series represenation and save in data dictionaries**\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "if (radio_input6.value=='Custom'):\n", + " predW=int(text3.value)\n", + "else:\n", + " predW=int(radio_input6.value[0].strip())\n", + "if (radio_input7.value=='Custom'):\n", + " bucket=int(text1.value)\n", + "else:\n", + " bucket=int(radio_input7.value[0].strip())\n", + "if (radio_input8.value=='Custom'):\n", + " include=int(text2.value)\n", + "else:\n", + " include=int(radio_input8.value.split()[1])\n", + "if (radio_impute.value=='forward fill and mean'):\n", + " impute='Mean'\n", + "elif (radio_impute.value=='forward fill and median'):\n", + " impute='Median'\n", + "else:\n", + " impute=False\n", + "\n", + "# if data_icu:\n", + "# gen=data_generation_icu.Generator(cohort_output,data_mort,data_admn,data_los,diag_flag,proc_flag,out_flag,chart_flag,med_flag,impute,include,bucket,predW)\n", + "# #gen=data_generation_icu.Generator(cohort_output,data_mort,diag_flag,False,False,chart_flag,False,impute,include,bucket,predW)\n", + "# #if chart_flag:\n", + "# # gen=data_generation_icu.Generator(cohort_output,data_mort,False,False,False,chart_flag,False,impute,include,bucket,predW)\n", + "# else:\n", + "# gen=data_generation.Generator(cohort_output,data_mort,data_admn,data_los,diag_flag,lab_flag,proc_flag,med_flag,impute,include,bucket,predW)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'cohort_Non-ICU_readmission_30_I25'" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cohort_extractor.cohort_output" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "from pipeline.data_generator import DataGenerator\n", + "#DataGenerator()\n", + "\n", + "#cohort = generate_admission_cohort(cohort_extractor.cohort_output)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "from pipeline.file_info.preproc.cohort import COHORT_PATH, CohortHeader, NonIcuCohortHeader\n", + "\n", + "data = pd.read_csv(\n", + " COHORT_PATH / f\"{cohort_extractor.cohort_output}.csv.gz\",\n", + " compression=\"gzip\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Machine Learning Models\n", + "\n", + "Below we provide options to select -\n", + "- Type of machine learning model\n", + "- Wheteher to concatenate or aggregate time-series features.\n", + " For example, if the EHR data has collected value for Blood Pressure for one year over 4 time windows of 3 months each then,\n", + " - **Conactenate** will concatenate all four values resulting in 4 different features for blood pressure,\n", + " - **Aggregate** will aggreagte(mean) over four tiem windows resulting in one feature for blood pressure." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=======Machine :earning Models=======\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "dd967624cad24063913264116696f534", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=2, options=('Logistic Regression', 'Random Forest', 'Gradient Bossting', 'Xgboost'), value=…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you wnat to conactenate the time-series feature\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b246493882164e79acdd545e94a40342", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('Conactenate', 'Aggregate'), value='Conactenate')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Please select below option for cross-validation\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "71e68d480e9742deacce186515771a75", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(index=1, options=('No CV', '5-fold CV', '10-fold CV'), value='5-fold CV')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Do you want to do oversampling for minority calss ?\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "545a474ddd31435392b099cfb0420b17", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RadioButtons(options=('True', 'False'), value='True')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(\"=======Machine :earning Models=======\")\n", + "radio_input5 = widgets.RadioButtons(options=['Logistic Regression','Random Forest','Gradient Bossting','Xgboost'],value='Gradient Bossting')\n", + "display(radio_input5)\n", + "print(\"Do you wnat to conactenate the time-series feature\")\n", + "radio_input6 = widgets.RadioButtons(options=['Conactenate','Aggregate'],value='Conactenate')\n", + "display(radio_input6)\n", + "print(\"Please select below option for cross-validation\")\n", + "radio_input7 = widgets.RadioButtons(options=['No CV','5-fold CV','10-fold CV'],value='5-fold CV')\n", + "display(radio_input7)\n", + "print(\"Do you want to do oversampling for minority calss ?\")\n", + "radio_input8 = widgets.RadioButtons(options=['True','False'],value='True')\n", + "display(radio_input8)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/model/data_generation.py b/model/data_generation.py index 5f1a55c2bb..3461d336dc 100644 --- a/model/data_generation.py +++ b/model/data_generation.py @@ -7,23 +7,44 @@ import os import sys from pathlib import Path -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + './../..') + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "./../..") if not os.path.exists("./data/dict"): os.makedirs("./data/dict") - -class Generator(): - def __init__(self,cohort_output,if_mort,if_admn,if_los,feat_cond,feat_lab,feat_proc,feat_med,impute,include_time=24,bucket=1,predW=0): - self.impute=impute - self.feat_cond,self.feat_proc,self.feat_med,self.feat_lab = feat_cond,feat_proc,feat_med,feat_lab - self.cohort_output=cohort_output - + + +class Generator: + def __init__( + self, + cohort_output, + if_mort, + if_admn, + if_los, + feat_cond, + feat_lab, + feat_proc, + feat_med, + impute, + include_time=24, + bucket=1, + predW=0, + ): + self.impute = impute + self.feat_cond, self.feat_proc, self.feat_med, self.feat_lab = ( + feat_cond, + feat_proc, + feat_med, + feat_lab, + ) + self.cohort_output = cohort_output + self.data = self.generate_adm() print("[ READ COHORT ]") self.generate_feat() print("[ READ ALL FEATURES ]") if if_mort: print(predW) - self.mortality_length(include_time,predW) + self.mortality_length(include_time, predW) print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") elif if_admn: self.readmission_length(include_time) @@ -32,510 +53,630 @@ def __init__(self,cohort_output,if_mort,if_admn,if_los,feat_cond,feat_lab,feat_p self.los_length(include_time) print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") self.smooth_meds(bucket) - - #if(self.feat_lab): + + # if(self.feat_lab): # print("[ ======READING LABS ]") # nhid=len(self.hids) # for n in range(0,nhids,10000): # self.generate_labs(self.hids[n,n+10000]) print("[ SUCCESSFULLY SAVED DATA DICTIONARIES ]") - + def generate_feat(self): - if(self.feat_cond): + if self.feat_cond: print("[ ======READING DIAGNOSIS ]") self.generate_cond() - if(self.feat_proc): + if self.feat_proc: print("[ ======READING PROCEDURES ]") self.generate_proc() - if(self.feat_med): + if self.feat_med: print("[ ======READING MEDICATIONS ]") self.generate_meds() - if(self.feat_lab): + if self.feat_lab: print("[ ======READING LABS ]") self.generate_labs() - - + def generate_adm(self): - data=pd.read_csv(f"./data/cohort/{self.cohort_output}.csv.gz", compression='gzip', header=0, index_col=None) - data['admittime'] = pd.to_datetime(data['admittime']) - data['dischtime'] = pd.to_datetime(data['dischtime']) - data['los']=pd.to_timedelta(data['dischtime']-data['admittime'],unit='h') - data['los']=data['los'].astype(str) - data[['days', 'dummy','hours']] = data['los'].str.split(' ', -1, expand=True) - data[['hours','min','sec']] = data['hours'].str.split(':', -1, expand=True) - data['los']=pd.to_numeric(data['days'])*24+pd.to_numeric(data['hours']) - data=data.drop(columns=['days', 'dummy','hours','min','sec']) - data=data[data['los']>0] - data['Age']=data['Age'].astype(int) + data = pd.read_csv( + f"./data/cohort/{self.cohort_output}.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + data["admittime"] = pd.to_datetime(data["admittime"]) + data["dischtime"] = pd.to_datetime(data["dischtime"]) + data["los"] = pd.to_timedelta(data["dischtime"] - data["admittime"], unit="h") + data["los"] = data["los"].astype(str) + data[["days", "dummy", "hours"]] = data["los"].str.split(" ", expand=True) + data[["hours", "min", "sec"]] = data["hours"].str.split(":", expand=True) + data["los"] = pd.to_numeric(data["days"]) * 24 + pd.to_numeric(data["hours"]) + data = data.drop(columns=["days", "dummy", "hours", "min", "sec"]) + data = data[data["los"] > 0] + data["Age"] = data["Age"].astype(int) return data - + def generate_cond(self): - cond=pd.read_csv("./data/features/preproc_diag.csv.gz", compression='gzip', header=0, index_col=None) - cond=cond[cond['hadm_id'].isin(self.data['hadm_id'])] - cond_per_adm = cond.groupby('hadm_id').size().max() + cond = pd.read_csv( + "./data/features/preproc_diag.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + cond = cond[cond["hadm_id"].isin(self.data["hadm_id"])] + cond_per_adm = cond.groupby("hadm_id").size().max() self.cond, self.cond_per_adm = cond, cond_per_adm - + def generate_proc(self): - proc=pd.read_csv("./data/features/preproc_proc.csv.gz", compression='gzip', header=0, index_col=None) - proc=proc[proc['hadm_id'].isin(self.data['hadm_id'])] - proc[['start_days', 'dummy','start_hours']] = proc['proc_time_from_admit'].str.split(' ', -1, expand=True) - proc[['start_hours','min','sec']] = proc['start_hours'].str.split(':', -1, expand=True) - proc['start_time']=pd.to_numeric(proc['start_days'])*24+pd.to_numeric(proc['start_hours']) - proc=proc.drop(columns=['start_days', 'dummy','start_hours','min','sec']) - proc=proc[proc['start_time']>=0] - + proc = pd.read_csv( + "./data/features/preproc_proc.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + proc = proc[proc["hadm_id"].isin(self.data["hadm_id"])] + proc[["start_days", "dummy", "start_hours"]] = proc[ + "proc_time_from_admit" + ].str.split(" ", expand=True) + proc[["start_hours", "min", "sec"]] = proc["start_hours"].str.split( + ":", expand=True + ) + proc["start_time"] = pd.to_numeric(proc["start_days"]) * 24 + pd.to_numeric( + proc["start_hours"] + ) + proc = proc.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) + proc = proc[proc["start_time"] >= 0] + ###Remove where event time is after discharge time - proc=pd.merge(proc,self.data[['hadm_id','los']],on='hadm_id',how='left') - proc['sanity']=proc['los']-proc['start_time'] - proc=proc[proc['sanity']>0] - del proc['sanity'] - - self.proc=proc - + proc = pd.merge(proc, self.data[["hadm_id", "los"]], on="hadm_id", how="left") + proc["sanity"] = proc["los"] - proc["start_time"] + proc = proc[proc["sanity"] > 0] + del proc["sanity"] + + self.proc = proc + def generate_labs(self): chunksize = 10000000 - final=pd.DataFrame() - for labs in tqdm(pd.read_csv("./data/features/preproc_labs.csv.gz", compression='gzip', header=0, index_col=None,chunksize=chunksize)): - labs=labs[labs['hadm_id'].isin(self.data['hadm_id'])] - labs[['start_days', 'dummy','start_hours']] = labs['lab_time_from_admit'].str.split(' ', -1, expand=True) - labs[['start_hours','min','sec']] = labs['start_hours'].str.split(':', -1, expand=True) - labs['start_time']=pd.to_numeric(labs['start_days'])*24+pd.to_numeric(labs['start_hours']) - labs=labs.drop(columns=['start_days', 'dummy','start_hours','min','sec']) - labs=labs[labs['start_time']>=0] + final = pd.DataFrame() + for labs in tqdm( + pd.read_csv( + "./data/features/preproc_labs.csv.gz", + compression="gzip", + header=0, + index_col=None, + chunksize=chunksize, + ) + ): + labs = labs[labs["hadm_id"].isin(self.data["hadm_id"])] + labs[["start_days", "dummy", "start_hours"]] = labs[ + "lab_time_from_admit" + ].str.split(" ", expand=True) + labs[["start_hours", "min", "sec"]] = labs["start_hours"].str.split( + ":", expand=True + ) + labs["start_time"] = pd.to_numeric(labs["start_days"]) * 24 + pd.to_numeric( + labs["start_hours"] + ) + labs = labs.drop( + columns=["start_days", "dummy", "start_hours", "min", "sec"] + ) + labs = labs[labs["start_time"] >= 0] ###Remove where event time is after discharge time - labs=pd.merge(labs,self.data[['hadm_id','los']],on='hadm_id',how='left') - labs['sanity']=labs['los']-labs['start_time'] - labs=labs[labs['sanity']>0] - del labs['sanity'] - + labs = pd.merge( + labs, self.data[["hadm_id", "los"]], on="hadm_id", how="left" + ) + labs["sanity"] = labs["los"] - labs["start_time"] + labs = labs[labs["sanity"] > 0] + del labs["sanity"] + if final.empty: - final=labs + final = labs else: - final=final.append(labs, ignore_index=True) + final = pd.concat([final, labs], ignore_index=True) + + self.labs = final - self.labs=final - def generate_meds(self): - meds=pd.read_csv("./data/features/preproc_med.csv.gz", compression='gzip', header=0, index_col=None) - meds[['start_days', 'dummy','start_hours']] = meds['start_hours_from_admit'].str.split(' ', -1, expand=True) - meds[['start_hours','min','sec']] = meds['start_hours'].str.split(':', -1, expand=True) - meds['start_time']=pd.to_numeric(meds['start_days'])*24+pd.to_numeric(meds['start_hours']) - meds[['start_days', 'dummy','start_hours']] = meds['stop_hours_from_admit'].str.split(' ', -1, expand=True) - meds[['start_hours','min','sec']] = meds['start_hours'].str.split(':', -1, expand=True) - meds['stop_time']=pd.to_numeric(meds['start_days'])*24+pd.to_numeric(meds['start_hours']) - meds=meds.drop(columns=['start_days', 'dummy','start_hours','min','sec']) + meds = pd.read_csv( + "./data/features/preproc_med.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + meds[["start_days", "dummy", "start_hours"]] = meds[ + "start_hours_from_admit" + ].str.split(" ", expand=True) + meds[["start_hours", "min", "sec"]] = meds["start_hours"].str.split( + ":", -1, expand=True + ) + meds["start_time"] = pd.to_numeric(meds["start_days"]) * 24 + pd.to_numeric( + meds["start_hours"] + ) + meds[["start_days", "dummy", "start_hours"]] = meds[ + "stop_hours_from_admit" + ].str.split(" ", expand=True) + meds[["start_hours", "min", "sec"]] = meds["start_hours"].str.split( + ":", expand=True + ) + meds["stop_time"] = pd.to_numeric(meds["start_days"]) * 24 + pd.to_numeric( + meds["start_hours"] + ) + meds = meds.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) #####Sanity check - meds['sanity']=meds['stop_time']-meds['start_time'] - meds=meds[meds['sanity']>0] - del meds['sanity'] + meds["sanity"] = meds["stop_time"] - meds["start_time"] + meds = meds[meds["sanity"] > 0] + del meds["sanity"] #####Select hadm_id as in main file - meds=meds[meds['hadm_id'].isin(self.data['hadm_id'])] - meds=pd.merge(meds,self.data[['hadm_id','los']],on='hadm_id',how='left') + meds = meds[meds["hadm_id"].isin(self.data["hadm_id"])] + meds = pd.merge(meds, self.data[["hadm_id", "los"]], on="hadm_id", how="left") #####Remove where start time is after end of visit - meds['sanity']=meds['los']-meds['start_time'] - meds=meds[meds['sanity']>0] - del meds['sanity'] + meds["sanity"] = meds["los"] - meds["start_time"] + meds = meds[meds["sanity"] > 0] + del meds["sanity"] ####Any stop_time after end of visit is set at end of visit - meds.loc[meds['stop_time'] > meds['los'],'stop_time']=meds.loc[meds['stop_time'] > meds['los'],'los'] - del meds['los'] - - meds['dose_val_rx']=meds['dose_val_rx'].apply(pd.to_numeric, errors='coerce') - - - self.meds=meds - - - def mortality_length(self,include_time,predW): - self.los=include_time - self.data=self.data[(self.data['los']>=include_time+predW)] - self.hids=self.data['hadm_id'].unique() - - if(self.feat_cond): - self.cond=self.cond[self.cond['hadm_id'].isin(self.data['hadm_id'])] - - self.data['los']=include_time + meds.loc[meds["stop_time"] > meds["los"], "stop_time"] = meds.loc[ + meds["stop_time"] > meds["los"], "los" + ] + del meds["los"] + + meds["dose_val_rx"] = meds["dose_val_rx"].apply(pd.to_numeric, errors="coerce") + + self.meds = meds + + def mortality_length(self, include_time, predW): + self.los = include_time + self.data = self.data[(self.data["los"] >= include_time + predW)] + self.hids = self.data["hadm_id"].unique() + + if self.feat_cond: + self.cond = self.cond[self.cond["hadm_id"].isin(self.data["hadm_id"])] + + self.data["los"] = include_time ###MEDS - if(self.feat_med): - self.meds=self.meds[self.meds['hadm_id'].isin(self.data['hadm_id'])] - self.meds=self.meds[self.meds['start_time']<=include_time] - self.meds.loc[self.meds.stop_time >include_time, 'stop_time']=include_time - - + if self.feat_med: + self.meds = self.meds[self.meds["hadm_id"].isin(self.data["hadm_id"])] + self.meds = self.meds[self.meds["start_time"] <= include_time] + self.meds.loc[ + self.meds.stop_time > include_time, "stop_time" + ] = include_time + ###PROCS - if(self.feat_proc): - self.proc=self.proc[self.proc['hadm_id'].isin(self.data['hadm_id'])] - self.proc=self.proc[self.proc['start_time']<=include_time] - + if self.feat_proc: + self.proc = self.proc[self.proc["hadm_id"].isin(self.data["hadm_id"])] + self.proc = self.proc[self.proc["start_time"] <= include_time] + ###LAB - if(self.feat_lab): - self.labs=self.labs[self.labs['hadm_id'].isin(self.data['hadm_id'])] - self.labs=self.labs[self.labs['start_time']<=include_time] - - - self.los=include_time - - def los_length(self,include_time): - self.los=include_time - self.data=self.data[(self.data['los']>=include_time)] - self.hids=self.data['hadm_id'].unique() - - if(self.feat_cond): - self.cond=self.cond[self.cond['hadm_id'].isin(self.data['hadm_id'])] - - self.data['los']=include_time + if self.feat_lab: + self.labs = self.labs[self.labs["hadm_id"].isin(self.data["hadm_id"])] + self.labs = self.labs[self.labs["start_time"] <= include_time] + + self.los = include_time + + def los_length(self, include_time): + self.los = include_time + self.data = self.data[(self.data["los"] >= include_time)] + self.hids = self.data["hadm_id"].unique() + + if self.feat_cond: + self.cond = self.cond[self.cond["hadm_id"].isin(self.data["hadm_id"])] + + self.data["los"] = include_time ###MEDS - if(self.feat_med): - self.meds=self.meds[self.meds['hadm_id'].isin(self.data['hadm_id'])] - self.meds=self.meds[self.meds['start_time']<=include_time] - self.meds.loc[self.meds.stop_time >include_time, 'stop_time']=include_time - - + if self.feat_med: + self.meds = self.meds[self.meds["hadm_id"].isin(self.data["hadm_id"])] + self.meds = self.meds[self.meds["start_time"] <= include_time] + self.meds.loc[ + self.meds.stop_time > include_time, "stop_time" + ] = include_time + ###PROCS - if(self.feat_proc): - self.proc=self.proc[self.proc['hadm_id'].isin(self.data['hadm_id'])] - self.proc=self.proc[self.proc['start_time']<=include_time] - + if self.feat_proc: + self.proc = self.proc[self.proc["hadm_id"].isin(self.data["hadm_id"])] + self.proc = self.proc[self.proc["start_time"] <= include_time] + ###LAB - if(self.feat_lab): - self.labs=self.labs[self.labs['hadm_id'].isin(self.data['hadm_id'])] - self.labs=self.labs[self.labs['start_time']<=include_time] - - - #self.los=include_time - - def readmission_length(self,include_time): - self.los=include_time - self.data=self.data[(self.data['los']>=include_time)] - self.hids=self.data['hadm_id'].unique() - if(self.feat_cond): - self.cond=self.cond[self.cond['hadm_id'].isin(self.data['hadm_id'])] - self.data['select_time']=self.data['los']-include_time - self.data['los']=include_time + if self.feat_lab: + self.labs = self.labs[self.labs["hadm_id"].isin(self.data["hadm_id"])] + self.labs = self.labs[self.labs["start_time"] <= include_time] + + # self.los=include_time + + def readmission_length(self, include_time): + self.los = include_time + self.data = self.data[(self.data["los"] >= include_time)] + self.hids = self.data["hadm_id"].unique() + if self.feat_cond: + self.cond = self.cond[self.cond["hadm_id"].isin(self.data["hadm_id"])] + self.data["select_time"] = self.data["los"] - include_time + self.data["los"] = include_time ####Make equal length input time series and remove data for pred window if needed - + ###MEDS - if(self.feat_med): - self.meds=self.meds[self.meds['hadm_id'].isin(self.data['hadm_id'])] - self.meds=pd.merge(self.meds,self.data[['hadm_id','select_time']],on='hadm_id',how='left') - self.meds['stop_time']=self.meds['stop_time']-self.meds['select_time'] - self.meds['start_time']=self.meds['start_time']-self.meds['select_time'] - self.meds=self.meds[self.meds['stop_time']>=0] - self.meds.loc[self.meds.start_time <0, 'start_time']=0 - + if self.feat_med: + self.meds = self.meds[self.meds["hadm_id"].isin(self.data["hadm_id"])] + self.meds = pd.merge( + self.meds, + self.data[["hadm_id", "select_time"]], + on="hadm_id", + how="left", + ) + self.meds["stop_time"] = self.meds["stop_time"] - self.meds["select_time"] + self.meds["start_time"] = self.meds["start_time"] - self.meds["select_time"] + self.meds = self.meds[self.meds["stop_time"] >= 0] + self.meds.loc[self.meds.start_time < 0, "start_time"] = 0 + ###PROCS - if(self.feat_proc): - self.proc=self.proc[self.proc['hadm_id'].isin(self.data['hadm_id'])] - self.proc=pd.merge(self.proc,self.data[['hadm_id','select_time']],on='hadm_id',how='left') - self.proc['start_time']=self.proc['start_time']-self.proc['select_time'] - self.proc=self.proc[self.proc['start_time']>=0] - + if self.feat_proc: + self.proc = self.proc[self.proc["hadm_id"].isin(self.data["hadm_id"])] + self.proc = pd.merge( + self.proc, + self.data[["hadm_id", "select_time"]], + on="hadm_id", + how="left", + ) + self.proc["start_time"] = self.proc["start_time"] - self.proc["select_time"] + self.proc = self.proc[self.proc["start_time"] >= 0] + ###LABS - if(self.feat_lab): - self.labs=self.labs[self.labs['hadm_id'].isin(self.data['hadm_id'])] - self.labs=pd.merge(self.labs,self.data[['hadm_id','select_time']],on='hadm_id',how='left') - self.labs['start_time']=self.labs['start_time']-self.labs['select_time'] - self.labs=self.labs[self.labs['start_time']>=0] - - - def smooth_meds(self,bucket): - final_meds=pd.DataFrame() - final_proc=pd.DataFrame() - final_labs=pd.DataFrame() - - if(self.feat_med): - self.meds=self.meds.sort_values(by=['start_time']) - if(self.feat_proc): - self.proc=self.proc.sort_values(by=['start_time']) - - t=0 - for i in tqdm(range(0,self.los,bucket)): + if self.feat_lab: + self.labs = self.labs[self.labs["hadm_id"].isin(self.data["hadm_id"])] + self.labs = pd.merge( + self.labs, + self.data[["hadm_id", "select_time"]], + on="hadm_id", + how="left", + ) + self.labs["start_time"] = self.labs["start_time"] - self.labs["select_time"] + self.labs = self.labs[self.labs["start_time"] >= 0] + + def smooth_meds(self, bucket): + final_meds = pd.DataFrame() + final_proc = pd.DataFrame() + final_labs = pd.DataFrame() + + if self.feat_med: + self.meds = self.meds.sort_values(by=["start_time"]) + if self.feat_proc: + self.proc = self.proc.sort_values(by=["start_time"]) + + t = 0 + for i in tqdm(range(0, self.los, bucket)): ###MEDS - if(self.feat_med): - sub_meds=self.meds[(self.meds['start_time']>=i) & (self.meds['start_time']= i) + & (self.meds["start_time"] < i + bucket) + ] + .groupby(["hadm_id", "drug_name"]) + .agg( + { + "stop_time": "max", + "subject_id": "max", + "dose_val_rx": np.nanmean, + } + ) + ) + sub_meds = sub_meds.reset_index() + sub_meds["start_time"] = t + sub_meds["stop_time"] = sub_meds["stop_time"] / bucket if final_meds.empty: - final_meds=sub_meds + final_meds = sub_meds else: - final_meds=final_meds.append(sub_meds) - + final_meds = pd.concat([final_meds, sub_meds], ignore_index=True) + + ###PROC - if(self.feat_proc): - sub_proc=self.proc[(self.proc['start_time']>=i) & (self.proc['start_time']= i) + & (self.proc["start_time"] < i + bucket) + ] + .groupby(["hadm_id", "icd_code"]) + .agg({"subject_id": "max"}) + ) + sub_proc = sub_proc.reset_index() + sub_proc["start_time"] = t if final_proc.empty: - final_proc=sub_proc - else: - final_proc=final_proc.append(sub_proc) - + final_proc = sub_proc + else: + final_proc = pd.concat([final_proc, sub_proc], ignore_index=True) + ###LABS - if(self.feat_lab): - sub_labs=self.labs[(self.labs['start_time']>=i) & (self.labs['start_time']= i) + & (self.labs["start_time"] < i + bucket) + ] + .groupby(["hadm_id", "itemid"]) + .agg({"subject_id": "max", "valuenum": np.nanmean}) + ) + sub_labs = sub_labs.reset_index() + sub_labs["start_time"] = t if final_labs.empty: - final_labs=sub_labs - else: - final_labs=final_labs.append(sub_labs) - - t=t+1 - los=int(self.los/bucket) - + final_labs = sub_labs + else: + final_labs = pd.concat([final_labs, sub_labs], ignore_index=True) + + + t = t + 1 + los = int(self.los / bucket) + ###MEDS - if(self.feat_med): - f2_meds=final_meds.groupby(['hadm_id','drug_name']).size() - self.med_per_adm=f2_meds.groupby('hadm_id').sum().reset_index()[0].max() - self.medlength_per_adm=final_meds.groupby('hadm_id').size().max() - + if self.feat_med: + f2_meds = final_meds.groupby(["hadm_id", "drug_name"]).size() + self.med_per_adm = f2_meds.groupby("hadm_id").sum().reset_index()[0].max() + self.medlength_per_adm = final_meds.groupby("hadm_id").size().max() + ###PROC - if(self.feat_proc): - f2_proc=final_proc.groupby(['hadm_id','icd_code']).size() - self.proc_per_adm=f2_proc.groupby('hadm_id').sum().reset_index()[0].max() - self.proclength_per_adm=final_proc.groupby('hadm_id').size().max() - - ###LABS - if(self.feat_lab): - f2_labs=final_labs.groupby(['hadm_id','itemid']).size() - self.labs_per_adm=f2_labs.groupby('hadm_id').sum().reset_index()[0].max() - self.labslength_per_adm=final_labs.groupby('hadm_id').size().max() + if self.feat_proc: + f2_proc = final_proc.groupby(["hadm_id", "icd_code"]).size() + self.proc_per_adm = f2_proc.groupby("hadm_id").sum().reset_index()[0].max() + self.proclength_per_adm = final_proc.groupby("hadm_id").size().max() + + ###LABS + if self.feat_lab: + f2_labs = final_labs.groupby(["hadm_id", "itemid"]).size() + self.labs_per_adm = f2_labs.groupby("hadm_id").sum().reset_index()[0].max() + self.labslength_per_adm = final_labs.groupby("hadm_id").size().max() ###CREATE DICT print("[ PROCESSED TIME SERIES TO EQUAL TIME INTERVAL ]") - self.create_Dict(final_meds,final_proc,final_labs,los) - - - def create_Dict(self,meds,proc,labs,los): + self.create_Dict(final_meds, final_proc, final_labs, los) + + def create_Dict(self, meds, proc, labs, los): print("[ CREATING DATA DICTIONARIES ]") - dataDic={} - labels_csv=pd.DataFrame(columns=['hadm_id','label']) - labels_csv['hadm_id']=pd.Series(self.hids) - labels_csv['label']=0 + dataDic = {} + labels_csv = pd.DataFrame(columns=["hadm_id", "label"]) + labels_csv["hadm_id"] = pd.Series(self.hids) + labels_csv["label"] = 0 for hid in self.hids: - grp=self.data[self.data['hadm_id']==hid] - #print(grp.head()) - #print(grp['gender']) - #print(int(grp['Age'])) - #print(grp['ethnicity'].iloc[0]) - dataDic[hid]={'Cond':{},'Proc':{},'Med':{},'Lab':{},'ethnicity':grp['ethnicity'].iloc[0],'age':int(grp['Age']),'gender':grp['gender'].iloc[0],'label':int(grp['label'])} - labels_csv.loc[labels_csv['hadm_id']==hid,'label']=int(grp['label']) + grp = self.data[self.data["hadm_id"] == hid] + # print(grp.head()) + # print(grp['gender']) + # print(int(grp['Age'])) + # print(grp['ethnicity'].iloc[0]) + dataDic[hid] = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Lab": {}, + "ethnicity": grp["ethnicity"].iloc[0], + "age": int(grp["Age"]), + "gender": grp["gender"].iloc[0], + "label": int(grp["label"]), + } + labels_csv.loc[labels_csv["hadm_id"] == hid, "label"] = int(grp["label"]) for hid in tqdm(self.hids): - grp=self.data[self.data['hadm_id']==hid] - demo_csv=grp[['Age','gender','ethnicity','insurance']] - if not os.path.exists("./data/csv/"+str(hid)): - os.makedirs("./data/csv/"+str(hid)) - demo_csv.to_csv('./data/csv/'+str(hid)+'/demo.csv',index=False) - - dyn_csv=pd.DataFrame() + grp = self.data[self.data["hadm_id"] == hid] + demo_csv = grp[["Age", "gender", "ethnicity", "insurance"]] + if not os.path.exists("./data/csv/" + str(hid)): + os.makedirs("./data/csv/" + str(hid)) + demo_csv.to_csv("./data/csv/" + str(hid) + "/demo.csv", index=False) + + dyn_csv = pd.DataFrame() ###MEDS - if(self.feat_med): - feat=meds['drug_name'].unique() - df2=meds[meds['hadm_id']==hid] - if df2.shape[0]==0: - val=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - val=val.fillna(0) - val.columns=pd.MultiIndex.from_product([["MEDS"], val.columns]) + if self.feat_med: + feat = meds["drug_name"].unique() + df2 = meds[meds["hadm_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) else: - val=df2.pivot_table(index='start_time',columns='drug_name',values='dose_val_rx') - df2=df2.pivot_table(index='start_time',columns='drug_name',values='stop_time') - #print(df2.shape) + val = df2.pivot_table( + index="start_time", columns="drug_name", values="dose_val_rx" + ) + df2 = df2.pivot_table( + index="start_time", columns="drug_name", values="stop_time" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.ffill() - df2=df2.fillna(0) - - val=pd.concat([val, add_df]) - val=val.sort_index() - val=val.ffill() - val=val.fillna(-1) - #print(df2.head()) - df2.iloc[:,0:]=df2.iloc[:,0:].sub(df2.index,0) - df2[df2>0]=1 - df2[df2<0]=0 - val.iloc[:,0:]=df2.iloc[:,0:]*val.iloc[:,0:] - #print(df2.head()) - dataDic[hid]['Med']['signal']=df2.iloc[:,0:].to_dict(orient="list") - dataDic[hid]['Med']['val']=val.iloc[:,0:].to_dict(orient="list") - - - feat_df=pd.DataFrame(columns=list(set(feat)-set(val.columns))) - - val=pd.concat([val,feat_df],axis=1) - - val=val[feat] - val=val.fillna(0) - - val.columns=pd.MultiIndex.from_product([["MEDS"], val.columns]) - if(dyn_csv.empty): - dyn_csv=val + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.ffill() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + val = val.ffill() + val = val.fillna(-1) + # print(df2.head()) + df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + val.iloc[:, 0:] = df2.iloc[:, 0:] * val.iloc[:, 0:] + # print(df2.head()) + dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + dataDic[hid]["Med"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + + val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + if dyn_csv.empty: + dyn_csv = val else: - dyn_csv=pd.concat([dyn_csv,val],axis=1) + dyn_csv = pd.concat([dyn_csv, val], axis=1) - - ###PROCS - if(self.feat_proc): - feat=proc['icd_code'].unique() - df2=proc[proc['hadm_id']==hid] - if df2.shape[0]==0: - df2=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - df2=df2.fillna(0) - df2.columns=pd.MultiIndex.from_product([["PROC"], df2.columns]) + if self.feat_proc: + feat = proc["icd_code"].unique() + df2 = proc[proc["hadm_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) else: - df2['val']=1 - df2=df2.pivot_table(index='start_time',columns='icd_code',values='val') - #print(df2.shape) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="icd_code", values="val" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.fillna(0) - df2[df2>0]=1 - #print(df2.head()) - dataDic[hid]['Proc']=df2.to_dict(orient="list") - - feat_df=pd.DataFrame(columns=list(set(feat)-set(df2.columns))) - df2=pd.concat([df2,feat_df],axis=1) - - df2=df2[feat] - df2=df2.fillna(0) - df2.columns=pd.MultiIndex.from_product([["PROC"], df2.columns]) - - if(dyn_csv.empty): - dyn_csv=df2 + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + # print(df2.head()) + dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 else: - dyn_csv=pd.concat([dyn_csv,df2],axis=1) - + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + ###LABS - if(self.feat_lab): - feat=labs['itemid'].unique() - df2=labs[labs['hadm_id']==hid] - if df2.shape[0]==0: - val=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - val=val.fillna(0) - val.columns=pd.MultiIndex.from_product([["LAB"], val.columns]) + if self.feat_lab: + feat = labs["itemid"].unique() + df2 = labs[labs["hadm_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) else: - val=df2.pivot_table(index='start_time',columns='itemid',values='valuenum') - df2['val']=1 - df2=df2.pivot_table(index='start_time',columns='itemid',values='val') - #print(df2.shape) + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.fillna(0) - - val=pd.concat([val, add_df]) - val=val.sort_index() - if self.impute=='Mean': - val=val.ffill() - val=val.bfill() - val=val.fillna(val.mean()) - elif self.impute=='Median': - val=val.ffill() - val=val.bfill() - val=val.fillna(val.median()) - val=val.fillna(0) - - df2[df2>0]=1 - df2[df2<0]=0 - - #print(df2.head()) - dataDic[hid]['Lab']['signal']=df2.iloc[:,0:].to_dict(orient="list") - dataDic[hid]['Lab']['val']=val.iloc[:,0:].to_dict(orient="list") - - feat_df=pd.DataFrame(columns=list(set(feat)-set(val.columns))) - val=pd.concat([val,feat_df],axis=1) - - val=val[feat] - val=val.fillna(0) - val.columns=pd.MultiIndex.from_product([["LAB"], val.columns]) - - if(dyn_csv.empty): - dyn_csv=val + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + + # print(df2.head()) + dataDic[hid]["Lab"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + dataDic[hid]["Lab"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + + if dyn_csv.empty: + dyn_csv = val else: - dyn_csv=pd.concat([dyn_csv,val],axis=1) - - #Save temporal data to csv - dyn_csv.to_csv('./data/csv/'+str(hid)+'/dynamic.csv',index=False) - + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + # Save temporal data to csv + dyn_csv.to_csv("./data/csv/" + str(hid) + "/dynamic.csv", index=False) + ##########COND######### - if(self.feat_cond): - feat=self.cond['new_icd_code'].unique() - grp=self.cond[self.cond['hadm_id']==hid] - if(grp.shape[0]==0): - dataDic[hid]['Cond']={'fids':list([''])} - feat_df=pd.DataFrame(np.zeros([1,len(feat)]),columns=feat) - grp=feat_df.fillna(0) - grp.columns=pd.MultiIndex.from_product([["COND"], grp.columns]) + if self.feat_cond: + feat = self.cond["new_icd_code"].unique() + grp = self.cond[self.cond["hadm_id"] == hid] + if grp.shape[0] == 0: + dataDic[hid]["Cond"] = {"fids": list([""])} + feat_df = pd.DataFrame(np.zeros([1, len(feat)]), columns=feat) + grp = feat_df.fillna(0) + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) else: - dataDic[hid]['Cond']={'fids':list(grp['new_icd_code'])} - grp['val']=1 - grp=grp.drop_duplicates() - grp=grp.pivot(index='hadm_id',columns='new_icd_code',values='val').reset_index(drop=True) - feat_df=pd.DataFrame(columns=list(set(feat)-set(grp.columns))) - grp=pd.concat([grp,feat_df],axis=1) - grp=grp.fillna(0) - grp=grp[feat] - grp.columns=pd.MultiIndex.from_product([["COND"], grp.columns]) - grp.to_csv('./data/csv/'+str(hid)+'/static.csv',index=False) - labels_csv.to_csv('./data/csv/labels.csv',index=False) - - + dataDic[hid]["Cond"] = {"fids": list(grp["new_icd_code"])} + grp["val"] = 1 + grp = grp.drop_duplicates() + grp = grp.pivot( + index="hadm_id", columns="new_icd_code", values="val" + ).reset_index(drop=True) + feat_df = pd.DataFrame(columns=list(set(feat) - set(grp.columns))) + grp = pd.concat([grp, feat_df], axis=1) + grp = grp.fillna(0) + grp = grp[feat] + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + grp.to_csv("./data/csv/" + str(hid) + "/static.csv", index=False) + labels_csv.to_csv("./data/csv/labels.csv", index=False) + ######SAVE DICTIONARIES############## - metaDic={'Cond':{},'Proc':{},'Med':{},'Lab':{},'LOS':{}} - metaDic['LOS']=los - with open("./data/dict/dataDic", 'wb') as fp: + metaDic = {"Cond": {}, "Proc": {}, "Med": {}, "Lab": {}, "LOS": {}} + metaDic["LOS"] = los + with open("./data/dict/dataDic", "wb") as fp: pickle.dump(dataDic, fp) - with open("./data/dict/hadmDic", 'wb') as fp: + with open("./data/dict/hadmDic", "wb") as fp: pickle.dump(self.hids, fp) - - with open("./data/dict/ethVocab", 'wb') as fp: - pickle.dump(list(self.data['ethnicity'].unique()), fp) - self.eth_vocab = self.data['ethnicity'].nunique() - - with open("./data/dict/ageVocab", 'wb') as fp: - pickle.dump(list(self.data['Age'].unique()), fp) - self.age_vocab = self.data['Age'].nunique() - - with open("./data/dict/insVocab", 'wb') as fp: - pickle.dump(list(self.data['insurance'].unique()), fp) - self.ins_vocab = self.data['insurance'].nunique() - - if(self.feat_med): - with open("./data/dict/medVocab", 'wb') as fp: - pickle.dump(list(meds['drug_name'].unique()), fp) - self.med_vocab = meds['drug_name'].nunique() - metaDic['Med']=self.med_per_adm - - if(self.feat_cond): - with open("./data/dict/condVocab", 'wb') as fp: - pickle.dump(list(self.cond['new_icd_code'].unique()), fp) - self.cond_vocab = self.cond['new_icd_code'].nunique() - metaDic['Cond']=self.cond_per_adm - - if(self.feat_proc): - with open("./data/dict/procVocab", 'wb') as fp: - pickle.dump(list(proc['icd_code'].unique()), fp) - self.proc_vocab = proc['icd_code'].unique() - metaDic['Proc']=self.proc_per_adm - - if(self.feat_lab): - with open("./data/dict/labsVocab", 'wb') as fp: - pickle.dump(list(labs['itemid'].unique()), fp) - self.lab_vocab = labs['itemid'].unique() - metaDic['Lab']=self.labs_per_adm - - with open("./data/dict/metaDic", 'wb') as fp: - pickle.dump(metaDic, fp) - + with open("./data/dict/ethVocab", "wb") as fp: + pickle.dump(list(self.data["ethnicity"].unique()), fp) + self.eth_vocab = self.data["ethnicity"].nunique() + + with open("./data/dict/ageVocab", "wb") as fp: + pickle.dump(list(self.data["Age"].unique()), fp) + self.age_vocab = self.data["Age"].nunique() + with open("./data/dict/insVocab", "wb") as fp: + pickle.dump(list(self.data["insurance"].unique()), fp) + self.ins_vocab = self.data["insurance"].nunique() + if self.feat_med: + with open("./data/dict/medVocab", "wb") as fp: + pickle.dump(list(meds["drug_name"].unique()), fp) + self.med_vocab = meds["drug_name"].nunique() + metaDic["Med"] = self.med_per_adm + + if self.feat_cond: + with open("./data/dict/condVocab", "wb") as fp: + pickle.dump(list(self.cond["new_icd_code"].unique()), fp) + self.cond_vocab = self.cond["new_icd_code"].nunique() + metaDic["Cond"] = self.cond_per_adm + + if self.feat_proc: + with open("./data/dict/procVocab", "wb") as fp: + pickle.dump(list(proc["icd_code"].unique()), fp) + self.proc_vocab = proc["icd_code"].unique() + metaDic["Proc"] = self.proc_per_adm + + if self.feat_lab: + with open("./data/dict/labsVocab", "wb") as fp: + pickle.dump(list(labs["itemid"].unique()), fp) + self.lab_vocab = labs["itemid"].unique() + metaDic["Lab"] = self.labs_per_adm + + with open("./data/dict/metaDic", "wb") as fp: + pickle.dump(metaDic, fp) diff --git a/model/data_generation_icu.py b/model/data_generation_icu.py index e9ed83dd0d..14ba0fa1fb 100644 --- a/model/data_generation_icu.py +++ b/model/data_generation_icu.py @@ -8,25 +8,48 @@ import os import sys from pathlib import Path -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + './../..') + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "./../..") if not os.path.exists("./data/dict"): os.makedirs("./data/dict") if not os.path.exists("./data/csv"): os.makedirs("./data/csv") - -class Generator(): - def __init__(self,cohort_output,if_mort,if_admn,if_los,feat_cond,feat_proc,feat_out,feat_chart,feat_med,impute,include_time=24,bucket=1,predW=6): - self.feat_cond,self.feat_proc,self.feat_out,self.feat_chart,self.feat_med = feat_cond,feat_proc,feat_out,feat_chart,feat_med - self.cohort_output=cohort_output - self.impute=impute + + +class Generator: + def __init__( + self, + cohort_output, + if_mort, + if_admn, + if_los, + feat_cond, + feat_proc, + feat_out, + feat_chart, + feat_med, + impute, + include_time=24, + bucket=1, + predW=6, + ): + ( + self.feat_cond, + self.feat_proc, + self.feat_out, + self.feat_chart, + self.feat_med, + ) = (feat_cond, feat_proc, feat_out, feat_chart, feat_med) + self.cohort_output = cohort_output + self.impute = impute self.data = self.generate_adm() print("[ READ COHORT ]") - + self.generate_feat() print("[ READ ALL FEATURES ]") - + if if_mort: - self.mortality_length(include_time,predW) + self.mortality_length(include_time, predW) print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") elif if_admn: self.readmission_length(include_time) @@ -34,701 +57,863 @@ def __init__(self,cohort_output,if_mort,if_admn,if_los,feat_cond,feat_proc,feat_ elif if_los: self.los_length(include_time) print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") - + self.smooth_meds(bucket) print("[ SUCCESSFULLY SAVED DATA DICTIONARIES ]") - + def generate_feat(self): - if(self.feat_cond): + if self.feat_cond: print("[ ======READING DIAGNOSIS ]") self.generate_cond() - if(self.feat_proc): + if self.feat_proc: print("[ ======READING PROCEDURES ]") self.generate_proc() - if(self.feat_out): + if self.feat_out: print("[ ======READING OUT EVENTS ]") self.generate_out() - if(self.feat_chart): + if self.feat_chart: print("[ ======READING CHART EVENTS ]") self.generate_chart() - if(self.feat_med): + if self.feat_med: print("[ ======READING MEDICATIONS ]") self.generate_meds() + breakpoint() def generate_adm(self): - data=pd.read_csv(f"./data/cohort/{self.cohort_output}.csv.gz", compression='gzip', header=0, index_col=None) - data['intime'] = pd.to_datetime(data['intime']) - data['outtime'] = pd.to_datetime(data['outtime']) - data['los']=pd.to_timedelta(data['outtime']-data['intime'],unit='h') - data['los']=data['los'].astype(str) - data[['days', 'dummy','hours']] = data['los'].str.split(' ', -1, expand=True) - data[['hours','min','sec']] = data['hours'].str.split(':', -1, expand=True) - data['los']=pd.to_numeric(data['days'])*24+pd.to_numeric(data['hours']) - data=data.drop(columns=['days', 'dummy','hours','min','sec']) - data=data[data['los']>0] - data['Age']=data['Age'].astype(int) - #print(data.head()) - #print(data.shape) + data = pd.read_csv( + f"./data/cohort/{self.cohort_output}.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + data["intime"] = pd.to_datetime(data["intime"]) + data["outtime"] = pd.to_datetime(data["outtime"]) + data["los"] = pd.to_timedelta(data["outtime"] - data["intime"], unit="h") + data["los"] = data["los"].astype(str) + data[["days", "dummy", "hours"]] = data["los"].str.split(" ", expand=True) + data[["hours", "min", "sec"]] = data["hours"].str.split(":", expand=True) + data["los"] = pd.to_numeric(data["days"]) * 24 + pd.to_numeric(data["hours"]) + data = data.drop(columns=["days", "dummy", "hours", "min", "sec"]) + data = data[data["los"] > 0] + data["Age"] = data["Age"].astype(int) + # print(data.head()) + # print(data.shape) return data - + def generate_cond(self): - cond=pd.read_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip', header=0, index_col=None) - cond=cond[cond['stay_id'].isin(self.data['stay_id'])] - cond_per_adm = cond.groupby('stay_id').size().max() + cond = pd.read_csv( + "./data/features/preproc_diag_icu.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + cond = cond[cond["stay_id"].isin(self.data["stay_id"])] + cond_per_adm = cond.groupby("stay_id").size().max() self.cond, self.cond_per_adm = cond, cond_per_adm - + def generate_proc(self): - proc=pd.read_csv("./data/features/preproc_proc_icu.csv.gz", compression='gzip', header=0, index_col=None) - proc=proc[proc['stay_id'].isin(self.data['stay_id'])] - proc[['start_days', 'dummy','start_hours']] = proc['event_time_from_admit'].str.split(' ', -1, expand=True) - proc[['start_hours','min','sec']] = proc['start_hours'].str.split(':', -1, expand=True) - proc['start_time']=pd.to_numeric(proc['start_days'])*24+pd.to_numeric(proc['start_hours']) - proc=proc.drop(columns=['start_days', 'dummy','start_hours','min','sec']) - proc=proc[proc['start_time']>=0] - + proc = pd.read_csv( + "./data/features/preproc_proc_icu.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + proc = proc[proc["stay_id"].isin(self.data["stay_id"])] + proc[["start_days", "dummy", "start_hours"]] = proc[ + "event_time_from_admit" + ].str.split(" ", expand=True) + proc[["start_hours", "min", "sec"]] = proc["start_hours"].str.split( + ":", expand=True + ) + proc["start_time"] = pd.to_numeric(proc["start_days"]) * 24 + pd.to_numeric( + proc["start_hours"] + ) + proc = proc.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) + proc = proc[proc["start_time"] >= 0] + ###Remove where event time is after discharge time - proc=pd.merge(proc,self.data[['stay_id','los']],on='stay_id',how='left') - proc['sanity']=proc['los']-proc['start_time'] - proc=proc[proc['sanity']>0] - del proc['sanity'] - - self.proc=proc - + proc = pd.merge(proc, self.data[["stay_id", "los"]], on="stay_id", how="left") + proc["sanity"] = proc["los"] - proc["start_time"] + proc = proc[proc["sanity"] > 0] + del proc["sanity"] + + self.proc = proc + def generate_out(self): - out=pd.read_csv("./data/features/preproc_out_icu.csv.gz", compression='gzip', header=0, index_col=None) - out=out[out['stay_id'].isin(self.data['stay_id'])] - out[['start_days', 'dummy','start_hours']] = out['event_time_from_admit'].str.split(' ', -1, expand=True) - out[['start_hours','min','sec']] = out['start_hours'].str.split(':', -1, expand=True) - out['start_time']=pd.to_numeric(out['start_days'])*24+pd.to_numeric(out['start_hours']) - out=out.drop(columns=['start_days', 'dummy','start_hours','min','sec']) - out=out[out['start_time']>=0] - + out = pd.read_csv( + "./data/features/preproc_out_icu.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + out = out[out["stay_id"].isin(self.data["stay_id"])] + out[["start_days", "dummy", "start_hours"]] = out[ + "event_time_from_admit" + ].str.split(" ", expand=True) + out[["start_hours", "min", "sec"]] = out["start_hours"].str.split( + ":", expand=True + ) + out["start_time"] = pd.to_numeric(out["start_days"]) * 24 + pd.to_numeric( + out["start_hours"] + ) + out = out.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) + out = out[out["start_time"] >= 0] + ###Remove where event time is after discharge time - out=pd.merge(out,self.data[['stay_id','los']],on='stay_id',how='left') - out['sanity']=out['los']-out['start_time'] - out=out[out['sanity']>0] - del out['sanity'] - - self.out=out - - + out = pd.merge(out, self.data[["stay_id", "los"]], on="stay_id", how="left") + out["sanity"] = out["los"] - out["start_time"] + out = out[out["sanity"] > 0] + del out["sanity"] + + self.out = out + def generate_chart(self): chunksize = 5000000 - final=pd.DataFrame() - for chart in tqdm(pd.read_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip', header=0, index_col=None,chunksize=chunksize)): - chart=chart[chart['stay_id'].isin(self.data['stay_id'])] - chart[['start_days', 'dummy','start_hours']] = chart['event_time_from_admit'].str.split(' ', -1, expand=True) - chart[['start_hours','min','sec']] = chart['start_hours'].str.split(':', -1, expand=True) - chart['start_time']=pd.to_numeric(chart['start_days'])*24+pd.to_numeric(chart['start_hours']) - chart=chart.drop(columns=['start_days', 'dummy','start_hours','min','sec','event_time_from_admit']) - chart=chart[chart['start_time']>=0] + final = pd.DataFrame() + for chart in tqdm( + pd.read_csv( + "./data/features/preproc_chart_icu.csv.gz", + compression="gzip", + header=0, + index_col=None, + chunksize=chunksize, + ) + ): + chart = chart[chart["stay_id"].isin(self.data["stay_id"])] + chart[["start_days", "dummy", "start_hours"]] = chart[ + "event_time_from_admit" + ].str.split(" ", expand=True) + chart[["start_hours", "min", "sec"]] = chart["start_hours"].str.split( + ":", expand=True + ) + chart["start_time"] = pd.to_numeric( + chart["start_days"] + ) * 24 + pd.to_numeric(chart["start_hours"]) + chart = chart.drop( + columns=[ + "start_days", + "dummy", + "start_hours", + "min", + "sec", + "event_time_from_admit", + ] + ) + chart = chart[chart["start_time"] >= 0] ###Remove where event time is after discharge time - chart=pd.merge(chart,self.data[['stay_id','los']],on='stay_id',how='left') - chart['sanity']=chart['los']-chart['start_time'] - chart=chart[chart['sanity']>0] - del chart['sanity'] - del chart['los'] - + chart = pd.merge( + chart, self.data[["stay_id", "los"]], on="stay_id", how="left" + ) + chart["sanity"] = chart["los"] - chart["start_time"] + chart = chart[chart["sanity"] > 0] + del chart["sanity"] + del chart["los"] + if final.empty: - final=chart + final = chart else: - final=final.append(chart, ignore_index=True) - - self.chart=final - - - + final = pd.concat([final, chart], ignore_index=True) + + self.chart = final + def generate_meds(self): - meds=pd.read_csv("./data/features/preproc_med_icu.csv.gz", compression='gzip', header=0, index_col=None) - meds[['start_days', 'dummy','start_hours']] = meds['start_hours_from_admit'].str.split(' ', -1, expand=True) - meds[['start_hours','min','sec']] = meds['start_hours'].str.split(':', -1, expand=True) - meds['start_time']=pd.to_numeric(meds['start_days'])*24+pd.to_numeric(meds['start_hours']) - meds[['start_days', 'dummy','start_hours']] = meds['stop_hours_from_admit'].str.split(' ', -1, expand=True) - meds[['start_hours','min','sec']] = meds['start_hours'].str.split(':', -1, expand=True) - meds['stop_time']=pd.to_numeric(meds['start_days'])*24+pd.to_numeric(meds['start_hours']) - meds=meds.drop(columns=['start_days', 'dummy','start_hours','min','sec']) + meds = pd.read_csv( + "./data/features/preproc_med_icu.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + meds[["start_days", "dummy", "start_hours"]] = meds[ + "start_hours_from_admit" + ].str.split(" ", expand=True) + meds[["start_hours", "min", "sec"]] = meds["start_hours"].str.split( + ":", expand=True + ) + meds["start_time"] = pd.to_numeric(meds["start_days"]) * 24 + pd.to_numeric( + meds["start_hours"] + ) + meds[["start_days", "dummy", "start_hours"]] = meds[ + "stop_hours_from_admit" + ].str.split(" ", expand=True) + meds[["start_hours", "min", "sec"]] = meds["start_hours"].str.split( + ":", expand=True + ) + meds["stop_time"] = pd.to_numeric(meds["start_days"]) * 24 + pd.to_numeric( + meds["start_hours"] + ) + meds = meds.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) #####Sanity check - meds['sanity']=meds['stop_time']-meds['start_time'] - meds=meds[meds['sanity']>0] - del meds['sanity'] + meds["sanity"] = meds["stop_time"] - meds["start_time"] + meds = meds[meds["sanity"] > 0] + del meds["sanity"] #####Select hadm_id as in main file - meds=meds[meds['stay_id'].isin(self.data['stay_id'])] - meds=pd.merge(meds,self.data[['stay_id','los']],on='stay_id',how='left') + meds = meds[meds["stay_id"].isin(self.data["stay_id"])] + meds = pd.merge(meds, self.data[["stay_id", "los"]], on="stay_id", how="left") #####Remove where start time is after end of visit - meds['sanity']=meds['los']-meds['start_time'] - meds=meds[meds['sanity']>0] - del meds['sanity'] + meds["sanity"] = meds["los"] - meds["start_time"] + meds = meds[meds["sanity"] > 0] + del meds["sanity"] ####Any stop_time after end of visit is set at end of visit - meds.loc[meds['stop_time'] > meds['los'],'stop_time']=meds.loc[meds['stop_time'] > meds['los'],'los'] - del meds['los'] - - meds['rate']=meds['rate'].apply(pd.to_numeric, errors='coerce') - meds['amount']=meds['amount'].apply(pd.to_numeric, errors='coerce') - - self.meds=meds - - def mortality_length(self,include_time,predW): - print("include_time",include_time) - self.los=include_time - self.data=self.data[(self.data['los']>=include_time+predW)] - self.hids=self.data['stay_id'].unique() - - if(self.feat_cond): - self.cond=self.cond[self.cond['stay_id'].isin(self.data['stay_id'])] - - self.data['los']=include_time + meds.loc[meds["stop_time"] > meds["los"], "stop_time"] = meds.loc[ + meds["stop_time"] > meds["los"], "los" + ] + del meds["los"] + + meds["rate"] = meds["rate"].apply(pd.to_numeric, errors="coerce") + meds["amount"] = meds["amount"].apply(pd.to_numeric, errors="coerce") + + self.meds = meds + + def mortality_length(self, include_time, predW): + print("include_time", include_time) + self.los = include_time + self.data = self.data[(self.data["los"] >= include_time + predW)] + self.hids = self.data["stay_id"].unique() + + if self.feat_cond: + self.cond = self.cond[self.cond["stay_id"].isin(self.data["stay_id"])] + + self.data["los"] = include_time ####Make equal length input time series and remove data for pred window if needed - + ###MEDS - if(self.feat_med): - self.meds=self.meds[self.meds['stay_id'].isin(self.data['stay_id'])] - self.meds=self.meds[self.meds['start_time']<=include_time] - self.meds.loc[self.meds.stop_time >include_time, 'stop_time']=include_time - - + if self.feat_med: + self.meds = self.meds[self.meds["stay_id"].isin(self.data["stay_id"])] + self.meds = self.meds[self.meds["start_time"] <= include_time] + self.meds.loc[ + self.meds.stop_time > include_time, "stop_time" + ] = include_time + ###PROCS - if(self.feat_proc): - self.proc=self.proc[self.proc['stay_id'].isin(self.data['stay_id'])] - self.proc=self.proc[self.proc['start_time']<=include_time] - + if self.feat_proc: + self.proc = self.proc[self.proc["stay_id"].isin(self.data["stay_id"])] + self.proc = self.proc[self.proc["start_time"] <= include_time] + ###OUT - if(self.feat_out): - self.out=self.out[self.out['stay_id'].isin(self.data['stay_id'])] - self.out=self.out[self.out['start_time']<=include_time] - - ###CHART - if(self.feat_chart): - self.chart=self.chart[self.chart['stay_id'].isin(self.data['stay_id'])] - self.chart=self.chart[self.chart['start_time']<=include_time] - - #self.los=include_time - def los_length(self,include_time): - print("include_time",include_time) - self.los=include_time - self.data=self.data[(self.data['los']>=include_time)] - self.hids=self.data['stay_id'].unique() - - if(self.feat_cond): - self.cond=self.cond[self.cond['stay_id'].isin(self.data['stay_id'])] - - self.data['los']=include_time + if self.feat_out: + self.out = self.out[self.out["stay_id"].isin(self.data["stay_id"])] + self.out = self.out[self.out["start_time"] <= include_time] + + ###CHART + if self.feat_chart: + self.chart = self.chart[self.chart["stay_id"].isin(self.data["stay_id"])] + self.chart = self.chart[self.chart["start_time"] <= include_time] + + # self.los=include_time + + def los_length(self, include_time): + print("include_time", include_time) + self.los = include_time + self.data = self.data[(self.data["los"] >= include_time)] + self.hids = self.data["stay_id"].unique() + + if self.feat_cond: + self.cond = self.cond[self.cond["stay_id"].isin(self.data["stay_id"])] + + self.data["los"] = include_time ####Make equal length input time series and remove data for pred window if needed - + ###MEDS - if(self.feat_med): - self.meds=self.meds[self.meds['stay_id'].isin(self.data['stay_id'])] - self.meds=self.meds[self.meds['start_time']<=include_time] - self.meds.loc[self.meds.stop_time >include_time, 'stop_time']=include_time - - + if self.feat_med: + self.meds = self.meds[self.meds["stay_id"].isin(self.data["stay_id"])] + self.meds = self.meds[self.meds["start_time"] <= include_time] + self.meds.loc[ + self.meds.stop_time > include_time, "stop_time" + ] = include_time + ###PROCS - if(self.feat_proc): - self.proc=self.proc[self.proc['stay_id'].isin(self.data['stay_id'])] - self.proc=self.proc[self.proc['start_time']<=include_time] - + if self.feat_proc: + self.proc = self.proc[self.proc["stay_id"].isin(self.data["stay_id"])] + self.proc = self.proc[self.proc["start_time"] <= include_time] + ###OUT - if(self.feat_out): - self.out=self.out[self.out['stay_id'].isin(self.data['stay_id'])] - self.out=self.out[self.out['start_time']<=include_time] - - ###CHART - if(self.feat_chart): - self.chart=self.chart[self.chart['stay_id'].isin(self.data['stay_id'])] - self.chart=self.chart[self.chart['start_time']<=include_time] - - def readmission_length(self,include_time): - self.los=include_time - self.data=self.data[(self.data['los']>=include_time)] - self.hids=self.data['stay_id'].unique() - - if(self.feat_cond): - self.cond=self.cond[self.cond['stay_id'].isin(self.data['stay_id'])] - self.data['select_time']=self.data['los']-include_time - self.data['los']=include_time + if self.feat_out: + self.out = self.out[self.out["stay_id"].isin(self.data["stay_id"])] + self.out = self.out[self.out["start_time"] <= include_time] + + ###CHART + if self.feat_chart: + self.chart = self.chart[self.chart["stay_id"].isin(self.data["stay_id"])] + self.chart = self.chart[self.chart["start_time"] <= include_time] + + def readmission_length(self, include_time): + self.los = include_time + self.data = self.data[(self.data["los"] >= include_time)] + self.hids = self.data["stay_id"].unique() + + if self.feat_cond: + self.cond = self.cond[self.cond["stay_id"].isin(self.data["stay_id"])] + self.data["select_time"] = self.data["los"] - include_time + self.data["los"] = include_time ####Make equal length input time series and remove data for pred window if needed - + ###MEDS - if(self.feat_med): - self.meds=self.meds[self.meds['stay_id'].isin(self.data['stay_id'])] - self.meds=pd.merge(self.meds,self.data[['stay_id','select_time']],on='stay_id',how='left') - self.meds['stop_time']=self.meds['stop_time']-self.meds['select_time'] - self.meds['start_time']=self.meds['start_time']-self.meds['select_time'] - self.meds=self.meds[self.meds['stop_time']>=0] - self.meds.loc[self.meds.start_time <0, 'start_time']=0 - + if self.feat_med: + self.meds = self.meds[self.meds["stay_id"].isin(self.data["stay_id"])] + self.meds = pd.merge( + self.meds, + self.data[["stay_id", "select_time"]], + on="stay_id", + how="left", + ) + self.meds["stop_time"] = self.meds["stop_time"] - self.meds["select_time"] + self.meds["start_time"] = self.meds["start_time"] - self.meds["select_time"] + self.meds = self.meds[self.meds["stop_time"] >= 0] + self.meds.loc[self.meds.start_time < 0, "start_time"] = 0 + ###PROCS - if(self.feat_proc): - self.proc=self.proc[self.proc['stay_id'].isin(self.data['stay_id'])] - self.proc=pd.merge(self.proc,self.data[['stay_id','select_time']],on='stay_id',how='left') - self.proc['start_time']=self.proc['start_time']-self.proc['select_time'] - self.proc=self.proc[self.proc['start_time']>=0] - + if self.feat_proc: + self.proc = self.proc[self.proc["stay_id"].isin(self.data["stay_id"])] + self.proc = pd.merge( + self.proc, + self.data[["stay_id", "select_time"]], + on="stay_id", + how="left", + ) + self.proc["start_time"] = self.proc["start_time"] - self.proc["select_time"] + self.proc = self.proc[self.proc["start_time"] >= 0] + ###OUT - if(self.feat_out): - self.out=self.out[self.out['stay_id'].isin(self.data['stay_id'])] - self.out=pd.merge(self.out,self.data[['stay_id','select_time']],on='stay_id',how='left') - self.out['start_time']=self.out['start_time']-self.out['select_time'] - self.out=self.out[self.out['start_time']>=0] - - ###CHART - if(self.feat_chart): - self.chart=self.chart[self.chart['stay_id'].isin(self.data['stay_id'])] - self.chart=pd.merge(self.chart,self.data[['stay_id','select_time']],on='stay_id',how='left') - self.chart['start_time']=self.chart['start_time']-self.chart['select_time'] - self.chart=self.chart[self.chart['start_time']>=0] - - - def smooth_meds(self,bucket): - final_meds=pd.DataFrame() - final_proc=pd.DataFrame() - final_out=pd.DataFrame() - final_chart=pd.DataFrame() - - if(self.feat_med): - self.meds=self.meds.sort_values(by=['start_time']) - if(self.feat_proc): - self.proc=self.proc.sort_values(by=['start_time']) - if(self.feat_out): - self.out=self.out.sort_values(by=['start_time']) - if(self.feat_chart): - self.chart=self.chart.sort_values(by=['start_time']) - - t=0 - for i in tqdm(range(0,self.los,bucket)): + if self.feat_out: + self.out = self.out[self.out["stay_id"].isin(self.data["stay_id"])] + self.out = pd.merge( + self.out, + self.data[["stay_id", "select_time"]], + on="stay_id", + how="left", + ) + self.out["start_time"] = self.out["start_time"] - self.out["select_time"] + self.out = self.out[self.out["start_time"] >= 0] + + ###CHART + if self.feat_chart: + self.chart = self.chart[self.chart["stay_id"].isin(self.data["stay_id"])] + self.chart = pd.merge( + self.chart, + self.data[["stay_id", "select_time"]], + on="stay_id", + how="left", + ) + self.chart["start_time"] = ( + self.chart["start_time"] - self.chart["select_time"] + ) + self.chart = self.chart[self.chart["start_time"] >= 0] + + def smooth_meds(self, bucket): + final_meds = pd.DataFrame() + final_proc = pd.DataFrame() + final_out = pd.DataFrame() + final_chart = pd.DataFrame() + + if self.feat_med: + self.meds = self.meds.sort_values(by=["start_time"]) + if self.feat_proc: + self.proc = self.proc.sort_values(by=["start_time"]) + if self.feat_out: + self.out = self.out.sort_values(by=["start_time"]) + if self.feat_chart: + self.chart = self.chart.sort_values(by=["start_time"]) + + t = 0 + for i in tqdm(range(0, self.los, bucket)): ###MEDS - if(self.feat_med): - sub_meds=self.meds[(self.meds['start_time']>=i) & (self.meds['start_time']= i) + & (self.meds["start_time"] < i + bucket) + ] + .groupby(["stay_id", "itemid", "orderid"]) + .agg( + { + "stop_time": "max", + "subject_id": "max", + "rate": np.nanmean, + "amount": np.nanmean, + } + ) + ) + sub_meds = sub_meds.reset_index() + sub_meds["start_time"] = t + sub_meds["stop_time"] = sub_meds["stop_time"] / bucket if final_meds.empty: - final_meds=sub_meds + final_meds = sub_meds else: - final_meds=final_meds.append(sub_meds) - + final_meds = pd.concat([final_meds, sub_meds], ignore_index=True) + ###PROC - if(self.feat_proc): - sub_proc=self.proc[(self.proc['start_time']>=i) & (self.proc['start_time']= i) + & (self.proc["start_time"] < i + bucket) + ] + .groupby(["stay_id", "itemid"]) + .agg({"subject_id": "max"}) + ) + sub_proc = sub_proc.reset_index() + sub_proc["start_time"] = t if final_proc.empty: - final_proc=sub_proc - else: - final_proc=final_proc.append(sub_proc) - - ###OUT - if(self.feat_out): - sub_out=self.out[(self.out['start_time']>=i) & (self.out['start_time']= i) + & (self.out["start_time"] < i + bucket) + ] + .groupby(["stay_id", "itemid"]) + .agg({"subject_id": "max"}) + ) + sub_out = sub_out.reset_index() + sub_out["start_time"] = t if final_out.empty: - final_out=sub_out - else: - final_out=final_out.append(sub_out) - - - ###CHART - if(self.feat_chart): - sub_chart=self.chart[(self.chart['start_time']>=i) & (self.chart['start_time']= i) + & (self.chart["start_time"] < i + bucket) + ] + .groupby(["stay_id", "itemid"]) + .agg({"valuenum": np.nanmean}) + ) + sub_chart = sub_chart.reset_index() + sub_chart["start_time"] = t if final_chart.empty: - final_chart=sub_chart - else: - final_chart=final_chart.append(sub_chart) - - t=t+1 - print("bucket",bucket) - los=int(self.los/bucket) - - + final_chart = sub_chart + else: + final_chart = pd.concat([final_chart, sub_chart], ignore_index=True) + + t = t + 1 + print("bucket", bucket) + los = int(self.los / bucket) + ###MEDS - if(self.feat_med): - f2_meds=final_meds.groupby(['stay_id','itemid','orderid']).size() - self.med_per_adm=f2_meds.groupby('stay_id').sum().reset_index()[0].max() - self.medlength_per_adm=final_meds.groupby('stay_id').size().max() - + if self.feat_med: + f2_meds = final_meds.groupby(["stay_id", "itemid", "orderid"]).size() + self.med_per_adm = f2_meds.groupby("stay_id").sum().reset_index()[0].max() + self.medlength_per_adm = final_meds.groupby("stay_id").size().max() + ###PROC - if(self.feat_proc): - f2_proc=final_proc.groupby(['stay_id','itemid']).size() - self.proc_per_adm=f2_proc.groupby('stay_id').sum().reset_index()[0].max() - self.proclength_per_adm=final_proc.groupby('stay_id').size().max() - + if self.feat_proc: + f2_proc = final_proc.groupby(["stay_id", "itemid"]).size() + self.proc_per_adm = f2_proc.groupby("stay_id").sum().reset_index()[0].max() + self.proclength_per_adm = final_proc.groupby("stay_id").size().max() + ###OUT - if(self.feat_out): - f2_out=final_out.groupby(['stay_id','itemid']).size() - self.out_per_adm=f2_out.groupby('stay_id').sum().reset_index()[0].max() - self.outlength_per_adm=final_out.groupby('stay_id').size().max() - - + if self.feat_out: + f2_out = final_out.groupby(["stay_id", "itemid"]).size() + self.out_per_adm = f2_out.groupby("stay_id").sum().reset_index()[0].max() + self.outlength_per_adm = final_out.groupby("stay_id").size().max() + ###chart - if(self.feat_chart): - f2_chart=final_chart.groupby(['stay_id','itemid']).size() - self.chart_per_adm=f2_chart.groupby('stay_id').sum().reset_index()[0].max() - self.chartlength_per_adm=final_chart.groupby('stay_id').size().max() - + if self.feat_chart: + f2_chart = final_chart.groupby(["stay_id", "itemid"]).size() + self.chart_per_adm = ( + f2_chart.groupby("stay_id").sum().reset_index()[0].max() + ) + self.chartlength_per_adm = final_chart.groupby("stay_id").size().max() + print("[ PROCESSED TIME SERIES TO EQUAL TIME INTERVAL ]") ###CREATE DICT -# if(self.feat_chart): -# self.create_chartDict(final_chart,los) -# else: - self.create_Dict(final_meds,final_proc,final_out,final_chart,los) - - - def create_chartDict(self,chart,los): - dataDic={} + # if(self.feat_chart): + # self.create_chartDict(final_chart,los) + # else: + self.create_Dict(final_meds, final_proc, final_out, final_chart, los) + + def create_chartDict(self, chart, los): + dataDic = {} for hid in self.hids: - grp=self.data[self.data['stay_id']==hid] - dataDic[hid]={'Chart':{},'label':int(grp['label'])} + grp = self.data[self.data["stay_id"] == hid] + dataDic[hid] = {"Chart": {}, "label": int(grp["label"])} for hid in tqdm(self.hids): ###CHART - if(self.feat_chart): - df2=chart[chart['stay_id']==hid] - val=df2.pivot_table(index='start_time',columns='itemid',values='valuenum') - df2['val']=1 - df2=df2.pivot_table(index='start_time',columns='itemid',values='val') - #print(df2.shape) + if self.feat_chart: + df2 = chart[chart["stay_id"] == hid] + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.fillna(0) - - val=pd.concat([val, add_df]) - val=val.sort_index() - if self.impute=='Mean': - val=val.ffill() - val=val.bfill() - val=val.fillna(val.mean()) - elif self.impute=='Median': - val=val.ffill() - val=val.bfill() - val=val.fillna(val.median()) - val=val.fillna(0) - - - df2[df2>0]=1 - df2[df2<0]=0 - #print(df2.head()) - dataDic[hid]['Chart']['signal']=df2.iloc[:,0:].to_dict(orient="list") - dataDic[hid]['Chart']['val']=val.iloc[:,0:].to_dict(orient="list") - - - + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + # print(df2.head()) + dataDic[hid]["Chart"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + dataDic[hid]["Chart"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + ######SAVE DICTIONARIES############## - with open("./data/dict/metaDic", 'rb') as fp: - metaDic=pickle.load(fp) - - with open("./data/dict/dataChartDic", 'wb') as fp: + with open("./data/dict/metaDic", "rb") as fp: + metaDic = pickle.load(fp) + + with open("./data/dict/dataChartDic", "wb") as fp: pickle.dump(dataDic, fp) - - with open("./data/dict/chartVocab", 'wb') as fp: - pickle.dump(list(chart['itemid'].unique()), fp) - self.chart_vocab = chart['itemid'].nunique() - metaDic['Chart']=self.chart_per_adm - - - with open("./data/dict/metaDic", 'wb') as fp: + with open("./data/dict/chartVocab", "wb") as fp: + pickle.dump(list(chart["itemid"].unique()), fp) + self.chart_vocab = chart["itemid"].nunique() + metaDic["Chart"] = self.chart_per_adm + + with open("./data/dict/metaDic", "wb") as fp: pickle.dump(metaDic, fp) - - - def create_Dict(self,meds,proc,out,chart,los): - dataDic={} + + def create_Dict(self, meds, proc, out, chart, los): + dataDic = {} print(los) - labels_csv=pd.DataFrame(columns=['stay_id','label']) - labels_csv['stay_id']=pd.Series(self.hids) - labels_csv['label']=0 -# print("# Unique gender",self.data.gender.nunique()) -# print("# Unique ethnicity",self.data.ethnicity.nunique()) -# print("# Unique insurance",self.data.insurance.nunique()) + labels_csv = pd.DataFrame(columns=["stay_id", "label"]) + labels_csv["stay_id"] = pd.Series(self.hids) + labels_csv["label"] = 0 + # print("# Unique gender",self.data.gender.nunique()) + # print("# Unique ethnicity",self.data.ethnicity.nunique()) + # print("# Unique insurance",self.data.insurance.nunique()) for hid in self.hids: - grp=self.data[self.data['stay_id']==hid] - dataDic[hid]={'Cond':{},'Proc':{},'Med':{},'Out':{},'Chart':{},'ethnicity':grp['ethnicity'].iloc[0],'age':int(grp['Age']),'gender':grp['gender'].iloc[0],'label':int(grp['label'])} - labels_csv.loc[labels_csv['stay_id']==hid,'label']=int(grp['label']) - + grp = self.data[self.data["stay_id"] == hid] + dataDic[hid] = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Out": {}, + "Chart": {}, + "ethnicity": grp["ethnicity"].iloc[0], + "age": int(grp["Age"]), + "gender": grp["gender"].iloc[0], + "label": int(grp["label"]), + } + labels_csv.loc[labels_csv["stay_id"] == hid, "label"] = int(grp["label"]) - #print(static_csv.head()) + # print(static_csv.head()) for hid in tqdm(self.hids): - grp=self.data[self.data['stay_id']==hid] - demo_csv=grp[['Age','gender','ethnicity','insurance']] - if not os.path.exists("./data/csv/"+str(hid)): - os.makedirs("./data/csv/"+str(hid)) - demo_csv.to_csv('./data/csv/'+str(hid)+'/demo.csv',index=False) - - dyn_csv=pd.DataFrame() + grp = self.data[self.data["stay_id"] == hid] + demo_csv = grp[["Age", "gender", "ethnicity", "insurance"]] + if not os.path.exists("./data/csv/" + str(hid)): + os.makedirs("./data/csv/" + str(hid)) + demo_csv.to_csv("./data/csv/" + str(hid) + "/demo.csv", index=False) + + dyn_csv = pd.DataFrame() ###MEDS - if(self.feat_med): - feat=meds['itemid'].unique() - df2=meds[meds['stay_id']==hid] - if df2.shape[0]==0: - amount=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - amount=amount.fillna(0) - amount.columns=pd.MultiIndex.from_product([["MEDS"], amount.columns]) + if self.feat_med: + feat = meds["itemid"].unique() + df2 = meds[meds["stay_id"] == hid] + if df2.shape[0] == 0: + amount = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + amount = amount.fillna(0) + amount.columns = pd.MultiIndex.from_product( + [["MEDS"], amount.columns] + ) else: - rate=df2.pivot_table(index='start_time',columns='itemid',values='rate') - #print(rate) - amount=df2.pivot_table(index='start_time',columns='itemid',values='amount') - df2=df2.pivot_table(index='start_time',columns='itemid',values='stop_time') - #print(df2.shape) + rate = df2.pivot_table( + index="start_time", columns="itemid", values="rate" + ) + # print(rate) + amount = df2.pivot_table( + index="start_time", columns="itemid", values="amount" + ) + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="stop_time" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.ffill() - df2=df2.fillna(0) - - rate=pd.concat([rate, add_df]) - rate=rate.sort_index() - rate=rate.ffill() - rate=rate.fillna(-1) - - amount=pd.concat([amount, add_df]) - amount=amount.sort_index() - amount=amount.ffill() - amount=amount.fillna(-1) - #print(df2.head()) - df2.iloc[:,0:]=df2.iloc[:,0:].sub(df2.index,0) - df2[df2>0]=1 - df2[df2<0]=0 - rate.iloc[:,0:]=df2.iloc[:,0:]*rate.iloc[:,0:] - amount.iloc[:,0:]=df2.iloc[:,0:]*amount.iloc[:,0:] - #print(df2.head()) - dataDic[hid]['Med']['signal']=df2.iloc[:,0:].to_dict(orient="list") - dataDic[hid]['Med']['rate']=rate.iloc[:,0:].to_dict(orient="list") - dataDic[hid]['Med']['amount']=amount.iloc[:,0:].to_dict(orient="list") - - - feat_df=pd.DataFrame(columns=list(set(feat)-set(amount.columns))) - # print(feat) - # print(amount.columns) - # print(amount.head()) - amount=pd.concat([amount,feat_df],axis=1) - - amount=amount[feat] - amount=amount.fillna(0) - # print(amount.columns) - amount.columns=pd.MultiIndex.from_product([["MEDS"], amount.columns]) - - if(dyn_csv.empty): - dyn_csv=amount + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.ffill() + df2 = df2.fillna(0) + + rate = pd.concat([rate, add_df]) + rate = rate.sort_index() + rate = rate.ffill() + rate = rate.fillna(-1) + + amount = pd.concat([amount, add_df]) + amount = amount.sort_index() + amount = amount.ffill() + amount = amount.fillna(-1) + # print(df2.head()) + df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + rate.iloc[:, 0:] = df2.iloc[:, 0:] * rate.iloc[:, 0:] + amount.iloc[:, 0:] = df2.iloc[:, 0:] * amount.iloc[:, 0:] + # print(df2.head()) + dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + dataDic[hid]["Med"]["rate"] = rate.iloc[:, 0:].to_dict( + orient="list" + ) + dataDic[hid]["Med"]["amount"] = amount.iloc[:, 0:].to_dict( + orient="list" + ) + + feat_df = pd.DataFrame( + columns=list(set(feat) - set(amount.columns)) + ) + # print(feat) + # print(amount.columns) + # print(amount.head()) + amount = pd.concat([amount, feat_df], axis=1) + + amount = amount[feat] + amount = amount.fillna(0) + # print(amount.columns) + amount.columns = pd.MultiIndex.from_product( + [["MEDS"], amount.columns] + ) + + if dyn_csv.empty: + dyn_csv = amount else: - dyn_csv=pd.concat([dyn_csv,amount],axis=1) - - - - - + dyn_csv = pd.concat([dyn_csv, amount], axis=1) + ###PROCS - if(self.feat_proc): - feat=proc['itemid'].unique() - df2=proc[proc['stay_id']==hid] - if df2.shape[0]==0: - df2=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - df2=df2.fillna(0) - df2.columns=pd.MultiIndex.from_product([["PROC"], df2.columns]) + if self.feat_proc: + feat = proc["itemid"].unique() + df2 = proc[proc["stay_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) else: - df2['val']=1 - #print(df2) - df2=df2.pivot_table(index='start_time',columns='itemid',values='val') - #print(df2.shape) + df2["val"] = 1 + # print(df2) + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.fillna(0) - df2[df2>0]=1 - #print(df2.head()) - dataDic[hid]['Proc']=df2.to_dict(orient="list") - - - feat_df=pd.DataFrame(columns=list(set(feat)-set(df2.columns))) - df2=pd.concat([df2,feat_df],axis=1) - - df2=df2[feat] - df2=df2.fillna(0) - df2.columns=pd.MultiIndex.from_product([["PROC"], df2.columns]) - - if(dyn_csv.empty): - dyn_csv=df2 + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + # print(df2.head()) + dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 else: - dyn_csv=pd.concat([dyn_csv,df2],axis=1) - - - - + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + ###OUT - if(self.feat_out): - feat=out['itemid'].unique() - df2=out[out['stay_id']==hid] - if df2.shape[0]==0: - df2=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - df2=df2.fillna(0) - df2.columns=pd.MultiIndex.from_product([["OUT"], df2.columns]) + if self.feat_out: + feat = out["itemid"].unique() + df2 = out[out["stay_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) else: - df2['val']=1 - df2=df2.pivot_table(index='start_time',columns='itemid',values='val') - #print(df2.shape) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.fillna(0) - df2[df2>0]=1 - #print(df2.head()) - dataDic[hid]['Out']=df2.to_dict(orient="list") - - feat_df=pd.DataFrame(columns=list(set(feat)-set(df2.columns))) - df2=pd.concat([df2,feat_df],axis=1) - - df2=df2[feat] - df2=df2.fillna(0) - df2.columns=pd.MultiIndex.from_product([["OUT"], df2.columns]) - - if(dyn_csv.empty): - dyn_csv=df2 + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + # print(df2.head()) + dataDic[hid]["Out"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 else: - dyn_csv=pd.concat([dyn_csv,df2],axis=1) - - - + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + ###CHART - if(self.feat_chart): - feat=chart['itemid'].unique() - df2=chart[chart['stay_id']==hid] - if df2.shape[0]==0: - val=pd.DataFrame(np.zeros([los,len(feat)]),columns=feat) - val=val.fillna(0) - val.columns=pd.MultiIndex.from_product([["CHART"], val.columns]) + if self.feat_chart: + feat = chart["itemid"].unique() + df2 = chart[chart["stay_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) else: - val=df2.pivot_table(index='start_time',columns='itemid',values='valuenum') - df2['val']=1 - df2=df2.pivot_table(index='start_time',columns='itemid',values='val') - #print(df2.shape) + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) add_indices = pd.Index(range(los)).difference(df2.index) - add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) - df2=pd.concat([df2, add_df]) - df2=df2.sort_index() - df2=df2.fillna(0) - - val=pd.concat([val, add_df]) - val=val.sort_index() - if self.impute=='Mean': - val=val.ffill() - val=val.bfill() - val=val.fillna(val.mean()) - elif self.impute=='Median': - val=val.ffill() - val=val.bfill() - val=val.fillna(val.median()) - val=val.fillna(0) - - - df2[df2>0]=1 - df2[df2<0]=0 - #print(df2.head()) - dataDic[hid]['Chart']['signal']=df2.iloc[:,0:].to_dict(orient="list") - dataDic[hid]['Chart']['val']=val.iloc[:,0:].to_dict(orient="list") - - feat_df=pd.DataFrame(columns=list(set(feat)-set(val.columns))) - val=pd.concat([val,feat_df],axis=1) - - val=val[feat] - val=val.fillna(0) - val.columns=pd.MultiIndex.from_product([["CHART"], val.columns]) - - if(dyn_csv.empty): - dyn_csv=val + add_df = pd.DataFrame( + index=add_indices, columns=df2.columns + ).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + # print(df2.head()) + dataDic[hid]["Chart"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + dataDic[hid]["Chart"]["val"] = val.iloc[:, 0:].to_dict( + orient="list" + ) + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + + if dyn_csv.empty: + dyn_csv = val else: - dyn_csv=pd.concat([dyn_csv,val],axis=1) - - #Save temporal data to csv - dyn_csv.to_csv('./data/csv/'+str(hid)+'/dynamic.csv',index=False) - + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + # Save temporal data to csv + dyn_csv.to_csv("./data/csv/" + str(hid) + "/dynamic.csv", index=False) + ##########COND######### - if(self.feat_cond): - feat=self.cond['new_icd_code'].unique() - grp=self.cond[self.cond['stay_id']==hid] - if(grp.shape[0]==0): - dataDic[hid]['Cond']={'fids':list([''])} - feat_df=pd.DataFrame(np.zeros([1,len(feat)]),columns=feat) - grp=feat_df.fillna(0) - grp.columns=pd.MultiIndex.from_product([["COND"], grp.columns]) + if self.feat_cond: + feat = self.cond["new_icd_code"].unique() + grp = self.cond[self.cond["stay_id"] == hid] + if grp.shape[0] == 0: + dataDic[hid]["Cond"] = {"fids": list([""])} + feat_df = pd.DataFrame(np.zeros([1, len(feat)]), columns=feat) + grp = feat_df.fillna(0) + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) else: - dataDic[hid]['Cond']={'fids':list(grp['new_icd_code'])} - grp['val']=1 - grp=grp.drop_duplicates() - grp=grp.pivot(index='stay_id',columns='new_icd_code',values='val').reset_index(drop=True) - feat_df=pd.DataFrame(columns=list(set(feat)-set(grp.columns))) - grp=pd.concat([grp,feat_df],axis=1) - grp=grp.fillna(0) - grp=grp[feat] - grp.columns=pd.MultiIndex.from_product([["COND"], grp.columns]) - grp.to_csv('./data/csv/'+str(hid)+'/static.csv',index=False) - labels_csv.to_csv('./data/csv/labels.csv',index=False) - - + dataDic[hid]["Cond"] = {"fids": list(grp["new_icd_code"])} + grp["val"] = 1 + grp = grp.drop_duplicates() + grp = grp.pivot( + index="stay_id", columns="new_icd_code", values="val" + ).reset_index(drop=True) + feat_df = pd.DataFrame(columns=list(set(feat) - set(grp.columns))) + grp = pd.concat([grp, feat_df], axis=1) + grp = grp.fillna(0) + grp = grp[feat] + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + grp.to_csv("./data/csv/" + str(hid) + "/static.csv", index=False) + labels_csv.to_csv("./data/csv/labels.csv", index=False) + ######SAVE DICTIONARIES############## - metaDic={'Cond':{},'Proc':{},'Med':{},'Out':{},'Chart':{},'LOS':{}} - metaDic['LOS']=los - with open("./data/dict/dataDic", 'wb') as fp: + metaDic = {"Cond": {}, "Proc": {}, "Med": {}, "Out": {}, "Chart": {}, "LOS": {}} + metaDic["LOS"] = los + with open("./data/dict/dataDic", "wb") as fp: pickle.dump(dataDic, fp) - with open("./data/dict/hadmDic", 'wb') as fp: + with open("./data/dict/hadmDic", "wb") as fp: pickle.dump(self.hids, fp) - - with open("./data/dict/ethVocab", 'wb') as fp: - pickle.dump(list(self.data['ethnicity'].unique()), fp) - self.eth_vocab = self.data['ethnicity'].nunique() - - with open("./data/dict/ageVocab", 'wb') as fp: - pickle.dump(list(self.data['Age'].unique()), fp) - self.age_vocab = self.data['Age'].nunique() - - with open("./data/dict/insVocab", 'wb') as fp: - pickle.dump(list(self.data['insurance'].unique()), fp) - self.ins_vocab = self.data['insurance'].nunique() - - if(self.feat_med): - with open("./data/dict/medVocab", 'wb') as fp: - pickle.dump(list(meds['itemid'].unique()), fp) - self.med_vocab = meds['itemid'].nunique() - metaDic['Med']=self.med_per_adm - - if(self.feat_out): - with open("./data/dict/outVocab", 'wb') as fp: - pickle.dump(list(out['itemid'].unique()), fp) - self.out_vocab = out['itemid'].nunique() - metaDic['Out']=self.out_per_adm - - if(self.feat_chart): - with open("./data/dict/chartVocab", 'wb') as fp: - pickle.dump(list(chart['itemid'].unique()), fp) - self.chart_vocab = chart['itemid'].nunique() - metaDic['Chart']=self.chart_per_adm - - if(self.feat_cond): - with open("./data/dict/condVocab", 'wb') as fp: - pickle.dump(list(self.cond['new_icd_code'].unique()), fp) - self.cond_vocab = self.cond['new_icd_code'].nunique() - metaDic['Cond']=self.cond_per_adm - - if(self.feat_proc): - with open("./data/dict/procVocab", 'wb') as fp: - pickle.dump(list(proc['itemid'].unique()), fp) - self.proc_vocab = proc['itemid'].nunique() - metaDic['Proc']=self.proc_per_adm - - with open("./data/dict/metaDic", 'wb') as fp: - pickle.dump(metaDic, fp) - - - + with open("./data/dict/ethVocab", "wb") as fp: + pickle.dump(list(self.data["ethnicity"].unique()), fp) + self.eth_vocab = self.data["ethnicity"].nunique() + + with open("./data/dict/ageVocab", "wb") as fp: + pickle.dump(list(self.data["Age"].unique()), fp) + self.age_vocab = self.data["Age"].nunique() + + with open("./data/dict/insVocab", "wb") as fp: + pickle.dump(list(self.data["insurance"].unique()), fp) + self.ins_vocab = self.data["insurance"].nunique() + if self.feat_med: + with open("./data/dict/medVocab", "wb") as fp: + pickle.dump(list(meds["itemid"].unique()), fp) + self.med_vocab = meds["itemid"].nunique() + metaDic["Med"] = self.med_per_adm + + if self.feat_out: + with open("./data/dict/outVocab", "wb") as fp: + pickle.dump(list(out["itemid"].unique()), fp) + self.out_vocab = out["itemid"].nunique() + metaDic["Out"] = self.out_per_adm + + if self.feat_chart: + with open("./data/dict/chartVocab", "wb") as fp: + pickle.dump(list(chart["itemid"].unique()), fp) + self.chart_vocab = chart["itemid"].nunique() + metaDic["Chart"] = self.chart_per_adm + + if self.feat_cond: + with open("./data/dict/condVocab", "wb") as fp: + pickle.dump(list(self.cond["new_icd_code"].unique()), fp) + self.cond_vocab = self.cond["new_icd_code"].nunique() + metaDic["Cond"] = self.cond_per_adm + + if self.feat_proc: + with open("./data/dict/procVocab", "wb") as fp: + pickle.dump(list(proc["itemid"].unique()), fp) + self.proc_vocab = proc["itemid"].nunique() + metaDic["Proc"] = self.proc_per_adm + + with open("./data/dict/metaDic", "wb") as fp: + pickle.dump(metaDic, fp) diff --git a/old_pipeline_script.py b/old_pipeline_script.py new file mode 100644 index 0000000000..6b4cb2dacf --- /dev/null +++ b/old_pipeline_script.py @@ -0,0 +1,65 @@ +from preprocessing.day_intervals_preproc.day_intervals_cohort_v2 import extract_data +from preprocessing.hosp_module_preproc.feature_selection_icu import ( + feature_icu, + preprocess_features_icu, + generate_summary_icu, + features_selection_icu, +) +from model.data_generation_icu import Generator + +cohort_output = extract_data( + "ICU", + "Mortality", + 0, + "No Disease Filter", + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline", + "", +) + +feature_icu("cohort_icu_mortality_0_", "mimiciv", True, True, True, True, True) + +preprocess_features_icu( + "cohort_icu_mortality_0_", + True, + "Convert ICD-9 to ICD-10 and group ICD-10 codes", + False, + False, + False, + 0, + 0, +) + +generate_summary_icu(True, True, True, True, True) +features_selection_icu( + "cohort_icu_mortality_0_", + True, + True, + True, + True, + True, + True, + True, + True, + True, + True, +) + +preprocess_features_icu( + "cohort_icu_mortality_0_", False, False, True, True, True, 98, 0 +) + +gen = Generator( + "cohort_icu_mortality_0_", + True, + False, + False, + True, + True, + True, + True, + True, + False, + 72, + 1, + 2, +) diff --git a/pipeline/cohort_extractor.py b/pipeline/cohort_extractor.py new file mode 100644 index 0000000000..1b2df6759f --- /dev/null +++ b/pipeline/cohort_extractor.py @@ -0,0 +1,145 @@ +from typing import Tuple +import pandas as pd +import logging +from pipeline.file_info.raw.hosp import ( + load_hosp_patients, + load_hosp_admissions, + HospAdmissions, +) +from pipeline.file_info.raw.icu import load_icu_icustays +from pipeline.file_info.preproc.cohort import CohortHeader +from pipeline.prediction_task import PredictionTask +from pipeline.preprocessing.visit import ( + make_patients, + make_icu_visits, + make_no_icu_visits, + filter_visits, +) +from pipeline.preprocessing.cohort import Cohort + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + + +class CohortExtractor: + """ + Extracts cohort data based on specified prediction tasks and ICU status. + + Attributes: + prediction_task (PredictionTask): The prediction task to be used for cohort extraction. + cohort_output (Path): The path for the output of the cohort data. + """ + + def __init__( + self, + prediction_task: PredictionTask, + cohort_output: str = None, + ): + self.prediction_task = prediction_task + self.cohort_output = cohort_output + + def get_icu_status(self) -> str: + """Determines the ICU status based on the prediction task.""" + return "ICU" if self.prediction_task.use_icu else "Non-ICU" + + def generate_extract_log(self) -> str: + """Generates a log message for the extraction process.""" + icu_log = self.get_icu_status() + task_info = f"{icu_log} | {self.prediction_task.target_type}" + if self.prediction_task.disease_readmission: + task_info += f" DUE TO {self.prediction_task.disease_readmission}" + + if self.prediction_task.disease_selection: + task_info += f" ADMITTED DUE TO {self.prediction_task.disease_selection}" + + return f"EXTRACTING FOR: {task_info} | {self.prediction_task.nb_days} |".upper() + + def generate_output_suffix(self) -> str: + """Generates a suffix for the output file based on the task details.""" + return ( + self.get_icu_status() # .lower() + + "_" + + self.prediction_task.target_type.lower().replace(" ", "_") + + "_" + + str(self.prediction_task.nb_days) + + "_" + + ( + self.prediction_task.disease_readmission + if self.prediction_task.disease_readmission + else "" + ) + ) + + def fill_outputs(self) -> None: + """Fills in the output details based on the prediction task.""" + disease_selection = ( + f"_{self.prediction_task.disease_selection}" + if self.prediction_task.disease_selection + else "" + ) + self.cohort_output = ( + self.cohort_output + or f"cohort_{self.generate_output_suffix()}{disease_selection}" + ) + + def load_hospital_data(self) -> Tuple[pd.DataFrame, pd.DataFrame]: + """Loads hospital patient and admission data.""" + return load_hosp_patients(), load_hosp_admissions() + + def create_visits(self, hosp_patients, hosp_admissions): + if self.prediction_task.use_icu: + icu_icustays = load_icu_icustays() + return make_icu_visits( + icu_icustays, hosp_patients, self.prediction_task.target_type + ) + else: + return make_no_icu_visits(hosp_admissions, self.prediction_task.target_type) + + def filter_and_merge_visits( + self, + visits: pd.DataFrame, + hosp_patients: pd.DataFrame, + hosp_admissions: pd.DataFrame, + ) -> pd.DataFrame: + """Filters and merges visit records with patient and admission data.""" + visits = filter_visits( + visits, + self.prediction_task.disease_readmission, + self.prediction_task.disease_selection, + ) + patients_data = make_patients(hosp_patients) + patients_filtered = patients_data.loc[patients_data["age"] >= 18] + admissions_info = hosp_admissions[ + [ + HospAdmissions.HOSPITAL_ADMISSION_ID, + HospAdmissions.INSURANCE, + HospAdmissions.RACE, + ] + ] + visits = visits.merge(patients_filtered, on=CohortHeader.PATIENT_ID) + visits = visits.merge(admissions_info, on=CohortHeader.HOSPITAL_ADMISSION_ID) + return visits + + def extract(self) -> Cohort: + """ + Extracts the cohort data based on specified criteria and saves it. + + Returns: + Cohort: The extracted and processed cohort data. + """ + logger.info("===========MIMIC-IV v2.0============") + self.fill_outputs() + logger.info(self.generate_extract_log()) + + hosp_patients, hosp_admissions = self.load_hospital_data() + visits = self.create_visits(hosp_patients, hosp_admissions) + visits = self.filter_and_merge_visits(visits, hosp_patients, hosp_admissions) + self.fill_outputs() + cohort = Cohort( + icu=self.prediction_task.use_icu, + name=self.cohort_output, + ) + cohort.prepare_labels(visits, self.prediction_task) + cohort.save() + cohort.save_summary() + return cohort diff --git a/pipeline/conversion/icd.py b/pipeline/conversion/icd.py new file mode 100644 index 0000000000..eb34d18493 --- /dev/null +++ b/pipeline/conversion/icd.py @@ -0,0 +1,40 @@ +import pandas as pd +import numpy as np + +from pipeline.file_info.common import load_static_icd_map, IcdMap +from pipeline.file_info.raw.hosp import HospDiagnosesIcd + +ROOT_ICD_CONVERT = "root_icd10_convert" + + +class IcdConverter: + def __init__(self): + self.conversions_icd_9_10 = self._get_conversions_icd_9_10() + + def _get_conversions_icd_9_10(self) -> dict: + """Create mapping dictionary ICD9 -> ICD10""" + icd_map_df = load_static_icd_map() + filtered_df = icd_map_df[icd_map_df[IcdMap.DIAGNOISIS_CODE].str.len() == 3] + filtered_df = filtered_df.drop_duplicates(subset=IcdMap.DIAGNOISIS_CODE) + return dict(zip(filtered_df[IcdMap.DIAGNOISIS_CODE], filtered_df[IcdMap.ICD10])) + + def standardize_icd(self, df: pd.DataFrame) -> pd.DataFrame: + """Standardizes ICD codes in a DataFrame.""" + df[ROOT_ICD_CONVERT] = df.apply( + lambda row: self.conversions_icd_9_10.get( + row[HospDiagnosesIcd.ICD_CODE][:3], np.nan + ) + if row[HospDiagnosesIcd.ICD_VERSION] == 9 + else row[HospDiagnosesIcd.ICD_CODE], + axis=1, + ) + df[HospDiagnosesIcd.ROOT] = df[ROOT_ICD_CONVERT].apply( + lambda x: x[:3] if type(x) is str else np.nan + ) + return df + + def get_pos_ids(self, diag: pd.DataFrame, ICD10_code: str) -> pd.Series: + """Extracts unique hospital admission IDs where 'root' contains a specific ICD-10 code.""" + return diag[diag[HospDiagnosesIcd.ROOT].str.contains(ICD10_code, na=False)][ + HospDiagnosesIcd.HOSPITAL_ADMISSION_ID + ].unique() diff --git a/pipeline/conversion/ndc.py b/pipeline/conversion/ndc.py new file mode 100644 index 0000000000..a96bb876b8 --- /dev/null +++ b/pipeline/conversion/ndc.py @@ -0,0 +1,65 @@ +import pandas as pd +import numpy as np + +from pipeline.file_info.common import MAP_NDC_PATH +from enum import StrEnum + + +class NdcMappingHeader(StrEnum): + PRODUCT_NDC = "productndc" + NON_PROPRIETARY_NAME = "nonproprietaryname" + PHARM_CLASSES = "pharm_classes" + NEW_NDC = "new_ndc" + + +def prepare_ndc_mapping() -> pd.DataFrame: + ndc_map = read_ndc_mapping()[ + [ + NdcMappingHeader.PRODUCT_NDC, + NdcMappingHeader.NON_PROPRIETARY_NAME, + NdcMappingHeader.PHARM_CLASSES, + ] + ] + ndc_map[NdcMappingHeader.NON_PROPRIETARY_NAME] = ( + ndc_map[NdcMappingHeader.NON_PROPRIETARY_NAME].fillna("").str.lower() + ) + # Normalize the NDC codes in the mapping table so that they can be merged + ndc_map.loc[:, NdcMappingHeader.NEW_NDC] = ndc_map[ + NdcMappingHeader.PRODUCT_NDC + ].apply(format_ndc_table) + ndc_map = ndc_map.drop_duplicates( + subset=[NdcMappingHeader.NEW_NDC, NdcMappingHeader.NON_PROPRIETARY_NAME] + ) + return ndc_map + + +def ndc_to_str(ndc: int) -> str: + """Converts NDC code to a string with leading zeros restored, keeping only the first 9 digits.""" + if ndc < 0: # Handling dummy values + return np.nan + ndc_str = str(ndc).zfill(11) + return ndc_str[:-2] + + +def format_ndc_table(ndc: str) -> str: + """Formats NDC code from the mapping table to the standard 11-digit format, taking only the first 9 digits.""" + parts = ndc.split("-") + formatted_ndc = "".join( + part.zfill(length) for part, length in zip(parts, [5, 4, 2]) + ) + return formatted_ndc[:9] # Taking only the manufacturer and product sections + + +def read_ndc_mapping() -> pd.DataFrame: + """Reads and processes NDC mapping table from a file.""" + ndc_map = pd.read_csv(MAP_NDC_PATH, delimiter="\t", encoding="latin1") + ndc_map.columns = ndc_map.columns.str.lower() + return ndc_map + + +def get_EPC(s: str) -> list: + """Extracts the Established Pharmacologic Class (EPC) from a string.""" + if not isinstance(s, str): + return np.nan + + return [phrase for phrase in s.split(",") if "[EPC]" in phrase] diff --git a/pipeline/conversion/uom.py b/pipeline/conversion/uom.py new file mode 100644 index 0000000000..d727c0120f --- /dev/null +++ b/pipeline/conversion/uom.py @@ -0,0 +1,32 @@ +import pandas as pd +import numpy as np + + +def drop_wrong_uom(data: pd.DataFrame, cut_off) -> pd.DataFrame: + """Drop rows with uncommon units of measurement for each itemid, based on a cut-off frequency. + + Args: + data (pd.DataFrame): The input DataFrame containing the data. + cut_off: The cut-off frequency used to determine uncommon units of measurement. + + Returns: + pd.DataFrame: The filtered DataFrame with rows dropped based on uncommon units of measurement. + """ + + # Create a function to filter each group + def filter_group(group): + value_counts = group["valueuom"].value_counts() + most_frequent_uom = value_counts.idxmax() + frequency = value_counts.max() + + # Check if the most frequent uom meets the cut-off criteria + if frequency / len(group) > cut_off: + return group[group["valueuom"] == most_frequent_uom] + return group + + # Apply the filter function to each group and concatenate the results + return ( + data.groupby("itemid", group_keys=False) + .apply(filter_group) + .reset_index(drop=True) + ) diff --git a/pipeline/data_generator.py b/pipeline/data_generator.py new file mode 100644 index 0000000000..963d90469a --- /dev/null +++ b/pipeline/data_generator.py @@ -0,0 +1,382 @@ +from typing import Dict +import pandas as pd +from tqdm import tqdm +from pipeline.dict_maker import DictMaker +from pipeline.feature.lab_events import Lab +from pipeline.feature.medications import Medications +from pipeline.feature.output_events import OutputEvents +from pipeline.feature.procedures import Procedures +from pipeline.file_info.common import PREPROC_PATH +from pipeline.file_info.preproc.cohort import COHORT_PATH +from pipeline.file_info.preproc.feature import ( + EXTRACT_CHART_ICU_PATH, + EXTRACT_DIAG_ICU_PATH, + EXTRACT_DIAG_PATH, + EXTRACT_LABS_PATH, + EXTRACT_MED_ICU_PATH, + EXTRACT_MED_PATH, + EXTRACT_OUT_ICU_PATH, + EXTRACT_PROC_ICU_PATH, + EXTRACT_PROC_PATH, +) +from pipeline.prediction_task import PredictionTask, TargetType +import logging + +from pipeline.features_extractor import FeatureExtractor +from pipeline.feature.chart_events import Chart, ChartEvents +from pipeline.feature.diagnoses import Diagnoses +from pipeline.preprocessing.cohort import read_cohort +from pipeline.feature.feature_abc import Feature + +logger = logging.getLogger() + + +class DataGenerator: + def __init__( + self, + cohort_output: pd.DataFrame, + feature_extractor: FeatureExtractor, + # impute: str, + include_time: int = 24, + bucket: int = 1, + predW: int = 0, + target_type: TargetType = TargetType.LOS, + ): + self.cohort_output = cohort_output + self.feature_extractor = feature_extractor + # self.impute = impute + self.include_time = include_time + self.bucket = bucket + self.predW = predW + self.target_type = target_type + self.dia = pd.DataFrame() + self.proc = pd.DataFrame() + self.out = pd.DataFrame() + self.chart = pd.DataFrame() + self.med = pd.DataFrame() + self.lab = pd.DataFrame() + self.med_per_adm = pd.DataFrame() + self.out_per_adm = pd.DataFrame() + self.chart_per_adm = pd.DataFrame() + self.dia_per_adm = pd.DataFrame() + self.proc_per_adm = pd.DataFrame() + self.labs_per_adm = pd.DataFrame() + + def generate_features(self): + print("[ ======READING DIAGNOSIS ]") + self.cohort = read_cohort(self.cohort_output, self.feature_extractor.use_icu) + if self.feature_extractor.for_diagnoses: + preproc_dia = pd.read_csv( + EXTRACT_DIAG_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_DIAG_PATH, + compression="gzip", + ) + dia = Diagnoses(use_icu=self.feature_extractor.use_icu, df=preproc_dia) + self.dia, self.dia_per_adm = dia.generate_fun(self.cohort) + if self.feature_extractor.for_procedures: + print("[ ======READING PROCEDURES ]") + preproc_proc = pd.read_csv( + EXTRACT_PROC_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_PROC_PATH, + compression="gzip", + ) + proc = Procedures(use_icu=self.feature_extractor.use_icu, df=preproc_proc) + self.proc = proc.generate_fun(self.cohort) + + if self.feature_extractor.use_icu and self.feature_extractor.for_output_events: + print("[ ======READING OUTPUT ]") + preproc_out = pd.read_csv(EXTRACT_OUT_ICU_PATH, compression="gzip") + out = OutputEvents(df=preproc_out) + self.out = out.generate_fun(self.cohort) + + if self.feature_extractor.use_icu and self.feature_extractor.for_chart_events: + print("[ ======READING CHART ]") + preproc_chart = pd.read_csv( + EXTRACT_CHART_ICU_PATH, compression="gzip", chunksize=5000000 + ) + chart = Chart(df=preproc_chart) + self.chart = chart.generate_fun(self.cohort) + + if self.feature_extractor.for_medications: + print("[ ======READING MEDICATIONS ]") + preproc_med = pd.read_csv( + EXTRACT_MED_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_MED_PATH, + compression="gzip", + ) + med = Medications(use_icu=self.feature_extractor.use_icu, df=preproc_med) + self.med = med.generate_fun(self.cohort) + if not (self.feature_extractor.use_icu) and self.feature_extractor.for_labs: + print("[ ======READING LABS ]") + preproc_labs = pd.read_csv( + EXTRACT_LABS_PATH, compression="gzip", chunksize=5000000 + ) + lab = Lab(df=preproc_labs) + self.lab = lab.generate_fun(self.cohort) + breakpoint() + + def length_by_target(self): + self.los = self.include_time + self.cohort = self.cohort[(self.cohort["los"] >= self.include_time)] + self.hids = self.cohort["hadm_id"].unique() + + if self.target_type == TargetType.MORTALITY: + if self.feature_extractor.for_diagnoses: + dia = Diagnoses(use_icu=self.feature_extractor.use_icu, df=self.dia) + dia.mortality_length(self.cohort) + self.dia = dia.df + if self.feature_extractor.for_procedures: + proc = Procedures(use_icu=self.feature_extractor.use_icu, df=self.proc) + proc.mortality_length(self.cohort, self.include_time) + self.proc = proc.df + if ( + self.feature_extractor.use_icu + and self.feature_extractor.for_output_events + ): + out = OutputEvents(df=self.out) + out.mortality_length(self.cohort, self.include_time) + self.out = out.df + if ( + self.feature_extractor.use_icu + and self.feature_extractor.for_chart_events + ): + chart = Chart(df=self.chart) + chart.mortality_length(self.cohort, self.include_time) + self.chart = chart.df + if self.feature_extractor.for_medications: + med = Medications(use_icu=self.feature_extractor.use_icu, df=self.chart) + med.mortality_length(self.cohort, self.include_time) + self.med = med.df + print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") + elif self.target_type == TargetType.READMISSION: + if self.feature_extractor.for_diagnoses: + dia = Diagnoses(use_icu=self.feature_extractor.use_icu, df=self.dia) + dia.read_length() + self.dia = dia.df + if self.feature_extractor.for_procedures: + proc = Procedures(use_icu=self.feature_extractor.use_icu, df=self.proc) + proc.read_length(self.cohort) + self.proc = proc.df + if ( + self.feature_extractor.use_icu + and self.feature_extractor.for_output_events + ): + out = OutputEvents(df=self.out) + out.read_length(self.cohort) + self.out = out.df + if ( + self.feature_extractor.use_icu + and self.feature_extractor.for_chart_events + ): + chart = Chart(df=self.chart) + chart.read_length(self.cohort) + self.chart = chart.df + if self.feature_extractor.for_medications: + med = Medications(use_icu=self.feature_extractor.use_icu, df=self.chart) + med.read_length(self.cohort) + self.med = med.df + print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") + elif self.target_type == TargetType.LOS: + if self.feature_extractor.for_diagnoses: + dia = Diagnoses(use_icu=self.feature_extractor.use_icu, df=self.dia) + dia.los_length(self.cohort) + self.dia = dia.df + if self.feature_extractor.for_procedures: + proc = Procedures(use_icu=self.feature_extractor.use_icu, df=self.proc) + proc.los_length(self.cohort, self.include_time) + self.proc = proc.df + if ( + self.feature_extractor.use_icu + and self.feature_extractor.for_output_events + ): + out = OutputEvents(df=self.out) + out.los_length(self.cohort, self.include_time) + self.out = out.df + if ( + self.feature_extractor.use_icu + and self.feature_extractor.for_chart_events + ): + chart = Chart(df=self.chart) + chart.los_length(self.cohort, self.include_time) + self.chart = chart.df + if self.feature_extractor.for_medications: + med = Medications(use_icu=self.feature_extractor.use_icu, df=self.med) + med.los_length(self.cohort, self.include_time) + self.med = med.df + print("[ PROCESSED TIME SERIES TO EQUAL LENGTH ]") + + def smooth_ini(self): + if self.feature_extractor.for_medications: + self.med = self.med.sort_values(by=["start_time"]) + if self.feature_extractor.for_procedures: + self.proc = self.proc.sort_values(by=["start_time"]) + if self.feature_extractor.for_output_events and self.feature_extractor.use_icu: + self.out = self.out.sort_values(by=["start_time"]) + if self.feature_extractor.for_chart_events and self.feature_extractor.use_icu: + self.chart = self.chart.sort_values(by=["start_time"]) + + return + + def smooth_tqdm(self): + final_proc = pd.DataFrame() + final_out = pd.DataFrame() + final_chart = pd.DataFrame() + final_meds = pd.DataFrame() + final_lab = pd.DataFrame() + t = 0 + for i in tqdm(range(0, self.include_time, self.bucket)): + if self.feature_extractor.for_medications: + med = Medications(use_icu=self.feature_extractor.use_icu, df=self.med) + sub_meds = med.smooth_meds_step(self.bucket, i, t) + if final_meds.empty: + final_meds = sub_meds + else: + final_meds = pd.concat([final_meds, sub_meds], ignore_index=True) + + if self.feature_extractor.for_procedures: + proc = Procedures(use_icu=self.feature_extractor.use_icu, df=self.proc) + sub_proc = proc.smooth_meds_step(self.bucket, i, t) + if final_proc.empty: + final_proc = sub_proc + else: + final_proc = pd.concat([final_proc, sub_proc], ignore_index=True) + + if ( + self.feature_extractor.for_output_events + and self.feature_extractor.use_icu + ): + out = OutputEvents(df=self.out) + sub_out = out.smooth_meds_step(self.bucket, i, t) + if final_out.empty: + final_out = sub_out + else: + final_out = pd.concat([final_out, sub_out], ignore_index=True) + + if ( + self.feature_extractor.for_chart_events + and self.feature_extractor.use_icu + ): + chart = Chart(df=self.chart) + sub_chart = chart.smooth_meds_step(self.bucket, i, t) + if final_chart.empty: + final_chart = sub_chart + else: + final_chart = pd.concat([final_chart, sub_chart], ignore_index=True) + + if self.feature_extractor.for_labs and not self.feature_extractor.use_icu: + lab = Lab(df=self.lab) + sub_lab = lab.smooth_meds_step(self.bucket, i, t) + if final_lab.empty: + final_lab = sub_lab + else: + final_lab = pd.concat([final_lab, sub_lab], ignore_index=True) + t = t + 1 + los = int(self.include_time / self.bucket) + + if self.feature_extractor.for_medications: + f2_meds = final_meds.groupby( + ["stay_id", "itemid", "orderid"] + if self.feature_extractor.use_icu + else ["hadm_id", "drug_name"] + ).size() + self.med_per_adm = ( + f2_meds.groupby( + "stay_id" if self.feature_extractor.use_icu else "hadm_id" + ) + .sum() + .reset_index()[0] + .max() + ) + self.medlength_per_adm = ( + final_meds.groupby( + "stay_id" if self.feature_extractor.use_icu else "hadm_id" + ) + .size() + .max() + ) + if self.feature_extractor.for_procedures: + f2_proc = final_proc.groupby( + ["stay_id", "itemid"] + if self.feature_extractor.use_icu + else ["hadm_id", "icd_code"] + ).size() + self.proc_per_adm = ( + f2_proc.groupby( + "stay_id" if self.feature_extractor.use_icu else "hadm_id" + ) + .sum() + .reset_index()[0] + .max() + ) + self.proclength_per_adm = ( + final_proc.groupby( + "stay_id" if self.feature_extractor.use_icu else "hadm_id" + ) + .size() + .max() + ) + if self.feature_extractor.use_icu: + if self.feature_extractor.for_output_events: + f2_out = final_out.groupby(["stay_id", "itemid"]).size() + self.out_per_adm = ( + f2_out.groupby( + "stay_id" if self.feature_extractor.use_icu else "hadm_id" + ) + .sum() + .reset_index()[0] + .max() + ) + self.outlength_per_adm = ( + final_out.groupby( + "stay_id" if self.feature_extractor.use_icu else "hadm_id" + ) + .size() + .max() + ) + if self.feature_extractor.for_chart_events: + f2_chart = final_chart.groupby(["stay_id", "itemid"]).size() + self.chart_per_adm = ( + f2_chart.groupby("stay_id").sum().reset_index()[0].max() + ) + self.chartlength_per_adm = final_chart.groupby("stay_id").size().max() + else: + if self.feature_extractor.for_procedures: + f2_labs = final_lab.groupby(["hadm_id", "itemid"]).size() + self.labs_per_adm = ( + f2_labs.groupby("hadm_id").sum().reset_index()[0].max() + ) + self.labslength_per_adm = final_lab.groupby("hadm_id").size().max() + + dict_maker = DictMaker( + self.feature_extractor, + self.hids, + self.med_per_adm, + self.out_per_adm, + self.chart_per_adm, + self.dia_per_adm, + self.proc_per_adm, + self.labs_per_adm, + ) + dict_maker.create_dict( + self.dia, + final_meds, + final_proc, + final_out, + final_lab, + final_chart, + self.cohort, + los, + ) + dict_maker.save_dictionaries( + self.dia, + final_meds, + final_proc, + final_out, + final_lab, + final_chart, + self.cohort, + los, + ) diff --git a/pipeline/dict_maker.py b/pipeline/dict_maker.py new file mode 100644 index 0000000000..045157892c --- /dev/null +++ b/pipeline/dict_maker.py @@ -0,0 +1,559 @@ +from typing import Dict +import numpy as np +import pandas as pd +from tqdm import tqdm +import pickle +import os +from pipeline.file_info.common import PREPROC_PATH + +import logging + +from pipeline.features_extractor import FeatureExtractor + +logger = logging.getLogger() + +CSVPATH = PREPROC_PATH / "csv" +DICT_PATH = PREPROC_PATH / "dict" + + +class DictMaker: + def __init__( + self, + feature_extractor: FeatureExtractor, + hids, + med_per_adm, + out_per_adm, + chart_per_adm, + dia_per_adm, + proc_per_adm, + labs_per_adm, + ): + self.feature_extractor = feature_extractor + self.hids = hids + self.med_per_adm = med_per_adm + self.out_per_adm = out_per_adm + self.chart_per_adm = chart_per_adm + self.dia_per_adm = dia_per_adm + self.proc_per_adm = proc_per_adm + self.labs_per_adm = labs_per_adm + + def create_dict( + self, diag, meds, proc, out, labs, chart, cohort: pd.DataFrame, los + ): + group_col = "stay_id" if self.feature_extractor.use_icu else "hadm_id" + self.dataDic = {} + self.labels_csv = pd.DataFrame(columns=[group_col, "label"]) + self.labels_csv[group_col] = pd.Series(self.hids) + self.labels_csv["label"] = 0 + + for hid in self.hids: + grp = cohort[cohort[group_col] == hid] + if len(grp) == 0: + self.dataDic[hid] = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Out": {}, + "Chart": {}, + "Lab": {}, + "ethnicity": {}, + "age": {}, + "gender": {}, + "label": {}, + } + + else: + self.dataDic[hid] = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Out": {}, + "Chart": {}, + "Lab": {}, + "ethnicity": grp["ethnicity"].iloc[0], + "age": int(grp["age"].iloc[0]), + "gender": grp["gender"].iloc[0], + "label": int(grp["label"].iloc[0]), + } + self.labels_csv.loc[self.labels_csv[group_col] == hid, "label"] = int( + grp["label"].iloc[0] + ) + for hid in tqdm(self.hids): + grp = cohort[cohort[group_col] == hid] + self.demo_csv = grp[["age", "gender", "ethnicity", "insurance"]] + if not os.path.exists(CSVPATH / str(hid)): + os.makedirs(CSVPATH / str(hid)) + self.demo_csv.to_csv(CSVPATH / str(hid) / "demo.csv", index=False) + + dyn_csv = pd.DataFrame() + if self.feature_extractor.for_medications: + self.process_med_by_hid(meds, los, hid, dyn_csv) + if self.feature_extractor.for_procedures: + self.process_proc_by_hid(proc, los, hid, dyn_csv) + if self.feature_extractor.for_labs and not self.feature_extractor.use_icu: + self.process_lab_by_hid(labs, los, hid, dyn_csv) + if self.feature_extractor.for_labs and self.feature_extractor.use_icu: + self.process_out_by_hid(out, los, hid, dyn_csv) + if ( + self.feature_extractor.for_chart_events + and self.feature_extractor.use_icu + ): + self.process_chart_by_hid(chart, los, hid, dyn_csv) + # self.save_csv_files() + dyn_csv.to_csv(CSVPATH / str(hid) / "dynamic.csv", index=False) + if self.feature_extractor.for_diagnoses: + self.process_dia_by_hid(diag, los, hid, dyn_csv) + + grp.to_csv(CSVPATH / str(hid) / "static.csv", index=False) + self.labels_csv.to_csv(CSVPATH / str(hid) / "labels.csv", index=False) + + def process_med_by_hid(self, feature, los, hid, dyn_csv): + group_col = "stay_id" if self.feature_extractor.use_icu else "hadm_id" + code_col = "itemid" if self.feature_extractor.use_icu else "drug_name" + feat = feature[code_col].unique() + df2 = feature[feature[group_col] == hid] + if df2.shape[0] == 0: + if self.feature_extractor.use_icu: + amount = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + amount = amount.fillna(0) + amount.columns = pd.MultiIndex.from_product([["MEDS"], amount.columns]) + else: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + else: + if self.feature_extractor.use_icu: + rate = df2.pivot_table( + index="start_time", columns="itemid", values="rate" + ) + amount = df2.pivot_table( + index="start_time", columns="itemid", values="amount" + ) + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="stop_time" + ) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.ffill() + df2 = df2.fillna(0) + rate = pd.concat([rate, add_df]) + rate = rate.sort_index() + rate = rate.ffill() + rate = rate.fillna(-1) + amount = pd.concat([amount, add_df]) + amount = amount.sort_index() + amount = amount.ffill() + amount = amount.fillna(-1) + df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + rate.iloc[:, 0:] = df2.iloc[:, 0:] * rate.iloc[:, 0:] + amount.iloc[:, 0:] = df2.iloc[:, 0:] * amount.iloc[:, 0:] + self.dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + self.dataDic[hid]["Med"]["rate"] = rate.iloc[:, 0:].to_dict( + orient="list" + ) + self.dataDic[hid]["Med"]["amount"] = amount.iloc[:, 0:].to_dict( + orient="list" + ) + feat_df = pd.DataFrame(columns=list(set(feat) - set(amount.columns))) + amount = pd.concat([amount, feat_df], axis=1) + amount = amount[feat] + amount = amount.fillna(0) + amount.columns = pd.MultiIndex.from_product([["MEDS"], amount.columns]) + else: + val = df2.pivot_table( + index="start_time", columns="drug_name", values="dose_val_rx" + ) + df2 = df2.pivot_table( + index="start_time", columns="drug_name", values="stop_time" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.ffill() + df2 = df2.fillna(0) + val = pd.concat([val, add_df]) + val = val.sort_index() + val = val.ffill() + val = val.fillna(-1) + df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + val.iloc[:, 0:] = df2.iloc[:, 0:] * val.iloc[:, 0:] + self.dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + self.dataDic[hid]["Med"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + + if self.feature_extractor.use_icu: + if dyn_csv.empty: + dyn_csv = amount + else: + dyn_csv = pd.concat([dyn_csv, amount], axis=1) + else: + if dyn_csv.empty: + dyn_csv = val + else: + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + def process_proc_by_hid(self, feature, los, hid, dyn_csv): + group_col = "stay_id" if self.feature_extractor.use_icu else "hadm_id" + code_col = "itemid" if self.feature_extractor.use_icu else "icd_code" + df2 = feature[feature[group_col] == hid] + feat = feature[code_col].unique() + if self.feature_extractor.use_icu: + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + else: + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + self.dataDic[hid]["Proc"] = df2.to_dict(orient="list") + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + else: + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + else: + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="icd_code", values="val" + ) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + self.dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 + else: + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + + def process_out_by_hid(self, feature, los, hid, dyn_csv): + feat = feature["itemid"].unique() + df2 = feature[feature["stay_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + else: + df2["val"] = 1 + df2 = df2.pivot_table(index="start_time", columns="itemid", values="val") + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + self.dataDic[hid]["Out"] = df2.to_dict(orient="list") + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + if dyn_csv.empty: + dyn_csv = df2 + else: + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + + def process_chart_by_hid(self, feature, los, hid, dyn_csv): + feat = feature["itemid"].unique() + df2 = feature[feature["stay_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + else: + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table(index="start_time", columns="itemid", values="val") + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + self.dataDic[hid]["Chart"]["signal"] = df2.iloc[:, 0:].to_dict( + orient="list" + ) + self.dataDic[hid]["Chart"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + + if dyn_csv.empty: + dyn_csv = val + else: + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + def process_lab_by_hid(self, feature, los, hid, dyn_csv): + feat = feature["itemid"].unique() + df2 = feature[feature["hadm_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + else: + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table(index="start_time", columns="itemid", values="val") + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + + # print(df2.head()) + self.dataDic[hid]["Lab"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + self.dataDic[hid]["Lab"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + + if dyn_csv.empty: + dyn_csv = val + else: + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + # if dyn_csv.empty: + # dyn_csv = amount_or_val + # else: + # dyn_csv = pd.concat([dyn_csv, amount_or_val], axis=1) + + def process_dia_by_hid(self, feature, los, hid, dyn_csv): + if self.feature_extractor.use_icu: + feat = feature["new_icd_code"].unique() + grp = feature[feature["stay_id"] == hid] + if grp.shape[0] == 0: + self.dataDic[hid]["Cond"] = {"fids": list([""])} + feat_df = pd.DataFrame(np.zeros([1, len(feat)]), columns=feat) + grp = feat_df.fillna(0) + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + else: + self.dataDic[hid]["Cond"] = {"fids": list(grp["new_icd_code"])} + grp["val"] = 1 + grp = grp.drop_duplicates() + grp = grp.pivot( + index="stay_id", columns="new_icd_code", values="val" + ).reset_index(drop=True) + feat_df = pd.DataFrame(columns=list(set(feat) - set(grp.columns))) + grp = pd.concat([grp, feat_df], axis=1) + grp = grp.fillna(0) + grp = grp[feat] + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + else: + feat = feature["new_icd_code"].unique() + grp = feature[feature["hadm_id"] == hid].copy() + if grp.shape[0] == 0: + self.dataDic[hid]["Cond"] = {"fids": list([""])} + feat_df = pd.DataFrame(np.zeros([1, len(feat)]), columns=feat) + grp = feat_df.fillna(0) + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + else: + self.dataDic[hid]["Cond"] = {"fids": list(grp["new_icd_code"])} + grp["val"] = 1 + grp = grp.drop_duplicates() + grp = grp.pivot( + index="hadm_id", columns="new_icd_code", values="val" + ).reset_index(drop=True) + feat_df = pd.DataFrame(columns=list(set(feat) - set(grp.columns))) + grp = pd.concat([grp, feat_df], axis=1) + grp = grp.fillna(0) + grp = grp[feat] + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + + def save_dictionaries( + self, diag, meds, proc, out, labs, chart, cohort: pd.DataFrame, los + ): + if self.feature_extractor.use_icu: + metaDic = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Out": {}, + "Chart": {}, + "LOS": {}, + } + metaDic["LOS"] = los + with open(DICT_PATH / "dataDic", "wb") as fp: + pickle.dump(self.dataDic, fp) + + with open(DICT_PATH / "hadmDic", "wb") as fp: + pickle.dump(self.hids, fp) + + with open(DICT_PATH / "ethVocab", "wb") as fp: + pickle.dump(list(cohort["ethnicity"].unique()), fp) + self.eth_vocab = cohort["ethnicity"].nunique() + + with open(DICT_PATH / "ageVocab", "wb") as fp: + pickle.dump(list(cohort["age"].unique()), fp) + self.age_vocab = cohort["age"].nunique() + + with open(DICT_PATH / "insVocab", "wb") as fp: + pickle.dump(list(cohort["insurance"].unique()), fp) + self.ins_vocab = cohort["insurance"].nunique() + + if self.feature_extractor.for_medications: + with open(DICT_PATH / "medVocab", "wb") as fp: + pickle.dump(list(meds["itemid"].unique()), fp) + self.med_vocab = meds["itemid"].nunique() + metaDic["Med"] = self.med_per_adm + + if self.feature_extractor.for_output_events: + with open(DICT_PATH / "outVocab", "wb") as fp: + pickle.dump(list(out["itemid"].unique()), fp) + self.out_vocab = out["itemid"].nunique() + metaDic["Out"] = self.out_per_adm + + if self.feature_extractor.for_chart_events: + with open(DICT_PATH / "chartVocab", "wb") as fp: + pickle.dump(list(chart["itemid"].unique()), fp) + self.chart_vocab = chart["itemid"].nunique() + metaDic["Chart"] = self.chart_per_adm + + if self.feature_extractor.for_diagnoses: + with open(DICT_PATH / "condVocab", "wb") as fp: + pickle.dump(list(diag["new_icd_code"].unique()), fp) + self.cond_vocab = diag["new_icd_code"].nunique() + metaDic["Cond"] = self.dia_per_adm + + if self.feature_extractor.for_procedures: + with open(DICT_PATH / "procVocab", "wb") as fp: + pickle.dump(list(proc["itemid"].unique()), fp) + self.proc_vocab = proc["itemid"].nunique() + metaDic["Proc"] = self.proc_per_adm + + with open(DICT_PATH / "metaDic", "wb") as fp: + pickle.dump(metaDic, fp) + else: + metaDic = {"Cond": {}, "Proc": {}, "Med": {}, "Lab": {}, "LOS": {}} + metaDic["LOS"] = los + with open(DICT_PATH / "dataDic", "wb") as fp: + pickle.dump(self.dataDic, fp) + + with open(DICT_PATH / "hadmDic", "wb") as fp: + pickle.dump(self.hids, fp) + + with open(DICT_PATH / "ethVocab", "wb") as fp: + pickle.dump(list(cohort["ethnicity"].unique()), fp) + self.eth_vocab = cohort["ethnicity"].nunique() + + with open(DICT_PATH / "ageVocab", "wb") as fp: + pickle.dump(list(cohort["age"].unique()), fp) + self.age_vocab = cohort["age"].nunique() + + with open(DICT_PATH / "insVocab", "wb") as fp: + pickle.dump(list(cohort["insurance"].unique()), fp) + self.ins_vocab = cohort["insurance"].nunique() + + if self.feature_extractor.for_medications: + with open(DICT_PATH / "medVocab", "wb") as fp: + pickle.dump(list(meds["drug_name"].unique()), fp) + self.med_vocab = meds["drug_name"].nunique() + metaDic["Med"] = self.med_per_adm + + if self.feature_extractor.for_diagnoses: + with open(DICT_PATH / "condVocab", "wb") as fp: + pickle.dump(list(diag["new_icd_code"].unique()), fp) + self.cond_vocab = diag["new_icd_code"].nunique() + metaDic["Cond"] = self.dia_per_adm + + if self.feature_extractor.for_procedures: + with open(DICT_PATH / "procVocab", "wb") as fp: + pickle.dump(list(proc["icd_code"].unique()), fp) + self.proc_vocab = proc["icd_code"].unique() + metaDic["Proc"] = self.proc_per_adm + + if self.feature_extractor.for_labs: + with open(DICT_PATH / "labsVocab", "wb") as fp: + pickle.dump(list(labs["itemid"].unique()), fp) + self.lab_vocab = labs["itemid"].unique() + metaDic["Lab"] = self.labs_per_adm + + with open(DICT_PATH / "metaDic", "wb") as fp: + pickle.dump(metaDic, fp) diff --git a/pipeline/feature/chart_events.py b/pipeline/feature/chart_events.py new file mode 100644 index 0000000000..6d555e65e9 --- /dev/null +++ b/pipeline/feature/chart_events.py @@ -0,0 +1,216 @@ +from tqdm import tqdm +from pipeline.feature.feature_abc import Feature, Name +import logging +import pandas as pd +from pipeline.preprocessing.outlier_removal import outlier_imputation +from pipeline.file_info.preproc.feature import ChartEventsHeader +from pipeline.file_info.preproc.cohort import IcuCohortHeader +from pipeline.file_info.raw.icu import ( + ICU_CHART_EVENTS_PATH, + load_icu_chart_events, + ChartEvents, +) + +from pipeline.conversion.uom import drop_wrong_uom + +logger = logging.getLogger() + + +class Chart(Feature): + def __init__(self, df: pd.DataFrame = pd.DataFrame(), chunksize: int = 10000000): + self.df = df + self.chunksize = chunksize + self.final_df = pd.DataFrame() + + def name() -> str: + return Name.CHART + + def df(self) -> pd.DataFrame: + return self.df + + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + """Function for processing hospital observations from a pickled cohort, optimized for memory efficiency.""" + logger.info("[EXTRACTING CHART EVENTS DATA]") + processed_chunks = [ + self.process_chunk_chart_events(chunk, cohort) + for chunk in tqdm(load_icu_chart_events(self.chunksize)) + ] + chart = pd.concat(processed_chunks, ignore_index=True) + + """Log statistics about the chart events before drop.""" + logger.info(f"# Unique Events: {chart[ChartEventsHeader.ITEM_ID].nunique()}") + logger.info(f"# Admissions: {chart[ChartEventsHeader.STAY_ID].nunique()}") + logger.info(f"Total rows: {chart.shape[0]}") + + chart = drop_wrong_uom(chart, 0.95) + """Log statistics about the chart events.""" + logger.info(f"# Unique Events: {chart[ChartEventsHeader.ITEM_ID].nunique()}") + logger.info(f"# Admissions: {chart[ChartEventsHeader.STAY_ID].nunique()}") + logger.info(f"Total rows: {chart.shape[0]}") + chart = chart[[h.value for h in ChartEventsHeader]] + self.df = chart + return chart + + def process_chunk_chart_events( + self, chunk: pd.DataFrame, cohort: pd.DataFrame + ) -> pd.DataFrame: + """Process a single chunk of chart events.""" + chunk = chunk.dropna(subset=[ChartEvents.VALUENUM]) + chunk = chunk.merge( + cohort[[IcuCohortHeader.STAY_ID, IcuCohortHeader.IN_TIME]], + on=ChartEvents.STAY_ID, + ) + chunk[ChartEventsHeader.EVENT_TIME_FROM_ADMIT] = ( + chunk[ChartEvents.CHARTTIME] - chunk[IcuCohortHeader.IN_TIME] + ) + chunk = chunk.drop(["charttime", "intime"], axis=1) + chunk = chunk.dropna() + chunk = chunk.drop_duplicates() + return chunk + + def summary(self): + chart: pd.DataFrame = self.df + freq = ( + chart.groupby([ChartEventsHeader.STAY_ID, ChartEventsHeader.ITEM_ID]) + .size() + .reset_index(name="mean_frequency") + ) + freq = ( + freq.groupby([ChartEventsHeader.ITEM_ID])["mean_frequency"] + .mean() + .reset_index() + ) + + missing = ( + chart[chart[ChartEventsHeader.VALUE_NUM] == 0] + .groupby(ChartEventsHeader.ITEM_ID) + .size() + .reset_index(name="missing_count") + ) + total = ( + chart.groupby(ChartEventsHeader.ITEM_ID) + .size() + .reset_index(name="total_count") + ) + summary = pd.merge(missing, total, on=ChartEventsHeader.ITEM_ID, how="right") + summary = pd.merge(freq, summary, on=ChartEventsHeader.ITEM_ID, how="right") + summary = summary.fillna(0) + return summary + + def preproc(self): + pass + + def impute_outlier(self, impute, thresh, left_thresh): + logger.info("[PROCESSING CHART EVENTS DATA]") + self.df = outlier_imputation( + self.df, + ChartEventsHeader.ITEM_ID, + ChartEventsHeader.VALUE_NUM, + thresh, + left_thresh, + impute, + ) + + logger.info("Total number of rows", self.df.shape[0]) + logger.info("[SUCCESSFULLY SAVED CHART EVENTS DATA]") + return self.df + + def generate_fun(self, cohort): + processed_chunks = [] + for chart in tqdm(self.df): + chart = chart[chart["stay_id"].isin(cohort["stay_id"])].copy() + # Convert 'event_time_from_admit' to numeric total hours + time_parts = chart["event_time_from_admit"].str.extract( + r"(\d+) days (\d+):(\d+):(\d+)" + ) + chart["start_time"] = pd.to_numeric(time_parts[0]) * 24 + pd.to_numeric( + time_parts[1] + ) + chart = pd.merge( + chart, cohort[["stay_id", "los"]], on="stay_id", how="left" + ) + chart = chart[chart["los"] - chart["start_time"] > 0] + chart = chart.drop(columns=["event_time_from_admit", "los"]) + processed_chunks.append(chart) + final = pd.concat(processed_chunks, ignore_index=True) + self.df = final + return final + + def mortality_length(self, cohort, include_time): + self.df = self.df[self.df["stay_id"].isin(cohort["stay_id"])] + self.df = self.df[self.df["start_time"] <= include_time] + return self.df + + def los_length(self, cohort, include_time): + self.df = self.df[self.df["stay_id"].isin(cohort["stay_id"])] + self.df = self.df[self.df["start_time"] <= include_time] + return self.df + + def read_length(self, cohort): + self.df = self.df[self.df["stay_id"].isin(cohort["stay_id"])] + self.df = pd.merge( + self.df, cohort[["stay_id", "select_time"]], on="stay_id", how="left" + ) + self.df["start_time"] = self.df["start_time"] - self.df["select_time"] + self.df = self.df[self.df["start_time"] >= 0] + return self.df + + def smooth_meds_step(self, bucket, i, t): + sub_chart = ( + self.df[(self.df["start_time"] >= i) & (self.df["start_time"] < i + bucket)] + .groupby(["stay_id", "itemid"]) + .agg({"valuenum": "mean"}) + ) + sub_chart = sub_chart.reset_index() + sub_chart["start_time"] = t + return sub_chart + + # def smooth_meds(self): + # f2_df = self.final_df.groupby(["stay_id", "itemid"]).size() + # df_per_adm = f2_df.groupby("stay_id").sum().reset_index()[0].max() + # dflength_per_adm = self.final_df.groupby("stay_id").size().max() + # return f2_df, df_per_adm, dflength_per_adm + + # def dict_step(self, hid, los, dataDic): + # feat = self.final_df["itemid"].unique() + # df2 = self.final_df[self.final_df["stay_id"] == hid] + # if df2.shape[0] == 0: + # val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + # val = val.fillna(0) + # val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + # else: + # val = df2.pivot_table( + # index="start_time", columns="itemid", values="valuenum" + # ) + # df2["val"] = 1 + # df2 = df2.pivot_table(index="start_time", columns="itemid", values="val") + # add_indices = pd.Index(range(los)).difference(df2.index) + # add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + # df2 = pd.concat([df2, add_df]) + # df2 = df2.sort_index() + # df2 = df2.fillna(0) + + # val = pd.concat([val, add_df]) + # val = val.sort_index() + # if self.impute == "Mean": + # val = val.ffill() + # val = val.bfill() + # val = val.fillna(val.mean()) + # elif self.impute == "Median": + # val = val.ffill() + # val = val.bfill() + # val = val.fillna(val.median()) + # val = val.fillna(0) + + # df2[df2 > 0] = 1 + # df2[df2 < 0] = 0 + # dataDic[hid]["Chart"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + # dataDic[hid]["Chart"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + # feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + # val = pd.concat([val, feat_df], axis=1) + + # val = val[feat] + # val = val.fillna(0) + # val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + # return val diff --git a/pipeline/feature/diagnoses.py b/pipeline/feature/diagnoses.py new file mode 100644 index 0000000000..970a64d545 --- /dev/null +++ b/pipeline/feature/diagnoses.py @@ -0,0 +1,122 @@ +from enum import StrEnum +from pipeline.conversion.icd import IcdConverter +from pipeline.feature.feature_abc import Feature, Name +import logging +import pandas as pd +from pipeline.file_info.preproc.feature import ( + DiagnosesHeader, + DiagnosesIcuHeader, +) +from pipeline.file_info.preproc.cohort import CohortHeader, IcuCohortHeader +from pipeline.file_info.preproc.feature import PreprocDiagnosesHeader +from pipeline.file_info.raw.hosp import load_hosp_diagnosis_icd + +logger = logging.getLogger() + + +class IcdGroupOption(StrEnum): + KEEP = "Keep both ICD-9 and ICD-10 codes" + CONVERT = "Convert ICD-9 to ICD-10 codes" + GROUP = "Convert ICD-9 to ICD-10 and group ICD-10 codes" + + +MEAN_FREQUENCY_HEADER = "mean_frequency" + + +class Diagnoses(Feature): + def __init__(self, use_icu: bool, df: pd.DataFrame = pd.DataFrame()): + self.use_icu = use_icu + self.df = df + + def name() -> str: + return Name.DIAGNOSES + + def df(self) -> pd.DataFrame: + return self.df + + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + logger.info("[EXTRACTING DIAGNOSIS DATA]") + hosp_diagnose = load_hosp_diagnosis_icd() + admissions_cohort_cols = ( + [ + CohortHeader.HOSPITAL_ADMISSION_ID, + IcuCohortHeader.STAY_ID, + CohortHeader.LABEL, + ] + if self.use_icu + else [CohortHeader.HOSPITAL_ADMISSION_ID, CohortHeader.LABEL] + ) + diag = hosp_diagnose.merge( + cohort[admissions_cohort_cols], + on=DiagnosesHeader.HOSPITAL_ADMISSION_ID, + ) + icd_converter = IcdConverter() + diag = icd_converter.standardize_icd(diag) + diag = diag[ + [h.value for h in DiagnosesHeader] + + ([DiagnosesIcuHeader.STAY_ID] if self.use_icu else []) + ] + self.df = diag + return diag + + def preproc(self, group_diag_icd: IcdGroupOption) -> pd.DataFrame: + logger.info(f"[PROCESSING DIAGNOSIS DATA]") + preproc_code = { + IcdGroupOption.KEEP: DiagnosesHeader.ICD_CODE, + IcdGroupOption.CONVERT: DiagnosesHeader.ROOT_ICD10, + IcdGroupOption.GROUP: DiagnosesHeader.ROOT, + }.get(group_diag_icd) + self.df[PreprocDiagnosesHeader.NEW_ICD_CODE] = self.df[preproc_code] + self.df = self.df[ + [c for c in PreprocDiagnosesHeader] + + ([DiagnosesIcuHeader.STAY_ID] if self.use_icu else []) + ] + self.icd_group_option = group_diag_icd + logger.info(f"Total number of rows: {self.df.shape[0]}") + return self.df + + def summary(self): + diag: pd.DataFrame = self.df + group_column = ( + DiagnosesIcuHeader.STAY_ID + if self.use_icu + else DiagnosesHeader.HOSPITAL_ADMISSION_ID + ) + freq = diag.groupby([group_column, PreprocDiagnosesHeader.NEW_ICD_CODE]).size() + freq = freq.reset_index(name="mean_frequency") + mean_freq = freq.groupby(PreprocDiagnosesHeader.NEW_ICD_CODE)[ + "mean_frequency" + ].mean() + total = ( + diag.groupby(PreprocDiagnosesHeader.NEW_ICD_CODE) + .size() + .reset_index(name="total_count") + ) + summary = pd.merge( + mean_freq, total, on=PreprocDiagnosesHeader.NEW_ICD_CODE, how="right" + ) + summary = summary.fillna(0) + return summary + + def generate_fun(self, cohort: pd.DataFrame): + diag: pd.DataFrame = self.df + diag = diag[ + diag[DiagnosesHeader.HOSPITAL_ADMISSION_ID].isin( + cohort[CohortHeader.HOSPITAL_ADMISSION_ID] + ) + ] + diag_per_adm = diag.groupby(DiagnosesHeader.HOSPITAL_ADMISSION_ID).size().max() + self.df = diag + return diag, diag_per_adm + + def mortality_length(self, cohort): + col = "stay_id" if self.use_icu else "hadm_id" + self.df = self.df[self.df[col].isin(cohort[col])] + + def los_length(self, cohort): + col = "stay_id" if self.use_icu else "hadm_id" + self.df = self.df[self.df[col].isin(cohort[col])] + + def read_length(self, cohort): + col = "stay_id" if self.use_icu else "hadm_id" + self.df = self.df[self.df[col].isin(cohort[col])] diff --git a/pipeline/feature/feature_abc.py b/pipeline/feature/feature_abc.py new file mode 100644 index 0000000000..9d2c9fc8d1 --- /dev/null +++ b/pipeline/feature/feature_abc.py @@ -0,0 +1,56 @@ +from abc import ABC, abstractmethod, abstractproperty +import pandas as pd +from enum import StrEnum + + +# dictionaire d info avec les path, le name, les options der group et de clean? +# feature name pour les log +# extract_path, extract_summary_path, preproc_path, summary_path, (cleaned_path?) +class Name(StrEnum): + DIAGNOSES = "DIAGNOSES" + PROCEDURES = "PROCEDURES" + MEDICATIONS = "MEDICATIONS" + OUTPUT = "OUTPUT EVENTS" + CHART = "CHART EVENTS" + LAB = "LAB EVENTS" + + +class Feature(ABC): + @staticmethod + @abstractmethod + def name(): + pass + + """ + Abstract base class for a feature in the dataset. + Defines the structure and required methods for a feature. + """ + + @abstractproperty + def df(self): + return self.df + + @abstractproperty + def df(self): + return self.df + + @abstractmethod + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + """ + Generate the feature data and return it as a DataFrame. + """ + pass + + @abstractmethod + def preproc(self) -> None: + """ + Preprocess the feature data. + """ + pass + + @abstractmethod + def summary(self) -> None: + """ + Generate a summary of the feature. + """ + pass diff --git a/pipeline/feature/lab_events.py b/pipeline/feature/lab_events.py new file mode 100644 index 0000000000..92d8cbb04a --- /dev/null +++ b/pipeline/feature/lab_events.py @@ -0,0 +1,259 @@ +from tqdm import tqdm +from pipeline.preprocessing.admission_imputer import ( + INPUTED_HOSPITAL_ADMISSION_ID_HEADER, + impute_hadm_ids, +) +from pipeline.feature.feature_abc import Feature, Name +import logging +import pandas as pd +from pipeline.preprocessing.outlier_removal import outlier_imputation +from pipeline.file_info.preproc.feature import LabEventsHeader +from pipeline.file_info.preproc.cohort import CohortHeader, NonIcuCohortHeader +from pipeline.file_info.raw.hosp import ( + HospAdmissions, + HospLabEvents, + load_hosp_admissions, + load_hosp_lab_events, +) +from pipeline.file_info.common import save_data +from pipeline.conversion.uom import drop_wrong_uom + +logger = logging.getLogger() + + +class Lab(Feature): + def name() -> str: + return Name.LAB + + def __init__(self, df: pd.DataFrame = pd.DataFrame(), chunksize: int = 10000000): + self.df = df + self.chunksize = chunksize + self.final_df = pd.DataFrame() + + def df(self): + return self.df + + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + """Process and transform lab events data.""" + logger.info("[EXTRACTING LABS DATA]") + admissions = load_hosp_admissions()[ + [ + HospAdmissions.PATIENT_ID, + HospAdmissions.ID, + HospAdmissions.ADMITTIME, + HospAdmissions.DISCHTIME, + ] + ] + usecols = [ + HospLabEvents.ITEM_ID, + HospLabEvents.PATIENT_ID, + HospLabEvents.HOSPITAL_ADMISSION_ID, + HospLabEvents.CHART_TIME, + HospLabEvents.VALUE_NUM, + HospLabEvents.VALUE_UOM, + ] + processed_chunks = [ + self.process_lab_chunk(chunk, admissions, cohort) + for chunk in tqdm( + load_hosp_lab_events(chunksize=self.chunksize, use_cols=usecols) + ) + ] + labevents = pd.concat(processed_chunks, ignore_index=True) + labevents = labevents[[h.value for h in LabEventsHeader]] + self.df = labevents + return labevents + + def process_lab_chunk( + self, chunk: pd.DataFrame, admissions: pd.DataFrame, cohort: pd.DataFrame + ) -> pd.DataFrame: + """Process a single chunk of lab events.""" + chunk = chunk.dropna(subset=[HospLabEvents.VALUE_NUM]).fillna( + {HospLabEvents.VALUE_UOM: 0} + ) + chunk = chunk[ + chunk[LabEventsHeader.PATIENT_ID].isin(cohort[CohortHeader.PATIENT_ID]) + ] + chunk_with_hadm, chunk_no_hadm = ( + chunk[chunk[HospLabEvents.HOSPITAL_ADMISSION_ID].notna()], + chunk[chunk[HospLabEvents.HOSPITAL_ADMISSION_ID].isna()], + ) + chunk_imputed = impute_hadm_ids(chunk_no_hadm.copy(), admissions) + chunk_imputed[HospLabEvents.HOSPITAL_ADMISSION_ID] = chunk_imputed[ + INPUTED_HOSPITAL_ADMISSION_ID_HEADER + ] + chunk_imputed = chunk_imputed[ + [ + HospLabEvents.PATIENT_ID, + HospLabEvents.HOSPITAL_ADMISSION_ID, + HospLabEvents.ITEM_ID, + HospLabEvents.CHART_TIME, + HospLabEvents.VALUE_NUM, + HospLabEvents.VALUE_UOM, + ] + ] + merged_chunk = pd.concat([chunk_with_hadm, chunk_imputed], ignore_index=True) + return self.merge_with_cohort_and_calculate_lab_time(merged_chunk, cohort) + + # in utils? + def merge_with_cohort_and_calculate_lab_time( + self, chunk: pd.DataFrame, cohort: pd.DataFrame + ) -> pd.DataFrame: + """Merge chunk with cohort data and calculate the lab time from admit time.""" + chunk = chunk.merge( + cohort[ + [ + CohortHeader.HOSPITAL_ADMISSION_ID, + NonIcuCohortHeader.ADMIT_TIME, + NonIcuCohortHeader.DISCH_TIME, + ] + ], + on=LabEventsHeader.HOSPITAL_ADMISSION_ID, + ) + chunk[LabEventsHeader.CHART_TIME] = pd.to_datetime( + chunk[LabEventsHeader.CHART_TIME] + ) + chunk[LabEventsHeader.LAB_TIME_FROM_ADMIT] = ( + chunk[LabEventsHeader.CHART_TIME] - chunk[LabEventsHeader.ADMIT_TIME] + ) + return chunk.dropna() + + def preproc(self): + pass + + def impute_outlier(self, impute, thresh, left_thresh): + print("[PROCESSING LABS DATA]") + self.df = outlier_imputation( + self.df, + HospLabEvents.ITEM_ID, + HospLabEvents.VALUE_NUM, + thresh, + left_thresh, + impute, + ) + print("Total number of rows", self.df.shape[0]) + print("[SUCCESSFULLY SAVED LABS DATA]") + return self.df + + def summary(self): + labs: pd.DataFrame = self.df + freq = ( + labs.groupby( + [LabEventsHeader.HOSPITAL_ADMISSION_ID, LabEventsHeader.ITEM_ID] + ) + .size() + .reset_index(name="mean_frequency") + ) + freq = ( + freq.groupby([LabEventsHeader.ITEM_ID])["mean_frequency"] + .mean() + .reset_index() + ) + + missing = ( + labs[labs[LabEventsHeader.VALUE_NUM] == 0] + .groupby(LabEventsHeader.ITEM_ID) + .size() + .reset_index(name="missing_count") + ) + total = ( + labs.groupby(LabEventsHeader.ITEM_ID).size().reset_index(name="total_count") + ) + summary = pd.merge(missing, total, on=LabEventsHeader.ITEM_ID, how="right") + summary = pd.merge(freq, summary, on=LabEventsHeader.ITEM_ID, how="right") + summary["missing%"] = 100 * (summary["missing_count"] / summary["total_count"]) + summary = summary.fillna(0) + + return summary + + def generate_fun(self, cohort): + processed_chunks = [] + for labs in tqdm(self.df): + labs = labs[labs["hadm_id"].isin(cohort["hadm_id"])].copy() + # Process 'lab_time_from_admit' to numeric total hours + time_parts = labs["lab_time_from_admit"].str.extract( + r"(\d+) days (\d+):(\d+):(\d+)" + ) + labs["start_time"] = pd.to_numeric(time_parts[0]) * 24 + pd.to_numeric( + time_parts[1] + ) + labs = pd.merge(labs, cohort[["hadm_id", "los"]], on="hadm_id", how="left") + labs = labs[labs["los"] - labs["start_time"] > 0] + labs = labs.drop(columns=["lab_time_from_admit", "los"]) + processed_chunks.append(labs) + final = pd.concat(processed_chunks, ignore_index=True) + self.df = final + return final + + def mortality_length(self, cohort, include_time): + self.df = self.df[self.df["hadm_id"].isin(cohort["hadm_id"])] + self.df = self.df[self.df["start_time"] <= include_time] + + def los_length(self, cohort, include_time): + self.df = self.df[self.df["hadm_id"].isin(cohort["hadm_id"])] + self.df = self.df[self.df["start_time"] <= include_time] + + def read_length(self, cohort): + self.df = self.df[self.df["hadm_id"].isin(cohort["hadm_id"])] + + def smooth_meds_step(self, bucket, i, t): + sub_labs = ( + self.df[(self.df["start_time"] >= i) & (self.df["start_time"] < i + bucket)] + .groupby(["hadm_id", "itemid"]) + .agg({"subject_id": "max", "valuenum": "mean"}) + ) + sub_labs = sub_labs.reset_index() + sub_labs["start_time"] = t + return sub_labs + + # def smooth_meds(self): + # f2_df = self.final_df.groupby(["hadm_id", "itemid"]).size() + # df_per_adm = f2_df.groupby("hadm_id").sum().reset_index()[0].max() + # dflength_per_adm = self.final_df.groupby("hadm_id").size().max() + # return f2_df, df_per_adm, dflength_per_adm + + # def dict_step(self, hid, los, dataDic): + # feat = self.final_df["itemid"].unique() + # df2 = self.final_df[self.final_df["hadm_id"] == hid] + # if df2.shape[0] == 0: + # val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + # val = val.fillna(0) + # val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + # else: + # val = df2.pivot_table( + # index="start_time", columns="itemid", values="valuenum" + # ) + # df2["val"] = 1 + # df2 = df2.pivot_table(index="start_time", columns="itemid", values="val") + # # print(df2.shape) + # add_indices = pd.Index(range(los)).difference(df2.index) + # add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + # df2 = pd.concat([df2, add_df]) + # df2 = df2.sort_index() + # df2 = df2.fillna(0) + + # val = pd.concat([val, add_df]) + # val = val.sort_index() + # if self.impute == "Mean": + # val = val.ffill() + # val = val.bfill() + # val = val.fillna(val.mean()) + # elif self.impute == "Median": + # val = val.ffill() + # val = val.bfill() + # val = val.fillna(val.median()) + # val = val.fillna(0) + + # df2[df2 > 0] = 1 + # df2[df2 < 0] = 0 + + # # print(df2.head()) + # dataDic[hid]["Lab"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + # dataDic[hid]["Lab"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + # feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + # val = pd.concat([val, feat_df], axis=1) + + # val = val[feat] + # val = val.fillna(0) + # val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + # return val diff --git a/pipeline/feature/medications.py b/pipeline/feature/medications.py new file mode 100644 index 0000000000..81e45e09a9 --- /dev/null +++ b/pipeline/feature/medications.py @@ -0,0 +1,364 @@ +from pipeline.feature.feature_abc import Feature, Name +import logging +import pandas as pd +import numpy as np +from pipeline.conversion.ndc import ( + NdcMappingHeader, + get_EPC, + ndc_to_str, + prepare_ndc_mapping, +) +from pipeline.file_info.preproc.feature import ( + MedicationsHeader, + IcuMedicationHeader, + NonIcuMedicationHeader, + PreprocMedicationHeader, +) +from pipeline.file_info.preproc.cohort import ( + CohortHeader, + IcuCohortHeader, + NonIcuCohortHeader, +) +from pipeline.file_info.raw.hosp import ( + HospPrescriptions, + load_hosp_prescriptions, +) +from pipeline.file_info.raw.icu import ( + InputEvents, + load_input_events, +) + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + + +class Medications(Feature): + def __init__( + self, use_icu: bool, df: pd.DataFrame = pd.DataFrame(), group_code: bool = False + ): + self.use_icu = use_icu + self.group_code = group_code + self.df = df + self.final_df = pd.DataFrame() + self.admid = ( + IcuCohortHeader.STAY_ID + if self.use_icu + else CohortHeader.HOSPITAL_ADMISSION_ID + ) + + def name() -> str: + return Name.MEDICATIONS + + def df(self) -> pd.DataFrame: + return self.df + + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + logger.info(f"[EXTRACTING MEDICATIONS DATA]") + cohort_headers = ( + [ + CohortHeader.HOSPITAL_ADMISSION_ID, + IcuCohortHeader.STAY_ID, + IcuCohortHeader.IN_TIME, + ] + if self.use_icu + else [CohortHeader.HOSPITAL_ADMISSION_ID, NonIcuCohortHeader.ADMIT_TIME] + ) + admissions = cohort[cohort_headers] + raw_med = load_input_events() if self.use_icu else load_hosp_prescriptions() + medications = raw_med.merge( + admissions, + on=self.admid, + ) + admit_header = ( + IcuCohortHeader.IN_TIME if self.use_icu else NonIcuCohortHeader.ADMIT_TIME + ) + + medications[MedicationsHeader.START_HOURS_FROM_ADMIT] = ( + medications[InputEvents.STARTTIME] - medications[admit_header] + ) + medications[MedicationsHeader.STOP_HOURS_FROM_ADMIT] = ( + medications[ + InputEvents.ENDTIME if self.use_icu else HospPrescriptions.STOP_TIME + ] + - medications[admit_header] + ) + medications = ( + medications.dropna() + if self.use_icu + else self.normalize_non_icu(medications) + ) + self.log_medication_stats(medications) + cols = [h.value for h in MedicationsHeader] + [ + h.value + for h in (IcuMedicationHeader if self.use_icu else NonIcuMedicationHeader) + ] + medications = medications[cols] + self.df = medications + return medications + + def normalize_non_icu(self, med: pd.DataFrame) -> pd.DataFrame: + """ + Normalize medication data for non-ICU cases. + + Args: + med (pd.DataFrame): The medication dataframe. + + Returns: + pd.DataFrame: The normalized dataframe. + """ + med[NonIcuMedicationHeader.DRUG] = ( + med[NonIcuMedicationHeader.DRUG] + .fillna("") + .astype(str) + .str.lower() + .str.strip() + .str.replace(" ", "_") + ) + med[HospPrescriptions.NDC] = med[HospPrescriptions.NDC].fillna(-1) + med[HospPrescriptions.NDC] = med[HospPrescriptions.NDC].astype("Int64") + med[NdcMappingHeader.NEW_NDC] = med[HospPrescriptions.NDC].apply(ndc_to_str) + ndc_map = prepare_ndc_mapping() + med = med.merge(ndc_map, on=NdcMappingHeader.NEW_NDC, how="left") + med[NonIcuMedicationHeader.EPC] = med["pharm_classes"].apply(get_EPC) + return med + + def log_medication_stats(self, med: pd.DataFrame) -> None: + """ + Log statistics for medication data. + + Args: + med (pd.DataFrame): The medication dataframe. + """ + unique_drug_count = med[ + InputEvents.ITEMID if self.use_icu else NonIcuMedicationHeader.DRUG + ].nunique() + unique_admission_count = med[ + InputEvents.STAY_ID if self.use_icu else CohortHeader.HOSPITAL_ADMISSION_ID + ].nunique() + logger.info(f"Number of unique types of drugs: {unique_drug_count}") + if not self.use_icu: + logger.info( + f"Number of unique type of drug after grouping: {med[NonIcuMedicationHeader.NON_PROPRIEATARY_NAME].nunique()}" + ) + logger.info(f"Number of admissions: {unique_admission_count}") + logger.info(f"Total number of rows: {med.shape[0]}") + + def preproc(self, group_code: bool): + med: pd.DataFrame = self.df + logger.info("[PROCESSING MEDICATIONS DATA]") + med[PreprocMedicationHeader.DRUG_NAME] = ( + med[NonIcuMedicationHeader.NON_PROPRIEATARY_NAME] + if group_code + else med[NonIcuMedicationHeader.DRUG] + ) + med = med.drop( + columns=[ + NonIcuMedicationHeader.NON_PROPRIEATARY_NAME, + NonIcuMedicationHeader.DRUG, + ] + ) + med.dropna() + self.group_code = group_code + self.df = med + logger.info(f"Total number of rows: {med.shape[0]}") + return med + + def summary(self) -> pd.DataFrame: + med: pd.DataFrame = self.df + feature_name = ( + IcuMedicationHeader.ITEM_ID.value + if self.use_icu + else PreprocMedicationHeader.DRUG_NAME.value + ) + group_columns = ( + [IcuMedicationHeader.STAY_ID, IcuMedicationHeader.ITEM_ID] + if self.use_icu + else [ + MedicationsHeader.HOSPITAL_ADMISSION_ID, + PreprocMedicationHeader.DRUG_NAME, + ] + ) + freq = med.groupby(group_columns).size().reset_index(name="mean_frequency") + amount_column = ( + IcuMedicationHeader.AMOUNT + if self.use_icu + else NonIcuMedicationHeader.DOSE_VAL_RX + ) + missing = ( + med[med[amount_column] == 0] + .groupby(feature_name) + .size() + .reset_index(name="missing_count") + ) + total = med.groupby(feature_name).size().reset_index(name="total_count") + summary = pd.merge(missing, total, on=feature_name, how="right") + summary = pd.merge(freq, summary, on=feature_name, how="right") + summary["missing%"] = 100 * (summary["missing_count"] / summary["total_count"]) + summary = summary.fillna(0) + return summary + + def generate_fun(self, cohort: pd.DataFrame): + meds: pd.DataFrame = self.df + meds[["start_days", "dummy", "start_hours"]] = meds[ + "start_hours_from_admit" + ].str.split(" ", expand=True) + meds[["start_hours", "min", "sec"]] = meds["start_hours"].str.split( + ":", expand=True + ) + meds["start_time"] = pd.to_numeric(meds["start_days"]) * 24 + pd.to_numeric( + meds["start_hours"] + ) + meds[["start_days", "dummy", "start_hours"]] = meds[ + "stop_hours_from_admit" + ].str.split(" ", expand=True) + meds[["start_hours", "min", "sec"]] = meds["start_hours"].str.split( + ":", expand=True + ) + meds["stop_time"] = pd.to_numeric(meds["start_days"]) * 24 + pd.to_numeric( + meds["start_hours"] + ) + meds = meds.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) + #####Sanity check + meds["sanity"] = meds["stop_time"] - meds["start_time"] + meds = meds[meds["sanity"] > 0] + del meds["sanity"] + #####Select hadm_id as in main file + meds = meds[meds[self.admid].isin(cohort[self.admid])] + meds = pd.merge(meds, cohort[[self.admid, "los"]], on=self.admid, how="left") + + #####Remove where start time is after end of visit + meds["sanity"] = meds["los"] - meds["start_time"] + meds = meds[meds["sanity"] > 0] + del meds["sanity"] + ####Any stop_time after end of visit is set at end of visit + meds.loc[meds["stop_time"] > meds["los"], "stop_time"] = meds.loc[ + meds["stop_time"] > meds["los"], "los" + ] + del meds["los"] + if self.use_icu: + meds["rate"] = meds["rate"].apply(pd.to_numeric, errors="coerce") + meds["amount"] = meds["amount"].apply(pd.to_numeric, errors="coerce") + else: + meds["dose_val_rx"] = meds["dose_val_rx"].apply( + pd.to_numeric, errors="coerce" + ) + + self.df = meds + return meds + + def mortality_length(self, cohort, include_time): + self.df = self.df[self.df[self.admid].isin(cohort[self.admid])] + self.df = self.df[self.df["start_time"] <= include_time] + self.df.loc[self.df["stop_time"] > include_time, "stop_time"] = include_time + return self.df + + def los_length(self, cohort, include_time): + self.df = self.df[self.df[self.admid].isin(cohort[self.admid])] + self.df = self.df[self.df["start_time"] <= include_time] + self.df.loc[self.df["stop_time"] > include_time, "stop_time"] = include_time + return self.df + + def read_length(self, cohort): + self.df = self.df[self.df[self.admid].isin(cohort[self.admid])] + self.df = pd.merge( + self.df, cohort[[self.admid, "select_time"]], on=self.admid, how="left" + ) + self.df["stop_time"] = self.df["stop_time"] - self.df["select_time"] + + self.df["start_time"] = self.df["start_time"] - self.df["select_time"] + self.df = self.df[self.df["stop_time"] >= 0] + self.df.loc[self.df["start_time"] < 0, "start_time"] = 0 + return self.df + + def smooth_meds_step(self, bucket, i, t): + group_cols = ( + ["stay_id", "itemid", "orderid"] + if self.use_icu + else ["hadm_id", "drug_name"] + ) + agg_funcs = ( + {"stop_time": "max", "subject_id": "max", "rate": "mean", "amount": "mean"} + if self.use_icu + else {"stop_time": "max", "subject_id": "max", "dose_val_rx": "mean"} + ) + + sub_meds = self.df[ + (self.df["start_time"] >= i) & (self.df["start_time"] < i + bucket) + ] + sub_meds = sub_meds.groupby(group_cols).agg(agg_funcs).reset_index() + sub_meds["start_time"] = t + sub_meds["stop_time"] = sub_meds["stop_time"] / bucket + return sub_meds + + # def dict_step(self, hid, los, dataDic): + # feat = self.final_df["itemid" if self.use_icu else "drug_name"].unique() + # df2 = self.final_df[ + # self.final_df["stay_id" if self.use_icu else "hadm_id"] == hid + # ] + # if df2.shape[0] == 0: + # val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + # val = val.fillna(0) + # val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + # else: + # if self.use_icu: + # rate = df2.pivot_table( + # index="start_time", columns="itemid", values="rate" + # ) + # amount = df2.pivot_table( + # index="start_time", columns="itemid", values="amount" + # ) + # else: + # val = df2.pivot_table( + # index="start_time", columns="drug_name", values="dose_val_rx" + # ) + + # df2 = df2.pivot_table( + # index="start_time", + # columns="itemid" if self.use_icu else "drug_name", + # values="stop_time", + # ) + # add_indices = pd.Index(range(los)).difference(df2.index) + # add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + # df2 = pd.concat([df2, add_df]) + # df2 = df2.sort_index() + # df2 = df2.ffill() + # df2 = df2.fillna(0) + # if self.use_icu: + # rate = pd.concat([rate, add_df]) + # rate = rate.sort_index() + # rate = rate.ffill() + # rate = rate.fillna(-1) + # amount = pd.concat([amount, add_df]) + # amount = amount.sort_index() + # amount = amount.ffill() + # amount = amount.fillna(-1) + # else: + # val = pd.concat([val, add_df]) + # val = val.sort_index() + # val = val.ffill() + # val = val.fillna(-1) + + # df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + # df2[df2 > 0] = 1 + # df2[df2 < 0] = 0 + # val.iloc[:, 0:] = df2.iloc[:, 0:] * val.iloc[:, 0:] + # # print(df2.head()) + # if self.use_icu: + # dataDic.iloc[:, 0:].to_dict(orient="list") + # dataDic[hid]["Med"]["rate"] = rate.iloc[:, 0:].to_dict(orient="list") + # dataDic[hid]["Med"]["amount"] = amount.iloc[:, 0:].to_dict( + # orient="list" + # ) + # else: + # dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + # dataDic[hid]["Med"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + # feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + + # val = pd.concat([val, feat_df], axis=1) + + # val = val[feat] + # val = val.fillna(0) + + # val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + # return val diff --git a/pipeline/feature/output_events.py b/pipeline/feature/output_events.py new file mode 100644 index 0000000000..9cba3bd4f8 --- /dev/null +++ b/pipeline/feature/output_events.py @@ -0,0 +1,152 @@ +from pipeline.feature.feature_abc import Feature, Name +import logging +import pandas as pd +import numpy as np +from pipeline.file_info.preproc.feature import ( + OutputEventsHeader, +) +from pipeline.file_info.preproc.cohort import IcuCohortHeader +from pipeline.file_info.raw.icu import load_icu_output_events, OuputputEvents + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + + +class OutputEvents(Feature): + def __init__(self, df: pd.DataFrame = pd.DataFrame()): + self.df = df + self.final_df = pd.DataFrame() + + def name() -> str: + return Name.OUTPUT + + def df(self): + return self.df + + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + """Function for getting hosp observations pertaining to a pickled cohort. + Function is structured to save memory when reading and transforming data.""" + logger.info("[EXTRACTING OUTPUT EVENTS DATA]") + raw_out = load_icu_output_events() + out = raw_out.merge( + cohort[ + [ + IcuCohortHeader.STAY_ID, + IcuCohortHeader.IN_TIME, + IcuCohortHeader.OUT_TIME, + ] + ], + on=IcuCohortHeader.STAY_ID, + ) + out[OutputEventsHeader.EVENT_TIME_FROM_ADMIT] = ( + out[OuputputEvents.CHART_TIME] - out[IcuCohortHeader.IN_TIME] + ) + out = out.dropna() + + # Print unique counts and value_counts + logger.info(f"# Unique Events: {out[OuputputEvents.ITEM_ID].nunique()}") + logger.info(f"# Admissions: {out[OuputputEvents.STAY_ID].nunique()}") + logger.info(f"Total rows: {out.shape[0]}") + out = out[[h.value for h in OutputEventsHeader]] + self.df = out + return out + + def preproc(self): + pass + + def summary(self): + out: pd.DataFrame = self.df + freq = ( + out.groupby([OutputEventsHeader.STAY_ID, OutputEventsHeader.ITEM_ID]) + .size() + .reset_index(name="mean_frequency") + ) + freq = freq.groupby(["itemid"])["mean_frequency"].mean().reset_index() + total = ( + out.groupby(OutputEventsHeader.ITEM_ID) + .size() + .reset_index(name="total_count") + ) + summary = pd.merge(freq, total, on=OutputEventsHeader.ITEM_ID, how="right") + summary = summary.fillna(0) + return summary + + def generate_fun(self, cohort): + """ + Processes event times in the data, adjusting based on the cohort stay_id and length of stay (los). + """ + breakpoint() + out: pd.DataFrame = self.df[self.df["stay_id"].isin(cohort["stay_id"])].copy() + time_split = out["event_time_from_admit"].str.extract( + r"(\d+) days (\d+):(\d+):(\d+)" + ) + out["start_time"] = pd.to_numeric(time_split[0]) * 24 + pd.to_numeric( + time_split[1] + ) + # Removing entries where event time is after discharge time + out = out.merge(cohort[["stay_id", "los"]], on="stay_id", how="left") + out = out[out["los"] - out["start_time"] > 0] + out = out.drop(columns=["los", "event_time_from_admit"]) + + self.df = out + return out + + def mortality_length(self, cohort, include_time): + self.df = self.df[self.df["stay_id"].isin(cohort["stay_id"])] + self.df = self.df[self.df["start_time"] <= include_time] + + def los_length(self, cohort, include_time): + self.df = self.df[self.df["stay_id"].isin(cohort["stay_id"])] + self.df = self.df[self.df["start_time"] <= include_time] + + def read_length(self, cohort): + self.df = self.df[self.df["stay_id"].isin(cohort["stay_id"])] + self.df = pd.merge( + self.df, cohort[["stay_id", "select_time"]], on="stay_id", how="left" + ) + self.df["start_time"] = self.df["start_time"] - self.df["select_time"] + self.df = self.df[self.df["start_time"] >= 0] + + def smooth_meds_step(self, bucket, i, t): + sub_out = ( + self.df[(self.df["start_time"] >= i) & (self.df["start_time"] < i + bucket)] + .groupby(["stay_id", "itemid"]) + .agg({"subject_id": "max"}) + ) + sub_out = sub_out.reset_index() + sub_out["start_time"] = t + return sub_out + + # def smooth_meds(self): + # f2_df = self.final_df.groupby(["stay_id", "itemid"]).size() + # df_per_adm = f2_df.groupby("stay_id").sum().reset_index()[0].max() + # dflength_per_adm = self.final_df.groupby("stay_id").size().max() + # return f2_df, df_per_adm, dflength_per_adm + + # def dict_step(self, hid, los, dataDic): + # feat = self.final_df["itemid"].unique() + # df2 = self.final_df[self.final_df["stay_id"] == hid] + # if df2.shape[0] == 0: + # df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + # df2 = df2.fillna(0) + # df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + # else: + # df2["val"] = 1 + # df2 = df2.pivot_table(index="start_time", columns="itemid", values="val") + # # print(df2.shape) + # add_indices = pd.Index(range(los)).difference(df2.index) + # add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna(np.nan) + # df2 = pd.concat([df2, add_df]) + # df2 = df2.sort_index() + # df2 = df2.fillna(0) + # df2[df2 > 0] = 1 + # # print(df2.head()) + # dataDic[hid]["Out"] = df2.to_dict(orient="list") + + # feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + # df2 = pd.concat([df2, feat_df], axis=1) + + # df2 = df2[feat] + # df2 = df2.fillna(0) + # df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + # return df2 diff --git a/pipeline/feature/procedures.py b/pipeline/feature/procedures.py new file mode 100644 index 0000000000..e9d6e1b2d1 --- /dev/null +++ b/pipeline/feature/procedures.py @@ -0,0 +1,284 @@ +from pipeline.feature.feature_abc import Feature, Name +import logging +import pandas as pd +import numpy as np +from pipeline.file_info.preproc.feature import ( + ProceduresHeader, + IcuProceduresHeader, + NonIcuProceduresHeader, +) +from pipeline.file_info.preproc.cohort import ( + CohortHeader, + IcuCohortHeader, + NonIcuCohortHeader, +) +from pipeline.file_info.raw.hosp import HospProceduresIcd, load_hosp_procedures_icd +from pipeline.file_info.raw.icu import load_icu_procedure_events + +logger = logging.getLogger() + + +class Procedures(Feature): + def __init__( + self, use_icu: bool, df: pd.DataFrame = pd.DataFrame, keep_icd9: bool = True + ): + self.use_icu = use_icu + self.keep_icd9 = keep_icd9 + self.df = df + self.final_df = pd.DataFrame() + self.adm_id = ( + IcuCohortHeader.STAY_ID + if self.use_icu + else CohortHeader.HOSPITAL_ADMISSION_ID + ) + self.time_from_admit = ( + IcuProceduresHeader.EVENT_TIME_FROM_ADMIT + if self.use_icu + else NonIcuProceduresHeader.PROC_TIME_FROM_ADMIT + ) + + def name() -> str: + return Name.PROCEDURES + + def df(self): + return self.df + + def extract_from(self, cohort: pd.DataFrame) -> pd.DataFrame: + logger.info("[EXTRACTING PROCEDURES DATA]") + raw_procedures = ( + load_icu_procedure_events() if self.use_icu else load_hosp_procedures_icd() + ) + procedures = raw_procedures.merge( + cohort[ + [ + CohortHeader.PATIENT_ID, + CohortHeader.HOSPITAL_ADMISSION_ID, + IcuCohortHeader.STAY_ID, + IcuCohortHeader.IN_TIME, + IcuCohortHeader.OUT_TIME, + ] + if self.use_icu + else [ + CohortHeader.HOSPITAL_ADMISSION_ID, + NonIcuCohortHeader.ADMIT_TIME, + NonIcuCohortHeader.DISCH_TIME, + ] + ], + on=IcuCohortHeader.STAY_ID + if self.use_icu + else HospProceduresIcd.HOSPITAL_ADMISSION_ID, + ) + procedures[self.time_from_admit] = ( + procedures[ + IcuProceduresHeader.START_TIME + if self.use_icu + else NonIcuProceduresHeader.CHART_DATE + ] + - procedures[ + IcuProceduresHeader.IN_TIME + if self.use_icu + else NonIcuProceduresHeader.ADMIT_TIME + ] + ) + procedures = procedures.dropna() + self.log_icu(procedures) if self.use_icu else self.log_non_icu(procedures) + procedures = procedures[ + [h.value for h in ProceduresHeader] + + [ + h.value + for h in ( + IcuProceduresHeader if self.use_icu else NonIcuProceduresHeader + ) + ] + ] + self.df = procedures + return procedures + + def log_icu(self, procedures: pd.DataFrame) -> None: + logger.info( + f"# Unique Events: {procedures[IcuProceduresHeader.ITEM_ID].dropna().nunique()}" + ) + logger.info( + f"# Admissions: {procedures[IcuProceduresHeader.STAY_ID].nunique()}" + ) + logger.info(f"Total rows: {procedures.shape[0]}") + + def log_non_icu(self, procedures: pd.DataFrame) -> None: + for v in [9, 10]: + unique_procedures_count = ( + procedures.loc[procedures[NonIcuProceduresHeader.ICD_VERSION] == v][ + NonIcuProceduresHeader.ICD_CODE + ] + .dropna() + .nunique() + ) + logger.info(f" # Unique ICD{v} Procedures:{ unique_procedures_count}") + + logger.info( + f"\nValue counts of each ICD version:\n {procedures[NonIcuProceduresHeader.ICD_VERSION].value_counts()}" + ) + logger.info( + f"# Admissions:{procedures[CohortHeader.HOSPITAL_ADMISSION_ID].nunique()}" + ) + logger.info(f"Total number of rows: {procedures.shape[0]}") + + def preproc(self, keep_icd9: bool): + logger.info("[PROCESSING PROCEDURES DATA]") + proc = self.df.copy() + if not keep_icd9: + proc = proc.loc[proc[NonIcuProceduresHeader.ICD_VERSION] == 10] + proc = proc[ + [ + ProceduresHeader.PATIENT_ID, + ProceduresHeader.HOSPITAL_ADMISSION_ID, + NonIcuProceduresHeader.ICD_CODE, + NonIcuProceduresHeader.CHART_DATE, + NonIcuProceduresHeader.ADMIT_TIME, + NonIcuProceduresHeader.PROC_TIME_FROM_ADMIT, + ] + ] + if not keep_icd9: + proc = proc.dropna() + self.keep_icd9 = keep_icd9 + logger.info(f"Total number of rows: {proc.shape[0]}") + self.df = proc + return self.df + + def summary(self): + proc: pd.DataFrame = self.df + feature_name = ( + IcuProceduresHeader.ITEM_ID + if self.use_icu + else NonIcuProceduresHeader.ICD_CODE + ) + freq = ( + proc.groupby([self.adm_id, feature_name]) + .size() + .reset_index(name="mean_frequency") + ) + freq = freq.groupby(feature_name)["mean_frequency"].mean().reset_index() + total = proc.groupby(feature_name).size().reset_index(name="total_count") + summary = pd.merge(freq, total, on=feature_name, how="right") + summary = summary.fillna(0) + return summary + + def generate_fun(self, cohort: pd.DataFrame): + proc: pd.DataFrame = self.df + proc = proc[proc[self.adm_id].isin(cohort[self.adm_id])] + proc[["start_days", "dummy", "start_hours"]] = proc[ + self.time_from_admit + ].str.split(" ", expand=True) + proc[["start_hours", "min", "sec"]] = proc["start_hours"].str.split( + ":", expand=True + ) + proc["start_time"] = pd.to_numeric(proc["start_days"]) * 24 + pd.to_numeric( + proc["start_hours"] + ) + proc = proc.drop(columns=["start_days", "dummy", "start_hours", "min", "sec"]) + proc = proc[proc["start_time"] >= 0] + + ###Remove where event time is after discharge time + proc = pd.merge(proc, cohort[[self.adm_id, "los"]], on=self.adm_id, how="left") + proc["sanity"] = proc["los"] - proc["start_time"] + proc = proc[proc["sanity"] > 0] + del proc["sanity"] + self.df = proc + return proc + + def mortality_length(self, cohort, include_time): + self.df = self.df[self.df[self.adm_id].isin(cohort[self.adm_id])] + self.df = self.df[self.df[self.adm_id] <= include_time] + + def los_length(self, cohort, include_time): + self.df = self.df[self.df[self.adm_id].isin(cohort[self.adm_id])] + self.df = self.df[self.df[self.adm_id] <= include_time] + + def read_length(self, cohort): + col = "stay_id" if self.use_icu else "hadm_id" + self.df = self.df[self.df[col].isin(cohort[col])] + self.df = pd.merge(self.df, cohort[[col, "select_time"]], on=col, how="left") + self.df["start_time"] = self.proc["start_time"] - self.proc["select_time"] + self.df = self.df[self.df["start_time"] >= 0] + + def smooth_meds_step(self, bucket, i, t): + sub_proc = ( + self.df[(self.df["start_time"] >= i) & (self.df["start_time"] < i + bucket)] + .groupby(["stay_id", "itemid"] if self.use_icu else ["hadm_id", "icd_code"]) + .agg({"subject_id": "max"}) + ) + sub_proc = sub_proc.reset_index() + sub_proc["start_time"] = t + return sub_proc + + # def smooth_meds(self): + # f2_df = self.final_df.groupby( + # ["stay_id", "itemid", "orderid"] + # if self.use_icu + # else ["hadm_id", "icd_code"] + # ).size() + # df_per_adm = f2_df.groupby(self.adm_id).sum().reset_index()[0].max() + # dflength_per_adm = self.final_df.groupby(self.adm_id).size().max() + # return f2_df, df_per_adm, dflength_per_adm + + # def dict_step(self, hid, los, dataDic): + # if self.use_icu: + # feat = self.final_df["itemid"].unique() + # df2 = self.final_df[self.final_df["stay_id"] == hid] + # if df2.shape[0] == 0: + # df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + # df2 = df2.fillna(0) + # df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + # else: + # df2["val"] = 1 + # # print(df2) + # df2 = df2.pivot_table( + # index="start_time", columns="itemid", values="val" + # ) + # # print(df2.shape) + # add_indices = pd.Index(range(los)).difference(df2.index) + # add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + # np.nan + # ) + # df2 = pd.concat([df2, add_df]) + # df2 = df2.sort_index() + # df2 = df2.fillna(0) + # df2[df2 > 0] = 1 + # # print(df2.head()) + # dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + # feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + # df2 = pd.concat([df2, feat_df], axis=1) + + # df2 = df2[feat] + # df2 = df2.fillna(0) + # df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + # else: + # feat = self.final_df["icd_code"].unique() + # df2 = self.final_df[self.final_df["hadm_id"] == hid] + # if df2.shape[0] == 0: + # df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + # df2 = df2.fillna(0) + # df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + # else: + # df2["val"] = 1 + # df2 = df2.pivot_table( + # index="start_time", columns="icd_code", values="val" + # ) + # add_indices = pd.Index(range(los)).difference(df2.index) + # add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + # np.nan + # ) + # df2 = pd.concat([df2, add_df]) + # df2 = df2.sort_index() + # df2 = df2.fillna(0) + # df2[df2 > 0] = 1 + # dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + # feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + # df2 = pd.concat([df2, feat_df], axis=1) + + # df2 = df2[feat] + # df2 = df2.fillna(0) + # df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + + # return df2 diff --git a/pipeline/feature_selector.py b/pipeline/feature_selector.py new file mode 100644 index 0000000000..3eb7aac901 --- /dev/null +++ b/pipeline/feature_selector.py @@ -0,0 +1,139 @@ +import pandas as pd +import logging +from pipeline.file_info.preproc.feature import ( + EXTRACT_DIAG_PATH, + EXTRACT_DIAG_ICU_PATH, + PreprocDiagnosesHeader, + EXTRACT_MED_ICU_PATH, + EXTRACT_MED_PATH, + IcuMedicationHeader, + PreprocMedicationHeader, + EXTRACT_OUT_ICU_PATH, + EXTRACT_LABS_PATH, + EXTRACT_PROC_PATH, + EXTRACT_PROC_ICU_PATH, + EXTRACT_CHART_ICU_PATH, + IcuProceduresHeader, + NonIcuProceduresHeader, +) +from typing import List +from pathlib import Path + +from pipeline.file_info.preproc.summary import ( + CHART_FEATURES_PATH, + DIAG_FEATURES_PATH, + LABS_FEATURES_PATH, + MED_FEATURES_PATH, + OUT_FEATURES_PATH, + PROC_FEATURES_PATH, +) + +logger = logging.getLogger() + + +# TODO REPLACE HARD CODED COLUMN AND CLASS NAMES +class FeatureSelector: + def __init__( + self, + use_icu: bool, + select_dia: bool, + select_med: bool, + select_proc: bool, + select_labs: bool, + select_chart: bool, + select_out: bool, + ): + self.use_icu = use_icu + + self.select_dia = select_dia + self.select_med = select_med + self.select_proc = select_proc + self.select_dia = select_dia + self.select_labs = select_labs + self.select_chart = select_chart + self.select_out = select_out + + def feature_selection(self) -> List[pd.DataFrame]: + features: List[pd.DataFrame] = [] + if self.select_dia: + features.append( + self.process_feature_selection( + EXTRACT_DIAG_ICU_PATH if self.use_icu else EXTRACT_DIAG_PATH, + DIAG_FEATURES_PATH, + PreprocDiagnosesHeader.NEW_ICD_CODE.value, + "Diagnosis", + ) + ) + + if self.select_med: + path = EXTRACT_MED_ICU_PATH if self.use_icu else EXTRACT_MED_PATH + feature_name = ( + IcuMedicationHeader.ITEM_ID + if self.use_icu + else PreprocMedicationHeader.DRUG_NAME + ) + features.append( + self.process_feature_selection( + path, MED_FEATURES_PATH, feature_name, "Medications" + ) + ) + + if self.select_proc: + path = EXTRACT_PROC_ICU_PATH if self.use_icu else EXTRACT_PROC_PATH + features.append( + self.process_feature_selection( + path, + PROC_FEATURES_PATH, + IcuProceduresHeader.ITEM_ID + if self.use_icu + else NonIcuProceduresHeader.ICD_CODE.value, + "Procedures", + ) + ) + + if self.select_labs: + labs = self.concat_csv_chunks(EXTRACT_LABS_PATH, 10000000) + feature_df = pd.read_csv(LABS_FEATURES_PATH) + labs = labs[labs["itemid"].isin(feature_df["itemid"].unique())] + self.log_and_save(labs, EXTRACT_LABS_PATH, "Labs") + features.append(labs) + + if self.select_chart: + features.append( + self.process_feature_selection( + EXTRACT_CHART_ICU_PATH, + CHART_FEATURES_PATH, + "itemid", + "Output Events", + ) + ) + + if self.select_out: + features.append( + self.process_feature_selection( + EXTRACT_OUT_ICU_PATH, OUT_FEATURES_PATH, "itemid", "Output Events" + ) + ) + + return features + + def process_feature_selection( + self, data_path: Path, feature_path: Path, feature_col: str, data_type: str + ): + """Generalized method for processing feature selection.""" + data_df = pd.read_csv(data_path, compression="gzip") + feature_df = pd.read_csv(feature_path) + data_df = data_df[data_df[feature_col].isin(feature_df[feature_col].unique())] + self.log_and_save(data_df, data_path, data_type) + return data_df + + def concat_csv_chunks(self, file_path: Path, chunksize: int): + """Concatenate chunks from a CSV file.""" + chunks = pd.read_csv(file_path, compression="gzip", chunksize=chunksize) + return pd.concat(chunks, ignore_index=True) + + def log_and_save(self, df: pd.DataFrame, path: Path, data_type: str): + """Log information and save DataFrame to a CSV file.""" + logger.info(f"Total number of rows in {data_type}: {df.shape[0]}") + df.to_csv(path, compression="gzip", index=False) + logger.info(f"[SUCCESSFULLY SAVED {data_type} DATA]") diff --git a/pipeline/features_extractor.py b/pipeline/features_extractor.py new file mode 100644 index 0000000000..4b2199cfb3 --- /dev/null +++ b/pipeline/features_extractor.py @@ -0,0 +1,115 @@ +from pathlib import Path +import pandas as pd +import logging +from pipeline.feature.feature_abc import Feature +from pipeline.feature.chart_events import Chart +from pipeline.feature.diagnoses import Diagnoses +from pipeline.feature.medications import Medications +from pipeline.feature.output_events import OutputEvents +from pipeline.feature.procedures import Procedures +from pipeline.file_info.common import save_data +from pipeline.file_info.preproc.cohort import load_cohort +from pipeline.feature.lab_events import Lab +from typing import List, Tuple + +from pipeline.file_info.preproc.feature import ( + EXTRACT_CHART_ICU_PATH, + EXTRACT_DIAG_ICU_PATH, + EXTRACT_DIAG_PATH, + EXTRACT_LABS_PATH, + EXTRACT_MED_ICU_PATH, + EXTRACT_MED_PATH, + EXTRACT_OUT_ICU_PATH, + EXTRACT_PROC_ICU_PATH, + EXTRACT_PROC_PATH, +) +from pipeline.feature.feature_abc import Name + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + + +class FeatureExtractor: + """ + Extracts various features from a cohort based on specified conditions. + + Attributes: + cohort_output (str): Output path or identifier for the cohort. + use_icu (bool): Flag to indicate whether ICU data should be used. + for_diagnoses (bool): Flag to extract diagnosis features. + for_output_events (bool): Flag to extract output event features. + for_chart_events (bool): Flag to extract chart event features. + for_procedures (bool): Flag to extract procedure features. + for_medications (bool): Flag to extract medication features. + for_labs (bool): Flag to extract lab event features. + """ + + def __init__( + self, + cohort_output: str, + use_icu: bool, + for_diagnoses: bool, + for_output_events: bool, + for_chart_events: bool, + for_procedures: bool, + for_medications: bool, + for_labs: bool, + ): + self.cohort_output = cohort_output + self.use_icu = use_icu + self.for_diagnoses = for_diagnoses + self.for_output_events = for_output_events + self.for_chart_events = for_chart_events + self.for_procedures = for_procedures + self.for_medications = for_medications + self.for_labs = for_labs + + def save_features(self) -> List[pd.DataFrame]: + """ + Loads the cohort and extracts features based on the specified conditions. + + Returns: + List[pd.DataFrame]: A list of DataFrames, each containing a type of extracted feature. + """ + cohort = load_cohort(self.use_icu, self.cohort_output) + feature_conditions: List[Tuple[bool, Feature, Path]] = [ + ( + self.for_diagnoses, + Diagnoses(use_icu=self.use_icu), + EXTRACT_DIAG_ICU_PATH if self.use_icu else EXTRACT_DIAG_PATH, + ), + ( + self.for_procedures, + Procedures(use_icu=self.use_icu), + EXTRACT_PROC_ICU_PATH if self.use_icu else EXTRACT_PROC_PATH, + ), + ( + self.for_medications, + Medications(use_icu=self.use_icu), + EXTRACT_MED_ICU_PATH if self.use_icu else EXTRACT_MED_PATH, + ), + ( + self.for_output_events and self.use_icu, + OutputEvents(), + EXTRACT_OUT_ICU_PATH, + ), + ( + self.for_chart_events and self.use_icu, + Chart(), + EXTRACT_CHART_ICU_PATH, + ), + ( + self.for_labs and not self.use_icu, + Lab(), + EXTRACT_LABS_PATH, + ), + ] + features = {} + for condition, feature, path in feature_conditions: + if condition: + extract_feature = feature.extract_from(cohort) + feature_name = feature.__class__.name() + features[feature_name] = extract_feature + save_data(extract_feature, path, feature_name) + + return features diff --git a/pipeline/features_preprocessor.py b/pipeline/features_preprocessor.py new file mode 100644 index 0000000000..11acdbe774 --- /dev/null +++ b/pipeline/features_preprocessor.py @@ -0,0 +1,99 @@ +import pandas as pd +import logging + +from pipeline.feature.diagnoses import IcdGroupOption +from pipeline.feature.lab_events import Lab +from pipeline.feature_selector import FeatureSelector +from pipeline.features_extractor import FeatureExtractor +from typing import List + +from pipeline.feature.chart_events import Chart +from pipeline.file_info.common import save_data +from pipeline.file_info.preproc.feature import EXTRACT_CHART_ICU_PATH, EXTRACT_LABS_PATH + +from pipeline.no_event_feature_preprocessor import NoEventFeaturePreprocessor +from pipeline.summarizer import Summarizer + +logger = logging.getLogger() + + +# REMOVE FEATURE EXTRACTOR? +class FeaturePreprocessor: + def __init__( + self, + feature_extractor: FeatureExtractor, + group_diag_icd: IcdGroupOption, + group_med_code: bool, + keep_proc_icd9: bool, + clean_chart: bool = False, + impute_outlier_chart: bool = False, + clean_labs: bool = False, + impute_labs: bool = False, + thresh: int = 100, + left_thresh: int = 0, + ): + self.feature_extractor = feature_extractor + self.group_diag_icd = group_diag_icd + self.group_med_code = group_med_code + self.keep_proc_icd9 = keep_proc_icd9 + self.clean_chart = clean_chart + self.impute_outlier_chart = impute_outlier_chart + self.clean_labs = clean_labs + self.impute_labs = impute_labs + self.thresh = thresh + self.left_thresh = left_thresh + + def preprocess_no_event_features(self): + preprocessor = NoEventFeaturePreprocessor( + self.feature_extractor, + self.group_diag_icd, + self.group_med_code, + self.keep_proc_icd9, + ) + return preprocessor.preprocess() + + def save_summaries(self): + summarizer = Summarizer(self.feature_extractor) + return summarizer.save_summaries() + + def feature_selection(self) -> List[pd.DataFrame]: + feature_selector = FeatureSelector( + use_icu=self.feature_extractor.use_icu, + select_dia=self.feature_extractor.for_diagnoses, + select_med=self.feature_extractor.for_medications, + select_proc=self.feature_extractor.for_procedures, + select_chart=self.feature_extractor.for_chart_events, + select_labs=self.feature_extractor.for_labs, + select_out=self.feature_extractor.for_output_events, + ) + return feature_selector.feature_selection() + + def preproc_events_features(self) -> List[pd.DataFrame]: + event_preproc_features: List[pd.DataFrame] = [] + if self.clean_chart and self.feature_extractor.use_icu: + extract_chart = pd.read_csv(EXTRACT_CHART_ICU_PATH, compression="gzip") + chart = Chart(df=extract_chart) + preproc_chart = chart.impute_outlier( + self.impute_outlier_chart, + self.thresh, + self.left_thresh, + ) + save_data(preproc_chart, EXTRACT_CHART_ICU_PATH, "CHART EVENTS") + event_preproc_features.append(preproc_chart) + if self.clean_labs and not self.feature_extractor.use_icu: + extract_labs = pd.read_csv(EXTRACT_LABS_PATH, compression="gzip") + lab = Lab(df=extract_labs) + preproc_lab = lab.impute_outlier( + impute=self.impute_labs, + thresh=self.thresh, + left_thresh=self.left_thresh, + ) + save_data(preproc_lab, EXTRACT_LABS_PATH, "LABS EVENTS") + event_preproc_features.append(lab.preproc()) + return event_preproc_features + + def preprocess(self): + self.preprocess_no_event_features() + self.save_summaries() + self.feature_selection() + self.preproc_events_features() diff --git a/pipeline/file_info/common.py b/pipeline/file_info/common.py new file mode 100644 index 0000000000..3609a8508c --- /dev/null +++ b/pipeline/file_info/common.py @@ -0,0 +1,41 @@ +from enum import StrEnum +from pathlib import Path +import pandas as pd +import logging + +RAW_PATH = Path("raw_data") / "mimiciv_2_0" +MAP_PATH = Path("utils") / "mappings" / "ICD9_to_ICD10_mapping.txt" +MAP_NDC_PATH = Path("utils") / "mappings" / "ndc_product.txt" +PREPROC_PATH = Path("preproc_data") + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + + +# icd mapping +class IcdMap(StrEnum): + DIAGNOISIS_TYPE = "diagnosis_type" + DIAGNOISIS_CODE = "diagnosis_code" + DIAGNOISIS_DESCRIPTION = "diagnosis_description" + ICD9 = "icd9cm" + ICD10 = "icd10cm" + FLAGS = "flags" + + +def load_static_icd_map() -> pd.DataFrame: + return pd.read_csv(MAP_PATH, delimiter="\t") + + +class NdcMap(StrEnum): + NON_PROPRIETARY_NAME = "NONPROPRIETARYNAME" + + +def load_ndc_mapping() -> pd.DataFrame: + return pd.read_csv(MAP_NDC_PATH, delimiter="\t") + + +def save_data(data: pd.DataFrame, path: Path, data_name: str) -> pd.DataFrame: + """Save DataFrame to specified path.""" + data.to_csv(path, compression="gzip", index=False) + logger.info(f"[SUCCESSFULLY SAVED {data_name} DATA]") + return data diff --git a/pipeline/file_info/preproc/cohort.py b/pipeline/file_info/preproc/cohort.py new file mode 100644 index 0000000000..65093e47c0 --- /dev/null +++ b/pipeline/file_info/preproc/cohort.py @@ -0,0 +1,53 @@ +from enum import StrEnum +from pipeline.file_info.common import PREPROC_PATH +import pandas as pd +import logging + +logger = logging.getLogger() +COHORT_PATH = PREPROC_PATH / "cohort" + + +# split common header icu header non icu header +class CohortHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + FIRST_CARE_UNIT = "first_careunit" + LAST_CARE_UNIT = "last_careunit" + LOS = "los" + AGE = "age" + MIN_VALID_YEAR = "min_valid_year" + DOD = "dod" + GENDER = "gender" + INSURANCE = "insurance" + ETHICITY = "ethnicity" + LABEL = "label" + + +class IcuCohortHeader(StrEnum): + STAY_ID = "stay_id" + IN_TIME = "intime" + OUT_TIME = "outtime" + + +class NonIcuCohortHeader(StrEnum): + ADMIT_TIME = "admittime" + DISCH_TIME = "dischtime" + + +def load_cohort(use_icu: bool, cohort_ouput: str) -> pd.DataFrame: + """Load cohort data from a CSV file.""" + cohort_path = COHORT_PATH / f"{cohort_ouput}.csv.gz" + try: + return pd.read_csv( + cohort_path, + compression="gzip", + parse_dates=[ + IcuCohortHeader.IN_TIME if use_icu else NonIcuCohortHeader.ADMIT_TIME + ], + ) + except FileNotFoundError: + logger.error(f"Cohort file not found at {cohort_path}") + raise + except Exception as e: + logger.error(f"Error loading cohort file: {e}") + raise diff --git a/pipeline/file_info/preproc/feature.py b/pipeline/file_info/preproc/feature.py new file mode 100644 index 0000000000..a26ca41278 --- /dev/null +++ b/pipeline/file_info/preproc/feature.py @@ -0,0 +1,126 @@ +from enum import StrEnum + +from pipeline.file_info.common import PREPROC_PATH + + +FEATURE_PATH = PREPROC_PATH / "features" +FEATURE_EXTRACT_PATH = FEATURE_PATH / "extract" +FEATURE_PREPROC_PATH = FEATURE_PATH / "preproc" +FEATURE_SUMMARY_PATH = FEATURE_PATH / "summary" + + +EXTRACT_DIAG_PATH = FEATURE_EXTRACT_PATH / "diag.csv.gz" +EXTRACT_DIAG_ICU_PATH = FEATURE_EXTRACT_PATH / "diag_icu.csv.gz" + + +class DiagnosesHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + ICD_CODE = "icd_code" + ROOT_ICD10 = "root_icd10_convert" + ROOT = "root" + + +class DiagnosesIcuHeader(StrEnum): + STAY_ID = "stay_id" + + +class PreprocDiagnosesHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + NEW_ICD_CODE = "new_icd_code" + + +EXTRACT_PROC_PATH = FEATURE_EXTRACT_PATH / "proc.csv.gz" +EXTRACT_PROC_ICU_PATH = FEATURE_EXTRACT_PATH / "proc_icu.csv.gz" + + +class ProceduresHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + + +class IcuProceduresHeader(StrEnum): + STAY_ID = "stay_id" + ITEM_ID = "itemid" + START_TIME = "starttime" + IN_TIME = "intime" + EVENT_TIME_FROM_ADMIT = "event_time_from_admit" + + +class NonIcuProceduresHeader(StrEnum): + ICD_CODE = "icd_code" + ICD_VERSION = "icd_version" + CHART_DATE = "chartdate" + ADMIT_TIME = "admittime" + PROC_TIME_FROM_ADMIT = "proc_time_from_admit" + + +EXTRACT_MED_ICU_PATH = FEATURE_EXTRACT_PATH / "med_icu.csv.gz" +EXTRACT_MED_PATH = FEATURE_EXTRACT_PATH / "med.csv.gz" + + +class MedicationsHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + START_TIME = "starttime" + START_HOURS_FROM_ADMIT = "start_hours_from_admit" + STOP_HOURS_FROM_ADMIT = "stop_hours_from_admit" + + +class IcuMedicationHeader(StrEnum): + STAY_ID = "stay_id" + ITEM_ID = "itemid" + END_TIME = "endtime" + RATE = "rate" + AMOUNT = "amount" + ORDER_ID = "orderid" + + +class NonIcuMedicationHeader(StrEnum): + STOP_TIME = "stoptime" + DRUG = "drug" + NON_PROPRIEATARY_NAME = "nonproprietaryname" + DOSE_VAL_RX = "dose_val_rx" + EPC = "EPC" + + +class PreprocMedicationHeader(StrEnum): + DRUG_NAME = "drug_name" + + +EXTRACT_OUT_ICU_PATH = FEATURE_EXTRACT_PATH / "out_icu.csv.gz" + + +class OutputEventsHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + STAY_ID = "stay_id" + ITEM_ID = "itemid" + CHART_TIME = "charttime" + IN_TIME = "intime" + EVENT_TIME_FROM_ADMIT = "event_time_from_admit" + + +EXTRACT_LABS_PATH = FEATURE_EXTRACT_PATH / "labs.csv.gz" +PREPROC_LABS_ICU_PATH = FEATURE_PREPROC_PATH / "labs.csv.gz" + + +class LabEventsHeader(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + ITEM_ID = "itemid" + CHART_TIME = "charttime" + ADMIT_TIME = "admittime" + LAB_TIME_FROM_ADMIT = "lab_time_from_admit" + VALUE_NUM = "valuenum" + + +EXTRACT_CHART_ICU_PATH = FEATURE_EXTRACT_PATH / "chart_icu.csv.gz" + + +class ChartEventsHeader(StrEnum): + STAY_ID = "stay_id" + ITEM_ID = "itemid" + VALUE_NUM = "valuenum" + EVENT_TIME_FROM_ADMIT = "event_time_from_admit" diff --git a/pipeline/file_info/preproc/summary.py b/pipeline/file_info/preproc/summary.py new file mode 100644 index 0000000000..fd10357b88 --- /dev/null +++ b/pipeline/file_info/preproc/summary.py @@ -0,0 +1,22 @@ +from pipeline.file_info.common import PREPROC_PATH + +SUMMARY_PATH = PREPROC_PATH / "summary" + +DIAG_FEATURES_PATH = SUMMARY_PATH / "diag_features.csv" +DIAG_SUMMARY_PATH = SUMMARY_PATH / "diag_summary.csv" + +MED_FEATURES_PATH = SUMMARY_PATH / "med_features.csv" +MED_SUMMARY_PATH = SUMMARY_PATH / "med_summary.csv" + +OUT_FEATURES_PATH = SUMMARY_PATH / "out_features.csv" +OUT_SUMMARY_PATH = SUMMARY_PATH / "out_summary.csv" + +PROC_FEATURES_PATH = SUMMARY_PATH / "proc_features.csv" +PROC_SUMMARY_PATH = SUMMARY_PATH / "proc_summary.csv" + +LABS_FEATURES_PATH = SUMMARY_PATH / "labs_features.csv" +LABS_SUMMARY_PATH = SUMMARY_PATH / "labs_summary.csv" + + +CHART_FEATURES_PATH = SUMMARY_PATH / "chart_features.csv" +CHART_SUMMARY_PATH = SUMMARY_PATH / "chart_summary.csv" diff --git a/pipeline/file_info/raw/hosp.py b/pipeline/file_info/raw/hosp.py new file mode 100644 index 0000000000..203ef75563 --- /dev/null +++ b/pipeline/file_info/raw/hosp.py @@ -0,0 +1,135 @@ +from enum import StrEnum +import pandas as pd +from pipeline.file_info.common import RAW_PATH + +""" +The Hosp module provides all data acquired from the hospital wide electronic health record +""" + +HOSP = "hosp" + +HOSP_DIAGNOSES_ICD_PATH = RAW_PATH / HOSP / "diagnoses_icd.csv.gz" +HOSP_PATIENTS_PATH = RAW_PATH / HOSP / "patients.csv.gz" +HOSP_LAB_EVENTS_PATH = RAW_PATH / HOSP / "labevents.csv.gz" +HOSP_ADMISSIONS_PATH = RAW_PATH / HOSP / "admissions.csv.gz" +HOSP_PREDICTIONS_PATH = RAW_PATH / HOSP / "prescriptions.csv.gz" +HOSP_PROCEDURES_ICD_PATH = RAW_PATH / HOSP / "procedures_icd.csv.gz" + + +# information regarding a patient +class HospPatients(StrEnum): + ID = "subject_id" # patient id + ANCHOR_YEAR = "anchor_year" # shifted year for the patient + ANCHOR_AGE = "anchor_age" # patient’s age in the anchor_year + ANCHOR_YEAR_GROUP = "anchor_year_group" # anchor_year occurred during this range + DOD = "dod" # de-identified date of death for the patient + GENDER = "gender" + + +def load_hosp_patients() -> pd.DataFrame: + return pd.read_csv( + HOSP_PATIENTS_PATH, + compression="gzip", + parse_dates=[HospPatients.DOD], + ) + + +# information regarding a patient’s admission to the hospital +class HospAdmissions(StrEnum): + ID = "hadm_id" # hospitalization id + PATIENT_ID = "subject_id" # patient id + ADMITTIME = "admittime" # datetime the patient was admitted to the hospital + DISCHTIME = "dischtime" # datetime the patient was discharged from the hospital + HOSPITAL_EXPIRE_FLAG = "hospital_expire_flag" # whether the patient died within the given hospitalization + LOS = "los" + HOSPITAL_ADMISSION_ID = "hadm_id" + INSURANCE = "insurance" + RACE = "race" + + +def load_hosp_admissions() -> pd.DataFrame: + return pd.read_csv( + HOSP_ADMISSIONS_PATH, + compression="gzip", + parse_dates=[HospAdmissions.ADMITTIME.value, HospAdmissions.DISCHTIME.value], + ) + + +class HospDiagnosesIcd(StrEnum): + SUBJECT_ID = "subject_id" # patient id + HOSPITAL_ADMISSION_ID = "hadm_id" # patient hospitalization id + SEQ_NUM = "seq_num" # priority assigned to the diagnoses + ICD_CODE = "icd_code" # International Coding Definitions code + ICD_VERSION = "icd_version" # version for the coding system + # added + ICD10 = "root_icd10_convert" + ROOT = "root" + + +def load_hosp_diagnosis_icd() -> pd.DataFrame: + return pd.read_csv(HOSP_DIAGNOSES_ICD_PATH, compression="gzip") + + +class HospLabEvents(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + CHART_TIME = "charttime" + ITEM_ID = "itemid" + ADMIT_TIME = "admittime" + LAB_TIME_FROM_ADMIT = "lab_time_from_admit" + VALUE_NUM = "valuenum" + VALUE_UOM = "valueuom" + + +def load_hosp_lab_events(chunksize: int, use_cols=None) -> pd.DataFrame: + return pd.read_csv( + HOSP_LAB_EVENTS_PATH, + compression="gzip", + parse_dates=["charttime"], + chunksize=chunksize, + usecols=use_cols, + ) + + +class HospProceduresIcd(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + SEQ_NUM = "seq_num" + CHART_DATE = "chartdate" + ICD_CODE = "icd_code" + ICD_VERSION = "icd_version" + + +def load_hosp_procedures_icd() -> pd.DataFrame: + return pd.read_csv( + HOSP_PROCEDURES_ICD_PATH, + compression="gzip", + parse_dates=[HospProceduresIcd.CHART_DATE.value], + ).drop_duplicates() + + +class HospPrescriptions(StrEnum): + PATIENT_ID = "subject_id" + HOSPITAL_ADMISSION_ID = "hadm_id" + DRUG = "drug" + START_TIME = "starttime" + STOP_TIME = "stoptime" + NDC = "ndc" + DOSE_VAL_RX = "dose_val_rx" + + +def load_hosp_prescriptions() -> pd.DataFrame: + return pd.read_csv( + HOSP_PREDICTIONS_PATH, + compression="gzip", + usecols=[ + HospPrescriptions.PATIENT_ID, + HospPrescriptions.HOSPITAL_ADMISSION_ID, + HospPrescriptions.DRUG, + HospPrescriptions.START_TIME, + HospPrescriptions.STOP_TIME, + HospPrescriptions.NDC, + HospPrescriptions.DOSE_VAL_RX, + ], + parse_dates=[HospPrescriptions.START_TIME, HospPrescriptions.STOP_TIME], + ) diff --git a/pipeline/file_info/raw/icu.py b/pipeline/file_info/raw/icu.py new file mode 100644 index 0000000000..66384668b3 --- /dev/null +++ b/pipeline/file_info/raw/icu.py @@ -0,0 +1,115 @@ +from enum import StrEnum +import pandas as pd +from pipeline.file_info.common import RAW_PATH + +""" +The ICU module contains information collected from the clinical information system used within the ICU. + +""" +ICU = "icu" + +ICU_ICUSTAY_PATH = RAW_PATH / ICU / "icustays.csv.gz" +ICU_INPUT_EVENT_PATH = RAW_PATH / ICU / "inputevents.csv.gz" +ICU_OUTPUT_EVENT_PATH = RAW_PATH / ICU / "outputevents.csv.gz" +ICU_CHART_EVENTS_PATH = RAW_PATH / ICU / "chartevents.csv.gz" +ICU_PROCEDURE_EVENTS_PATH = RAW_PATH / ICU / "procedureevents.csv.gz" + + +# information regarding ICU stays +class IcuStays(StrEnum): + PATIENT_ID = "subject_id" # patient id + ID = "stay_id" # icu stay id + HOSPITAL_ADMISSION_ID = "hadm_id" # patient hospitalization id + INTIME = "intime" # datetime the patient was transferred into the ICU. + OUTTIME = "outtime" # datetime the patient was transferred out the ICU. + LOS = "los" # length of stay for the patient for the given ICU stay in fractional days. + # added? + ADMITTIME = "admittime" + + +def load_icu_icustays() -> pd.DataFrame: + return pd.read_csv( + ICU_ICUSTAY_PATH, + compression="gzip", + parse_dates=[IcuStays.INTIME, IcuStays.OUTTIME], + ) + + +# Information regarding patient outputs including urine, drainage... +class OuputputEvents(StrEnum): + SUBJECT_ID = "subject_id" # patient id + HOSPITAL_ADMISSION_ID = "hadm_id" # patient hospitalization id + STAY_ID = "stay_id" # patient icu stay id + ITEM_ID = "itemid" # single measurement type id + CHART_TIME = "charttime" # time of an output event + + +def load_icu_output_events() -> pd.DataFrame: + return pd.read_csv( + ICU_OUTPUT_EVENT_PATH, + compression="gzip", + parse_dates=[OuputputEvents.CHART_TIME], + ).drop_duplicates() + + +class ChartEvents(StrEnum): + STAY_ID = "stay_id" + CHARTTIME = "charttime" + ITEMID = "itemid" + VALUENUM = "valuenum" + VALUEOM = "valueuom" + + +def load_icu_chart_events(chunksize: int) -> pd.DataFrame: + return pd.read_csv( + ICU_CHART_EVENTS_PATH, + compression="gzip", + usecols=[c for c in ChartEvents], + parse_dates=[ChartEvents.CHARTTIME], + chunksize=chunksize, + ) + + +def load_icu_chart_events(chunksize: int) -> pd.DataFrame: + return pd.read_csv( + ICU_CHART_EVENTS_PATH, + compression="gzip", + usecols=[c for c in ChartEvents], + parse_dates=[ChartEvents.CHARTTIME.value], + chunksize=chunksize, + ) + + +class InputEvents(StrEnum): + SUBJECT_ID = "subject_id" + STAY_ID = "stay_id" + ITEMID = "itemid" + STARTTIME = "starttime" + ENDTIME = "endtime" + RATE = "rate" + AMOUNT = "amount" + ORDERID = "orderid" + + +def load_input_events() -> pd.DataFrame: + return pd.read_csv( + ICU_INPUT_EVENT_PATH, + compression="gzip", + usecols=[f for f in InputEvents], + parse_dates=[InputEvents.STARTTIME, InputEvents.ENDTIME], + ) + + +class ProceduresEvents(StrEnum): + STAY_ID = "stay_id" + START_TIME = "starttime" + ITEM_ID = "itemid" + + +def load_icu_procedure_events() -> pd.DataFrame: + return pd.read_csv( + ICU_PROCEDURE_EVENTS_PATH, + compression="gzip", + usecols=[h for h in ProceduresEvents], + parse_dates=[ProceduresEvents.START_TIME], + ).drop_duplicates() diff --git a/pipeline/no_event_feature_preprocessor.py b/pipeline/no_event_feature_preprocessor.py new file mode 100644 index 0000000000..40c06b8644 --- /dev/null +++ b/pipeline/no_event_feature_preprocessor.py @@ -0,0 +1,71 @@ +import pandas as pd +import logging +from pipeline.feature.diagnoses import Diagnoses, IcdGroupOption +from pipeline.feature.medications import Medications +from pipeline.feature.procedures import Procedures +from pipeline.features_extractor import FeatureExtractor +from typing import List + +from pipeline.file_info.common import save_data +from pipeline.file_info.preproc.feature import ( + EXTRACT_DIAG_ICU_PATH, + EXTRACT_DIAG_PATH, + EXTRACT_MED_PATH, + EXTRACT_PROC_PATH, + EXTRACT_DIAG_ICU_PATH, + EXTRACT_DIAG_PATH, +) + +logger = logging.getLogger() + + +class NoEventFeaturePreprocessor: + def __init__( + self, + feature_extractor: FeatureExtractor, + group_diag_icd: IcdGroupOption, + group_med_code: bool, + keep_proc_icd9: bool, + ): + self.feature_extractor = feature_extractor + self.group_diag_icd = group_diag_icd + self.group_med_code = group_med_code + self.keep_proc_icd9 = keep_proc_icd9 + + def preprocess(self) -> List[pd.DataFrame]: + no_event_preproc_features = [] + if self.feature_extractor.for_diagnoses: + extract_dia = pd.read_csv( + EXTRACT_DIAG_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_DIAG_PATH, + compression="gzip", + ) + dia = Diagnoses( + use_icu=self.feature_extractor.use_icu, + df=extract_dia, + ) + preproc_dia = dia.preproc(self.group_diag_icd) + save_data( + preproc_dia, + EXTRACT_DIAG_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_DIAG_PATH, + "DIAGNOSES", + ) + no_event_preproc_features.append(preproc_dia) + if not self.feature_extractor.use_icu: + if self.feature_extractor.for_medications: + extract_med = pd.read_csv(EXTRACT_MED_PATH, compression="gzip") + med = Medications(use_icu=False, df=extract_med) + preproc_med = med.preproc(self.group_med_code) + save_data(preproc_med, EXTRACT_MED_PATH, "MEDICATIONS") + + no_event_preproc_features.append(preproc_med) + if self.feature_extractor.for_procedures: + extract_proc = pd.read_csv(EXTRACT_PROC_PATH, compression="gzip") + proc = Procedures(use_icu=False, df=extract_proc) + preproc_proc = proc.preproc(self.keep_proc_icd9) + save_data(preproc_proc, EXTRACT_PROC_PATH, "PROCEDURES") + no_event_preproc_features.append(preproc_proc) + return no_event_preproc_features diff --git a/pipeline/prediction_task.py b/pipeline/prediction_task.py new file mode 100644 index 0000000000..56cc76f4fc --- /dev/null +++ b/pipeline/prediction_task.py @@ -0,0 +1,34 @@ +from enum import StrEnum + + +class TargetType(StrEnum): + MORTALITY = "Mortality" + LOS = "Lenghth of stay" + READMISSION = "Readmission" + + +class DiseaseCode(StrEnum): + HEARTH_FAILURE = "I50" + CAD = "I25" # Coronary Artery Disease + CKD = "N18" # Chronic Kidney Disease + COPD = "J44" # Chronic obstructive pulmonary disease + + +class PredictionTask: + def __init__( + self, + target_type: TargetType, + disease_readmission: DiseaseCode | None, + disease_selection: DiseaseCode | None, + nb_days: int | None, + use_icu: bool, + ): + if nb_days is not None and nb_days < 0: + raise ValueError( + "the number of days after a readmission should be positive." + ) + self.target_type = target_type + self.disease_readmission = disease_readmission + self.disease_selection = disease_selection + self.nb_days = nb_days + self.use_icu = use_icu diff --git a/pipeline/preprocessing/admission_imputer.py b/pipeline/preprocessing/admission_imputer.py new file mode 100644 index 0000000000..1c615bbb69 --- /dev/null +++ b/pipeline/preprocessing/admission_imputer.py @@ -0,0 +1,92 @@ +import pandas as pd +from collections import defaultdict +from typing import Union, List, Tuple +from functools import partial +from multiprocessing import Pool + +from pipeline.file_info.raw.hosp import HospAdmissions +from pipeline.file_info.preproc.feature import LabEventsHeader + + +INPUTED_HOSPITAL_ADMISSION_ID_HEADER = "hadm_id_new" + + +def hadm_imputer( + charttime: pd.Timestamp, + hadm_old: Union[str, float], + hadm_ids_w_timestamps: List[Tuple[str, pd.Timestamp, pd.Timestamp]], +) -> Tuple[str, pd.Timestamp, pd.Timestamp]: + """ + Impute hospital admission ID based on the chart time and a list of admission IDs with timestamps. + """ + + # If old HADM ID exists and is valid, use that + if not pd.isna(hadm_old): + hadm_old = str(int(hadm_old)) + for h_id, adm_time, disch_time in hadm_ids_w_timestamps: + if h_id == hadm_old: + return hadm_old, adm_time, disch_time + + # Filter and sort HADM IDs based on their proximity to the lab event charttime + valid_hadm_ids = [ + (hadm_id, admittime, dischtime) + for hadm_id, admittime, dischtime in hadm_ids_w_timestamps + if admittime <= charttime <= dischtime + ] + valid_hadm_ids.sort(key=lambda x: abs(charttime - x[1])) + + # Return the most relevant HADM ID or None if no valid ID is found + return valid_hadm_ids[0] if valid_hadm_ids else (None, None, None) + + +def impute_row(row, subject_hadm_admittime_tracker): + """Helper function to impute data for a single row.""" + new_hadm_id, new_admittime, new_dischtime = hadm_imputer( + row[LabEventsHeader.CHART_TIME], + row[LabEventsHeader.HOSPITAL_ADMISSION_ID], + subject_hadm_admittime_tracker.get(row[LabEventsHeader.PATIENT_ID], []), + ) + return pd.Series( + [new_hadm_id, new_admittime, new_dischtime], + index=[ + INPUTED_HOSPITAL_ADMISSION_ID_HEADER, + HospAdmissions.ADMITTIME, + HospAdmissions.DISCHTIME, + ], + ) + + +def process_chunk( + chunk: pd.DataFrame, subject_hadm_admittime_tracker: dict +) -> pd.DataFrame: + """Process a single chunk for imputing HADM IDs.""" + imputed_data = chunk.apply( + lambda row: impute_row(row, subject_hadm_admittime_tracker), axis=1 + ) + return pd.concat([chunk, imputed_data], axis=1) + + +def impute_hadm_ids(lab_table: pd.DataFrame, admissions: pd.DataFrame) -> pd.DataFrame: + """Impute missing HADM IDs in the lab table.""" + # Create tracker from admission table + subject_hadm_admittime_tracker = defaultdict(list) + for row in admissions.itertuples(): + subject_hadm_admittime_tracker[row.subject_id].append( + (row.hadm_id, row.admittime, row.dischtime) + ) + + # Prepare chunks and function for parallel processing + chunk_size = 100 + chunks = [ + lab_table[i : i + chunk_size] for i in range(0, len(lab_table), chunk_size) + ] + process_func = partial( + process_chunk, subject_hadm_admittime_tracker=subject_hadm_admittime_tracker + ) + + # Parallel processing + with Pool(8) as pool: + processed_chunks = pool.map(process_func, chunks) + non_empty_chunks = [chunk.dropna(how="all", axis=1) for chunk in processed_chunks] + # Consolidate processed chunks + return pd.concat(non_empty_chunks, ignore_index=True) diff --git a/pipeline/preprocessing/cohort.py b/pipeline/preprocessing/cohort.py new file mode 100644 index 0000000000..1d6f8f642e --- /dev/null +++ b/pipeline/preprocessing/cohort.py @@ -0,0 +1,123 @@ +import pandas as pd +import numpy as np +import datetime +from pipeline.file_info.common import save_data +from pipeline.file_info.preproc.cohort import ( + COHORT_PATH, + CohortHeader, + NonIcuCohortHeader, + IcuCohortHeader, +) +import logging +from pathlib import Path +from pipeline.file_info.raw.hosp import HospAdmissions + +from pipeline.prediction_task import PredictionTask, TargetType + +logger = logging.getLogger() + + +class Cohort: + def __init__( + self, + icu: bool, + name: str, + df: pd.DataFrame = pd.DataFrame(), + ): + self.df = df + self.icu = icu + self.name = name + self.summary_name = f"summary_{name}" + self.admit_col = ( + IcuCohortHeader.IN_TIME if self.icu else NonIcuCohortHeader.ADMIT_TIME + ) + self.disch_col = ( + IcuCohortHeader.OUT_TIME if self.icu else NonIcuCohortHeader.DISCH_TIME + ) + + def prepare_mort_labels(self, visits: pd.DataFrame): + visits = visits.dropna(subset=[self.admit_col, self.disch_col]) + visits[CohortHeader.DOD] = pd.to_datetime(visits[CohortHeader.DOD]) + visits[CohortHeader.LABEL] = np.where( + (visits[CohortHeader.DOD] >= visits[self.admit_col]) + & (visits[CohortHeader.DOD] <= visits[self.disch_col]), + 1, + 0, + ) + logger.info( + f"[ MORTALITY LABELS FINISHED: {visits[CohortHeader.LABEL].sum()} Mortality Cases ]" + ) + return visits + + def prepare_read_labels(self, visits: pd.DataFrame, nb_days: int): + gap = datetime.timedelta(days=nb_days) + visits["next_admit"] = ( + visits.sort_values(by=[self.admit_col]) + .groupby(CohortHeader.PATIENT_ID)[self.admit_col] + .shift(-1) + ) + visits["time_to_next"] = visits["next_admit"] - visits[self.disch_col] + visits[CohortHeader.LABEL] = ( + visits["time_to_next"].notnull() & (visits["time_to_next"] <= gap) + ).astype(int) + readmit_cases = visits[CohortHeader.LABEL].sum() + logger.info( + f"[ READMISSION LABELS FINISHED: {readmit_cases} Readmission Cases ]" + ) + return visits.drop(columns=["next_admit", "time_to_next"]) + + def prepare_los_labels(self, visits: pd.DataFrame, nb_days): + visits = visits.dropna( + subset=[self.admit_col, self.disch_col, CohortHeader.LOS] + ) + visits[CohortHeader.LABEL] = (visits[CohortHeader.LOS] > nb_days).astype(int) + logger.info( + f"[ LOS LABELS FINISHED: {visits[CohortHeader.LABEL].sum()} LOS Cases ]" + ) + return visits + + def prepare_labels(self, visits: pd.DataFrame, prediction_task: PredictionTask): + if prediction_task.target_type == TargetType.MORTALITY: + df = self.prepare_mort_labels(visits) + elif prediction_task.target_type == TargetType.READMISSION: + df = self.prepare_read_labels(visits, prediction_task.nb_days) + elif prediction_task.target_type == TargetType.LOS: + df = self.prepare_los_labels(visits, prediction_task.nb_days) + df = df.sort_values(by=[CohortHeader.PATIENT_ID, self.admit_col]) + self.df = df.rename(columns={HospAdmissions.RACE: CohortHeader.ETHICITY}) + + def save(self): + save_data(self.df, COHORT_PATH / f"{self.name}.csv.gz", "COHORT") + + def save_summary(self): + summary = "\n".join( + [ + f"{self.df} FOR {' ICU' if self.icu else ''} DATA", + f"# Admission Records: {self.df.shape[0]}", + f"# Patients: {self.df[CohortHeader.PATIENT_ID].nunique()}", + f"# Positive cases: {self.df[self.df[CohortHeader.LABEL]==1].shape[0]}", + f"# Negative cases: {self.df[self.df[CohortHeader.LABEL]==0].shape[0]}", + ] + ) + with open(COHORT_PATH / f"{self.summary_name}.txt", "w") as f: + f.write(summary) + + +def read_cohort(name: str, use_icu: bool) -> pd.DataFrame: + data = pd.read_csv( + COHORT_PATH / f"{name}.csv.gz", + compression="gzip", + ) + start_time = IcuCohortHeader.IN_TIME if use_icu else NonIcuCohortHeader.ADMIT_TIME + stop_time = IcuCohortHeader.OUT_TIME if use_icu else NonIcuCohortHeader.DISCH_TIME + for col in [start_time, stop_time]: + data[col] = pd.to_datetime(data[col]) + data[CohortHeader.LOS] = ( + (data[stop_time] - data[start_time]).dt.total_seconds() / 3600 + ).astype(int) + data = data[data[CohortHeader.LOS] > 0] + data[CohortHeader.AGE] = data[CohortHeader.AGE].astype(int) + + logger.info("[ READ COHORT ]") + + return data diff --git a/pipeline/preprocessing/data_gen.py b/pipeline/preprocessing/data_gen.py new file mode 100644 index 0000000000..eb5b877e05 --- /dev/null +++ b/pipeline/preprocessing/data_gen.py @@ -0,0 +1,26 @@ +# import pandas as pd +# import logging +# from pipeline.file_info.preproc.cohort import COHORT_PATH, CohortHeader + +# logger = logging.getLogger() + + +# def generate_admission_cohort(cohort_output: str) -> pd.DataFrame: +# data = pd.read_csv( +# COHORT_PATH / f"{cohort_output}.csv.gz", +# compression="gzip", +# ) +# for col in [CohortHeader.ADMIT_TIME, CohortHeader.DISCH_TIME]: +# data[col] = pd.to_datetime(data[col]) + +# data[CohortHeader.LOS] = ( +# ( +# data[CohortHeader.DISCH_TIME] - data[CohortHeader.ADMIT_TIME] +# ).dt.total_seconds() +# / 3600 +# ).astype(int) +# data = data[data[CohortHeader.LOS] > 0] +# data[CohortHeader.AGE] = data[CohortHeader.AGE].astype(int) + +# logger.info("[ READ COHORT ]") +# return data diff --git a/pipeline/preprocessing/outlier_removal.py b/pipeline/preprocessing/outlier_removal.py new file mode 100644 index 0000000000..5489765e5b --- /dev/null +++ b/pipeline/preprocessing/outlier_removal.py @@ -0,0 +1,65 @@ +import pandas as pd +import numpy as np + + +def compute_outlier_imputation( + arr: np.ndarray, cut_off: float, left_thresh: float, impute: bool +) -> np.ndarray: + """Imputes or flags outliers in an array based on percentile thresholds. + + Args: + arr (np.ndarray): The input array. + cut_off (float): The upper percentile threshold to define outliers. + left_thresh (float): The lower percentile threshold to define outliers. + impute (bool): If True, outliers are imputed with threshold values. If False, they are replaced with NaN. + + Returns: + np.ndarray: The array with outliers imputed or flagged. + """ + lower_bound = np.percentile(arr, left_thresh) + upper_bound = np.percentile(arr, cut_off) + + if impute: + np.clip(arr, lower_bound, upper_bound, out=arr) + else: + arr = np.where((arr < lower_bound) | (arr > upper_bound), np.nan, arr) + + +def outlier_imputation( + data: pd.DataFrame, + id_attribute: str, + value_attribute: str, + cut_off: float, + left_thresh: float, + impute: bool, +) -> pd.DataFrame: + """ + Applies outlier imputation or removal to a specific attribute of a DataFrame, grouped by another attribute. + + Args: + data (pd.DataFrame): The input DataFrame. + id_attribute (str): The attribute to group by. + value_attribute (str): The attribute to apply outlier processing. + cut_off (float): Upper percentile threshold for defining outliers. + left_thresh (float): Lower percentile threshold for defining outliers. + impute (bool): If True, imputes outliers with threshold values; if False, replaces them with NaN. + + Returns: + pd.DataFrame: The DataFrame with outlier processing applied. + """ + + def impute_group(group: pd.Series) -> pd.Series: + arr = group.values + imputed_arr = compute_outlier_imputation(arr, cut_off, left_thresh, impute) + return pd.Series(imputed_arr, index=group.index) + + # Apply the outlier imputation or removal to each group + data[value_attribute] = data.groupby(id_attribute)[value_attribute].transform( + impute_group + ) + + # Optionally drop rows with NaN values in the value_attribute column + if not impute: + data = data.dropna(subset=[value_attribute]) + + return data diff --git a/pipeline/preprocessing/visit.py b/pipeline/preprocessing/visit.py new file mode 100644 index 0000000000..0d295fb2b0 --- /dev/null +++ b/pipeline/preprocessing/visit.py @@ -0,0 +1,126 @@ +import pandas as pd +from pipeline.conversion.icd import IcdConverter +from pipeline.file_info.raw.hosp import ( + HospDiagnosesIcd, + HospPatients, + HospAdmissions, + load_hosp_diagnosis_icd, +) +from pipeline.file_info.raw.icu import IcuStays + +from pipeline.file_info.preproc.cohort import ( + CohortHeader, + IcuCohortHeader, + NonIcuCohortHeader, +) +from pipeline.prediction_task import TargetType +from pipeline.prediction_task import DiseaseCode +from typing import Optional +import logging + + +logger = logging.getLogger() + + +def make_patients(hosp_patients: pd.DataFrame) -> pd.DataFrame: + patients = hosp_patients[ + [ + HospPatients.ID, + HospPatients.ANCHOR_YEAR, + HospPatients.ANCHOR_YEAR_GROUP, + HospPatients.ANCHOR_AGE, + HospPatients.DOD, + HospPatients.GENDER, + ] + ].copy() + max_anchor_year_group = ( + patients[HospPatients.ANCHOR_YEAR_GROUP].str.slice(start=-4).astype(int) + ) + # To identify visits with prediction windows outside the range 2008-2019. + patients[CohortHeader.MIN_VALID_YEAR] = ( + hosp_patients[HospPatients.ANCHOR_YEAR] + 2019 - max_anchor_year_group + ) + return patients.rename(columns={HospPatients.ANCHOR_AGE: CohortHeader.AGE})[ + [ + HospPatients.ID, + CohortHeader.AGE, + CohortHeader.MIN_VALID_YEAR, + HospPatients.DOD, + HospPatients.GENDER, + ] + ] + + +def make_icu_visits( + icu_icustays: pd.DataFrame, hosp_patients: pd.DataFrame, target_type: TargetType +) -> pd.DataFrame: + if target_type != TargetType.READMISSION: + return icu_icustays + # Filter out stays where either there is no death or the death occurred after ICU discharge + patients_dod = hosp_patients[[HospPatients.ID, HospPatients.DOD]] + visits = icu_icustays.merge(patients_dod, on=IcuStays.PATIENT_ID) + filtered_visits = visits.loc[ + (visits[HospPatients.DOD].isna()) + | (visits[HospPatients.DOD] >= visits[IcuStays.OUTTIME]) + ] + return filtered_visits[ + [ + CohortHeader.PATIENT_ID, + IcuCohortHeader.STAY_ID, + CohortHeader.HOSPITAL_ADMISSION_ID, + IcuCohortHeader.IN_TIME, + IcuCohortHeader.OUT_TIME, + CohortHeader.LOS, + ] + ] + + +def make_no_icu_visits( + hosp_admissions: pd.DataFrame, target_type: TargetType +) -> pd.DataFrame: + hosp_admissions[HospAdmissions.LOS] = ( + hosp_admissions[HospAdmissions.DISCHTIME] + - hosp_admissions[HospAdmissions.ADMITTIME] + ).dt.days + + if target_type == TargetType.READMISSION: + # Filter out hospitalizations where the patient expired + hosp_admissions = hosp_admissions[ + hosp_admissions[HospAdmissions.HOSPITAL_EXPIRE_FLAG] == 0 + ] + return hosp_admissions[ + [ + CohortHeader.PATIENT_ID, + CohortHeader.HOSPITAL_ADMISSION_ID, + NonIcuCohortHeader.ADMIT_TIME, + NonIcuCohortHeader.DISCH_TIME, + CohortHeader.LOS, + ] + ] + + +def filter_visits( + visits: pd.DataFrame, + disease_readmission: Optional[DiseaseCode], + disease_selection: Optional[DiseaseCode], +) -> pd.DataFrame: + """# Filter visits based on readmission due to a specific disease and on disease selection""" + icd_converter = IcdConverter() + diag = load_hosp_diagnosis_icd()[ + [ + HospDiagnosesIcd.ICD_CODE, + HospDiagnosesIcd.ICD_VERSION, + HospDiagnosesIcd.HOSPITAL_ADMISSION_ID, + ] + ] + diag = icd_converter.standardize_icd(diag) + diag.dropna(subset=[HospDiagnosesIcd.ROOT], inplace=True) + if disease_readmission: + hids = icd_converter.get_pos_ids(diag, disease_readmission) + visits = visits[visits[CohortHeader.HOSPITAL_ADMISSION_ID].isin(hids)] + + if disease_selection: + hids = icd_converter.get_pos_ids(diag, disease_selection) + visits = visits[visits[CohortHeader.HOSPITAL_ADMISSION_ID].isin(hids)] + + return visits diff --git a/pipeline/summarizer.py b/pipeline/summarizer.py new file mode 100644 index 0000000000..5ddb18f55d --- /dev/null +++ b/pipeline/summarizer.py @@ -0,0 +1,165 @@ +import pandas as pd +import logging + +from tqdm import tqdm +from pipeline.feature.feature_abc import Feature +from pipeline.feature.diagnoses import Diagnoses +from pipeline.feature.lab_events import Lab +from pipeline.feature.medications import Medications +from pipeline.feature.output_events import OutputEvents +from pipeline.feature.procedures import Procedures +from pipeline.features_extractor import FeatureExtractor +from typing import List, Type + +from pipeline.feature.chart_events import Chart +from pipeline.file_info.common import save_data +from pipeline.file_info.preproc.feature import ( + EXTRACT_CHART_ICU_PATH, + EXTRACT_LABS_PATH, + EXTRACT_MED_ICU_PATH, + EXTRACT_MED_PATH, + EXTRACT_OUT_ICU_PATH, + EXTRACT_PROC_ICU_PATH, + EXTRACT_PROC_PATH, + EXTRACT_DIAG_PATH, + EXTRACT_DIAG_ICU_PATH, + ChartEventsHeader, + IcuMedicationHeader, + IcuProceduresHeader, + LabEventsHeader, + NonIcuProceduresHeader, + OutputEventsHeader, + PreprocDiagnosesHeader, + PreprocMedicationHeader, +) +from pipeline.file_info.preproc.summary import ( + CHART_FEATURES_PATH, + CHART_SUMMARY_PATH, + DIAG_FEATURES_PATH, + DIAG_SUMMARY_PATH, + LABS_FEATURES_PATH, + LABS_SUMMARY_PATH, + MED_FEATURES_PATH, + MED_SUMMARY_PATH, + OUT_FEATURES_PATH, + OUT_SUMMARY_PATH, + PROC_FEATURES_PATH, + PROC_SUMMARY_PATH, +) +from pipeline.no_event_feature_preprocessor import NoEventFeaturePreprocessor +from pathlib import Path + +logger = logging.getLogger() + + +class Summarizer: + def __init__( + self, + feature_extractor: FeatureExtractor, + ): + self.feature_extractor = feature_extractor + + def process_feature( + self, + feature_class: Type[Feature], + path: Path, + summary_path: Path, + feature_name: str, + features_path: str, + use_icu: bool = True, + ) -> pd.DataFrame: + """ + Process a feature, save its summary, and export relevant data to a CSV file. + """ + feature = ( + feature_class( + use_icu=self.feature_extractor.use_icu, + df=pd.read_csv(path, compression="gzip"), + ) + if use_icu + else feature_class(df=pd.read_csv(path, compression="gzip")) + ) + summary = feature.summary() + save_data(summary, summary_path, f"{feature_class.__name__.upper()} SUMMARY") + summary[feature_name].to_csv(features_path, index=False) + return summary + + def save_summaries(self) -> List[pd.DataFrame]: + summaries = [] + if self.feature_extractor.for_diagnoses: + summary = self.process_feature( + Diagnoses, + EXTRACT_DIAG_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_DIAG_PATH, + DIAG_SUMMARY_PATH, + PreprocDiagnosesHeader.NEW_ICD_CODE, + DIAG_FEATURES_PATH, + ) + summaries.append(summary) + if self.feature_extractor.for_medications: + summary = self.process_feature( + Medications, + EXTRACT_MED_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_MED_PATH, + MED_SUMMARY_PATH, + IcuMedicationHeader.ITEM_ID + if self.feature_extractor.use_icu + else PreprocMedicationHeader.DRUG_NAME, + MED_FEATURES_PATH, + ) + summaries.append(summary) + + if self.feature_extractor.for_procedures: + summary = self.process_feature( + Procedures, + EXTRACT_PROC_ICU_PATH + if self.feature_extractor.use_icu + else EXTRACT_PROC_PATH, + PROC_SUMMARY_PATH, + IcuProceduresHeader.ITEM_ID + if self.feature_extractor.use_icu + else NonIcuProceduresHeader.ICD_CODE, + PROC_FEATURES_PATH, + ) + summaries.append(summary) + + if self.feature_extractor.for_output_events: + summary = self.process_feature( + OutputEvents, + EXTRACT_OUT_ICU_PATH, + OUT_SUMMARY_PATH, + OutputEventsHeader.ITEM_ID, + OUT_FEATURES_PATH, + use_icu=False, + ) + summaries.append(summary) + + if self.feature_extractor.for_chart_events: + summary = self.process_feature( + Chart, + EXTRACT_CHART_ICU_PATH, + CHART_SUMMARY_PATH, + ChartEventsHeader.ITEM_ID, + CHART_FEATURES_PATH, + use_icu=False, + ) + summaries.append(summary) + + if self.feature_extractor.for_labs: + # Special handling for labs by chunk + labs = pd.concat( + tqdm( + pd.read_csv( + EXTRACT_LABS_PATH, compression="gzip", chunksize=10000000 + ) + ), + ignore_index=True, + ) + lab = Lab(df=labs) + summary = lab.summary() + save_data(summary, LABS_SUMMARY_PATH, "LABS SUMMARY") + summary[LabEventsHeader.ITEM_ID].to_csv(LABS_FEATURES_PATH, index=False) + summaries.append(summary) + return summaries diff --git a/pipeline/temp_icu.py b/pipeline/temp_icu.py new file mode 100644 index 0000000000..c1b19c026b --- /dev/null +++ b/pipeline/temp_icu.py @@ -0,0 +1,311 @@ +def create_Dict(self, meds, proc, out, chart, los): + dataDic = {} + print(los) + labels_csv = pd.DataFrame(columns=["stay_id", "label"]) + labels_csv["stay_id"] = pd.Series(self.hids) + labels_csv["label"] = 0 + # print("# Unique gender",self.data.gender.nunique()) + # print("# Unique ethnicity",self.data.ethnicity.nunique()) + # print("# Unique insurance",self.data.insurance.nunique()) + + for hid in self.hids: + grp = self.data[self.data["stay_id"] == hid] + dataDic[hid] = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Out": {}, + "Chart": {}, + "ethnicity": grp["ethnicity"].iloc[0], + "age": int(grp["Age"]), + "gender": grp["gender"].iloc[0], + "label": int(grp["label"]), + } + labels_csv.loc[labels_csv["stay_id"] == hid, "label"] = int(grp["label"]) + + # print(static_csv.head()) + for hid in tqdm(self.hids): + grp = self.data[self.data["stay_id"] == hid] + demo_csv = grp[["Age", "gender", "ethnicity", "insurance"]] + if not os.path.exists("./data/csv/" + str(hid)): + os.makedirs("./data/csv/" + str(hid)) + demo_csv.to_csv("./data/csv/" + str(hid) + "/demo.csv", index=False) + + dyn_csv = pd.DataFrame() + ###MEDS + if self.feat_med: + feat = meds["itemid"].unique() + df2 = meds[meds["stay_id"] == hid] + if df2.shape[0] == 0: + amount = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + amount = amount.fillna(0) + amount.columns = pd.MultiIndex.from_product([["MEDS"], amount.columns]) + else: + rate = df2.pivot_table( + index="start_time", columns="itemid", values="rate" + ) + # print(rate) + amount = df2.pivot_table( + index="start_time", columns="itemid", values="amount" + ) + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="stop_time" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.ffill() + df2 = df2.fillna(0) + + rate = pd.concat([rate, add_df]) + rate = rate.sort_index() + rate = rate.ffill() + rate = rate.fillna(-1) + + amount = pd.concat([amount, add_df]) + amount = amount.sort_index() + amount = amount.ffill() + amount = amount.fillna(-1) + # print(df2.head()) + df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + rate.iloc[:, 0:] = df2.iloc[:, 0:] * rate.iloc[:, 0:] + amount.iloc[:, 0:] = df2.iloc[:, 0:] * amount.iloc[:, 0:] + # print(df2.head()) + dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + dataDic[hid]["Med"]["rate"] = rate.iloc[:, 0:].to_dict(orient="list") + dataDic[hid]["Med"]["amount"] = amount.iloc[:, 0:].to_dict( + orient="list" + ) + + feat_df = pd.DataFrame(columns=list(set(feat) - set(amount.columns))) + # print(feat) + # print(amount.columns) + # print(amount.head()) + amount = pd.concat([amount, feat_df], axis=1) + + amount = amount[feat] + amount = amount.fillna(0) + # print(amount.columns) + amount.columns = pd.MultiIndex.from_product([["MEDS"], amount.columns]) + + if dyn_csv.empty: + dyn_csv = amount + else: + dyn_csv = pd.concat([dyn_csv, amount], axis=1) + + ###PROCS + if self.feat_proc: + feat = proc["itemid"].unique() + df2 = proc[proc["stay_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + else: + df2["val"] = 1 + # print(df2) + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + # print(df2.head()) + dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 + else: + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + + ###OUT + if self.feat_out: + feat = out["itemid"].unique() + df2 = out[out["stay_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + else: + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + # print(df2.head()) + dataDic[hid]["Out"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["OUT"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 + else: + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + + ###CHART + if self.feat_chart: + feat = chart["itemid"].unique() + df2 = chart[chart["stay_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + else: + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + # print(df2.head()) + dataDic[hid]["Chart"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + dataDic[hid]["Chart"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["CHART"], val.columns]) + + if dyn_csv.empty: + dyn_csv = val + else: + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + # Save temporal data to csv + dyn_csv.to_csv("./data/csv/" + str(hid) + "/dynamic.csv", index=False) + + ##########COND######### + if self.feat_cond: + feat = self.cond["new_icd_code"].unique() + grp = self.cond[self.cond["stay_id"] == hid] + if grp.shape[0] == 0: + dataDic[hid]["Cond"] = {"fids": list([""])} + feat_df = pd.DataFrame(np.zeros([1, len(feat)]), columns=feat) + grp = feat_df.fillna(0) + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + else: + dataDic[hid]["Cond"] = {"fids": list(grp["new_icd_code"])} + grp["val"] = 1 + grp = grp.drop_duplicates() + grp = grp.pivot( + index="stay_id", columns="new_icd_code", values="val" + ).reset_index(drop=True) + feat_df = pd.DataFrame(columns=list(set(feat) - set(grp.columns))) + grp = pd.concat([grp, feat_df], axis=1) + grp = grp.fillna(0) + grp = grp[feat] + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + grp.to_csv("./data/csv/" + str(hid) + "/static.csv", index=False) + labels_csv.to_csv("./data/csv/labels.csv", index=False) + + ######SAVE DICTIONARIES############## + metaDic = {"Cond": {}, "Proc": {}, "Med": {}, "Out": {}, "Chart": {}, "LOS": {}} + metaDic["LOS"] = los + with open("./data/dict/dataDic", "wb") as fp: + pickle.dump(dataDic, fp) + + with open("./data/dict/hadmDic", "wb") as fp: + pickle.dump(self.hids, fp) + + with open("./data/dict/ethVocab", "wb") as fp: + pickle.dump(list(self.data["ethnicity"].unique()), fp) + self.eth_vocab = self.data["ethnicity"].nunique() + + with open("./data/dict/ageVocab", "wb") as fp: + pickle.dump(list(self.data["Age"].unique()), fp) + self.age_vocab = self.data["Age"].nunique() + + with open("./data/dict/insVocab", "wb") as fp: + pickle.dump(list(self.data["insurance"].unique()), fp) + self.ins_vocab = self.data["insurance"].nunique() + + if self.feat_med: + with open("./data/dict/medVocab", "wb") as fp: + pickle.dump(list(meds["itemid"].unique()), fp) + self.med_vocab = meds["itemid"].nunique() + metaDic["Med"] = self.med_per_adm + + if self.feat_out: + with open("./data/dict/outVocab", "wb") as fp: + pickle.dump(list(out["itemid"].unique()), fp) + self.out_vocab = out["itemid"].nunique() + metaDic["Out"] = self.out_per_adm + + if self.feat_chart: + with open("./data/dict/chartVocab", "wb") as fp: + pickle.dump(list(chart["itemid"].unique()), fp) + self.chart_vocab = chart["itemid"].nunique() + metaDic["Chart"] = self.chart_per_adm + + if self.feat_cond: + with open("./data/dict/condVocab", "wb") as fp: + pickle.dump(list(self.cond["new_icd_code"].unique()), fp) + self.cond_vocab = self.cond["new_icd_code"].nunique() + metaDic["Cond"] = self.cond_per_adm + + if self.feat_proc: + with open("./data/dict/procVocab", "wb") as fp: + pickle.dump(list(proc["itemid"].unique()), fp) + self.proc_vocab = proc["itemid"].nunique() + metaDic["Proc"] = self.proc_per_adm + + with open("./data/dict/metaDic", "wb") as fp: + pickle.dump(metaDic, fp) diff --git a/pipeline/temp_no_icu b/pipeline/temp_no_icu new file mode 100644 index 0000000000..b4a6c8a9bf --- /dev/null +++ b/pipeline/temp_no_icu @@ -0,0 +1,249 @@ +def create_Dict(self, meds, proc, labs, los): + print("[ CREATING DATA DICTIONARIES ]") + dataDic = {} + labels_csv = pd.DataFrame(columns=["hadm_id", "label"]) + labels_csv["hadm_id"] = pd.Series(self.hids) + labels_csv["label"] = 0 + for hid in self.hids: + grp = self.data[self.data["hadm_id"] == hid] + # print(grp.head()) + # print(grp['gender']) + # print(int(grp['Age'])) + # print(grp['ethnicity'].iloc[0]) + dataDic[hid] = { + "Cond": {}, + "Proc": {}, + "Med": {}, + "Lab": {}, + "ethnicity": grp["ethnicity"].iloc[0], + "age": int(grp["Age"]), + "gender": grp["gender"].iloc[0], + "label": int(grp["label"]), + } + labels_csv.loc[labels_csv["hadm_id"] == hid, "label"] = int(grp["label"]) + for hid in tqdm(self.hids): + grp = self.data[self.data["hadm_id"] == hid] + demo_csv = grp[["Age", "gender", "ethnicity", "insurance"]] + if not os.path.exists("./data/csv/" + str(hid)): + os.makedirs("./data/csv/" + str(hid)) + demo_csv.to_csv("./data/csv/" + str(hid) + "/demo.csv", index=False) + + dyn_csv = pd.DataFrame() + ###MEDS + if self.feat_med: + feat = meds["drug_name"].unique() + df2 = meds[meds["hadm_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + else: + val = df2.pivot_table( + index="start_time", columns="drug_name", values="dose_val_rx" + ) + df2 = df2.pivot_table( + index="start_time", columns="drug_name", values="stop_time" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.ffill() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + val = val.ffill() + val = val.fillna(-1) + # print(df2.head()) + df2.iloc[:, 0:] = df2.iloc[:, 0:].sub(df2.index, 0) + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + val.iloc[:, 0:] = df2.iloc[:, 0:] * val.iloc[:, 0:] + # print(df2.head()) + dataDic[hid]["Med"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + dataDic[hid]["Med"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + + val.columns = pd.MultiIndex.from_product([["MEDS"], val.columns]) + if dyn_csv.empty: + dyn_csv = val + else: + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + ###PROCS + if self.feat_proc: + feat = proc["icd_code"].unique() + df2 = proc[proc["hadm_id"] == hid] + if df2.shape[0] == 0: + df2 = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + else: + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="icd_code", values="val" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + df2[df2 > 0] = 1 + # print(df2.head()) + dataDic[hid]["Proc"] = df2.to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(df2.columns))) + df2 = pd.concat([df2, feat_df], axis=1) + + df2 = df2[feat] + df2 = df2.fillna(0) + df2.columns = pd.MultiIndex.from_product([["PROC"], df2.columns]) + + if dyn_csv.empty: + dyn_csv = df2 + else: + dyn_csv = pd.concat([dyn_csv, df2], axis=1) + + ###LABS + if self.feat_lab: + feat = labs["itemid"].unique() + df2 = labs[labs["hadm_id"] == hid] + if df2.shape[0] == 0: + val = pd.DataFrame(np.zeros([los, len(feat)]), columns=feat) + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + else: + val = df2.pivot_table( + index="start_time", columns="itemid", values="valuenum" + ) + df2["val"] = 1 + df2 = df2.pivot_table( + index="start_time", columns="itemid", values="val" + ) + # print(df2.shape) + add_indices = pd.Index(range(los)).difference(df2.index) + add_df = pd.DataFrame(index=add_indices, columns=df2.columns).fillna( + np.nan + ) + df2 = pd.concat([df2, add_df]) + df2 = df2.sort_index() + df2 = df2.fillna(0) + + val = pd.concat([val, add_df]) + val = val.sort_index() + if self.impute == "Mean": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.mean()) + elif self.impute == "Median": + val = val.ffill() + val = val.bfill() + val = val.fillna(val.median()) + val = val.fillna(0) + + df2[df2 > 0] = 1 + df2[df2 < 0] = 0 + + # print(df2.head()) + dataDic[hid]["Lab"]["signal"] = df2.iloc[:, 0:].to_dict(orient="list") + dataDic[hid]["Lab"]["val"] = val.iloc[:, 0:].to_dict(orient="list") + + feat_df = pd.DataFrame(columns=list(set(feat) - set(val.columns))) + val = pd.concat([val, feat_df], axis=1) + + val = val[feat] + val = val.fillna(0) + val.columns = pd.MultiIndex.from_product([["LAB"], val.columns]) + + if dyn_csv.empty: + dyn_csv = val + else: + dyn_csv = pd.concat([dyn_csv, val], axis=1) + + # Save temporal data to csv + dyn_csv.to_csv("./data/csv/" + str(hid) + "/dynamic.csv", index=False) + + ##########COND######### + if self.feat_cond: + feat = self.cond["new_icd_code"].unique() + grp = self.cond[self.cond["hadm_id"] == hid] + if grp.shape[0] == 0: + dataDic[hid]["Cond"] = {"fids": list([""])} + feat_df = pd.DataFrame(np.zeros([1, len(feat)]), columns=feat) + grp = feat_df.fillna(0) + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + else: + dataDic[hid]["Cond"] = {"fids": list(grp["new_icd_code"])} + grp["val"] = 1 + grp = grp.drop_duplicates() + grp = grp.pivot( + index="hadm_id", columns="new_icd_code", values="val" + ).reset_index(drop=True) + feat_df = pd.DataFrame(columns=list(set(feat) - set(grp.columns))) + grp = pd.concat([grp, feat_df], axis=1) + grp = grp.fillna(0) + grp = grp[feat] + grp.columns = pd.MultiIndex.from_product([["COND"], grp.columns]) + grp.to_csv("./data/csv/" + str(hid) + "/static.csv", index=False) + labels_csv.to_csv("./data/csv/labels.csv", index=False) + + ######SAVE DICTIONARIES############## + metaDic = {"Cond": {}, "Proc": {}, "Med": {}, "Lab": {}, "LOS": {}} + metaDic["LOS"] = los + with open("./data/dict/dataDic", "wb") as fp: + pickle.dump(dataDic, fp) + + with open("./data/dict/hadmDic", "wb") as fp: + pickle.dump(self.hids, fp) + + with open("./data/dict/ethVocab", "wb") as fp: + pickle.dump(list(self.data["ethnicity"].unique()), fp) + self.eth_vocab = self.data["ethnicity"].nunique() + + with open("./data/dict/ageVocab", "wb") as fp: + pickle.dump(list(self.data["Age"].unique()), fp) + self.age_vocab = self.data["Age"].nunique() + + with open("./data/dict/insVocab", "wb") as fp: + pickle.dump(list(self.data["insurance"].unique()), fp) + self.ins_vocab = self.data["insurance"].nunique() + + if self.feat_med: + with open("./data/dict/medVocab", "wb") as fp: + pickle.dump(list(meds["drug_name"].unique()), fp) + self.med_vocab = meds["drug_name"].nunique() + metaDic["Med"] = self.med_per_adm + + if self.feat_cond: + with open("./data/dict/condVocab", "wb") as fp: + pickle.dump(list(self.cond["new_icd_code"].unique()), fp) + self.cond_vocab = self.cond["new_icd_code"].nunique() + metaDic["Cond"] = self.cond_per_adm + + if self.feat_proc: + with open("./data/dict/procVocab", "wb") as fp: + pickle.dump(list(proc["icd_code"].unique()), fp) + self.proc_vocab = proc["icd_code"].unique() + metaDic["Proc"] = self.proc_per_adm + + if self.feat_lab: + with open("./data/dict/labsVocab", "wb") as fp: + pickle.dump(list(labs["itemid"].unique()), fp) + self.lab_vocab = labs["itemid"].unique() + metaDic["Lab"] = self.labs_per_adm + + with open("./data/dict/metaDic", "wb") as fp: + pickle.dump(metaDic, fp) diff --git a/preprocess_outcomes.py b/preprocess_outcomes.py index 412995dc60..a09fd5fdbe 100644 --- a/preprocess_outcomes.py +++ b/preprocess_outcomes.py @@ -1,4 +1,4 @@ -''' +""" Lrasmy@Zhilab Jan 2021 # This script processes originally extracted data on a distributed platform @@ -23,10 +23,11 @@ # .pts: List of unique Patient ids. Created for validation and comparison purposes # .types: Python dictionary that maps string diagnosis codes to integer diagnosis codes. # Main output files for the baseline RNN models are .combined -''' +""" import sys from optparse import OptionParser + try: import cPickle as pickle except: @@ -37,247 +38,293 @@ from datetime import datetime as dt from datetime import timedelta import glob -#import timeit ( for time tracking if required) - - -def load_data( dataFile, labelFile , typeFile , dist=False, exclude=[]): - ## loading Case - print('loading data') - - if dist: - all_files1 = glob.glob(dataFile + "/*.csv") - li1 = [] - for filename in all_files1: - df = pd.read_csv(filename) - li1.append(df) - data_dat = pd.concat(li1).drop_duplicates() - else: data_dat=pd.read_table(dataFile) - - data_dat.columns = ["Pt_id", "ICD", "Time"] - - if len(exclude)>0: - data_dat=data_dat[~(data_dat["ICD"].str.startswith(tuple(exclude)))] - - print('loaded data for: ',data_dat["Pt_id"].nunique()) - print('loading labels') - - if dist: - all_files = glob.glob(labelFile + "/*.csv") - li = [] - for filename in all_files: - df = pd.read_csv(filename) - li.append(df) - - data_lbl_v1 = pd.concat(li).drop_duplicates() - else: data_lbl_v1=pd.read_table(labelFile) - - data_lbl_v1.columns = ["Pt_id", "mort_label","LOS"]#,"vent_label","time_to_intub","Readmission_label","plos_label"] - data_lbl=pd.merge(data_dat["Pt_id"].drop_duplicates(),data_lbl_v1, how='inner').drop_duplicates() - print('loaded labels for: ',data_lbl_v1["Pt_id"].nunique() , ' after primary cleaning ',data_lbl["Pt_id"].nunique()) - print('Mortality Case counts: ',data_lbl[data_lbl["mort_label"]==1]["Pt_id"].nunique()) - #print('Intubation Case counts: ',data_lbl[data_lbl["vent_label"]==1]["Pt_id"].nunique()) - #print('Intubation Case with tti >=1 : ',data_lbl[(data_lbl["vent_label"]==1)& (data_lbl["time_to_intub"]>=1)]["Pt_id"].nunique()) - print('LOS>7 : ',data_lbl[data_lbl["LOS"]>7]["Pt_id"].nunique()) - #print('pLOS>7 : ',data_lbl[data_lbl["plos_label"]==1]["Pt_id"].nunique()) - #print('Readmission case counts : ',data_lbl[data_lbl["Readmission_label"]==1]["Pt_id"].nunique()) - - ### An example of sampling code: Control Sampling - #print('pt sampling') - #data_sk=data_dat["Pt_id"] - #data_sk=data_sk.drop_duplicates() - #data_sk_samp=data_sk.sample(n=samplesize_pts) ## that is an input arg 7 - #data_dat=data_dat[data_dat["Pt_id"].isin(data_sk_samp.values.tolist())] - #data_lbl=data_lbl[data_lbl["Pt_id"].isin(data_sk_samp.values.tolist())] - - - - ## loading the types - - if typeFile=='NA': - types={"zero_pad":0} - print('new types dictionary') - else: - with open(typeFile, 'rb') as t2: - types=pickle.load(t2) - print('types dictionary loaded') - #end_time = timeit.timeit() - #print ("consumed time for data loading",(_start -end_time)/1000.0 ) - return data_dat, data_lbl, types - - -def pickle_data (data_dat, data_lbl, types, reverse=True): - - full_list=[] + +# import timeit ( for time tracking if required) + + +def load_data(dataFile, labelFile, typeFile, dist=False, exclude=[]): + ## loading Case + print("loading data") + + if dist: + all_files1 = glob.glob(dataFile + "/*.csv") + li1 = [] + for filename in all_files1: + df = pd.read_csv(filename) + li1.append(df) + data_dat = pd.concat(li1).drop_duplicates() + else: + data_dat = pd.read_table(dataFile) + + data_dat.columns = ["Pt_id", "ICD", "Time"] + + if len(exclude) > 0: + data_dat = data_dat[~(data_dat["ICD"].str.startswith(tuple(exclude)))] + + print("loaded data for: ", data_dat["Pt_id"].nunique()) + print("loading labels") + + if dist: + all_files = glob.glob(labelFile + "/*.csv") + li = [] + for filename in all_files: + df = pd.read_csv(filename) + li.append(df) + + data_lbl_v1 = pd.concat(li).drop_duplicates() + else: + data_lbl_v1 = pd.read_table(labelFile) + + data_lbl_v1.columns = [ + "Pt_id", + "mort_label", + "LOS", + ] # ,"vent_label","time_to_intub","Readmission_label","plos_label"] + data_lbl = pd.merge( + data_dat["Pt_id"].drop_duplicates(), data_lbl_v1, how="inner" + ).drop_duplicates() + print( + "loaded labels for: ", + data_lbl_v1["Pt_id"].nunique(), + " after primary cleaning ", + data_lbl["Pt_id"].nunique(), + ) + print( + "Mortality Case counts: ", + data_lbl[data_lbl["mort_label"] == 1]["Pt_id"].nunique(), + ) + # print('Intubation Case counts: ',data_lbl[data_lbl["vent_label"]==1]["Pt_id"].nunique()) + # print('Intubation Case with tti >=1 : ',data_lbl[(data_lbl["vent_label"]==1)& (data_lbl["time_to_intub"]>=1)]["Pt_id"].nunique()) + print("LOS>7 : ", data_lbl[data_lbl["LOS"] > 7]["Pt_id"].nunique()) + # print('pLOS>7 : ',data_lbl[data_lbl["plos_label"]==1]["Pt_id"].nunique()) + # print('Readmission case counts : ',data_lbl[data_lbl["Readmission_label"]==1]["Pt_id"].nunique()) + + ### An example of sampling code: Control Sampling + # print('pt sampling') + # data_sk=data_dat["Pt_id"] + # data_sk=data_sk.drop_duplicates() + # data_sk_samp=data_sk.sample(n=samplesize_pts) ## that is an input arg 7 + # data_dat=data_dat[data_dat["Pt_id"].isin(data_sk_samp.values.tolist())] + # data_lbl=data_lbl[data_lbl["Pt_id"].isin(data_sk_samp.values.tolist())] + + ## loading the types + + if typeFile == "NA": + types = {"zero_pad": 0} + print("new types dictionary") + else: + with open(typeFile, "rb") as t2: + types = pickle.load(t2) + print("types dictionary loaded") + # end_time = timeit.timeit() + # print ("consumed time for data loading",(_start -end_time)/1000.0 ) + return data_dat, data_lbl, types + + +def pickle_data(data_dat, data_lbl, types, reverse=True): + full_list = [] index_date = {} time_list = [] - dates_list =[] + dates_list = [] label_list = [] pt_list = [] - dur_list=[] + dur_list = [] newVisit_list = [] - count=0 - - for Pt, group in data_dat.groupby('Pt_id'): - data_i_c = [] - data_dt_c = [] - for Time, subgroup in group.sort_values(['Time'], ascending= not reverse).groupby('Time', sort=False): ### ascending=True normal order ascending=False reveresed order - data_i_c.append(np.array(subgroup['ICD']).tolist())# get ICD codes for each admission separately - data_dt_c.append(dt.strptime(Time, '%Y-%m-%d'))#concat dischargetime of each admission - if len(data_i_c) > 0: - # creating the duration in days between visits list, first visit marked with 0 (last in reversed order) - v_dur_c=[] - if len(data_dt_c)<=1: - v_dur_c=[0] - else: - for jx in range (len(data_dt_c)): - if jx==0: - v_dur_c.append(jx) - else: - #xx = ((dt.strptime(data_dt_c[jx-1], '%d-%b-%y'))-(dt.strptime(data_dt_c[jx], '%d-%b-%y'))).days ## use if original data have time information or different date format - if reverse: xx = (data_dt_c[jx-1] - data_dt_c[jx]).days ## reversed order - else: xx = (data_dt_c[jx]- data_dt_c[jx-1]).days ### normal order - v_dur_c.append(xx) - #print(data_i_c) - #print(data_dt_c) - #print(v_dur_c) - #print(types) - ### Diagnosis recoding - newPatient_c = [] - for visit in data_i_c: - newVisit_c = [] - for code in visit: - if code in types: newVisit_c.append(types[code]) - else: - types[code] = max(types.values())+1 - newVisit_c.append(types[code]) - newPatient_c.append(newVisit_c) - #print(newPatient_c) - - if len(data_i_c) > 0: ## only save non-empty entries - label_list.append(data_lbl.loc[data_lbl.Pt_id == Pt, ['mort_label','LOS']#,'vent_label','time_to_intub','Readmission_label','plos_label'] - ].values.squeeze().tolist()) #### LR ammended for multilabel - pt_list.append(Pt) - newVisit_list.append(newPatient_c) - dur_list.append(v_dur_c) - print(label_list) - print(pt_list) - print(dur_list) - print(newVisit_list) - count=count+1 - if count % 1000 == 0: print ('processed %d pts' % count) - return types,pt_list,label_list,newVisit_list,dur_list - -def reparsing(pt_list,label_list,newVisit_list,dur_list): - ### Create the combined list for the Pytorch RNN - fset=[] - print ('Reparsing') + count = 0 + + for Pt, group in data_dat.groupby("Pt_id"): + data_i_c = [] + data_dt_c = [] + for Time, subgroup in group.sort_values( + ["Time"], ascending=not reverse + ).groupby( + "Time", sort=False + ): ### ascending=True normal order ascending=False reveresed order + data_i_c.append( + np.array(subgroup["ICD"]).tolist() + ) # get ICD codes for each admission separately + data_dt_c.append( + dt.strptime(Time, "%Y-%m-%d") + ) # concat dischargetime of each admission + if len(data_i_c) > 0: + # creating the duration in days between visits list, first visit marked with 0 (last in reversed order) + v_dur_c = [] + if len(data_dt_c) <= 1: + v_dur_c = [0] + else: + for jx in range(len(data_dt_c)): + if jx == 0: + v_dur_c.append(jx) + else: + # xx = ((dt.strptime(data_dt_c[jx-1], '%d-%b-%y'))-(dt.strptime(data_dt_c[jx], '%d-%b-%y'))).days ## use if original data have time information or different date format + if reverse: + xx = (data_dt_c[jx - 1] - data_dt_c[jx]).days ## reversed order + else: + xx = (data_dt_c[jx] - data_dt_c[jx - 1]).days ### normal order + v_dur_c.append(xx) + # print(data_i_c) + # print(data_dt_c) + # print(v_dur_c) + # print(types) + ### Diagnosis recoding + newPatient_c = [] + for visit in data_i_c: + newVisit_c = [] + for code in visit: + if code in types: + newVisit_c.append(types[code]) + else: + types[code] = max(types.values()) + 1 + newVisit_c.append(types[code]) + newPatient_c.append(newVisit_c) + # print(newPatient_c) + + if len(data_i_c) > 0: ## only save non-empty entries + label_list.append( + data_lbl.loc[ + data_lbl.Pt_id == Pt, + [ + "mort_label", + "LOS", + ], # ,'vent_label','time_to_intub','Readmission_label','plos_label'] + ] + .values.squeeze() + .tolist() + ) #### LR ammended for multilabel + pt_list.append(Pt) + newVisit_list.append(newPatient_c) + dur_list.append(v_dur_c) + print(label_list) + print(pt_list) + print(dur_list) + print(newVisit_list) + count = count + 1 + if count % 1000 == 0: + print("processed %d pts" % count) + return types, pt_list, label_list, newVisit_list, dur_list + + +def reparsing(pt_list, label_list, newVisit_list, dur_list): + ### Create the combined list for the Pytorch RNN + fset = [] + print("Reparsing") for pt_idx in range(len(pt_list)): - pt_sk= pt_list[pt_idx] - pt_lbl= label_list[pt_idx] - pt_vis= newVisit_list[pt_idx] - pt_td= dur_list[pt_idx] - d_gr=[] - n_seq=[] - d_a_v=[] - for v in range(len(pt_vis)): - nv=[] - nv.append([pt_td[v]]) - nv.append(pt_vis[v]) - n_seq.append(nv) - n_pt= [pt_sk,pt_lbl,n_seq] - print("n_pt",n_pt) - fset.append(n_pt) + pt_sk = pt_list[pt_idx] + pt_lbl = label_list[pt_idx] + pt_vis = newVisit_list[pt_idx] + pt_td = dur_list[pt_idx] + d_gr = [] + n_seq = [] + d_a_v = [] + for v in range(len(pt_vis)): + nv = [] + nv.append([pt_td[v]]) + nv.append(pt_vis[v]) + n_seq.append(nv) + n_pt = [pt_sk, pt_lbl, n_seq] + print("n_pt", n_pt) + fset.append(n_pt) return fset -def split_data(fset, pt_list, pts_file_pre,outFile): - +def split_data(fset, pt_list, pts_file_pre, outFile): ### Random split to train ,test and validation sets - print ("Splitting") + print("Splitting") - if pts_file_pre=='NA': - print('random split') + if pts_file_pre == "NA": + print("random split") dataSize = len(pt_list) - #np.random.seed(0) + # np.random.seed(0) ind = np.random.permutation(dataSize) nTest = int(0.2 * dataSize) nValid = int(0.1 * dataSize) test_indices = ind[:nTest] - valid_indices = ind[nTest:nTest+nValid] - train_indices = ind[nTest+nValid:] + valid_indices = ind[nTest : nTest + nValid] + train_indices = ind[nTest + nValid :] else: - print ('loading previous splits') - pt_train=pickle.load(open(pts_file_pre+'.train', 'rb')) - pt_valid=pickle.load(open(pts_file_pre+'.valid', 'rb')) - pt_test=pickle.load(open(pts_file_pre+'.test', 'rb')) - test_indices = np.intersect1d(pt_list, pt_test,assume_unique=True, return_indices=True)[1] - valid_indices= np.intersect1d(pt_list, pt_valid,assume_unique=True, return_indices=True)[1] - train_indices= np.intersect1d(pt_list, pt_train,assume_unique=True, return_indices=True)[1] - - for subset in ['train','valid','test']: - if subset =='train': + print("loading previous splits") + pt_train = pickle.load(open(pts_file_pre + ".train", "rb")) + pt_valid = pickle.load(open(pts_file_pre + ".valid", "rb")) + pt_test = pickle.load(open(pts_file_pre + ".test", "rb")) + test_indices = np.intersect1d( + pt_list, pt_test, assume_unique=True, return_indices=True + )[1] + valid_indices = np.intersect1d( + pt_list, pt_valid, assume_unique=True, return_indices=True + )[1] + train_indices = np.intersect1d( + pt_list, pt_train, assume_unique=True, return_indices=True + )[1] + + for subset in ["train", "valid", "test"]: + if subset == "train": indices = train_indices - elif subset =='valid': + elif subset == "valid": indices = valid_indices - elif subset =='test': + elif subset == "test": indices = test_indices - else: - print ('error') + else: + print("error") break - + #### below comments are mainly because I'm no longer need those theano RETAIN needed data, so comment for now #### only using Pts file , so keeping them for now - - #subset_x = [newVisit_list[i] for i in indices] - #subset_y = [label_list[i] for i in indices] - #subset_t = [dur_list[i] for i in indices] - subset_p = [pt_list[i] for i in indices] - #nseqfile = outFile +'.visits.'+subset - #nlabfile = outFile +'.labels.'+subset - #ntimefile = outFile +'.days.'+subset - nptfile = outFile +'.pts.'+subset - #pickle.dump(subset_x, open(nseqfile, 'wb'),protocol=2) - #pickle.dump(subset_y, open(nlabfile, 'wb'),protocol=2) - #pickle.dump(subset_t, open(ntimefile, 'wb'),protocol=2) - pickle.dump(subset_p, open(nptfile, 'wb'),protocol=2) - - subset_full= [fset[i] for i in indices] - ncombfile = outFile +'.combined.'+subset - pickle.dump(subset_full, open(ncombfile, 'wb'), -1) - -def dump_split_process_data(dataFile, labelFile , typeFile ,outFile , pts_file_pre , dist=False, exclude=[],reverse=True): - - data_dat, data_lbl, types = load_data( dataFile, labelFile , typeFile , dist=dist, exclude=exclude) - types, pt_list , label_list,newVisit_list,dur_list = pickle_data (data_dat, data_lbl, types, reverse=reverse) - fset= reparsing(pt_list , label_list , newVisit_list , dur_list) - split_data(fset, pt_list , pts_file_pre,outFile) - pickle.dump(types, open(outFile+'.types', 'wb'), -1) - - ### Creating the full pickled lists ### uncomment if you need to dump the all data before splitting - #pickle.dump(label_list, open(outFile+'.labels', 'wb'), -1) - #pickle.dump(newVisit_list, open(outFile+'.visits', 'wb'), -1) - #pickle.dump(pt_list, open(outFile+'.pts', 'wb'), -1) - #pickle.dump(dur_list, open(outFile+'.days', 'wb'), -1) - - -if __name__ == '__main__': - - dataFile= sys.argv[1] - labelFile= sys.argv[2] - typeFile= sys.argv[3] - outFile = sys.argv[4] - pts_file_pre = sys.argv[5] - #cls_type= sys.argv[6] - #samplesize_pts = int(sys.argv[7]) - parser = OptionParser() - (options, args) = parser.parse_args() - dump_split_process_data(dataFile, labelFile , typeFile ,outFile , pts_file_pre , dist=False, exclude=[]) - + # subset_x = [newVisit_list[i] for i in indices] + # subset_y = [label_list[i] for i in indices] + # subset_t = [dur_list[i] for i in indices] + subset_p = [pt_list[i] for i in indices] + # nseqfile = outFile +'.visits.'+subset + # nlabfile = outFile +'.labels.'+subset + # ntimefile = outFile +'.days.'+subset + nptfile = outFile + ".pts." + subset + # pickle.dump(subset_x, open(nseqfile, 'wb'),protocol=2) + # pickle.dump(subset_y, open(nlabfile, 'wb'),protocol=2) + # pickle.dump(subset_t, open(ntimefile, 'wb'),protocol=2) + pickle.dump(subset_p, open(nptfile, "wb"), protocol=2) + subset_full = [fset[i] for i in indices] + ncombfile = outFile + ".combined." + subset + pickle.dump(subset_full, open(ncombfile, "wb"), -1) +def dump_split_process_data( + dataFile, + labelFile, + typeFile, + outFile, + pts_file_pre, + dist=False, + exclude=[], + reverse=True, +): + data_dat, data_lbl, types = load_data( + dataFile, labelFile, typeFile, dist=dist, exclude=exclude + ) + types, pt_list, label_list, newVisit_list, dur_list = pickle_data( + data_dat, data_lbl, types, reverse=reverse + ) + fset = reparsing(pt_list, label_list, newVisit_list, dur_list) + split_data(fset, pt_list, pts_file_pre, outFile) + pickle.dump(types, open(outFile + ".types", "wb"), -1) - + ### Creating the full pickled lists ### uncomment if you need to dump the all data before splitting + # pickle.dump(label_list, open(outFile+'.labels', 'wb'), -1) + # pickle.dump(newVisit_list, open(outFile+'.visits', 'wb'), -1) + # pickle.dump(pt_list, open(outFile+'.pts', 'wb'), -1) + # pickle.dump(dur_list, open(outFile+'.days', 'wb'), -1) +if __name__ == "__main__": + dataFile = sys.argv[1] + labelFile = sys.argv[2] + typeFile = sys.argv[3] + outFile = sys.argv[4] + pts_file_pre = sys.argv[5] + # cls_type= sys.argv[6] + # samplesize_pts = int(sys.argv[7]) + parser = OptionParser() + (options, args) = parser.parse_args() + dump_split_process_data( + dataFile, labelFile, typeFile, outFile, pts_file_pre, dist=False, exclude=[] + ) diff --git a/preprocessing/day_intervals_preproc/day_intervals_cohort_v2.py b/preprocessing/day_intervals_preproc/day_intervals_cohort_v2.py index 080cbf543b..8da71149e5 100644 --- a/preprocessing/day_intervals_preproc/day_intervals_cohort_v2.py +++ b/preprocessing/day_intervals_preproc/day_intervals_cohort_v2.py @@ -6,14 +6,31 @@ from pathlib import Path from tqdm import tqdm import importlib -import disease_cohort -importlib.reload(disease_cohort) -import disease_cohort -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + './../..') + +import preprocessing.day_intervals_preproc.disease_cohort as disease_cohort + +# importlib.reload(disease_cohort) +# import disease_cohort + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "./../..") if not os.path.exists("./data/cohort"): os.makedirs("./data/cohort") - -def get_visit_pts(mimic4_path:str, group_col:str, visit_col:str, admit_col:str, disch_col:str, adm_visit_col:str, use_mort:bool, use_los:bool, los:int, use_admn:bool, disease_label:str,use_ICU:bool): + + +def get_visit_pts( + mimic4_path: str, + group_col: str, + visit_col: str, + admit_col: str, + disch_col: str, + adm_visit_col: str, + use_mort: bool, + use_los: bool, + los: int, + use_admn: bool, + disease_label: str, + use_ICU: bool, +): """Combines the MIMIC-IV core/patients table information with either the icu/icustays or core/admissions data. Parameters: @@ -25,83 +42,186 @@ def get_visit_pts(mimic4_path:str, group_col:str, visit_col:str, admit_col:str, use_ICU: describes whether to speficially look at ICU visits in icu/icustays OR look at general admissions from core/admissions """ - visit = None # df containing visit information depending on using ICU or not + visit = None # df containing visit information depending on using ICU or not if use_ICU: - visit = pd.read_csv(mimic4_path + "icu/icustays.csv.gz", compression='gzip', header=0, index_col=None, parse_dates=[admit_col, disch_col]) + visit = pd.read_csv( + mimic4_path + "icu/icustays.csv.gz", + compression="gzip", + header=0, + index_col=None, + parse_dates=[admit_col, disch_col], + ) if use_admn: # icustays doesn't have a way to identify if patient died during visit; must # use core/patients to remove such stay_ids for readmission labels - pts = pd.read_csv(mimic4_path + "hosp/patients.csv.gz", compression='gzip', header=0, index_col=None, usecols=['subject_id', 'dod'], parse_dates=['dod']) - visit = visit.merge(pts, how='inner', left_on='subject_id', right_on='subject_id') + pts = pd.read_csv( + mimic4_path + "hosp/patients.csv.gz", + compression="gzip", + header=0, + index_col=None, + usecols=["subject_id", "dod"], + parse_dates=["dod"], + ) + visit = visit.merge( + pts, how="inner", left_on="subject_id", right_on="subject_id" + ) visit = visit.loc[(visit.dod.isna()) | (visit.dod >= visit[disch_col])] if len(disease_label): - hids=disease_cohort.extract_diag_cohort(visit['hadm_id'],disease_label,mimic4_path) - visit=visit[visit['hadm_id'].isin(hids['hadm_id'])] - print("[ READMISSION DUE TO "+disease_label+" ]") - + hids = disease_cohort.extract_diag_cohort( + visit["hadm_id"], disease_label, mimic4_path + ) + visit = visit[visit["hadm_id"].isin(hids["hadm_id"])] + print("[ READMISSION DUE TO " + disease_label + " ]") + else: - visit = pd.read_csv(mimic4_path + "hosp/admissions.csv.gz", compression='gzip', header=0, index_col=None, parse_dates=[admit_col, disch_col]) - visit['los']=visit[disch_col]-visit[admit_col] + visit = pd.read_csv( + mimic4_path + "hosp/admissions.csv.gz", + compression="gzip", + header=0, + index_col=None, + parse_dates=[admit_col, disch_col], + ) + visit["los"] = visit[disch_col] - visit[admit_col] visit[admit_col] = pd.to_datetime(visit[admit_col]) - visit[disch_col] = pd.to_datetime(visit[disch_col]) - visit['los']=pd.to_timedelta(visit[disch_col]-visit[admit_col],unit='h') - visit['los']=visit['los'].astype(str) - visit[['days', 'dummy','hours']] = visit['los'].str.split(' ', -1, expand=True) - visit['los']=pd.to_numeric(visit['days']) - visit=visit.drop(columns=['days', 'dummy','hours']) - - + visit[disch_col] = pd.to_datetime(visit[disch_col]) + visit["los"] = pd.to_timedelta(visit[disch_col] - visit[admit_col], unit="h") + visit["los"] = visit["los"].astype(str) + visit[["days", "dummy", "hours"]] = visit["los"].str.split(" ", expand=True) + visit["los"] = pd.to_numeric(visit["days"]) + visit = visit.drop(columns=["days", "dummy", "hours"]) + if use_admn: # remove hospitalizations with a death; impossible for readmission for such visits visit = visit.loc[visit.hospital_expire_flag == 0] if len(disease_label): - hids=disease_cohort.extract_diag_cohort(visit['hadm_id'],disease_label,mimic4_path) - visit=visit[visit['hadm_id'].isin(hids['hadm_id'])] - print("[ READMISSION DUE TO "+disease_label+" ]") + hids = disease_cohort.extract_diag_cohort( + visit["hadm_id"], disease_label, mimic4_path + ) + visit = visit[visit["hadm_id"].isin(hids["hadm_id"])] + print("[ READMISSION DUE TO " + disease_label + " ]") pts = pd.read_csv( - mimic4_path + "hosp/patients.csv.gz", compression='gzip', header=0, index_col = None, usecols=[group_col, 'anchor_year', 'anchor_age', 'anchor_year_group', 'dod','gender'] - ) - pts['yob']= pts['anchor_year'] - pts['anchor_age'] # get yob to ensure a given visit is from an adult - pts['min_valid_year'] = pts['anchor_year'] + (2019 - pts['anchor_year_group'].str.slice(start=-4).astype(int)) - + mimic4_path + "hosp/patients.csv.gz", + compression="gzip", + header=0, + index_col=None, + usecols=[ + group_col, + "anchor_year", + "anchor_age", + "anchor_year_group", + "dod", + "gender", + ], + ) + pts["yob"] = ( + pts["anchor_year"] - pts["anchor_age"] + ) # get yob to ensure a given visit is from an adult + pts["min_valid_year"] = pts["anchor_year"] + ( + 2019 - pts["anchor_year_group"].str.slice(start=-4).astype(int) + ) + # Define anchor_year corresponding to the anchor_year_group 2017-2019. This is later used to prevent consideration # of visits with prediction windows outside the dataset's time range (2008-2019) - #[[group_col, visit_col, admit_col, disch_col]] + # [[group_col, visit_col, admit_col, disch_col]] if use_ICU: - visit_pts = visit[[group_col, visit_col, adm_visit_col, admit_col, disch_col,'los']].merge( - pts[[group_col, 'anchor_year', 'anchor_age', 'yob', 'min_valid_year', 'dod','gender']], how='inner', left_on=group_col, right_on=group_col + visit_pts = visit[ + [group_col, visit_col, adm_visit_col, admit_col, disch_col, "los"] + ].merge( + pts[ + [ + group_col, + "anchor_year", + "anchor_age", + "yob", + "min_valid_year", + "dod", + "gender", + ] + ], + how="inner", + left_on=group_col, + right_on=group_col, ) else: - visit_pts = visit[[group_col, visit_col, admit_col, disch_col,'los']].merge( - pts[[group_col, 'anchor_year', 'anchor_age', 'yob', 'min_valid_year', 'dod','gender']], how='inner', left_on=group_col, right_on=group_col - ) + visit_pts = visit[[group_col, visit_col, admit_col, disch_col, "los"]].merge( + pts[ + [ + group_col, + "anchor_year", + "anchor_age", + "yob", + "min_valid_year", + "dod", + "gender", + ] + ], + how="inner", + left_on=group_col, + right_on=group_col, + ) # only take adult patients -# visit_pts['Age']=visit_pts[admit_col].dt.year - visit_pts['yob'] -# visit_pts = visit_pts.loc[visit_pts['Age'] >= 18] - visit_pts['Age']=visit_pts['anchor_age'] - visit_pts = visit_pts.loc[visit_pts['Age'] >= 18] - + # visit_pts['Age']=visit_pts[admit_col].dt.year - visit_pts['yob'] + # visit_pts = visit_pts.loc[visit_pts['Age'] >= 18] + visit_pts["Age"] = visit_pts["anchor_age"] + visit_pts = visit_pts.loc[visit_pts["Age"] >= 18] + ##Add Demo data - eth = pd.read_csv(mimic4_path + "hosp/admissions.csv.gz", compression='gzip', header=0, usecols=['hadm_id', 'insurance','race'], index_col=None) - visit_pts= visit_pts.merge(eth, how='inner', left_on='hadm_id', right_on='hadm_id') - + eth = pd.read_csv( + mimic4_path + "hosp/admissions.csv.gz", + compression="gzip", + header=0, + usecols=["hadm_id", "insurance", "race"], + index_col=None, + ) + visit_pts = visit_pts.merge(eth, how="inner", left_on="hadm_id", right_on="hadm_id") + if use_ICU: - return visit_pts[[group_col, visit_col, adm_visit_col, admit_col, disch_col,'los', 'min_valid_year', 'dod','Age','gender','race', 'insurance']] + return visit_pts[ + [ + group_col, + visit_col, + adm_visit_col, + admit_col, + disch_col, + "los", + "min_valid_year", + "dod", + "Age", + "gender", + "race", + "insurance", + ] + ] else: - return visit_pts.dropna(subset=['min_valid_year'])[[group_col, visit_col, admit_col, disch_col,'los', 'min_valid_year', 'dod','Age','gender','race', 'insurance']] + return visit_pts.dropna(subset=["min_valid_year"])[ + [ + group_col, + visit_col, + admit_col, + disch_col, + "los", + "min_valid_year", + "dod", + "Age", + "gender", + "race", + "insurance", + ] + ] def validate_row(row, ctrl, invalid, max_year, disch_col, valid_col, gap): """Checks if visit's prediction window potentially extends beyond the dataset range (2008-2019). An 'invalid row' is NOT guaranteed to be outside the range, only potentially outside due to de-identification of MIMIC-IV being done through 3-year time ranges. - + To be invalid, the end of the prediction window's year must both extend beyond the maximum seen year - for a patient AND beyond the year that corresponds to the 2017-2019 anchor year range for a patient""" - print("disch_col",row[disch_col]) + for a patient AND beyond the year that corresponds to the 2017-2019 anchor year range for a patient + """ + print("disch_col", row[disch_col]) print(gap) pred_year = (row[disch_col] + gap).year if max_year < pred_year and pred_year > row[valid_col]: @@ -111,109 +231,162 @@ def validate_row(row, ctrl, invalid, max_year, disch_col, valid_col, gap): return ctrl, invalid -def partition_by_los(df:pd.DataFrame, los:int, group_col:str, visit_col:str, admit_col:str, disch_col:str, valid_col:str): - - invalid = df.loc[(df[admit_col].isna()) | (df[disch_col].isna()) | (df['los'].isna())] - cohort = df.loc[(~df[admit_col].isna()) & (~df[disch_col].isna()) & (~df['los'].isna())] - - - #cohort=cohort.fillna(0) - pos_cohort=cohort[cohort['los']>los] - neg_cohort=cohort[cohort['los']<=los] - neg_cohort=neg_cohort.fillna(0) - pos_cohort=pos_cohort.fillna(0) - - pos_cohort['label']=1 - neg_cohort['label']=0 - - cohort=pd.concat([pos_cohort,neg_cohort], axis=0) - cohort=cohort.sort_values(by=[group_col,admit_col]) - #print("cohort",cohort.shape) +def partition_by_los( + df: pd.DataFrame, + los: int, + group_col: str, + visit_col: str, + admit_col: str, + disch_col: str, + valid_col: str, +): + invalid = df.loc[ + (df[admit_col].isna()) | (df[disch_col].isna()) | (df["los"].isna()) + ] + cohort = df.loc[ + (~df[admit_col].isna()) & (~df[disch_col].isna()) & (~df["los"].isna()) + ] + + # cohort=cohort.fillna(0) + pos_cohort = cohort[cohort["los"] > los] + neg_cohort = cohort[cohort["los"] <= los] + neg_cohort = neg_cohort.fillna(0) + pos_cohort = pos_cohort.fillna(0) + + pos_cohort["label"] = 1 + neg_cohort["label"] = 0 + + cohort = pd.concat([pos_cohort, neg_cohort], axis=0) + cohort = cohort.sort_values(by=[group_col, admit_col]) + # print("cohort",cohort.shape) print("[ LOS LABELS FINISHED ]") return cohort, invalid - - -def partition_by_readmit(df:pd.DataFrame, gap:datetime.timedelta, group_col:str, visit_col:str, admit_col:str, disch_col:str, valid_col:str): + + +def partition_by_readmit( + df: pd.DataFrame, + gap: datetime.timedelta, + group_col: str, + visit_col: str, + admit_col: str, + disch_col: str, + valid_col: str, +): """Applies labels to individual visits according to whether or not a readmission has occurred within the specified `gap` days. For a given visit, another visit must occur within the gap window for a positive readmission label. - The gap window starts from the disch_col time and the admit_col of subsequent visits are considered.""" - - case = pd.DataFrame() # hadm_ids with readmission within the gap period - ctrl = pd.DataFrame() # hadm_ids without readmission within the gap period - invalid = pd.DataFrame() # hadm_ids that are not considered in the cohort + The gap window starts from the disch_col time and the admit_col of subsequent visits are considered. + """ + + case = pd.DataFrame() # hadm_ids with readmission within the gap period + ctrl = pd.DataFrame() # hadm_ids without readmission within the gap period + invalid = pd.DataFrame() # hadm_ids that are not considered in the cohort # Iterate through groupbys based on group_col (subject_id). Data is sorted by subject_id and admit_col (admittime) # to ensure that the most current hadm_id is last in a group. - #grouped= df[[group_col, visit_col, admit_col, disch_col, valid_col]].sort_values(by=[group_col, admit_col]).groupby(group_col) - grouped= df.sort_values(by=[group_col, admit_col]).groupby(group_col) + # grouped= df[[group_col, visit_col, admit_col, disch_col, valid_col]].sort_values(by=[group_col, admit_col]).groupby(group_col) + grouped = df.sort_values(by=[group_col, admit_col]).groupby(group_col) for subject, group in tqdm(grouped): max_year = group.max()[disch_col].year if group.shape[0] <= 1: - #ctrl, invalid = validate_row(group.iloc[0], ctrl, invalid, max_year, disch_col, valid_col, gap) # A group with 1 row has no readmission; goes to ctrl - ctrl = ctrl.append(group.iloc[0]) + # ctrl, invalid = validate_row(group.iloc[0], ctrl, invalid, max_year, disch_col, valid_col, gap) # A group with 1 row has no readmission; goes to ctrl + ctrl = pd.concat([ctrl, pd.DataFrame([group.iloc[0]])], ignore_index=True) else: - for idx in range(group.shape[0]-1): - visit_time = group.iloc[idx][disch_col] # For each index (a unique hadm_id), get its timestamp - if group.loc[ - (group[admit_col] > visit_time) & # Readmissions must come AFTER the current timestamp - (group[admit_col] - visit_time <= gap) # Distance between a timestamp and readmission must be within gap - ].shape[0] >= 1: # If ANY rows meet above requirements, a readmission has occurred after that visit - - case = case.append(group.iloc[idx]) + for idx in range(group.shape[0] - 1): + visit_time = group.iloc[idx][ + disch_col + ] # For each index (a unique hadm_id), get its timestamp + if ( + group.loc[ + (group[admit_col] > visit_time) + & ( # Readmissions must come AFTER the current timestamp + group[admit_col] - visit_time <= gap + ) # Distance between a timestamp and readmission must be within gap + ].shape[0] + >= 1 + ): # If ANY rows meet above requirements, a readmission has occurred after that visit + case = pd.concat( + [case, pd.DataFrame([group.iloc[idx]])], ignore_index=True + ) else: # If no readmission is found, only add to ctrl if prediction window is guaranteed to be within the # time range of the dataset (2008-2019). Visits with prediction windows existing in potentially out-of-range # dates (like 2018-2020) are excluded UNLESS the prediction window takes place the same year as the visit, # in which case it is guaranteed to be within 2008-2019 - ctrl = ctrl.append(group.iloc[idx]) + ctrl = pd.concat( + [ctrl, pd.DataFrame([group.iloc[idx]])], ignore_index=True + ) - #ctrl, invalid = validate_row(group.iloc[-1], ctrl, invalid, max_year, disch_col, valid_col, gap) # The last hadm_id datewise is guaranteed to have no readmission logically - ctrl = ctrl.append(group.iloc[-1]) - #print(f"[ {gap.days} DAYS ] {case.shape[0] + ctrl.shape[0]}/{df.shape[0]} {visit_col}s processed") + # ctrl, invalid = validate_row(group.iloc[-1], ctrl, invalid, max_year, disch_col, valid_col, gap) # The last hadm_id datewise is guaranteed to have no readmission logically + ctrl = pd.concat([ctrl, pd.DataFrame([group.iloc[-1]])], ignore_index=True) + # print(f"[ {gap.days} DAYS ] {case.shape[0] + ctrl.shape[0]}/{df.shape[0]} {visit_col}s processed") print("[ READMISSION LABELS FINISHED ]") return case, ctrl, invalid -def partition_by_mort(df:pd.DataFrame, group_col:str, visit_col:str, admit_col:str, disch_col:str, death_col:str): +def partition_by_mort( + df: pd.DataFrame, + group_col: str, + visit_col: str, + admit_col: str, + disch_col: str, + death_col: str, +): """Applies labels to individual visits according to whether or not a death has occurred within the times of the specified admit_col and disch_col""" invalid = df.loc[(df[admit_col].isna()) | (df[disch_col].isna())] cohort = df.loc[(~df[admit_col].isna()) & (~df[disch_col].isna())] - -# cohort["label"] = ( -# (~cohort[death_col].isna()) -# & (cohort[death_col] >= cohort[admit_col]) -# & (cohort[death_col] <= cohort[disch_col]) -# ) -# cohort["label"] = cohort["label"].astype("Int32") - #print("cohort",cohort.shape) - #print(np.where(~cohort[death_col].isna(),1,0)) - #print(np.where(cohort.loc[death_col] >= cohort.loc[admit_col],1,0)) - #print(np.where(cohort.loc[death_col] <= cohort.loc[disch_col],1,0)) - cohort['label']=0 - #cohort=cohort.fillna(0) - pos_cohort=cohort[~cohort[death_col].isna()] - neg_cohort=cohort[cohort[death_col].isna()] - neg_cohort=neg_cohort.fillna(0) - pos_cohort=pos_cohort.fillna(0) + + # cohort["label"] = ( + # (~cohort[death_col].isna()) + # & (cohort[death_col] >= cohort[admit_col]) + # & (cohort[death_col] <= cohort[disch_col]) + # ) + # cohort["label"] = cohort["label"].astype("Int32") + # print("cohort",cohort.shape) + # print(np.where(~cohort[death_col].isna(),1,0)) + # print(np.where(cohort.loc[death_col] >= cohort.loc[admit_col],1,0)) + # print(np.where(cohort.loc[death_col] <= cohort.loc[disch_col],1,0)) + cohort["label"] = 0 + # cohort=cohort.fillna(0) + pos_cohort = cohort[~cohort[death_col].isna()] + neg_cohort = cohort[cohort[death_col].isna()] + neg_cohort = neg_cohort.fillna(0) + pos_cohort = pos_cohort.fillna(0) pos_cohort[death_col] = pd.to_datetime(pos_cohort[death_col]) - pos_cohort['label'] = np.where((pos_cohort[death_col] >= pos_cohort[admit_col]) & (pos_cohort[death_col] <= pos_cohort[disch_col]),1,0) - - pos_cohort['label'] = pos_cohort['label'].astype("Int32") - cohort=pd.concat([pos_cohort,neg_cohort], axis=0) - cohort=cohort.sort_values(by=[group_col,admit_col]) - #print("cohort",cohort.shape) + pos_cohort["label"] = np.where( + (pos_cohort[death_col] >= pos_cohort[admit_col]) + & (pos_cohort[death_col] <= pos_cohort[disch_col]), + 1, + 0, + ) + + pos_cohort["label"] = pos_cohort["label"].astype("Int32") + cohort = pd.concat([pos_cohort, neg_cohort], axis=0) + cohort = cohort.sort_values(by=[group_col, admit_col]) + # print("cohort",cohort.shape) print("[ MORTALITY LABELS FINISHED ]") return cohort, invalid -def get_case_ctrls(df:pd.DataFrame, gap:int, group_col:str, visit_col:str, admit_col:str, disch_col:str, valid_col:str, death_col:str, use_mort=False,use_admn=False,use_los=False) -> pd.DataFrame: +def get_case_ctrls( + df: pd.DataFrame, + gap: int, + group_col: str, + visit_col: str, + admit_col: str, + disch_col: str, + valid_col: str, + death_col: str, + use_mort=False, + use_admn=False, + use_los=False, +) -> pd.DataFrame: """Handles logic for creating the labelled cohort based on arguments passed to extract(). Parameters: @@ -228,86 +401,142 @@ def get_case_ctrls(df:pd.DataFrame, gap:int, group_col:str, visit_col:str, admit """ case = None # hadm_ids with readmission within the gap period - ctrl = None # hadm_ids without readmission within the gap period - invalid = None # hadm_ids that are not considered in the cohort + ctrl = None # hadm_ids without readmission within the gap period + invalid = None # hadm_ids that are not considered in the cohort if use_mort: - return partition_by_mort(df, group_col, visit_col, admit_col, disch_col, death_col) + return partition_by_mort( + df, group_col, visit_col, admit_col, disch_col, death_col + ) elif use_admn: gap = datetime.timedelta(days=gap) # transform gap into a timedelta to compare with datetime columns - case, ctrl, invalid = partition_by_readmit(df, gap, group_col, visit_col, admit_col, disch_col, valid_col) + case, ctrl, invalid = partition_by_readmit( + df, gap, group_col, visit_col, admit_col, disch_col, valid_col + ) # case hadm_ids are labelled 1 for readmission, ctrls have a 0 label - case['label'] = np.ones(case.shape[0]).astype(int) - ctrl['label'] = np.zeros(ctrl.shape[0]).astype(int) + case["label"] = np.ones(case.shape[0]).astype(int) + ctrl["label"] = np.zeros(ctrl.shape[0]).astype(int) return pd.concat([case, ctrl], axis=0), invalid elif use_los: - return partition_by_los(df, gap, group_col, visit_col, admit_col, disch_col, death_col) + return partition_by_los( + df, gap, group_col, visit_col, admit_col, disch_col, death_col + ) # print(f"[ {gap.days} DAYS ] {invalid.shape[0]} hadm_ids are invalid") -def extract_data(use_ICU:str, label:str, time:int, icd_code:str, root_dir, disease_label, cohort_output=None, summary_output=None): +# create extract options: use_icu... +# root_dir (path) -> raw_data_dir and preproc_data_dir + + +def extract_data( + use_ICU: str, + label: str, + time: int, + icd_code: str, + root_dir, + disease_label, + cohort_output=None, + summary_output=None, +): """Extracts cohort data and summary from MIMIC-IV data based on provided parameters. Parameters: cohort_output: name of labelled cohort output file summary_output: name of summary output file use_ICU: state whether to use ICU patient data or not - label: Can either be '{day} day Readmission' or 'Mortality', decides what binary data label signifies""" + label: Can either be '{day} day Readmission' or 'Mortality', decides what binary data label signifies + """ print("===========MIMIC-IV v2.0============") if not cohort_output: - cohort_output="cohort_" + use_ICU.lower() + "_" + label.lower().replace(" ", "_") + "_" + str(time) + "_" + disease_label + cohort_output = ( + "cohort_" + + use_ICU.lower() + + "_" + + label.lower().replace(" ", "_") + + "_" + + str(time) + + "_" + + disease_label + ) if not summary_output: - summary_output="summary_" + use_ICU.lower() + "_" + label.lower().replace(" ", "_") + "_" + str(time) + "_" + disease_label - - if icd_code=="No Disease Filter": + summary_output = ( + "summary_" + + use_ICU.lower() + + "_" + + label.lower().replace(" ", "_") + + "_" + + str(time) + + "_" + + disease_label + ) + + if icd_code == "No Disease Filter": if len(disease_label): - print(f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} DUE TO {disease_label.upper()} | {str(time)} | ") + print( + f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} DUE TO {disease_label.upper()} | {str(time)} | " + ) else: - print(f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} | {str(time)} |") + print( + f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} | {str(time)} |" + ) else: if len(disease_label): - print(f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} DUE TO {disease_label.upper()} | ADMITTED DUE TO {icd_code.upper()} | {str(time)} |") + print( + f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} DUE TO {disease_label.upper()} | ADMITTED DUE TO {icd_code.upper()} | {str(time)} |" + ) else: - print(f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} | ADMITTED DUE TO {icd_code.upper()} | {str(time)} |") - #print(label) - cohort, invalid = None, None # final labelled output and df of invalid records, respectively + print( + f"EXTRACTING FOR: | {use_ICU.upper()} | {label.upper()} | ADMITTED DUE TO {icd_code.upper()} | {str(time)} |" + ) + # print(label) + cohort, invalid = ( + None, + None, + ) # final labelled output and df of invalid records, respectively pts = None # valid patients generated by get_visit_pts based on use_ICU and label - ICU=use_ICU - group_col, visit_col, admit_col, disch_col, death_col, adm_visit_col = "", "", "", "", "", "" - #print(label) - use_mort = label == "Mortality" # change to boolean value - use_admn=label=='Readmission' - los=0 - use_los= label=='Length of Stay' - - #print(use_mort) - #print(use_admn) - #print(use_los) + ICU = use_ICU + group_col, visit_col, admit_col, disch_col, death_col, adm_visit_col = ( + "", + "", + "", + "", + "", + "", + ) + # print(label) + use_mort = label == "Mortality" # change to boolean value + use_admn = label == "Readmission" + los = 0 + use_los = label == "Length of Stay" + + # print(use_mort) + # print(use_admn) + # print(use_los) if use_los: - los=time - use_ICU = use_ICU == "ICU" # change to boolean value - use_disease=icd_code!="No Disease Filter" - + los = time + use_ICU = use_ICU == "ICU" # change to boolean value + use_disease = icd_code != "No Disease Filter" + if use_ICU: - group_col='subject_id' - visit_col='stay_id' - admit_col='intime' - disch_col='outtime' - death_col='dod' - adm_visit_col='hadm_id' + group_col = "subject_id" + visit_col = "stay_id" + admit_col = "intime" + disch_col = "outtime" + death_col = "dod" + adm_visit_col = "hadm_id" else: - group_col='subject_id' - visit_col='hadm_id' - admit_col='admittime' - disch_col='dischtime' - death_col='dod' + group_col = "subject_id" + visit_col = "hadm_id" + admit_col = "admittime" + disch_col = "dischtime" + death_col = "dod" pts = get_visit_pts( - mimic4_path=root_dir+"/mimiciv/2.0/", + mimic4_path=root_dir + "\\raw_data\\mimiciv_2_0\\", group_col=group_col, visit_col=visit_col, admit_col=admit_col, @@ -318,49 +547,104 @@ def extract_data(use_ICU:str, label:str, time:int, icd_code:str, root_dir, disea los=los, use_admn=use_admn, disease_label=disease_label, - use_ICU=use_ICU + use_ICU=use_ICU, ) - #print("pts",pts.head()) - + # print("pts",pts.head()) + # cols to be extracted from get_case_ctrls - cols = [group_col, visit_col, admit_col, disch_col, 'Age','gender','ethnicity','insurance','label'] + cols = [ + group_col, + visit_col, + admit_col, + disch_col, + "Age", + "gender", + "ethnicity", + "insurance", + "label", + ] if use_mort: cols.append(death_col) - cohort, invalid = get_case_ctrls(pts, None, group_col, visit_col, admit_col, disch_col,'min_valid_year', death_col, use_mort=True,use_admn=False,use_los=False) + cohort, invalid = get_case_ctrls( + pts, + None, + group_col, + visit_col, + admit_col, + disch_col, + "min_valid_year", + death_col, + use_mort=True, + use_admn=False, + use_los=False, + ) elif use_admn: interval = time - cohort, invalid = get_case_ctrls(pts, interval, group_col, visit_col, admit_col, disch_col,'min_valid_year', death_col, use_mort=False,use_admn=True,use_los=False) + cohort, invalid = get_case_ctrls( + pts, + interval, + group_col, + visit_col, + admit_col, + disch_col, + "min_valid_year", + death_col, + use_mort=False, + use_admn=True, + use_los=False, + ) elif use_los: - cohort, invalid = get_case_ctrls(pts, los, group_col, visit_col, admit_col, disch_col,'min_valid_year', death_col, use_mort=False,use_admn=False,use_los=True) - #print(cohort.head()) - + cohort, invalid = get_case_ctrls( + pts, + los, + group_col, + visit_col, + admit_col, + disch_col, + "min_valid_year", + death_col, + use_mort=False, + use_admn=False, + use_los=True, + ) + # print(cohort.head()) + if use_ICU: cols.append(adm_visit_col) - #print(cohort.head()) - + # print(cohort.head()) + if use_disease: - hids=disease_cohort.extract_diag_cohort(cohort['hadm_id'],icd_code,root_dir+"/mimiciv/2.0/") - #print(hids.shape) - #print(cohort.shape) - #print(len(list(set(hids['hadm_id'].unique()).intersection(set(cohort['hadm_id'].unique()))))) - cohort=cohort[cohort['hadm_id'].isin(hids['hadm_id'])] - cohort_output=cohort_output+"_"+icd_code - summary_output=summary_output+"_"+icd_code - #print(cohort[cols].head()) + hids = disease_cohort.extract_diag_cohort( + cohort["hadm_id"], + icd_code, + root_dir + "\\raw_data\\mimiciv_2_0\\", + ) + # print(hids.shape) + # print(cohort.shape) + # print(len(list(set(hids['hadm_id'].unique()).intersection(set(cohort['hadm_id'].unique()))))) + cohort = cohort[cohort["hadm_id"].isin(hids["hadm_id"])] + cohort_output = cohort_output + "_" + icd_code + summary_output = summary_output + "_" + icd_code + # print(cohort[cols].head()) # save output - cohort=cohort.rename(columns={"race":"ethnicity"}) - cohort[cols].to_csv(root_dir+"/data/cohort/"+cohort_output+".csv.gz", index=False, compression='gzip') + cohort = cohort.rename(columns={"race": "ethnicity"}) + cohort[cols].to_csv( + root_dir + "/data/cohort/" + cohort_output + ".csv.gz", + index=False, + compression="gzip", + ) print("[ COHORT SUCCESSFULLY SAVED ]") - summary = "\n".join([ - f"{label} FOR {ICU} DATA", - f"# Admission Records: {cohort.shape[0]}", - f"# Patients: {cohort[group_col].nunique()}", - f"# Positive cases: {cohort[cohort['label']==1].shape[0]}", - f"# Negative cases: {cohort[cohort['label']==0].shape[0]}" - ]) - + summary = "\n".join( + [ + f"{label} FOR {ICU} DATA", + f"# Admission Records: {cohort.shape[0]}", + f"# Patients: {cohort[group_col].nunique()}", + f"# Positive cases: {cohort[cohort['label']==1].shape[0]}", + f"# Negative cases: {cohort[cohort['label']==0].shape[0]}", + ] + ) # save basic summary of data with open(f"./data/cohort/{summary_output}.txt", "w") as f: f.write(summary) @@ -371,22 +655,30 @@ def extract_data(use_ICU:str, label:str, time:int, icd_code:str, root_dir, disea return cohort_output -if __name__ == '__main__': +if __name__ == "__main__": # use_ICU = input("Use ICU Data? (ICU/Non_ICU)\n").strip() # label = input("Please input the intended label:\n").strip() # extract(use_ICU, label) + extract_data( + "Non-ICU", + "Length of Stay", + 3, + "No Disease Filter", + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline", + "", + ) - response = input('Extra all datasets? (y/n)').strip().lower() - if response == 'y': - extract_data("ICU", "Mortality") - extract_data("Non-ICU", "Mortality") + # response = input("Extra all datasets? (y/n)").strip().lower() + # if response == "y": + # extract_data("ICU", "Mortality") + # extract_data("Non-ICU", "Mortality") - extract_data("ICU", "30 Day Readmission") - extract_data("Non-ICU", "30 Day Readmission") + # extract_data("ICU", "30 Day Readmission") + # extract_data("Non-ICU", "30 Day Readmission") - extract_data("ICU", "60 Day Readmission") - extract_data("Non-ICU", "60 Day Readmission") + # extract_data("ICU", "60 Day Readmission") + # extract_data("Non-ICU", "60 Day Readmission") - extract_data("ICU", "120 Day Readmission") - extract_data("Non-ICU", "120 Day Readmission") \ No newline at end of file + # extract_data("ICU", "120 Day Readmission") + # extract_data("Non-ICU", "120 Day Readmission") diff --git a/preprocessing/day_intervals_preproc/disease_cohort.py b/preprocessing/day_intervals_preproc/disease_cohort.py index 94097584f8..1c99debbd4 100644 --- a/preprocessing/day_intervals_preproc/disease_cohort.py +++ b/preprocessing/day_intervals_preproc/disease_cohort.py @@ -3,17 +3,20 @@ # In[ ]: - +from pathlib import Path import pandas as pd import numpy as np import os import sys -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + './../..') + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "./../..") +MAP_PATH = Path("utils") / "mappings" / "ICD9_to_ICD10_mapping.txt" + def read_icd_mapping(map_path: str) -> pd.DataFrame: """Reads in mapping table for converting ICD9 to ICD10 codes""" - mapping = pd.read_csv(map_path, header=0, delimiter="\t") + mapping = pd.read_csv(MAP_PATH, header=0, delimiter="\t") mapping.diagnosis_description = mapping.diagnosis_description.apply(str.lower) return mapping @@ -22,7 +25,7 @@ def get_diagnosis_icd(module_path: str) -> pd.DataFrame: """Reads in diagnosis_icd table""" return pd.read_csv( - module_path + "/hosp/diagnoses_icd.csv.gz", compression="gzip", header=0 + module_path + "hosp\\diagnoses_icd.csv.gz", compression="gzip", header=0 ) @@ -67,15 +70,14 @@ def icd_9to10(icd): diag.at[idx, col_name] = new_code count += group.shape[0] - #print(f"{count}/{diag.shape[0]} rows processed") + # print(f"{count}/{diag.shape[0]} rows processed") # Column for just the roots of the converted ICD10 column diag["root"] = diag[col_name].apply(lambda x: x[:3] if type(x) is str else np.nan) - -def preproc_icd_module(h_ids, - module_path: str, ICD10_code: str, icd_map_path: str +def preproc_icd_module( + h_ids, module_path: str, ICD10_code: str, icd_map_path: str ) -> tuple: """Takes an module dataset with ICD codes and puts it in long_format, mapping ICD-codes by a mapping table path""" @@ -89,7 +91,7 @@ def preproc_icd_module(h_ids, diag.dropna(subset=["root"], inplace=True) pos_ids = pd.DataFrame( diag.loc[diag.root.str.contains(ICD10_code)].hadm_id.unique(), - columns=["hadm_id"] + columns=["hadm_id"], ) return pos_ids @@ -98,15 +100,11 @@ def extract_diag_cohort( h_ids, label: str, module_path, - icd_map_path="./utils/mappings/ICD9_to_ICD10_mapping.txt" + icd_map_path="./utils/mappings/ICD9_to_ICD10_mapping.txt", ) -> str: """Takes UserInterface parameters, then creates and saves a labelled cohort summary, and error file""" - cohort = preproc_icd_module(h_ids, - module_path, label, icd_map_path - ) + cohort = preproc_icd_module(h_ids, module_path, label, icd_map_path) return cohort - - diff --git a/preprocessing/hosp_module_preproc/feature_selection_icu.py b/preprocessing/hosp_module_preproc/feature_selection_icu.py index a5fa9037fa..44433bcb59 100644 --- a/preprocessing/hosp_module_preproc/feature_selection_icu.py +++ b/preprocessing/hosp_module_preproc/feature_selection_icu.py @@ -2,23 +2,27 @@ import pickle import glob import importlib -#print(os.getcwd()) -#os.chdir('../../') -#print(os.getcwd()) + +# print(os.getcwd()) +# os.chdir('../../') +# print(os.getcwd()) import utils.icu_preprocess_util -from utils.icu_preprocess_util import * +from utils.icu_preprocess_util import * + importlib.reload(utils.icu_preprocess_util) import utils.icu_preprocess_util -from utils.icu_preprocess_util import *# module of preprocessing functions +from utils.icu_preprocess_util import * # module of preprocessing functions import utils.outlier_removal -from utils.outlier_removal import * +from utils.outlier_removal import * + importlib.reload(utils.outlier_removal) import utils.outlier_removal from utils.outlier_removal import * import utils.uom_conversion -from utils.uom_conversion import * +from utils.uom_conversion import * + importlib.reload(utils.uom_conversion) import utils.uom_conversion from utils.uom_conversion import * @@ -29,195 +33,383 @@ if not os.path.exists("./data/features/chartevents"): os.makedirs("./data/features/chartevents") -def feature_icu(cohort_output, version_path, diag_flag=True,out_flag=True,chart_flag=True,proc_flag=True,med_flag=True): + +def feature_icu( + cohort_output, + version_path, + diag_flag=True, + out_flag=True, + chart_flag=True, + proc_flag=True, + med_flag=True, +): if diag_flag: print("[EXTRACTING DIAGNOSIS DATA]") - diag = preproc_icd_module("./"+version_path+"/hosp/diagnoses_icd.csv.gz", './data/cohort/'+cohort_output+'.csv.gz', './utils/mappings/ICD9_to_ICD10_mapping.txt', map_code_colname='diagnosis_code') - diag[['subject_id', 'hadm_id', 'stay_id', 'icd_code','root_icd10_convert','root']].to_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip', index=False) + # diag = preproc_icd_module("./"+version_path+"/hosp/diagnoses_icd.csv.gz", './data/cohort/'+cohort_output+'.csv.gz', './utils/mappings/ICD9_to_ICD10_mapping.txt', map_code_colname='diagnosis_code') + diag = preproc_icd_module( + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\raw_data\\mimiciv_2_0\\hosp\\diagnoses_icd.csv.gz", + "./data/cohort/" + cohort_output + ".csv.gz", + "./utils/mappings/ICD9_to_ICD10_mapping.txt", + map_code_colname="diagnosis_code", + ) + diag[ + [ + "subject_id", + "hadm_id", + "stay_id", + "icd_code", + "root_icd10_convert", + "root", + ] + ].to_csv( + "./data/features/preproc_diag_icu.csv.gz", compression="gzip", index=False + ) print("[SUCCESSFULLY SAVED DIAGNOSIS DATA]") - - if out_flag: + + if out_flag: print("[EXTRACTING OUPTPUT EVENTS DATA]") - out = preproc_out("./"+version_path+"/icu/outputevents.csv.gz", './data/cohort/'+cohort_output+'.csv.gz', 'charttime', dtypes=None, usecols=None) - out[['subject_id', 'hadm_id', 'stay_id', 'itemid', 'charttime', 'intime', 'event_time_from_admit']].to_csv("./data/features/preproc_out_icu.csv.gz", compression='gzip', index=False) + + # out = preproc_out("./"+version_path+"/icu/outputevents.csv.gz", './data/cohort/'+cohort_output+'.csv.gz', 'charttime', dtypes=None, usecols=None) + out = preproc_out( + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\raw_data\\mimiciv_2_0\\icu\\outputevents.csv.gz", + "./data/cohort/" + cohort_output + ".csv.gz", + "charttime", + dtypes=None, + usecols=None, + ) + out[ + [ + "subject_id", + "hadm_id", + "stay_id", + "itemid", + "charttime", + "intime", + "event_time_from_admit", + ] + ].to_csv( + "./data/features/preproc_out_icu.csv.gz", compression="gzip", index=False + ) print("[SUCCESSFULLY SAVED OUPTPUT EVENTS DATA]") - + if chart_flag: print("[EXTRACTING CHART EVENTS DATA]") - chart=preproc_chart("./"+version_path+"/icu/chartevents.csv.gz", './data/cohort/'+cohort_output+'.csv.gz', 'charttime', dtypes=None, usecols=['stay_id','charttime','itemid','valuenum','valueuom']) + chart = preproc_chart( + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\raw_data\\mimiciv_2_0\\icu\\chartevents.csv.gz", + "./data/cohort/" + cohort_output + ".csv.gz", + "charttime", + dtypes=None, + usecols=["stay_id", "charttime", "itemid", "valuenum", "valueuom"], + ) chart = drop_wrong_uom(chart, 0.95) - chart[['stay_id', 'itemid','event_time_from_admit','valuenum']].to_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip', index=False) + chart[["stay_id", "itemid", "event_time_from_admit", "valuenum"]].to_csv( + "./data/features/preproc_chart_icu.csv.gz", compression="gzip", index=False + ) print("[SUCCESSFULLY SAVED CHART EVENTS DATA]") - + if proc_flag: print("[EXTRACTING PROCEDURES DATA]") - proc = preproc_proc("./"+version_path+"/icu/procedureevents.csv.gz", './data/cohort/'+cohort_output+'.csv.gz', 'starttime', dtypes=None, usecols=['stay_id','starttime','itemid']) - proc[['subject_id', 'hadm_id', 'stay_id', 'itemid', 'starttime', 'intime', 'event_time_from_admit']].to_csv("./data/features/preproc_proc_icu.csv.gz", compression='gzip', index=False) + proc = preproc_proc( + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\raw_data\\mimiciv_2_0\\icu\\procedureevents.csv.gz", + "./data/cohort/" + cohort_output + ".csv.gz", + "starttime", + dtypes=None, + usecols=["stay_id", "starttime", "itemid"], + ) + proc[ + [ + "subject_id", + "hadm_id", + "stay_id", + "itemid", + "starttime", + "intime", + "event_time_from_admit", + ] + ].to_csv( + "./data/features/preproc_proc_icu.csv.gz", compression="gzip", index=False + ) print("[SUCCESSFULLY SAVED PROCEDURES DATA]") - + if med_flag: print("[EXTRACTING MEDICATIONS DATA]") - med = preproc_meds("./"+version_path+"/icu/inputevents.csv.gz", './data/cohort/'+cohort_output+'.csv.gz') - med[['subject_id', 'hadm_id', 'stay_id', 'itemid' ,'starttime','endtime', 'start_hours_from_admit', 'stop_hours_from_admit','rate','amount','orderid']].to_csv('./data/features/preproc_med_icu.csv.gz', compression='gzip', index=False) + med = preproc_meds( + "d:\\Work\\Repos\\MIMIC-IV-Data-Pipeline\\raw_data\\mimiciv_2_0\\icu\\inputevents.csv.gz", + "./data/cohort/" + cohort_output + ".csv.gz", + ) + med[ + [ + "subject_id", + "hadm_id", + "stay_id", + "itemid", + "starttime", + "endtime", + "start_hours_from_admit", + "stop_hours_from_admit", + "rate", + "amount", + "orderid", + ] + ].to_csv( + "./data/features/preproc_med_icu.csv.gz", compression="gzip", index=False + ) print("[SUCCESSFULLY SAVED MEDICATIONS DATA]") -def preprocess_features_icu(cohort_output, diag_flag, group_diag,chart_flag,clean_chart,impute_outlier_chart,thresh,left_thresh): + +def preprocess_features_icu( + cohort_output, + diag_flag, + group_diag, + chart_flag, + clean_chart, + impute_outlier_chart, + thresh, + left_thresh, +): if diag_flag: print("[PROCESSING DIAGNOSIS DATA]") - diag = pd.read_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip',header=0) - if(group_diag=='Keep both ICD-9 and ICD-10 codes'): - diag['new_icd_code']=diag['icd_code'] - if(group_diag=='Convert ICD-9 to ICD-10 codes'): - diag['new_icd_code']=diag['root_icd10_convert'] - if(group_diag=='Convert ICD-9 to ICD-10 and group ICD-10 codes'): - diag['new_icd_code']=diag['root'] - - diag=diag[['subject_id', 'hadm_id', 'stay_id', 'new_icd_code']].dropna() - print("Total number of rows",diag.shape[0]) - diag.to_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip', index=False) + diag = pd.read_csv( + "./data/features/preproc_diag_icu.csv.gz", compression="gzip", header=0 + ) + if group_diag == "Keep both ICD-9 and ICD-10 codes": + diag["new_icd_code"] = diag["icd_code"] + if group_diag == "Convert ICD-9 to ICD-10 codes": + diag["new_icd_code"] = diag["root_icd10_convert"] + if group_diag == "Convert ICD-9 to ICD-10 and group ICD-10 codes": + diag["new_icd_code"] = diag["root"] + + diag = diag[["subject_id", "hadm_id", "stay_id", "new_icd_code"]].dropna() + print("Total number of rows", diag.shape[0]) + diag.to_csv( + "./data/features/preproc_diag_icu.csv.gz", compression="gzip", index=False + ) print("[SUCCESSFULLY SAVED DIAGNOSIS DATA]") - + if chart_flag: - if clean_chart: + if clean_chart: print("[PROCESSING CHART EVENTS DATA]") - chart = pd.read_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip',header=0) - chart = outlier_imputation(chart, 'itemid', 'valuenum', thresh,left_thresh,impute_outlier_chart) - -# for i in [227441, 229357, 229358, 229360]: -# try: -# maj = chart.loc[chart.itemid == i].valueuom.value_counts().index[0] -# chart = chart.loc[~((chart.itemid == i) & (chart.valueuom == maj))] -# except IndexError: -# print(f"{idx} not found") - print("Total number of rows",chart.shape[0]) - chart.to_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip', index=False) + chart = pd.read_csv( + "./data/features/preproc_chart_icu.csv.gz", compression="gzip", header=0 + ) + chart = outlier_imputation( + chart, "itemid", "valuenum", thresh, left_thresh, impute_outlier_chart + ) + + # for i in [227441, 229357, 229358, 229360]: + # try: + # maj = chart.loc[chart.itemid == i].valueuom.value_counts().index[0] + # chart = chart.loc[~((chart.itemid == i) & (chart.valueuom == maj))] + # except IndexError: + # print(f"{idx} not found") + print("Total number of rows", chart.shape[0]) + chart.to_csv( + "./data/features/preproc_chart_icu.csv.gz", + compression="gzip", + index=False, + ) print("[SUCCESSFULLY SAVED CHART EVENTS DATA]") - - - -def generate_summary_icu(diag_flag,proc_flag,med_flag,out_flag,chart_flag): + + +def generate_summary_icu(diag_flag, proc_flag, med_flag, out_flag, chart_flag): print("[GENERATING FEATURE SUMMARY]") if diag_flag: - diag = pd.read_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip',header=0) - freq=diag.groupby(['stay_id','new_icd_code']).size().reset_index(name="mean_frequency") - freq=freq.groupby(['new_icd_code'])['mean_frequency'].mean().reset_index() - total=diag.groupby('new_icd_code').size().reset_index(name="total_count") - summary=pd.merge(freq,total,on='new_icd_code',how='right') - summary=summary.fillna(0) - summary.to_csv('./data/summary/diag_summary.csv',index=False) - summary['new_icd_code'].to_csv('./data/summary/diag_features.csv',index=False) - + diag = pd.read_csv( + "./data/features/preproc_diag_icu.csv.gz", compression="gzip", header=0 + ) + freq = ( + diag.groupby(["stay_id", "new_icd_code"]) + .size() + .reset_index(name="mean_frequency") + ) + freq = freq.groupby(["new_icd_code"])["mean_frequency"].mean().reset_index() + total = diag.groupby("new_icd_code").size().reset_index(name="total_count") + summary = pd.merge(freq, total, on="new_icd_code", how="right") + summary = summary.fillna(0) + summary.to_csv("./data/summary/diag_summary.csv", index=False) + summary["new_icd_code"].to_csv("./data/summary/diag_features.csv", index=False) if med_flag: - med = pd.read_csv("./data/features/preproc_med_icu.csv.gz", compression='gzip',header=0) - freq=med.groupby(['stay_id','itemid']).size().reset_index(name="mean_frequency") - freq=freq.groupby(['itemid'])['mean_frequency'].mean().reset_index() - - missing=med[med['amount']==0].groupby('itemid').size().reset_index(name="missing_count") - total=med.groupby('itemid').size().reset_index(name="total_count") - summary=pd.merge(missing,total,on='itemid',how='right') - summary=pd.merge(freq,summary,on='itemid',how='right') - #summary['missing%']=100*(summary['missing_count']/summary['total_count']) - summary=summary.fillna(0) - summary.to_csv('./data/summary/med_summary.csv',index=False) - summary['itemid'].to_csv('./data/summary/med_features.csv',index=False) - - - + med = pd.read_csv( + "./data/features/preproc_med_icu.csv.gz", compression="gzip", header=0 + ) + freq = ( + med.groupby(["stay_id", "itemid"]).size().reset_index(name="mean_frequency") + ) + freq = freq.groupby(["itemid"])["mean_frequency"].mean().reset_index() + + missing = ( + med[med["amount"] == 0] + .groupby("itemid") + .size() + .reset_index(name="missing_count") + ) + total = med.groupby("itemid").size().reset_index(name="total_count") + summary = pd.merge(missing, total, on="itemid", how="right") + summary = pd.merge(freq, summary, on="itemid", how="right") + # summary['missing%']=100*(summary['missing_count']/summary['total_count']) + summary = summary.fillna(0) + summary.to_csv("./data/summary/med_summary.csv", index=False) + summary["itemid"].to_csv("./data/summary/med_features.csv", index=False) + if proc_flag: - proc = pd.read_csv("./data/features/preproc_proc_icu.csv.gz", compression='gzip',header=0) - freq=proc.groupby(['stay_id','itemid']).size().reset_index(name="mean_frequency") - freq=freq.groupby(['itemid'])['mean_frequency'].mean().reset_index() - total=proc.groupby('itemid').size().reset_index(name="total_count") - summary=pd.merge(freq,total,on='itemid',how='right') - summary=summary.fillna(0) - summary.to_csv('./data/summary/proc_summary.csv',index=False) - summary['itemid'].to_csv('./data/summary/proc_features.csv',index=False) - - + proc = pd.read_csv( + "./data/features/preproc_proc_icu.csv.gz", compression="gzip", header=0 + ) + freq = ( + proc.groupby(["stay_id", "itemid"]) + .size() + .reset_index(name="mean_frequency") + ) + freq = freq.groupby(["itemid"])["mean_frequency"].mean().reset_index() + total = proc.groupby("itemid").size().reset_index(name="total_count") + summary = pd.merge(freq, total, on="itemid", how="right") + summary = summary.fillna(0) + summary.to_csv("./data/summary/proc_summary.csv", index=False) + summary["itemid"].to_csv("./data/summary/proc_features.csv", index=False) + if out_flag: - out = pd.read_csv("./data/features/preproc_out_icu.csv.gz", compression='gzip',header=0) - freq=out.groupby(['stay_id','itemid']).size().reset_index(name="mean_frequency") - freq=freq.groupby(['itemid'])['mean_frequency'].mean().reset_index() - total=out.groupby('itemid').size().reset_index(name="total_count") - summary=pd.merge(freq,total,on='itemid',how='right') - summary=summary.fillna(0) - summary.to_csv('./data/summary/out_summary.csv',index=False) - summary['itemid'].to_csv('./data/summary/out_features.csv',index=False) - + out = pd.read_csv( + "./data/features/preproc_out_icu.csv.gz", compression="gzip", header=0 + ) + freq = ( + out.groupby(["stay_id", "itemid"]).size().reset_index(name="mean_frequency") + ) + freq = freq.groupby(["itemid"])["mean_frequency"].mean().reset_index() + total = out.groupby("itemid").size().reset_index(name="total_count") + summary = pd.merge(freq, total, on="itemid", how="right") + summary = summary.fillna(0) + summary.to_csv("./data/summary/out_summary.csv", index=False) + summary["itemid"].to_csv("./data/summary/out_features.csv", index=False) + if chart_flag: - chart=pd.read_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip',header=0) - freq=chart.groupby(['stay_id','itemid']).size().reset_index(name="mean_frequency") - freq=freq.groupby(['itemid'])['mean_frequency'].mean().reset_index() - - missing=chart[chart['valuenum']==0].groupby('itemid').size().reset_index(name="missing_count") - total=chart.groupby('itemid').size().reset_index(name="total_count") - summary=pd.merge(missing,total,on='itemid',how='right') - summary=pd.merge(freq,summary,on='itemid',how='right') - #summary['missing_perc']=100*(summary['missing_count']/summary['total_count']) - #summary=summary.fillna(0) - -# final.groupby('itemid')['missing_count'].sum().reset_index() -# final.groupby('itemid')['total_count'].sum().reset_index() -# final.groupby('itemid')['missing%'].mean().reset_index() - summary=summary.fillna(0) - summary.to_csv('./data/summary/chart_summary.csv',index=False) - summary['itemid'].to_csv('./data/summary/chart_features.csv',index=False) + chart = pd.read_csv( + "./data/features/preproc_chart_icu.csv.gz", compression="gzip", header=0 + ) + freq = ( + chart.groupby(["stay_id", "itemid"]) + .size() + .reset_index(name="mean_frequency") + ) + freq = freq.groupby(["itemid"])["mean_frequency"].mean().reset_index() + + missing = ( + chart[chart["valuenum"] == 0] + .groupby("itemid") + .size() + .reset_index(name="missing_count") + ) + total = chart.groupby("itemid").size().reset_index(name="total_count") + summary = pd.merge(missing, total, on="itemid", how="right") + summary = pd.merge(freq, summary, on="itemid", how="right") + # summary['missing_perc']=100*(summary['missing_count']/summary['total_count']) + # summary=summary.fillna(0) + + # final.groupby('itemid')['missing_count'].sum().reset_index() + # final.groupby('itemid')['total_count'].sum().reset_index() + # final.groupby('itemid')['missing%'].mean().reset_index() + summary = summary.fillna(0) + summary.to_csv("./data/summary/chart_summary.csv", index=False) + summary["itemid"].to_csv("./data/summary/chart_features.csv", index=False) print("[SUCCESSFULLY SAVED FEATURE SUMMARY]") - -def features_selection_icu(cohort_output, diag_flag,proc_flag,med_flag,out_flag,chart_flag,group_diag,group_med,group_proc,group_out,group_chart): + + +def features_selection_icu( + cohort_output, + diag_flag, + proc_flag, + med_flag, + out_flag, + chart_flag, + group_diag, + group_med, + group_proc, + group_out, + group_chart, +): if diag_flag: if group_diag: print("[FEATURE SELECTION DIAGNOSIS DATA]") - diag = pd.read_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip',header=0) - features=pd.read_csv("./data/summary/diag_features.csv",header=0) - diag=diag[diag['new_icd_code'].isin(features['new_icd_code'].unique())] - - print("Total number of rows",diag.shape[0]) - diag.to_csv("./data/features/preproc_diag_icu.csv.gz", compression='gzip', index=False) + diag = pd.read_csv( + "./data/features/preproc_diag_icu.csv.gz", compression="gzip", header=0 + ) + features = pd.read_csv("./data/summary/diag_features.csv", header=0) + diag = diag[diag["new_icd_code"].isin(features["new_icd_code"].unique())] + + print("Total number of rows", diag.shape[0]) + diag.to_csv( + "./data/features/preproc_diag_icu.csv.gz", + compression="gzip", + index=False, + ) print("[SUCCESSFULLY SAVED DIAGNOSIS DATA]") - - if med_flag: - if group_med: + + if med_flag: + if group_med: print("[FEATURE SELECTION MEDICATIONS DATA]") - med = pd.read_csv("./data/features/preproc_med_icu.csv.gz", compression='gzip',header=0) - features=pd.read_csv("./data/summary/med_features.csv",header=0) - med=med[med['itemid'].isin(features['itemid'].unique())] - print("Total number of rows",med.shape[0]) - med.to_csv('./data/features/preproc_med_icu.csv.gz', compression='gzip', index=False) + med = pd.read_csv( + "./data/features/preproc_med_icu.csv.gz", compression="gzip", header=0 + ) + features = pd.read_csv("./data/summary/med_features.csv", header=0) + med = med[med["itemid"].isin(features["itemid"].unique())] + print("Total number of rows", med.shape[0]) + med.to_csv( + "./data/features/preproc_med_icu.csv.gz", + compression="gzip", + index=False, + ) print("[SUCCESSFULLY SAVED MEDICATIONS DATA]") - - + if proc_flag: if group_proc: print("[FEATURE SELECTION PROCEDURES DATA]") - proc = pd.read_csv("./data/features/preproc_proc_icu.csv.gz", compression='gzip',header=0) - features=pd.read_csv("./data/summary/proc_features.csv",header=0) - proc=proc[proc['itemid'].isin(features['itemid'].unique())] - print("Total number of rows",proc.shape[0]) - proc.to_csv("./data/features/preproc_proc_icu.csv.gz", compression='gzip', index=False) + proc = pd.read_csv( + "./data/features/preproc_proc_icu.csv.gz", compression="gzip", header=0 + ) + features = pd.read_csv("./data/summary/proc_features.csv", header=0) + proc = proc[proc["itemid"].isin(features["itemid"].unique())] + print("Total number of rows", proc.shape[0]) + proc.to_csv( + "./data/features/preproc_proc_icu.csv.gz", + compression="gzip", + index=False, + ) print("[SUCCESSFULLY SAVED PROCEDURES DATA]") - - + if out_flag: - if group_out: + if group_out: print("[FEATURE SELECTION OUTPUT EVENTS DATA]") - out = pd.read_csv("./data/features/preproc_out_icu.csv.gz", compression='gzip',header=0) - features=pd.read_csv("./data/summary/out_features.csv",header=0) - out=out[out['itemid'].isin(features['itemid'].unique())] - print("Total number of rows",out.shape[0]) - out.to_csv("./data/features/preproc_out_icu.csv.gz", compression='gzip', index=False) + out = pd.read_csv( + "./data/features/preproc_out_icu.csv.gz", compression="gzip", header=0 + ) + features = pd.read_csv("./data/summary/out_features.csv", header=0) + out = out[out["itemid"].isin(features["itemid"].unique())] + print("Total number of rows", out.shape[0]) + out.to_csv( + "./data/features/preproc_out_icu.csv.gz", + compression="gzip", + index=False, + ) print("[SUCCESSFULLY SAVED OUTPUT EVENTS DATA]") - + if chart_flag: - if group_chart: + if group_chart: print("[FEATURE SELECTION CHART EVENTS DATA]") - - chart=pd.read_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip',header=0, index_col=None) - - features=pd.read_csv("./data/summary/chart_features.csv",header=0) - chart=chart[chart['itemid'].isin(features['itemid'].unique())] - print("Total number of rows",chart.shape[0]) - chart.to_csv("./data/features/preproc_chart_icu.csv.gz", compression='gzip', index=False) - print("[SUCCESSFULLY SAVED CHART EVENTS DATA]") \ No newline at end of file + + chart = pd.read_csv( + "./data/features/preproc_chart_icu.csv.gz", + compression="gzip", + header=0, + index_col=None, + ) + + features = pd.read_csv("./data/summary/chart_features.csv", header=0) + chart = chart[chart["itemid"].isin(features["itemid"].unique())] + print("Total number of rows", chart.shape[0]) + chart.to_csv( + "./data/features/preproc_chart_icu.csv.gz", + compression="gzip", + index=False, + ) + print("[SUCCESSFULLY SAVED CHART EVENTS DATA]") diff --git a/requirements.txt b/requirements.txt index 58020deb8b..3a5579dc19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,13 @@ -import_ipynb==0.1.3 -ipywidgets==7.5.1 -Jinja2==2.11.2 -matplotlib==3.2.2 -numpy==1.18.5 -pandas==1.0.5 -scikit_learn==1.0.2 -torch==1.6.0 -tqdm==4.47.0 +matplotlib +pandas +scikit_learn +torch +tqdm +ipywidgets +ipykernel +xgboost +imblearn +import_ipynb +captum +behrt_model +pytorch_pretrained_bert \ No newline at end of file diff --git a/script_pathlib.py b/script_pathlib.py new file mode 100644 index 0000000000..fbef31de4f --- /dev/null +++ b/script_pathlib.py @@ -0,0 +1,71 @@ +from pipeline.cohort_extractor import CohortExtractor +from pipeline.feature.diagnoses import IcdGroupOption +from pipeline.feature_selector import FeatureSelector +from pipeline.features_preprocessor import FeaturePreprocessor +from pipeline.prediction_task import TargetType, PredictionTask, DiseaseCode +from pipeline.features_extractor import FeatureExtractor + +# from pipeline.data_generator import DataGenerator + + +if __name__ == "__main__": + prediction_task = PredictionTask( + target_type=TargetType.READMISSION, + disease_readmission=DiseaseCode.CAD, + disease_selection=None, + nb_days=30, + use_icu=False, + ) + + cohort_extractor = CohortExtractor(prediction_task=prediction_task) + cohort = cohort_extractor.extract() + feature_extractor = FeatureExtractor( + cohort_output=cohort_extractor.cohort_output, + use_icu=prediction_task.use_icu, + for_diagnoses=True, + for_labs=not prediction_task.use_icu, + for_chart_events=prediction_task.use_icu, + for_medications=True, + for_output_events=prediction_task.use_icu, + for_procedures=True, + ) + features = feature_extractor.save_features() + + feat_preproc = FeaturePreprocessor( + feature_extractor=feature_extractor, + group_diag_icd=IcdGroupOption.KEEP, + group_med_code=True, + keep_proc_icd9=False, + clean_chart=False, + impute_outlier_chart=False, + clean_labs=False, + impute_labs=False, + ) + preproc = feat_preproc.preprocess_no_event_features() + summaries = feat_preproc.save_summaries() + + feat_select = FeatureSelector( + prediction_task.use_icu, + feature_extractor.for_diagnoses, + feature_extractor.for_medications, + feature_extractor.for_procedures, + not (prediction_task.use_icu) and feature_extractor.for_labs, + prediction_task.use_icu and feature_extractor.for_chart_events, + prediction_task.use_icu and feature_extractor.for_output_events, + ) + + selection = feat_select.feature_selection() + + feat_preproc = FeaturePreprocessor( + feature_extractor=feature_extractor, + group_diag_icd=IcdGroupOption.KEEP, + group_med_code=False, + keep_proc_icd9=False, + clean_chart=False, + impute_outlier_chart=False, + clean_labs=True, + impute_labs=True, + thresh=98, + left_thresh=0, + ) + feat_preproc.preproc_events_features() diff --git a/tests/conversion/test_icd.py b/tests/conversion/test_icd.py new file mode 100644 index 0000000000..9d941a68e7 --- /dev/null +++ b/tests/conversion/test_icd.py @@ -0,0 +1,79 @@ +import pandas as pd +from pipeline.conversion.icd import IcdConverter +from pipeline.file_info.raw.hosp import HospDiagnosesIcd + + +def test_converter(): + """ + Tests the IcdConverter class for standardizing ICD codes and extracting hospital + admission IDs based on specific ICD-10 codes. + + This test validates: + - The conversion of ICD codes from version 9 to version 10. + - The extraction of 'root' ICD codes. + - The retrieval of hospital admission IDs for a given ICD-10 code. + """ + + # Given: Sample ICD codes, versions, and hospital admission IDs + icd_codes = [ + "4139", + "V707", + "41401", + "D696", + "S030XXA", + "S25512A", + "I5022", + "42821", + "4280", + ] + icd_versions = [9, 9, 9, 10, 10, 10, 10, 9, 9] + + admissions = [ + 1, + 1, + 1, + 2, + 3, + 3, + 3, + 4, + 4, + ] + df = pd.DataFrame( + { + "icd_code": icd_codes, + "icd_version": icd_versions, + "hadm_id": admissions, + } + ) + + icd_converter = IcdConverter() + st_dia = icd_converter.standardize_icd(df) + hids = icd_converter.get_pos_ids(st_dia, "I50") + + # Expected results for root ICD-10 conversion and hospital admission IDs + expected_root_icd10 = [ + "I208", + "Z0000", + "I2510", + "D696", + "S030XXA", + "S25512A", + "I5022", + "I50814", + "I50814", + ] + expected_root = [ + "I20", + "Z00", + "I25", + "D69", + "S03", + "S25", + "I50", + "I50", + "I50", + ] + assert st_dia["root_icd10_convert"].values.tolist() == expected_root_icd10 + assert st_dia["root"].values.tolist() == expected_root + assert hids.tolist() == [3, 4] diff --git a/tests/test_cohort_extractor.py b/tests/test_cohort_extractor.py new file mode 100644 index 0000000000..87fcaf99bd --- /dev/null +++ b/tests/test_cohort_extractor.py @@ -0,0 +1,50 @@ +import pytest +from pipeline.cohort_extractor import CohortExtractor +from pipeline.prediction_task import PredictionTask, TargetType + + +@pytest.mark.parametrize( + "use_icu, target_type, nb_days, disease_readmission, disease_selection, expected_admission_records_count, expected_patients_count, expected_positive_cases_count", + [ + (True, TargetType.MORTALITY, 0, None, None, 140, 100, 10), + (True, TargetType.LOS, 3, None, None, 140, 100, 55), + (True, TargetType.LOS, 7, None, None, 140, 100, 20), + (True, TargetType.READMISSION, 30, None, None, 128, 93, 18), + (True, TargetType.READMISSION, 90, None, None, 128, 93, 22), + (True, TargetType.READMISSION, 30, "I50", None, 27, 20, 2), + (True, TargetType.READMISSION, 30, "I25", None, 32, 29, 2), + (True, TargetType.READMISSION, 30, "N18", None, 25, 18, 2), + (True, TargetType.READMISSION, 30, "J44", None, 17, 12, 3), + (False, TargetType.MORTALITY, 0, None, None, 275, 100, 15), + (False, TargetType.LOS, 3, None, None, 275, 100, 163), + (False, TargetType.LOS, 7, None, None, 275, 100, 76), + (False, TargetType.READMISSION, 30, None, None, 260, 95, 52), + (False, TargetType.READMISSION, 90, None, None, 260, 95, 86), + (False, TargetType.READMISSION, 30, "I50", None, 55, 23, 13), + # heart failure + (False, TargetType.READMISSION, 30, "I25", None, 68, 32, 13), + (False, TargetType.READMISSION, 30, "N18", None, 63, 22, 10), + (False, TargetType.READMISSION, 30, "J44", None, 26, 12, 7), + (True, TargetType.MORTALITY, 0, None, "I50", 32, 22, 5), + ], +) +def test_cohort_extractor( + use_icu, + target_type, + nb_days, + disease_readmission, + disease_selection, + expected_admission_records_count, + expected_patients_count, + expected_positive_cases_count, +): + prediction_task = PredictionTask( + target_type, disease_readmission, disease_selection, nb_days, use_icu + ) + cohort_extractor = CohortExtractor( + prediction_task=prediction_task, + ) + df = cohort_extractor.extract().df + assert len(df) == expected_admission_records_count + assert df["subject_id"].nunique() == expected_patients_count + assert df["label"].sum() == expected_positive_cases_count diff --git a/tests/test_feature_extractor.py b/tests/test_feature_extractor.py new file mode 100644 index 0000000000..6a7574c69a --- /dev/null +++ b/tests/test_feature_extractor.py @@ -0,0 +1,125 @@ +from pipeline.features_extractor import ( + FeatureExtractor, +) +from pipeline.feature.feature_abc import Name + + +def test_feature_icu_all_true(): + feature_extractor = FeatureExtractor( + cohort_output="cohort_icu_mortality_0_", + use_icu=True, + for_diagnoses=True, + for_output_events=True, + for_chart_events=True, + for_procedures=True, + for_medications=True, + for_labs=True, + ) + result = feature_extractor.save_features() + assert len(result) == 5 + assert len(result[Name.DIAGNOSES]) == 2647 + assert result[Name.DIAGNOSES].columns.tolist() == [ + "subject_id", + "hadm_id", + "icd_code", + "root_icd10_convert", + "root", + "stay_id", + ] + assert len(result[Name.PROCEDURES]) == 1435 + assert result[Name.PROCEDURES].columns.tolist() == [ + "subject_id", + "hadm_id", + "stay_id", + "itemid", + "starttime", + "intime", + "event_time_from_admit", + ] + assert len(result[Name.MEDICATIONS]) == 11038 + assert result[Name.MEDICATIONS].columns.tolist() == [ + "subject_id", + "hadm_id", + "starttime", + "start_hours_from_admit", + "stop_hours_from_admit", + "stay_id", + "itemid", + "endtime", + "rate", + "amount", + "orderid", + ] + assert len(result[Name.OUTPUT]) == 9362 + assert result[Name.OUTPUT].columns.tolist() == [ + "subject_id", + "hadm_id", + "stay_id", + "itemid", + "charttime", + "intime", + "event_time_from_admit", + ] + assert len(result[Name.CHART]) == 162571 + assert result[Name.CHART].columns.tolist() == [ + "stay_id", + "itemid", + "valuenum", + "event_time_from_admit", + ] + + +def test_feature_non_icu_all_true(): + feature_extractor = FeatureExtractor( + cohort_output="cohort_Non-ICU_readmission_30_I50", + use_icu=False, + for_diagnoses=True, + for_output_events=True, + for_chart_events=True, + for_procedures=True, + for_medications=True, + for_labs=True, + ) + result = feature_extractor.save_features() + assert len(result) == 4 + assert len(result[Name.DIAGNOSES]) == 1273 + assert result[Name.DIAGNOSES].columns.tolist() == [ + "subject_id", + "hadm_id", + "icd_code", + "root_icd10_convert", + "root", + ] + assert len(result[Name.PROCEDURES]) == 136 + assert result[Name.PROCEDURES].columns.tolist() == [ + "subject_id", + "hadm_id", + "icd_code", + "icd_version", + "chartdate", + "admittime", + "proc_time_from_admit", + ] + assert len(result[Name.MEDICATIONS]) == 4803 + assert result[Name.MEDICATIONS].columns.tolist() == [ + "subject_id", + "hadm_id", + "starttime", + "start_hours_from_admit", + "stop_hours_from_admit", + "stoptime", + "drug", + "nonproprietaryname", + "dose_val_rx", + "EPC", + ] + assert len(result[Name.LAB]) == 22029 + assert result[Name.LAB].columns.tolist() == [ + "subject_id", + "hadm_id", + "itemid", + "charttime", + "admittime", + "lab_time_from_admit", + "valuenum", + ] diff --git a/tests/test_feature_preprocessor.py b/tests/test_feature_preprocessor.py new file mode 100644 index 0000000000..e3a97c1f42 --- /dev/null +++ b/tests/test_feature_preprocessor.py @@ -0,0 +1,75 @@ +from pipeline.features_extractor import FeatureExtractor +from pipeline.features_preprocessor import FeaturePreprocessor, IcdGroupOption +from pipeline.data_generator import DataGenerator + + +def test_feature_icu_all_true(): + extractor = FeatureExtractor( + cohort_output="cohort_icu_mortality_0_", + use_icu=True, + for_diagnoses=True, + for_output_events=True, + for_chart_events=True, + for_procedures=True, + for_medications=True, + for_labs=True, + ) + preprocessor = FeaturePreprocessor( + feature_extractor=extractor, + group_diag_icd=IcdGroupOption.GROUP, + group_med_code=True, + keep_proc_icd9=False, + clean_chart=True, + impute_outlier_chart=True, + impute_labs=True, + thresh=98, + left_thresh=2, + clean_labs=True, + ) + extractor.save_features() + preprocessor.preprocess() + generator = DataGenerator( + cohort_output=extractor.cohort_output, + feature_extractor=extractor, + ) + generator.generate_features() + generator.length_by_target() + generator.smooth_ini() + generator.smooth_tqdm() + assert 5 == 5 + + +# def test_feature_non_icu_all_true(): +# extractor = FeatureExtractor( +# cohort_output="cohort_Non-ICU_readmission_30_I50", +# use_icu=False, +# for_diagnoses=True, +# for_output_events=True, +# for_chart_events=True, +# for_procedures=True, +# for_medications=True, +# for_labs=True, +# ) +# preprocessor = FeaturePreprocessor( +# feature_extractor=extractor, +# group_diag_icd=IcdGroupOption.GROUP, +# group_med_code=True, +# keep_proc_icd9=False, +# clean_chart=True, +# impute_outlier_chart=True, +# impute_labs=True, +# thresh=95, +# left_thresh=5, +# clean_labs=True, +# ) +# extractor.save_features() +# preprocessor.preprocess() +# generator = DataGenerator( +# cohort_output=extractor.cohort_output, +# feature_extractor=extractor, +# ) +# generator.generate_features() +# generator.length_by_target() +# generator.smooth_ini() +# generator.smooth_tqdm() +# assert 4 == 4 diff --git a/todo.md b/todo.md new file mode 100644 index 0000000000..3918e87b10 --- /dev/null +++ b/todo.md @@ -0,0 +1,66 @@ +# FeatureExtractor + +- cohort preprocessing +- raw file preprocessing +- feature preprocessing / feature cleaning ? + + + +# module my preprocessing + +- mkdir... + +- precommit + +- implementer options pour l extract + +- raw extract column csv + +- handle columns (fewarure selection?) + +- refact filter?, conversion + +- CLARIFY LOG + +- EXPLICIT OPTION CONSEQUENCE ICU... option =-> get columns... + +- summary, more comments, clarification of the pipeline + + + + + +# module feature selection + +- define tests + +- summary, more comments, clarification of the whole pipeline (use column emum...) + +- what are the features: define enum to clarify with header enums + +# finalize preprocessing +- cleaning +- other preprocessing transformation +- output files +- features summary + + +# data_generation + + +# ML_models + +# DL_models + +# tokenization.BEHRT_model + +# evaluation + +# calibraation + +callibrate_output.callibrate + +# project +- requirements.txt +- (docker pipeline?) +- options diff --git a/utils/icu_preprocess_util.py b/utils/icu_preprocess_util.py index 43764a2561..9c99092a36 100644 --- a/utils/icu_preprocess_util.py +++ b/utils/icu_preprocess_util.py @@ -9,14 +9,24 @@ from sklearn.preprocessing import MultiLabelBinarizer + ########################## GENERAL ########################## -def dataframe_from_csv(path, compression='gzip', header=0, index_col=0, chunksize=None): - return pd.read_csv(path, compression=compression, header=header, index_col=index_col, chunksize=None) +def dataframe_from_csv(path, compression="gzip", header=0, index_col=0, chunksize=None): + return pd.read_csv( + path, + compression=compression, + header=header, + index_col=index_col, + chunksize=None, + ) + def read_admissions_table(mimic4_path): - admits = dataframe_from_csv(os.path.join(mimic4_path, 'core/admissions.csv.gz')) - admits=admits.reset_index() - admits = admits[['subject_id', 'hadm_id', 'admittime', 'dischtime', 'deathtime', 'ethnicity']] + admits = dataframe_from_csv(os.path.join(mimic4_path, "core/admissions.csv.gz")) + admits = admits.reset_index() + admits = admits[ + ["subject_id", "hadm_id", "admittime", "dischtime", "deathtime", "ethnicity"] + ] admits.admittime = pd.to_datetime(admits.admittime) admits.dischtime = pd.to_datetime(admits.dischtime) admits.deathtime = pd.to_datetime(admits.deathtime) @@ -24,31 +34,43 @@ def read_admissions_table(mimic4_path): def read_patients_table(mimic4_path): - pats = dataframe_from_csv(os.path.join(mimic4_path, 'core/patients.csv.gz')) + pats = dataframe_from_csv(os.path.join(mimic4_path, "core/patients.csv.gz")) pats = pats.reset_index() - pats = pats[['subject_id', 'gender','dod','anchor_age','anchor_year', 'anchor_year_group']] - pats['yob']= pats['anchor_year'] - pats['anchor_age'] - #pats.dob = pd.to_datetime(pats.dob) + pats = pats[ + [ + "subject_id", + "gender", + "dod", + "anchor_age", + "anchor_year", + "anchor_year_group", + ] + ] + pats["yob"] = pats["anchor_year"] - pats["anchor_age"] + # pats.dob = pd.to_datetime(pats.dob) pats.dod = pd.to_datetime(pats.dod) return pats ########################## DIAGNOSES ########################## def read_diagnoses_icd_table(mimic4_path): - diag = dataframe_from_csv(os.path.join(mimic4_path, 'hosp/diagnoses_icd.csv.gz')) + diag = dataframe_from_csv(os.path.join(mimic4_path, "hosp/diagnoses_icd.csv.gz")) diag.reset_index(inplace=True) return diag def read_d_icd_diagnoses_table(mimic4_path): - d_icd = dataframe_from_csv(os.path.join(mimic4_path, 'hosp/d_icd_diagnoses.csv.gz')) + d_icd = dataframe_from_csv(os.path.join(mimic4_path, "hosp/d_icd_diagnoses.csv.gz")) d_icd.reset_index(inplace=True) - return d_icd[['icd_code', 'long_title']] + return d_icd[["icd_code", "long_title"]] def read_diagnoses(mimic4_path): return read_diagnoses_icd_table(mimic4_path).merge( - read_d_icd_diagnoses_table(mimic4_path), how='inner', left_on=['icd_code'], right_on=['icd_code'] + read_d_icd_diagnoses_table(mimic4_path), + how="inner", + left_on=["icd_code"], + right_on=["icd_code"], ) @@ -67,12 +89,13 @@ def icd_9to10(icd): return np.nan # Create new column with original codes as default - col_name = 'icd10_convert' - if root: col_name = 'root_' + col_name - df[col_name] = df['icd_code'].values + col_name = "icd10_convert" + if root: + col_name = "root_" + col_name + df[col_name] = df["icd_code"].values # Group identical ICD9 codes, then convert all ICD9 codes within a group to ICD10 - for code, group in df.loc[df.icd_version == 9].groupby(by='icd_code'): + for code, group in df.loc[df.icd_version == 9].groupby(by="icd_code"): new_code = icd_9to10(code) for idx in group.index.values: # Modify values of original df at the indexes in the groups @@ -81,71 +104,108 @@ def icd_9to10(icd): ########################## PROCEDURES ########################## def read_procedures_icd_table(mimic4_path): - proc = dataframe_from_csv(os.path.join(mimic4_path, 'hosp/procedures_icd.csv.gz')) + proc = dataframe_from_csv(os.path.join(mimic4_path, "hosp/procedures_icd.csv.gz")) proc.reset_index(inplace=True) return proc def read_d_icd_procedures_table(mimic4_path): - p_icd = dataframe_from_csv(os.path.join(mimic4_path, 'hosp/d_icd_procedures.csv.gz')) + p_icd = dataframe_from_csv( + os.path.join(mimic4_path, "hosp/d_icd_procedures.csv.gz") + ) p_icd.reset_index(inplace=True) - return p_icd[['icd_code', 'long_title']] + return p_icd[["icd_code", "long_title"]] def read_procedures(mimic4_path): return read_procedures_icd_table(mimic4_path).merge( - read_d_icd_procedures_table(mimic4_path), how='inner', left_on=['icd_code'], right_on=['icd_code'] + read_d_icd_procedures_table(mimic4_path), + how="inner", + left_on=["icd_code"], + right_on=["icd_code"], ) ########################## MAPPING ########################## def read_icd_mapping(map_path): - mapping = pd.read_csv(map_path, header=0, delimiter='\t') + mapping = pd.read_csv(map_path, header=0, delimiter="\t") mapping.diagnosis_description = mapping.diagnosis_description.apply(str.lower) return mapping ########################## PREPROCESSING ########################## -def preproc_meds(module_path:str, adm_cohort_path:str) -> pd.DataFrame: - - adm = pd.read_csv(adm_cohort_path, usecols=['hadm_id', 'stay_id', 'intime'], parse_dates = ['intime']) - med = pd.read_csv(module_path, compression='gzip', usecols=['subject_id', 'stay_id', 'itemid', 'starttime', 'endtime','rate','amount','orderid'], parse_dates = ['starttime', 'endtime']) - med = med.merge(adm, left_on = 'stay_id', right_on = 'stay_id', how = 'inner') - med['start_hours_from_admit'] = med['starttime'] - med['intime'] - med['stop_hours_from_admit'] = med['endtime'] - med['intime'] - - #print(med.isna().sum()) - med=med.dropna() - #med[['amount','rate']]=med[['amount','rate']].fillna(0) + +def preproc_meds(module_path: str, adm_cohort_path: str) -> pd.DataFrame: + adm = pd.read_csv( + adm_cohort_path, + usecols=["hadm_id", "stay_id", "intime"], + parse_dates=["intime"], + ) + med = pd.read_csv( + module_path, + compression="gzip", + usecols=[ + "subject_id", + "stay_id", + "itemid", + "starttime", + "endtime", + "rate", + "amount", + "orderid", + ], + parse_dates=["starttime", "endtime"], + ) + med = med.merge(adm, left_on="stay_id", right_on="stay_id", how="inner") + med["start_hours_from_admit"] = med["starttime"] - med["intime"] + med["stop_hours_from_admit"] = med["endtime"] - med["intime"] + + # print(med.isna().sum()) + med = med.dropna() + # med[['amount','rate']]=med[['amount','rate']].fillna(0) print("# of unique type of drug: ", med.itemid.nunique()) print("# Admissions: ", med.stay_id.nunique()) - print("# Total rows", med.shape[0]) - + print("# Total rows", med.shape[0]) + return med - -def preproc_proc(dataset_path: str, cohort_path:str, time_col:str, dtypes: dict, usecols: list) -> pd.DataFrame: + + +def preproc_proc( + dataset_path: str, cohort_path: str, time_col: str, dtypes: dict, usecols: list +) -> pd.DataFrame: """Function for getting hosp observations pertaining to a pickled cohort. Function is structured to save memory when reading and transforming data.""" def merge_module_cohort() -> pd.DataFrame: """Gets the initial module data with patients anchor year data and only the year of the charttime""" - + # read module w/ custom params - module = pd.read_csv(dataset_path, compression='gzip', usecols=usecols, dtype=dtypes, parse_dates=[time_col]).drop_duplicates() - #print(module.head()) + module = pd.read_csv( + dataset_path, + compression="gzip", + usecols=usecols, + dtype=dtypes, + parse_dates=[time_col], + ).drop_duplicates() + # print(module.head()) # Only consider values in our cohort - cohort = pd.read_csv(cohort_path, compression='gzip', parse_dates = ['intime']) - - #print(module.head()) - #print(cohort.head()) + cohort = pd.read_csv(cohort_path, compression="gzip", parse_dates=["intime"]) + + # print(module.head()) + # print(cohort.head()) # merge module and cohort - return module.merge(cohort[['subject_id','hadm_id','stay_id', 'intime','outtime']], how='inner', left_on='stay_id', right_on='stay_id') + return module.merge( + cohort[["subject_id", "hadm_id", "stay_id", "intime", "outtime"]], + how="inner", + left_on="stay_id", + right_on="stay_id", + ) df_cohort = merge_module_cohort() - df_cohort['event_time_from_admit'] = df_cohort[time_col] - df_cohort['intime'] - - df_cohort=df_cohort.dropna() + df_cohort["event_time_from_admit"] = df_cohort[time_col] - df_cohort["intime"] + + df_cohort = df_cohort.dropna() # Print unique counts and value_counts print("# Unique Events: ", df_cohort.itemid.dropna().nunique()) print("# Admissions: ", df_cohort.stay_id.nunique()) @@ -154,27 +214,41 @@ def merge_module_cohort() -> pd.DataFrame: # Only return module measurements within the observation range, sorted by subject_id return df_cohort -def preproc_out(dataset_path: str, cohort_path:str, time_col:str, dtypes: dict, usecols: list) -> pd.DataFrame: + +def preproc_out( + dataset_path: str, cohort_path: str, time_col: str, dtypes: dict, usecols: list +) -> pd.DataFrame: """Function for getting hosp observations pertaining to a pickled cohort. Function is structured to save memory when reading and transforming data.""" def merge_module_cohort() -> pd.DataFrame: """Gets the initial module data with patients anchor year data and only the year of the charttime""" - + # read module w/ custom params - module = pd.read_csv(dataset_path, compression='gzip', usecols=usecols, dtype=dtypes, parse_dates=[time_col]).drop_duplicates() - #print(module.head()) + module = pd.read_csv( + dataset_path, + compression="gzip", + usecols=usecols, + dtype=dtypes, + parse_dates=[time_col], + ).drop_duplicates() + # print(module.head()) # Only consider values in our cohort - cohort = pd.read_csv(cohort_path, compression='gzip', parse_dates = ['intime']) - - #print(module.head()) - #print(cohort.head()) + cohort = pd.read_csv(cohort_path, compression="gzip", parse_dates=["intime"]) + + # print(module.head()) + # print(cohort.head()) # merge module and cohort - return module.merge(cohort[['stay_id', 'intime','outtime']], how='inner', left_on='stay_id', right_on='stay_id') + return module.merge( + cohort[["stay_id", "intime", "outtime"]], + how="inner", + left_on="stay_id", + right_on="stay_id", + ) df_cohort = merge_module_cohort() - df_cohort['event_time_from_admit'] = df_cohort[time_col] - df_cohort['intime'] - df_cohort=df_cohort.dropna() + df_cohort["event_time_from_admit"] = df_cohort[time_col] - df_cohort["intime"] + df_cohort = df_cohort.dropna() # Print unique counts and value_counts print("# Unique Events: ", df_cohort.itemid.nunique()) print("# Admissions: ", df_cohort.stay_id.nunique()) @@ -183,46 +257,62 @@ def merge_module_cohort() -> pd.DataFrame: # Only return module measurements within the observation range, sorted by subject_id return df_cohort -def preproc_chart(dataset_path: str, cohort_path:str, time_col:str, dtypes: dict, usecols: list) -> pd.DataFrame: + +def preproc_chart( + dataset_path: str, cohort_path: str, time_col: str, dtypes: dict, usecols: list +) -> pd.DataFrame: """Function for getting hosp observations pertaining to a pickled cohort. Function is structured to save memory when reading and transforming data.""" - + # Only consider values in our cohort - cohort = pd.read_csv(cohort_path, compression='gzip', parse_dates = ['intime']) - df_cohort=pd.DataFrame() - # read module w/ custom params + cohort = pd.read_csv(cohort_path, compression="gzip", parse_dates=["intime"]) + df_cohort = pd.DataFrame() + # read module w/ custom params chunksize = 10000000 - count=0 - nitem=[] - nstay=[] - nrows=0 - for chunk in tqdm(pd.read_csv(dataset_path, compression='gzip', usecols=usecols, dtype=dtypes, parse_dates=[time_col],chunksize=chunksize)): - #print(chunk.head()) - count=count+1 - #chunk['valuenum']=chunk['valuenum'].fillna(0) - chunk=chunk.dropna(subset=['valuenum']) - chunk_merged=chunk.merge(cohort[['stay_id', 'intime']], how='inner', left_on='stay_id', right_on='stay_id') - chunk_merged['event_time_from_admit'] = chunk_merged[time_col] - chunk_merged['intime'] - - del chunk_merged[time_col] - del chunk_merged['intime'] - chunk_merged=chunk_merged.dropna() - chunk_merged=chunk_merged.drop_duplicates() + count = 0 + nitem = [] + nstay = [] + nrows = 0 + for chunk in tqdm( + pd.read_csv( + dataset_path, + compression="gzip", + usecols=usecols, + dtype=dtypes, + parse_dates=[time_col], + chunksize=chunksize, + ) + ): + # print(chunk.head()) + count = count + 1 + # chunk['valuenum']=chunk['valuenum'].fillna(0) + chunk = chunk.dropna(subset=["valuenum"]) + chunk_merged = chunk.merge( + cohort[["stay_id", "intime"]], + how="inner", + left_on="stay_id", + right_on="stay_id", + ) + chunk_merged["event_time_from_admit"] = ( + chunk_merged[time_col] - chunk_merged["intime"] + ) + + del chunk_merged[time_col] + del chunk_merged["intime"] + chunk_merged = chunk_merged.dropna() + chunk_merged = chunk_merged.drop_duplicates() if df_cohort.empty: - df_cohort=chunk_merged + df_cohort = chunk_merged else: - df_cohort=df_cohort.append(chunk_merged, ignore_index=True) - - -# nitem.append(chunk_merged.itemid.dropna().unique()) -# nstay=nstay.append(chunk_merged.stay_id.unique()) -# nrows=nrows+chunk_merged.shape[0] - - - + df_cohort = df_cohort.append(chunk_merged, ignore_index=True) + + # nitem.append(chunk_merged.itemid.dropna().unique()) + # nstay=nstay.append(chunk_merged.stay_id.unique()) + # nrows=nrows+chunk_merged.shape[0] + # Print unique counts and value_counts -# print("# Unique Events: ", len(set(nitem))) -# print("# Admissions: ", len(set(nstay))) -# print("Total rows", nrows) + # print("# Unique Events: ", len(set(nitem))) + # print("# Admissions: ", len(set(nstay))) + # print("Total rows", nrows) print("# Unique Events: ", df_cohort.itemid.nunique()) print("# Admissions: ", df_cohort.stay_id.nunique()) print("Total rows", df_cohort.shape[0]) @@ -230,21 +320,33 @@ def preproc_chart(dataset_path: str, cohort_path:str, time_col:str, dtypes: dict # Only return module measurements within the observation range, sorted by subject_id return df_cohort -def preproc_icd_module(module_path:str, adm_cohort_path:str, icd_map_path=None, map_code_colname=None, only_icd10=True) -> pd.DataFrame: - """Takes an module dataset with ICD codes and puts it in long_format, optionally mapping ICD-codes by a mapping table path""" - - def get_module_cohort(module_path:str, cohort_path:str): - module = pd.read_csv(module_path, compression='gzip', header=0) - adm_cohort = pd.read_csv(adm_cohort_path, compression='gzip', header=0) - #print(module.head()) - #print(adm_cohort.head()) - - #adm_cohort = adm_cohort.loc[(adm_cohort.timedelta_years <= 6) & (~adm_cohort.timedelta_years.isna())] - return module.merge(adm_cohort[['hadm_id', 'stay_id', 'label']], how='inner', left_on='hadm_id', right_on='hadm_id') + +def preproc_icd_module( + module_path: str, + adm_cohort_path: str, + icd_map_path=None, + map_code_colname=None, + only_icd10=True, +) -> pd.DataFrame: + """Takes an module dataset with ICD codes and puts it in long_format, optionally mapping ICD-codes by a mapping table path""" + + def get_module_cohort(module_path: str, cohort_path: str): + module = pd.read_csv(module_path, compression="gzip", header=0) + adm_cohort = pd.read_csv(adm_cohort_path, compression="gzip", header=0) + # print(module.head()) + # print(adm_cohort.head()) + + # adm_cohort = adm_cohort.loc[(adm_cohort.timedelta_years <= 6) & (~adm_cohort.timedelta_years.isna())] + return module.merge( + adm_cohort[["hadm_id", "stay_id", "label"]], + how="inner", + left_on="hadm_id", + right_on="hadm_id", + ) def standardize_icd(mapping, df, root=False): """Takes an ICD9 -> ICD10 mapping table and a modulenosis dataframe; adds column with converted ICD10 column""" - + def icd_9to10(icd): # If root is true, only map an ICD 9 -> 10 according to the ICD9's root (first 3 digits) if root: @@ -253,16 +355,17 @@ def icd_9to10(icd): # Many ICD-9's do not have a 1-to-1 mapping; get first index of mapped codes return mapping.loc[mapping[map_code_colname] == icd].icd10cm.iloc[0] except: - #print("Error on code", icd) + # print("Error on code", icd) return np.nan # Create new column with original codes as default - col_name = 'icd10_convert' - if root: col_name = 'root_' + col_name - df[col_name] = df['icd_code'].values + col_name = "icd10_convert" + if root: + col_name = "root_" + col_name + df[col_name] = df["icd_code"].values # Group identical ICD9 codes, then convert all ICD9 codes within a group to ICD10 - for code, group in df.loc[df.icd_version == 9].groupby(by='icd_code'): + for code, group in df.loc[df.icd_version == 9].groupby(by="icd_code"): new_code = icd_9to10(code) for idx in group.index.values: # Modify values of original df at the indexes in the groups @@ -270,30 +373,52 @@ def icd_9to10(icd): if only_icd10: # Column for just the roots of the converted ICD10 column - df['root'] = df[col_name].apply(lambda x: x[:3] if type(x) is str else np.nan) + df["root"] = df[col_name].apply( + lambda x: x[:3] if type(x) is str else np.nan + ) module = get_module_cohort(module_path, adm_cohort_path) - #print(module.shape) - #print(module['icd_code'].nunique()) + # print(module.shape) + # print(module['icd_code'].nunique()) # Optional ICD mapping if argument passed if icd_map_path: icd_map = read_icd_mapping(icd_map_path) - #print(icd_map) + # print(icd_map) standardize_icd(icd_map, module, root=True) - print("# unique ICD-9 codes",module[module['icd_version']==9]['icd_code'].nunique()) - print("# unique ICD-10 codes",module[module['icd_version']==10]['icd_code'].nunique()) - print("# unique ICD-10 codes (After converting ICD-9 to ICD-10)",module['root_icd10_convert'].nunique()) - print("# unique ICD-10 codes (After clinical gruping ICD-10 codes)",module['root'].nunique()) + print( + "# unique ICD-9 codes", + module[module["icd_version"] == 9]["icd_code"].nunique(), + ) + print( + "# unique ICD-10 codes", + module[module["icd_version"] == 10]["icd_code"].nunique(), + ) + print( + "# unique ICD-10 codes (After converting ICD-9 to ICD-10)", + module["root_icd10_convert"].nunique(), + ) + print( + "# unique ICD-10 codes (After clinical gruping ICD-10 codes)", + module["root"].nunique(), + ) print("# Admissions: ", module.stay_id.nunique()) print("Total rows", module.shape[0]) return module -def pivot_cohort(df: pd.DataFrame, prefix: str, target_col:str, values='values', use_mlb=False, ohe=True, max_features=None): +def pivot_cohort( + df: pd.DataFrame, + prefix: str, + target_col: str, + values="values", + use_mlb=False, + ohe=True, + max_features=None, +): """Pivots long_format data into a multiindex array: - || feature 1 || ... || feature n || - || subject_id || label || timedelta || + || feature 1 || ... || feature n || + || subject_id || label || timedelta || """ aggfunc = np.mean pivot_df = df.dropna(subset=[target_col]) @@ -303,18 +428,50 @@ def pivot_cohort(df: pd.DataFrame, prefix: str, target_col:str, values='values', output = mlb.fit_transform(pivot_df[target_col].apply(ast.literal_eval)) output = pd.DataFrame(output, columns=mlb.classes_) if max_features: - top_features = output.sum().sort_values(ascending=False).index[:max_features] + top_features = ( + output.sum().sort_values(ascending=False).index[:max_features] + ) output = output[top_features] - pivot_df = pd.concat([pivot_df[['subject_id', 'label', 'timedelta']].reset_index(drop=True), output], axis=1) - pivot_df = pd.pivot_table(pivot_df, index=['subject_id', 'label', 'timedelta'], values=pivot_df.columns[3:], aggfunc=np.max) + pivot_df = pd.concat( + [ + pivot_df[["subject_id", "label", "timedelta"]].reset_index(drop=True), + output, + ], + axis=1, + ) + pivot_df = pd.pivot_table( + pivot_df, + index=["subject_id", "label", "timedelta"], + values=pivot_df.columns[3:], + aggfunc=np.max, + ) else: if max_features: - top_features = pd.Series(pivot_df[['subject_id', target_col]].drop_duplicates()[target_col].value_counts().index[:max_features], name=target_col) - pivot_df = pivot_df.merge(top_features, how='inner', left_on=target_col, right_on=target_col) + top_features = pd.Series( + pivot_df[["subject_id", target_col]] + .drop_duplicates()[target_col] + .value_counts() + .index[:max_features], + name=target_col, + ) + pivot_df = pivot_df.merge( + top_features, how="inner", left_on=target_col, right_on=target_col + ) if ohe: - pivot_df = pd.concat([pivot_df.reset_index(drop=True), pd.Series(np.ones(pivot_df.shape[0], dtype=int), name='values')], axis=1) + pivot_df = pd.concat( + [ + pivot_df.reset_index(drop=True), + pd.Series(np.ones(pivot_df.shape[0], dtype=int), name="values"), + ], + axis=1, + ) aggfunc = np.max - pivot_df = pivot_df.pivot_table(index=['subject_id', 'label', 'timedelta'], columns=target_col, values=values, aggfunc=aggfunc) + pivot_df = pivot_df.pivot_table( + index=["subject_id", "label", "timedelta"], + columns=target_col, + values=values, + aggfunc=aggfunc, + ) pivot_df.columns = [prefix + str(i) for i in pivot_df.columns] - return pivot_df \ No newline at end of file + return pivot_df