Создание и обработка медицинской базы данных с помощью python/R

от автора

  • Идея

  • Реализация

  • Результат

Идея: в медицинском учреждении выписные эпикризы (информация из истории болезни) пациентов хранятся в общегоспитальной локальной сети.

Необходимо сформировать базу данных пациентов с перенесенным заболеванием COVID-19 (один выписной эпикриз ДО заболевания COVID-19, один выписной эпикриз во время заболевания и один ПОСЛЕ заболевания).

Вот как это выглядит

Реализация:

  1. Сформированы папки с файлами

  1. Формирование базы:

    с помощью модуля docx на Python можно перевести *.docx файл в обычный текст, называем этот скрипт readDocx.py (решение найдено на просторах интернета):

import docx   def getText(filename):     doc = docx.Document(filename)     fullText = []     for para in doc.paragraphs:         fullText.append((' ' + para.text))     return '\n'.join(fullText)

Проблемы с которыми я столкнулся: некоторые файлы были в старом формате *.doc и *.rtf. для решения пришлось установить Libre Office и применить следующие команды в Терминале:

 /Applications/LibreOffice.app/Contents/MacOS/soffice --headless --convert-to docx --outdir ~/Desktop/output ~/Desktop/COVID-2019/**/*.doc  /Applications/LibreOffice.app/Contents/MacOS/soffice --headless --convert-to docx --outdir ~/Desktop/output ~/Desktop/COVID-2019/**/*.rtf

Для получения базы каждый файл в директории переводится в текстовый формат, затем с помощью регулярных выражений требуемые показатели находятся и формируется общая таблица в формате *.xlsx (Exel):

import os, readDocx, re, pandas as pd ROOT_DIR = r'/Users/insomnia/Documents/disser/COVID-2019' docx_files = [] for root, dirs, files in os.walk(ROOT_DIR):     for file in files:         if file.endswith(".docx"):             docx_files.append(os.path.join(root, file)) print(docx_files) setoftuples = [] for i in docx_files:     x = readDocx.getText(i)     name = [i]     if re.search(r'\d\d[.]\d\d[.]\d{4}', x):         birthdate = re.search(r'\d\d[.]\d\d[.]\d{4}', x).group()     else:         birthdate = "NA"     if len(re.findall(r'\d\d[.]\d\d[.]\d{4}', x)) > 2:         admission = re.findall(r'\d\d[.]\d\d[.]\d{4}', x)[1]         discharge = re.findall(r'\d\d[.]\d\d[.]\d{4}', x)[2]     else:         admission = "NA"         discharge = "NA"     if re.findall(r'[Кк]орон[а]?[о]?вирусная инфекция', x):         COVID = re.findall(r'[Кк]орон[а]?[о]?вирусная инфекция', x)     else:         COVID = "NA"     if re.findall(r'КТ.?[0-4]', x):         CT = re.findall(r'КТ.?[0-4]', x)     else:         CT = "NA"     if re.findall(r'ПОСМЕРТНЫЙ|'                   r'\bумер\b|'                   r'\bсмерть\b', x):         Death = re.findall(r'ПОСМЕРТНЫЙ|'                   r'\bумер\b|'                   r'\bсмерть\b', x)     else:         Death = "NA"     if re.findall(r'фибрил\w+', x):         AF = re.findall(r'фибрил\w+', x)     else:         AF = "NA"     if re.findall(r'гемоглобин\s\S?\s?\d\d\d?|'                   r'Hb\D?\D?\D?\d\d\d?', x):         hb = re.findall(r'гемоглобин\s\S?\s?\d\d\d?|'                   r'Hb\D?\D?\D?\d\d\d?', x)     else:         hb = "NA"     if re.findall(r'Эр\w*\s?\S?\s?[0-9][.,][0-9]?[0-9]?|'                   r'эр\w*\s?\S?\s?[0-9][.,][0-9]?[0-9]?', x):         RBC = re.findall(r'Эр\w*\s?\S?\s?[0-9][.,][0-9]?[0-9]?|'                          r'эр\w*\s?\S?\s?[0-9][.,][0-9]?[0-9]?', x)     else:         RBC = "NA"     if re.findall(r'лейк\w+\s\S?\s?\d\d?[,.]\d?\d?|'                   r'Л – \d\d?,?\.?\d?|'                   r'Л-\d\d?,?\.?\d?|'                   r'Le \d\d?,?\.?\d?', x):         leu = re.findall(r'лейк\w+\s\S?\s?\d\d?[,.]\d?\d?|'                   r'Л – \d\d?,?\.?\d?|'                   r'Л-\d\d?,?\.?\d?|'                   r'Le \d\d?,?\.?\d?', x)     else:         leu = "NA"     if re.findall(r'лимф\w+\s?\S?\s?[0-9][.,][0-9]?[0-9?]?[0-9?]?', x):         limf = re.findall(r'лимф\w+\s?\S?\s?[0-9][.,][0-9]?[0-9?]?[0-9?]?', x)     else:         limf = "NA"     if re.findall(r'С.?реактивный белок\D*\d?\d?[.]?\d?\d?[.]?\d?\d?\d?\d?\D*\d\d?\d?[.,]\d?\d?|'                   r'СРБ \D?\D? ?\d?\d.\d\d.\d\d\d?\d?\D*[0-9][0-9]?[0-9]?[.,][0-9]?[0-9]?|'                   r'СРБ\D?\D?\D?\d\d?\d?\S?\d?\d?|'                   r'С-реактивный белок – \d\d.\d\d.\d\d\d?\d? г. – \d\d?\d?\W?\d?\d?', x):         CRP = re.findall(r'С.?реактивный белок\D*\d?\d?[.]?\d?\d?[.]?\d?\d?\d?\d?\D*\d\d?\d?[.,]\d?\d?|'                          r'СРБ \D?\D? ?\d?\d.\d\d.\d\d\d?\d?\D*[0-9][0-9]?[0-9]?[.,][0-9]?[0-9]?|'                          r'СРБ\D?\D?\D?\d\d?\d?\S?\d?\d?|'                          r'С-реактивный белок – \d\d.\d\d.\d\d\d?\d? г. – \d\d?\d?\W?\d?\d?', x)     else:         CRP = "NA"     if re.findall(r'[Хх]олестерин\D?\D?\D?\d\S?\d?\d?|'                   r'[Хх]олестерин общий\D?\D?\D?\d\S?\d?\d?|'                   r'[Cc]hol|CHOL\D?\D?\D?\d\S?\d?\d?', x):         chol = re.findall(r'[Хх]олестерин\D?\D?\D?\d\S?\d?\d?|'                           r'[Хх]олестерин общий\D?\D?\D?\d\S?\d?\d?|'                           r'[Cc]hol|CHOL\D?\D?\D?\d\S?\d?\d?', x)     else:         chol = "NA"     if re.findall(r'[Тт]риглицериды\D?\D?\D?\d\S?\d?\d?|'                   r'TRIG[L]?\D?\D?\D?\d\S?\d?\d?|'                   r'Триглицериды\D*\d\S?\d?\d?', x):         TG = re.findall(r'триглицериды\D?\D?\D?\d\S?\d?\d?|'                         r'TRIG[L]?\D?\D?\D?\d\S?\d?\d?|'                         r'Триглицериды\D*\d\S?\d?\d?', x)     else:         TG = "NA"     if re.findall(r'UHDL\D?\D?\D?\d\S?\d?\d?|'                   r'ЛПВП\D?\D?\D?\d\S?\d?\d?', x):         UHDL = re.findall(r'UHDL\D?\D?\D?\d\S?\d?\d?|'                           r'ЛПВП\D?\D?\D?\d\S?\d?\d?', x)     else:         UHDL = "NA"     if re.findall(r'DLDL\D?\D?\D?\d\S?\d?\d?|'                   r'ЛПНП\D?\D?\D?\d\S?\d?\d?', x):         DLDL = re.findall(r'DLDL\D?\D?\D?\d\S?\d?\d?|'                           r'ЛПНП\D?\D?\D?\d\S?\d?\d?', x)     else:         DLDL = "NA"     if re.findall(r'креатинин\D?\D?\D?\d\d?\d?\S?\d?', x):         crea = re.findall(r'креатинин\D?\D?\D?\d\d?\d?\S?\d?', x)     else:         crea = "NA"     if re.findall(r'КДРЛЖ\w?\s?\S?\s?\d\d\d?', x):         LVEDD = re.findall(r'КДРЛЖ\w?\s?\S?\s?\d\d\d?', x)     else:         LVEDD = "NA"     if re.findall(r'КС[Р]?ЛЖ\w?\s?\S?\s?\d\d\d?', x):         LVESD = re.findall(r'КС[Р]?ЛЖ\w?\s?\S?\s?\d\d\d?', x)     else:         LVESD = "NA"     if re.findall(r'КДОЛЖ\w?\s?\S?\s?\d\d\d?\d?', x):         LVEDV = re.findall(r'КДОЛЖ\w?\s?\S?\s?\d\d\d?\d?', x)     else:         LVEDV = "NA"     if re.findall(r'ИММЛЖ\w?\s?\S?\s?\d\d\d?|'                   r'индекс массы\s?\S?\s?\d\d\d?', x):         LVMI = re.findall(r'ИММЛЖ\w?\s?\S?\s?\d\d\d?|'                           r'индекс массы\s?\S?\s?\d\d\d?', x)     else:         LVMI = "NA"     if re.findall(r'ФВ[в]?[м?]?\s?\W?\s?[0-9]{2}', x):         EF = re.findall(r'ФВ[в]?[м?]?\s?\W?\s?[0-9]{2}', x)     else:         EF = "NA"     if re.findall(r'Систол\D?\s?ДЛА\s?\S?\s?[0-9]{2}|'                   r'СДЛА\s?\S?\s?[0-9]{2}|'                   r'Сист.ДЛА\s?\S?\s?[0-9]{2}', x):         PASP = re.findall(r'Систол\D?\s?ДЛА\s?\S?\s?[0-9]{2}|'                           r'СДЛА\s?\S?\s?[0-9]{2}|'                           r'Сист.ДЛА\s?\S?\s?[0-9]{2}', x)     else:         PASP = "NA"     if re.findall(r'ИОЛП.\s?\S?\s?[0-9]{2}', x):         LAVI = re.findall(r'ИОЛП.\s?\S?\s?[0-9]{2}', x)     else:         LAVI = "NA"     if re.findall(r'ИБС|'                   r'[Ии]шемическая болезнь сердца', x):         IHD = re.findall(r'ИБС|'                          r'[Ии]шемическая болезнь сердца', x)     else:         IHD = "NA"     if re.findall(r'[Ии]нфаркт миокарда|'                   r'[Пп]остинфарктный', x):         MI = re.findall(r'[Ии]нфаркт миокарда|'                   r'[Пп]остинфарктный', x)     else:         MI = "NA"     if re.findall(r'[Cc]ахарный диабет', x):         Diabetus = re.findall(r'[Cc]ахарный диабет', x)     else:         Diabetus = "NA"     if re.findall(r'[Оо]жирение', x):         Obesity = re.findall(r'[Оо]жирение', x)     else:         Obesity = "NA"     if re.findall(r'ХОБЛ', x):         COPD = re.findall(r'ХОБЛ', x)     else:         COPD = "NA"     if re.findall(r'[Бб]ронхиальная астма', x):         asthma = re.findall(r'[Бб]ронхиальная астма', x)     else:         asthma = "NA"     my_list = (name, birthdate, admission, discharge, COVID, CT, Death, hb, RBC, leu,                limf, CRP, chol, TG, UHDL, DLDL, crea, LVEDD,                LVESD, LVEDV, LVMI, EF, PASP, LAVI, AF, IHD, MI, Diabetus, Obesity,                COPD, asthma)     setoftuples.append(my_list) print(setoftuples) df = pd.DataFrame(list(setoftuples),                   columns=['name','birthdate','admission', 'discharge', 'COVID',                            'CT', 'Death', 'hb', 'RBC',                            'leu', 'limf', 'CRP', 'chol', 'TG', 'UHDL', 'DLDL',                            'crea', 'LVEDD', 'LVESD', 'LVEDV',                            'LVMI', 'EF', 'PASP', 'LAVI', 'AF', 'IHD',                            'MI', 'Diabetus', 'Obesity', 'COPD', 'asthma']) print(df) df.to_excel(r'/Users/insomnia/Documents/disser/dataframe.xlsx', index=False)  

Промежуточный результат:

количество строк - 934, столбцов - 31

количество строк — 934, столбцов — 31

Полученные данные необходимо привести в формат с которым можно работать — очистить от мусора, задать каждой колонке тип данных, и проч. Этот процесс называется очисткой данных (Data cleaning).

В общих словах определение таково: Data cleaning is the process of fixing or removing incorrect, corrupted, incorrectly formatted, duplicate, or incomplete data within a dataset.

Для этого процесса я воспользовался программой RStudio IDE (язык R), можно было и на Python сделать, но R для меня удобнее. Постарался оставить комментарии на каждом из этапов:

library(openxlsx) data <- read.xlsx('/Users/insomnia/Documents/disser/dataframe.xlsx') library(tidyverse) # initial data glimpse(data)  ### data cleaning process:  # name column  name <- data$name |> str_split("/") name_unlisted <- unlist(lapply(name, '[[', 7)) # This returns a vector with the seven element name_unlisted <- as.factor(name_unlisted) ### lvls <- levels(name_unlisted) ### levels(name_unlisted) <- seq_along(lvls) ### data$name <- name_unlisted data$name <- data$name |> as.factor() data <- rename(data, patient_ID = name)  # 2,3,4 (time) columns  data <- data |> mutate(birthdate = dmy(birthdate),                admission = dmy(admission),                discharge = dmy(discharge))  data$admission[1]-data$birthdate[1]  #COVID column  covid <- data$COVID covid <- lapply(covid, function(x) replace(x,!is.na(x),1)) covid <- lapply(covid, function(x) replace(x,is.na(x),0)) covid <- as.numeric(covid) covid <- as.factor(covid) data$COVID <- covid  # CT column  matches <- regmatches(data$CT, gregexpr("[[:digit:]]+", data$CT)) data$CT <- matches data <- data |> rowwise() |> mutate(CT = max(CT)) |> ungroup() data$CT <- as.numeric(data$CT)  #Death column                  death <- data$Death death <- lapply(death, function(x) replace(x,!is.na(x),1)) death <- lapply(death, function(x) replace(x,is.na(x),0)) death <- as.numeric(death) data$Death <- death  # hb column                  extracted_hb <- str_replace_all(data$hb,fixed(","), fixed(".")) extracted_hb <- str_extract_all(extracted_hb, pattern = "[0-9][0-9][0-9]?") extracted_hb <- lapply(extracted_hb,as.numeric) extracted_hb <- sapply(extracted_hb, mean, 0-20) extracted_hb[906:913] data$hb <- extracted_hb  # RBC column  extracted_RBC <- str_replace_all(data$RBC,fixed(","), fixed(".")) extracted_RBC <- str_extract_all(extracted_RBC, pattern = "[0-9][.]?[0-9]?[0-9]?") extracted_RBC <- lapply(extracted_RBC,as.numeric) extracted_RBC <- sapply(extracted_RBC, mean, 0-20) extracted_RBC[906:913] data$RBC <- extracted_RBC  # leu column  str(data) extracted_leu <- str_replace_all(data$leu,fixed(","), fixed(".")) extracted_leu <- str_extract_all(extracted_leu, pattern = "[2-9][.]?[0-9]?|[1-3][0-9]?[.]?[0-9]?") # spread 2-22 extracted_leu <- lapply(extracted_leu,as.numeric) extracted_leu <- sapply(extracted_leu, mean, 0-20) extracted_leu[906:913] data$leu <- extracted_leu  # limf column                  extracted_limf <- str_replace_all(data$limf,fixed(","), fixed(".")) extracted_limf <- str_extract_all(extracted_limf, pattern = "[0-9][.]?[0-9]?") # spread 0-9 extracted_limf<- lapply(extracted_limf,as.numeric) extracted_limf <- sapply(extracted_limf, min) extracted_limf[906:913] data$limf <- extracted_limf data <- rename(data, limf_min = limf)  # CRP column                  data$CRP[7] extracted_CRP<- str_replace_all(data$CRP,fixed(","), fixed(".")) # Removing date from CRP data: extracted_CRP <- str_remove_all(extracted_CRP,                                  pattern = "[0-9][0-9]?[.][0-9][0-9][.][0-9][0-9][0-9]?[0-9]?")  extracted_CRP <- str_extract_all(extracted_CRP, pattern = "[0-9][0-9]?[0-9]?[.][0-9]?[0-9]?[0-9]?") # spread 0-999 extracted_CRP<- lapply(extracted_CRP,as.numeric) # getting CRP max value which is more valuable in this case then mean() extracted_CRP <- sapply(extracted_CRP, max) extracted_CRP data$CRP <- extracted_CRP data <- rename(data, CRP_max = CRP)  # Chol column                  extracted_chol <- str_replace_all(data$chol,fixed(","), fixed(".")) extracted_chol <- str_extract_all(extracted_chol, pattern = "[0-9][.][0-9]?[0-9]?") # spread 0-19 extracted_chol<- lapply(extracted_chol,as.numeric) extracted_chol <- sapply(extracted_chol, mean) extracted_chol[2] data$chol <- extracted_chol  # TG column                  extracted_TG <- str_replace_all(data$TG,fixed(","), fixed(".")) extracted_TG <- str_extract_all(extracted_TG, pattern = "[0-9][.]?[0-9]?[0-9]?") # spread 0-9 extracted_TG<- lapply(extracted_TG,as.numeric) extracted_TG <- sapply(extracted_TG, mean) extracted_TG[15] data$TG <- extracted_TG  # UHDL colunm                  extracted_UHDL <- str_replace_all(data$UHDL,fixed(","), fixed(".")) extracted_UHDL <- str_extract_all(extracted_UHDL, pattern = "[0-9][.]?[0-9]?[0-9]?") # spread 0-9 extracted_UHDL<- lapply(extracted_UHDL,as.numeric) extracted_UHDL <- sapply(extracted_UHDL, mean, 0-20) extracted_UHDL[906:913] data$UHDL <- extracted_UHDL  # DLDL colunm                  extracted_DLDL <- str_replace_all(data$DLDL,fixed(","), fixed(".")) extracted_DLDL <- str_extract_all(extracted_DLDL, pattern = "[0-9][.]?[0-9]?[0-9]?") # spread 0-9 extracted_DLDL<- lapply(extracted_DLDL,as.numeric) extracted_DLDL <- sapply(extracted_DLDL, mean, 0-20) extracted_DLDL[906:913] data$DLDL <- extracted_DLDL  # crea column                  extracted_crea <- str_replace_all(data$crea,fixed(","), fixed(".")) extracted_crea <- str_extract_all(extracted_crea, pattern = "[0-9][0-9][0-9]?[.]?[0-9]?") # spread 0-9 extracted_crea<- lapply(extracted_crea,as.numeric) extracted_crea <- sapply(extracted_crea, mean, 0-20) extracted_crea[906:913] data$crea <- extracted_crea  #creating age column                  x = year(data$admission) y = year(data$birthdate) data <- data |> mutate(age = x-y) data <- data |> relocate(age, .before = admission)  #creating LOS (length of stay(days of hospitalization)) column                  x_LOS = (data$admission) y_LOS = (data$discharge) data <- data |> mutate(LOS = y_LOS-x_LOS) data <- data |> relocate(LOS, .before = COVID) data$LOS <- as.numeric(data$LOS)  # filtering data with 2 or more cases of hospitalization                  matches <- data |> group_by(patient_ID) |> summarise(n=n()) |> filter(n>2) matches <- matches$patient_ID matched_data <- data |> filter(patient_ID %in% matches) class(matched_data$COVID) matched_data <- matched_data |> filter(patient_ID != "****") matched_data |>group_by(patient_ID) |> summarise(n=n())  # admission/covid plot                  matched_data |> ggplot(aes(x = admission, y = patient_ID))+   geom_point(aes(color = COVID))+   scale_color_manual(values = c("blue", "red"))                  # запись обновленного файла: write.xlsx(data, file = "structured_output.xlsx", colNames = T, borders = "columns")

Итоговый результат:

Итоговая таблица

Итоговая таблица

Общее время затраченное всё = 3-4 месяца. Из них 40% — поиск, сбор файлов(вручную). 30% — написание кода. 30% — проверка на ошибки, их исправление.

Буду рад услышать Ваши комментарии и замечания по выполненной работе!


ссылка на оригинал статьи https://habr.com/ru/articles/836734/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *