Наука о данных, Машинное обучение

Прогнозирование выживаемости при сердечной недостаточности с помощью моделей машинного обучения - Часть I

Пошаговое описание анализа данных о выживаемости на питоническом языке с объяснением на уровне предметной области.

Предисловие

Сердечно-сосудистые заболевания - это заболевания сердца и кровеносных сосудов, обычно включающие сердечные приступы, инсульты и сердечную недостаточность [1]. По данным Всемирной организации здравоохранения (ВОЗ), сердечно-сосудистые заболевания, такие как ишемическая болезнь сердца и инсульт, были основными причинами смерти во всем мире за последние полтора десятилетия [2].

Мотивация

Несколько месяцев назад на Kaggle был загружен новый набор данных о сердечной недостаточности. Этот набор данных содержал истории болезни 299 анонимных пациентов и 12 клинических особенностей и особенностей образа жизни. Задача заключалась в прогнозировании сердечной недостаточности с использованием этих функций.

В этом посте я стремлюсь задокументировать свой рабочий процесс по этой задаче и представить его как исследовательское упражнение. Таким образом, это, естественно, потребует немного знаний в предметной области, ссылок на журнальные статьи и извлечения информации из них.

Предупреждение: этот пост длится почти 10 минут, и при прокрутке вниз все может стать немного сложнее, но я рекомендую вам попробовать.

О данных

Набор данных был первоначально выпущен Ахмедом и др. В 2017 году [3] в качестве дополнения к их анализу выживаемости пациентов с сердечной недостаточностью в Институте кардиологии Фейсалабада и в больнице Allied в Фейсалабаде, Пакистан. Впоследствии к этому набору данных обратились и проанализировали Chicco и Jurman в 2020 году для прогнозирования сердечной недостаточности с использованием набора методов машинного обучения [4]. Набор данных, размещенный на Kaggle, цитирует этих авторов и их исследовательские работы.

Набор данных в основном состоит из клинических характеристик и особенностей образа жизни 105 женщин и 194 мужчин с сердечной недостаточностью. Вы можете найти объяснение каждой функции на рисунке ниже.

Рабочий процесс проекта

Рабочий процесс будет довольно простым -

  1. Предварительная обработка данных - очистка данных, вменение пропущенных значений, создание новых функций при необходимости и т. д.
  2. Исследовательский анализ данных. Сюда входит сводная статистика, построение взаимосвязей, отображение тенденций и т. д.
  3. Построение модели - создание базовой модели прогнозирования, за которой следуют как минимум 2 модели классификации для обучения и тестирования.
  4. Настройка гиперпараметров - тонкая настройка гиперпараметров каждой модели для достижения приемлемых уровней прогнозных показателей.
  5. Обобщение результатов - четкое и краткое представление соответствующих результатов.

Весь проект можно найти в виде записной книжки Jupyter в моем репозитории GitHub.

Начнем!

Предварительная обработка данных

Давайте прочитаем файл .csv во фрейм данных -

df = pd.read_csv('heart_failure_clinical_records_dataset.csv')

df.info() - это быстрый способ получить сводку типов данных фрейма данных. Мы видим, что в наборе данных нет недостающих или ложных значений и он достаточно чист, чтобы начать исследование данных.

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 299 entries, 0 to 298
Data columns (total 13 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   age                       299 non-null    float64
 1   anaemia                   299 non-null    int64  
 2   creatinine_phosphokinase  299 non-null    int64  
 3   diabetes                  299 non-null    int64  
 4   ejection_fraction         299 non-null    int64  
 5   high_blood_pressure       299 non-null    int64  
 6   platelets                 299 non-null    float64
 7   serum_creatinine          299 non-null    float64
 8   serum_sodium              299 non-null    int64  
 9   sex                       299 non-null    int64  
 10  smoking                   299 non-null    int64  
 11  time                      299 non-null    int64  
 12  DEATH_EVENT               299 non-null    int64  
dtypes: float64(3), int64(10)
memory usage: 30.5 KB

Но перед этим давайте изменим порядок и переименуем некоторые функции, добавим еще одну функцию под названием chk (которая будет полезна позже во время EDA) и заменим двоичные значения в категориальных функциях их метками ( опять же, полезно во время EDA).

df = df.rename(columns={'smoking':'smk',
                        'diabetes':'dia',
                        'anaemia':'anm',
                        'platelets':'plt',
                        'high_blood_pressure':'hbp',
                        'creatinine_phosphokinase':'cpk',
                        'ejection_fraction':'ejf',
                        'serum_creatinine':'scr',
                        'serum_sodium':'sna',
                        'DEATH_EVENT':'death'})
df['chk'] = 1
df['sex'] = df['sex'].apply(lambda x: 'Female' if x==0 else 'Male')
df['smk'] = df['smk'].apply(lambda x: 'No' if x==0 else 'Yes')
df['dia'] = df['dia'].apply(lambda x: 'No' if x==0 else 'Yes')
df['anm'] = df['anm'].apply(lambda x: 'No' if x==0 else 'Yes')
df['hbp'] = df['hbp'].apply(lambda x: 'No' if x==0 else 'Yes')
df['death'] = df['death'].apply(lambda x: 'No' if x==0 else 'Yes')
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 299 entries, 0 to 298
Data columns (total 14 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   sex     299 non-null    object 
 1   age     299 non-null    float64
 2   smk     299 non-null    object 
 3   dia     299 non-null    object 
 4   hbp     299 non-null    object 
 5   anm     299 non-null    object 
 6   plt     299 non-null    float64
 7   ejf     299 non-null    int64  
 8   cpk     299 non-null    int64  
 9   scr     299 non-null    float64
 10  sna     299 non-null    int64  
 11  time    299 non-null    int64  
 12  death   299 non-null    object 
 13  chk     299 non-null    int64  
dtypes: float64(3), int64(5), object(6)
memory usage: 32.8+ KB

Мы заметили, что sex, dia, anm, hbp, smk иdeath являются категориальными характеристиками (object), а age, _10 _, _ 11_, ejf, scr, time и sna являются числовыми характеристиками (int64 или float64 ). Все функции, кроме death, будут потенциальными предикторами, а death - целью для нашей предполагаемой модели машинного обучения.

Исследовательский анализ данных

A. Суммарная статистика числовых характеристик

Поскольку наш набор данных имеет много числовых функций, было бы полезно взглянуть на некоторые агрегированные показатели имеющихся данных с помощью df.describe() (Обычно этот метод дает значения до 6 знаков после запятой, поэтому было бы лучше округлите до двух на df.describe().round(2) )

  • Возраст: мы видим, что средний возраст пациентов составляет 60 лет, при этом большинство пациентов (‹75%) моложе 70 лет и старше 40 лет. Срок наблюдения после сердечной недостаточности также варьируется от 4 до 285 дней, в среднем 130 дней.
  • Тромбоциты: Это тип клеток крови, который отвечает за восстановление поврежденных кровеносных сосудов. У нормального человека количество тромбоцитов составляет 150 000–400 000 кило тромбоцитов / мл крови [5 ]. Согласно нашему набору данных, у 75% пациентов количество тромбоцитов находится в этом диапазоне.
  • Фракция выброса: - это мера (в%) количества крови, откачиваемой из желудочка при каждом сокращении. Чтобы освежить немного анатомии человека - сердце имеет 4 камеры, из которых предсердия получают кровь из разных частей тела, а желудочки перекачивают ее назад. Левый желудочек является самой толстой камерой и перекачивает кровь к остальной части тела, в то время как правый желудочек перекачивает кровь к легким. У здорового взрослого эта фракция составляет 55%, а сердечная недостаточность со сниженной фракцией выброса подразумевает значение 75% пациентов имеют это значение ‹45%, что является ожидаемым, потому что все они в первую очередь пациенты с сердечной недостаточностью.
  • Креатининфосфокиназа: это фермент, который присутствует в крови и помогает восстанавливать поврежденные ткани. Высокий уровень КФК означает сердечную недостаточность или травму. Нормальный уровень у мужчин составляет 55–170 мкг / л, а у женщин - 30–135 мкг / л [7]. В нашем наборе данных, поскольку у всех пациентов была сердечная недостаточность, среднее значение (550 мкг / л) и медиана (250 мкг / л) выше нормы.
  • Креатинин сыворотки: Это ненужный продукт, который вырабатывается в процессе метаболизма мышц, особенно во время их разрушения. Этот креатинин фильтруется почками, и его повышенный уровень свидетельствует о плохом сердечном выбросе и возможной почечной недостаточности [8]. Нормальные уровни составляют от 0,84 до 1,21 мг / дл [9], а в нашем наборе данных среднее значение и медиана выше 1,10 мг / дл, что довольно близко к верхнему пределу нормального диапазона.
  • Натрий в сыворотке крови: Это относится к уровню натрия в крови, а высокий уровень ›135 мэкв / л называется гипернатриемией, которая считается типичной для пациентов с сердечной недостаточностью [ 10]. В нашем наборе данных мы обнаружили, что среднее значение и медиана составляют ›135 мЭкв / л.

Удобный способ визуализировать эту статистику - использовать boxenplot, который показывает разброс и распределение значений (линия в центре - это медиана, а ромбики в конце - это выбросы).

fig,ax = plt.subplots(3,2,figsize=[10,10])
num_features_set1 = ['age', 'scr', 'sna']
num_features_set2 = ['plt', 'ejf', 'cpk']
for i in range(0,3):
    sns.boxenplot(df[num_features_set1[i]], ax=ax[i,0], color='steelblue')
    sns.boxenplot(df[num_features_set2[i]], ax=ax[i,1], color='steelblue')

Б. Сводная статистика категориальных признаков

Количество пациентов, принадлежащих к каждой категориальной особенности образа жизни, можно суммировать с помощью простого bar plot.

fig = plt.subplots(figsize=[10,6])
bar1 = df.smk.value_counts().values
bar2 = df.hbp.value_counts().values
bar3 = df.dia.value_counts().values
bar4 = df.anm.value_counts().values
ticks = np.arange(0,3, 2)
width = 0.3
plt.bar(ticks, bar1, width=width, color='teal', label='smoker')
plt.bar(ticks+width, bar2, width=width, color='darkorange', label='high blood pressure')
plt.bar(ticks+2*width, bar3, width=width, color='limegreen', label='diabetes')
plt.bar(ticks+3*width, bar4, width=width, color='tomato', label='anaemic')
plt.xticks(ticks+1.5*width, ['Yes', 'No'])
plt.ylabel('Number of patients')
plt.legend()

Дополнительные сводки можно создать с помощью функции crosstab в пандах. Пример показан для категориального признака smk. Результаты могут быть нормализованы относительно общего числа курильщиков («индекс») или общего числа смертей («столбцы»). Поскольку нас интересует прогнозирование выживания, мы нормализуем отношение к смерти.

pd.crosstab(index=df['smk'], columns=df['death'], values=df['chk'], aggfunc=np.sum, margins=True)
pd.crosstab(index=df['smk'], columns=df['death'], values=df['chk'], aggfunc=np.sum, margins=True, normalize='columns').round(2)*100

Мы видим, что 68% всех пациентов с сердечной недостаточностью не курили, а 32% курили. Из тех, кто умер, 69% не курили, а 31% курили. Из тех, кто выжил, 67% были некурящими и 33% курили. На данный момент трудно окончательно сказать, что у курильщиков с сердечной недостаточностью больше шансов умереть.

Таким же образом давайте суммируем остальные категориальные признаки и нормализуем результаты в отношении смертей.

  • 65% мужчин и 35% женщин с сердечными заболеваниями умерли.
  • 48% умерших пациентов страдали анемией, в то время как 41% выживших пациентов также страдали анемией.
  • 42% умерших и 42% выживших пациентов были диабетиками.
  • 31% умерших были курильщиками, а 33% выживших курили.
  • 41% умерших имели высокое кровяное давление, в то время как 33% выживших также имели высокое кровяное давление.

Основываясь на этой статистике, мы получаем приблизительное представление о том, что особенности образа жизни почти одинаково распределяются между теми, кто умер, и теми, кто выжил. Разница является наибольшей в случае высокого кровяного давления, которое, возможно, может иметь большее влияние на выживаемость пациентов с сердечными заболеваниями.

C. Изучение взаимосвязей между числовыми характеристиками

Следующий шаг - визуализировать взаимосвязь между функциями. Мы начнем с числовых функций, написав одну строку кода для построения парного построения функций с использованием pairplot— seaborn.

sns.pairplot(df[['plt', 'ejf', 'cpk', 'scr', 'sna', 'death']], 
             hue='death', palette='husl', corner=True)

Мы наблюдаем несколько интересных моментов -

  • У большинства пациентов, умерших от сердечной недостаточности, фракция выброса ниже, чем у выживших. Они также имеют немного более высокие уровни креатинина сыворотки и креатинфосфокиназы. Они также имеют тенденцию быть на высоте 80 лет.
  • Между функциями нет сильной корреляции, и это можно проверить, рассчитав коэффициент корреляции Спирмена R (Мы рассматриваем копейщика, потому что мы не уверены в распределении населения, из которого взяты значения признаков).
df[['plt', 'ejf', 'cpk', 'scr', 'sna']].corr(method='spearman')

  • Как видно, коэффициенты корреляции умеренно обнадеживающие для возрастного креатинина сыворотки и сывороточного креатинина сывороточного натрия. Из литературы мы видим, что с возрастом содержание креатинина в сыворотке увеличивается [11], что объясняет их слегка положительную взаимосвязь. Литература также сообщает нам [12], что отношение натрия к креатинину сыворотки является высоким в случае хронического заболевания почек, что подразумевает отрицательную связь между ними. Небольшой отрицательный коэффициент корреляции также указывает на преобладание почечных проблем у пациентов.

D. Изучение отношений между категориальными признаками

Один из способов связать категориальные функции - создать сводную таблицу и сводную информацию о подмножестве функций. Это даст нам количество значений для определенного подмножества значений характеристик. Для этого набора данных давайте рассмотрим особенности образа жизни - курение, анемию, высокое кровяное давление и диабет.

lifestyle_surv = pd.pivot_table(df.loc[df.death=='No'], 
                                values='chk', 
                                columns=['hbp','dia'], 
                                index=['smk','anm'], 
                                aggfunc=np.sum)
lifestyle_dead = pd.pivot_table(df.loc[df.death=='Yes'], 
                                values='chk', 
                                columns=['hbp','dia'], 
                                index=['smk','anm'], 
                                aggfunc=np.sum)
fig, ax= plt.subplots(1, 2, figsize=[15,6])
sns.heatmap(lifestyle_surv, cmap='Greens', annot=True, ax=ax[0])
ax[0].set_title('Survivors')
sns.heatmap(lifestyle_dead, cmap='Reds', annot=True, ax=ax[1])
ax[1].set_title('Deceased')

Можно сделать несколько выводов -

  • Многие пациенты не курили, не страдали анемией и не страдали высоким кровяным давлением или диабетом.
  • Было очень мало пациентов, у которых были все четыре особенности образа жизни.
  • Многие из выживших были либо только курильщиками, либо только диабетиками.
  • Большинство умерших не имели никаких особенностей образа жизни или, в лучшем случае, страдали анемией.
  • Многие из умерших также страдали анемией и диабетом.

E. Изучение взаимосвязей между всеми функциями

Простой способ объединить категориальные и числовые характеристики в единый график - обойти категориальный признак как hue вход. В этом случае мы используем двоичную death функцию и строим скрипичные сюжеты для визуализации взаимосвязей между всеми функциями.

fig,ax = plt.subplots(6, 5, figsize=[20,22])
cat_features = ['sex','smk','anm', 'dia', 'hbp']
num_features = ['age', 'scr', 'sna', 'plt', 'ejf', 'cpk']
for i in range(0,6):
    for j in range(0,5):
        sns.violinplot(data=df, x=cat_features[j],y=num_features[i], hue='death', split=True, palette='husl',facet_kws={'despine':False}, ax=ax[i,j])
        ax[i,j].legend(title='death', loc='upper center')

Вот несколько выводов из этих сюжетов -

  • Пол. Среди умерших пациентов фракция выброса у мужчин ниже, чем у женщин. Кроме того, креатининфосфокиназа, по-видимому, выше у мужчин, чем у женщин.
  • Курение. У умерших курильщиков фракция выброса была несколько ниже, чем у умерших некурящих. Уровни креатининфосфокиназы, по-видимому, выше у выживших курильщиков, чем у выживших некурящих.
  • Анемия: пациенты с анемией, как правило, имеют более низкий уровень креатининфосфокиназы и более высокий уровень креатинина в сыворотке, чем пациенты без анемии. Среди пациентов с анемией фракция выброса у умерших ниже, чем у выживших.
  • Диабет: пациенты с диабетом, как правило, имеют более низкий уровень натрия, и, опять же, фракция выброса у умерших ниже, чем у выживших.
  • Высокое кровяное давление. Фракция выброса, по-видимому, больше варьируется у умерших пациентов с высоким кровяным давлением, чем у умерших пациентов без высокого кровяного давления.

Надеюсь, вы нашли это полезным. Шаги по созданию моделей машинного обучения, настройке их гиперпараметров и консолидации результатов будут показаны в следующем посте.

Чао!

использованная литература

[1] https://www.who.int/cardiovascular_diseases/world-heart-day/en/

[2] https://www.who.int/gho/mortality_burden_disease/causes_death/top_10/en/

[3] Ахмад Т., Мунир А., Бхатти С.Х., Афтаб М., Али Раза М. Анализ выживаемости пациентов с сердечной недостаточностью: тематическое исследование. Набор данных. Https://plos.figshare.com/ статьи / Survival_analysis_of_heart_failure_patients_A_case_study / 5227684/1.

[4] Chicco и Jurman, BMC Medical Informatics and Decision Making (2020) 20:16

[5] https://www.hopkinsmedicine.org/heart_vascular_institute/centers_excellence/women_cardiovascular_health_center/patient_information/health_topics/platelets.html

[6] https://www.uwhealth.org/health/topic/special/heart-failure-with-reduced-ejection-fraction-systolic-heart-failure/tx4090abc.html#:~:text= A% 20normal% 20ejection% 20fraction% 20is, фракция% 20is% 2040% 25% 20или% 20less.

[7] https://www.labpedia.net/creatine-kinase-ck-creatine-phosphokinase-cpk/

[8] https://www.onlinejcf.com/article/S1071-9164(02)25410-X/fulltext#:~:text=We%20believe%20the%20more%20likely,tolerate%20inpatient% 20heart% 20failure% 20treatment.

[9] https://www.mayoclinic.org/tests-procedures/creatinine-test/about/pac-20384646#:~:text=Results%20of%20the%20creatinine%20blood,and%20women% 2C% 20and% 20by% 20age.

[10] Абебе Т.Б., Гебрейоханнес Э.А., Тефера Ю.Г., Бхагаватула А.С., Эрку Д.А., Белачью С.А. и др. (2018) Прогноз для пациентов с сердечной недостаточностью: играет ли значительную роль уровень натрия? PLoS ONE 13 (11): e0207242

[11] Тяо Дж. Ю., Семменс Дж. Б., Масарей Дж. Р., Лоуренс-Браун М. М.. Влияние возраста на уровни креатинина в сыворотке у стареющего населения: актуальность для сосудистой хирургии. Cardiovasc Surg. 2002. 10 (5): 445–451. DOI: 10.1016 / s0967–2109 (02) 00056-x

[12] https://doi.org/10.1161/HYPERTENSIONAHA.113.03093