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

Реализация:
-
Сформированы папки с файлами

-
Формирование базы:
с помощью модуля 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)
Промежуточный результат:
Полученные данные необходимо привести в формат с которым можно работать — очистить от мусора, задать каждой колонке тип данных, и проч. Этот процесс называется очисткой данных (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/
Добавить комментарий