-
Идея
-
Реализация
-
Результат
Идея: в медицинском учреждении выписные эпикризы (информация из истории болезни) пациентов хранятся в общегоспитальной локальной сети.
Необходимо сформировать базу данных пациентов с перенесенным заболеванием 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'dd[.]dd[.]d{4}', x):
birthdate = re.search(r'dd[.]dd[.]d{4}', x).group()
else:
birthdate = "NA"
if len(re.findall(r'dd[.]dd[.]d{4}', x)) > 2:
admission = re.findall(r'dd[.]dd[.]d{4}', x)[1]
discharge = re.findall(r'dd[.]dd[.]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'гемоглобинsS?s?ddd?|'
r'HbD?D?D?ddd?', x):
hb = re.findall(r'гемоглобинsS?s?ddd?|'
r'HbD?D?D?ddd?', 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+sS?s?dd?[,.]d?d?|'
r'Л – dd?,?.?d?|'
r'Л-dd?,?.?d?|'
r'Le dd?,?.?d?', x):
leu = re.findall(r'лейкw+sS?s?dd?[,.]d?d?|'
r'Л – dd?,?.?d?|'
r'Л-dd?,?.?d?|'
r'Le dd?,?.?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*dd?d?[.,]d?d?|'
r'СРБ D?D? ?d?d.dd.ddd?d?D*[0-9][0-9]?[0-9]?[.,][0-9]?[0-9]?|'
r'СРБD?D?D?dd?d?S?d?d?|'
r'С-реактивный белок – dd.dd.ddd?d? г. – dd?d?W?d?d?', x):
CRP = re.findall(r'С.?реактивный белокD*d?d?[.]?d?d?[.]?d?d?d?d?D*dd?d?[.,]d?d?|'
r'СРБ D?D? ?d?d.dd.ddd?d?D*[0-9][0-9]?[0-9]?[.,][0-9]?[0-9]?|'
r'СРБD?D?D?dd?d?S?d?d?|'
r'С-реактивный белок – dd.dd.ddd?d? г. – dd?d?W?d?d?', x)
else:
CRP = "NA"
if re.findall(r'[Хх]олестеринD?D?D?dS?d?d?|'
r'[Хх]олестерин общийD?D?D?dS?d?d?|'
r'[Cc]hol|CHOLD?D?D?dS?d?d?', x):
chol = re.findall(r'[Хх]олестеринD?D?D?dS?d?d?|'
r'[Хх]олестерин общийD?D?D?dS?d?d?|'
r'[Cc]hol|CHOLD?D?D?dS?d?d?', x)
else:
chol = "NA"
if re.findall(r'[Тт]риглицеридыD?D?D?dS?d?d?|'
r'TRIG[L]?D?D?D?dS?d?d?|'
r'ТриглицеридыD*dS?d?d?', x):
TG = re.findall(r'триглицеридыD?D?D?dS?d?d?|'
r'TRIG[L]?D?D?D?dS?d?d?|'
r'ТриглицеридыD*dS?d?d?', x)
else:
TG = "NA"
if re.findall(r'UHDLD?D?D?dS?d?d?|'
r'ЛПВПD?D?D?dS?d?d?', x):
UHDL = re.findall(r'UHDLD?D?D?dS?d?d?|'
r'ЛПВПD?D?D?dS?d?d?', x)
else:
UHDL = "NA"
if re.findall(r'DLDLD?D?D?dS?d?d?|'
r'ЛПНПD?D?D?dS?d?d?', x):
DLDL = re.findall(r'DLDLD?D?D?dS?d?d?|'
r'ЛПНПD?D?D?dS?d?d?', x)
else:
DLDL = "NA"
if re.findall(r'креатининD?D?D?dd?d?S?d?', x):
crea = re.findall(r'креатининD?D?D?dd?d?S?d?', x)
else:
crea = "NA"
if re.findall(r'КДРЛЖw?s?S?s?ddd?', x):
LVEDD = re.findall(r'КДРЛЖw?s?S?s?ddd?', x)
else:
LVEDD = "NA"
if re.findall(r'КС[Р]?ЛЖw?s?S?s?ddd?', x):
LVESD = re.findall(r'КС[Р]?ЛЖw?s?S?s?ddd?', x)
else:
LVESD = "NA"
if re.findall(r'КДОЛЖw?s?S?s?ddd?d?', x):
LVEDV = re.findall(r'КДОЛЖw?s?S?s?ddd?d?', x)
else:
LVEDV = "NA"
if re.findall(r'ИММЛЖw?s?S?s?ddd?|'
r'индекс массыs?S?s?ddd?', x):
LVMI = re.findall(r'ИММЛЖw?s?S?s?ddd?|'
r'индекс массыs?S?s?ddd?', 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% - проверка на ошибки, их исправление.
Буду рад услышать Ваши комментарии и замечания по выполненной работе!
Автор: pogozhy