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

  • Идея

  • Реализация

  • Результат

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

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

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

a8d2e096d71826d1b2cd4910317ad278.png

Реализация:

  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% — проверка на ошибки, их исправление.

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

© Habrahabr.ru