Введение
Сепсис — потенциально опасное для жизни заболевание, возникающее в результате реакции организма на инфекцию. Когда организм обнаруживает инфекцию, он выделяет химические вещества в кровоток для борьбы с вторгшимися микроорганизмами. Однако при сепсисе реакция организма сбивается, что приводит к распространенному воспалению по всему телу.
Это воспаление может вызвать повреждение различных органов и тканей, что приводит к дисфункции органов и, в тяжелых случаях, к органной недостаточности. Сепсис может быстро прогрессировать и может стать неотложной медицинской помощью. Он может поражать людей всех возрастов, включая детей и пожилых людей, и может возникать в результате инфекций, таких как бактериальные, вирусные или грибковые инфекции. Некоторые распространенные источники инфекции, которые могут привести к сепсису, включают инфекции мочевыводящих путей, респираторные инфекции, абдоминальные инфекции и кожные инфекции.
Этот проект/статья посвящен анализу распространенности сепсиса среди пациентов и определению групп пациентов, у которых, вероятно, будет диагностирован сепсис. Также создать модель машинного обучения, встроенную в веб-приложение с помощью FastAPI, для классификации пациентов с сепсисом.
Методология
Согласно IBM, CRISP-DM, что означает межотраслевой стандартный процесс интеллектуального анализа данных, является проверенным в отрасли способом управления вашими усилиями по интеллектуальному анализу данных.
- В качестве методологии он включает описания типичных этапов проекта, задач, связанных с каждым этапом, и объяснение отношений между этими задачами.
- Как модель процесса, CRISP-DM предоставляет обзор жизненного цикла интеллектуального анализа данных.
Шесть этапов жизненного цикла интеллектуального анализа данных CRISP-DM:
Понимание бизнеса
Основная цель этого проекта — разработать модель машинного обучения, которая может предсказать, есть ли у пациента сепсис или нет. Этот прогноз призван помочь медицинским работникам в выявлении пациентов, которые могут подвергаться риску развития сепсиса. Кроме того, проект направлен на предоставление оценки правдоподобия, указывающей, насколько вероятно, что прогноз положительный или отрицательный.
Проект также включает создание интерфейса прикладного программирования (API) с использованием FastAPI для интеграции разработанной модели машинного обучения в удобное для пользователя приложение.
Гипотеза и вопросы
Нулевая гипотеза: H0: нет никакой связи между возрастом и сепсисом.
Альтернативная гипотеза: H1: Существует связь между возрастом и сепсисом.
Предположения:
- Предполагалось, что использовалось артериальное давление диастолического типа.
Вопросы
- Каково распределение возрастов пациентов, охваченных данными?
- Каково число больных сепсисом?
- Сколько пациентов имеют страховку?
- Влияет ли артериальное давление на возраст?
- В каких возрастных группах больше всего случаев сепсиса?
- Какая связь между возрастом и индексом массы тела?
- Увеличивает ли отсутствие страховки шансы пациента заболеть сепсисом?
- Сколько больных попадает в категорию нормального, повышенного и высокого артериального давления?
- Сколько пациентов в каждой категории индекса массы тела имеют сепсис?
Понимание данных
Наборы данных (обучающие и тестовые) для этого проекта представлены в формате csv. Ниже описаны столбцы, присутствующие в данных.
ID – число, представляющее идентификатор пациента.
PRG — глюкоза плазмы
PL — Результат анализа крови-1 (мЕд/мл)
PR – артериальное давление (мм рт. ст.)
SK — Результат анализа крови-2 (мм)
TS — Результат анализа крови-3 (мЕд/мл)
M11 — индекс массы тела (вес в кг/(рост в м)²
BD2 — Результат анализа крови-4 (мкЕд/мл)
Возраст — возраст пациентов (лет).
Страховка — если у пациента есть действующая страховая карта.
Сепсис — положительный результат: если у пациента в отделении интенсивной терапии разовьется сепсис, и отрицательный результат: в противном случае.
Подготовка данных
Как правило, этап подготовки данных включает в себя очистку данных, исследовательский анализ данных (одномерный и двумерный), вычисление пропущенных значений, проектирование признаков и т. д.
Импорт всех соответствующих библиотек и пакетов
# Data Manipuulation import pandas as pd import numpy as np # Vizualisation (Matplotlib, Plotly, Seaborn, etc. ) %matplotlib inline import matplotlib.pyplot as plt import seaborn as sns import plotly.graph_objects as go import plotly.express as px # Feature Processing (Scikit-learn processing, etc. ) from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.metrics import confusion_matrix , classification_report, f1_score, accuracy_score,\ precision_score, recall_score, fbeta_score, make_scorer, roc_auc_score from sklearn.model_selection import train_test_split, GridSearchCV from skopt import BayesSearchCV from sklearn.utils import class_weight import scipy.stats as stats # models from sklearn import svm from xgboost import XGBClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier # model interpretation import shap import pickle import os import warnings warnings.filterwarnings("ignore")
Обзор набора данных
#load preview the dataset sepsis_data = pd.read_csv("Paitients_Files_Train.csv") sepsis_data.head()
Базовая проверка набора данных
# Listing the columns in the data sepsis_data.columns.to_list()
# Checking the shape of the data sepsis_data.shape
# Checking for more information about the data sepsis_data.info()
# Checking for missing/null values sepsis_data.isna().sum()
# Checking for duplicates sepsis_data.duplicated().sum()
# Create a function to print the number of unique values def print_unique_values(data): values = {} for col in data.columns: number = data[col].nunique() print(f'{col}: has {number} of unique values') print_unique_values(sepsis_data)
# Create a function to check if there are zeros in the data def number_of_zeros(data): for col in data.columns: if col != 'Sepssis': count = (data[col] == 0).sum() print(f'Count of zeros in Column {col} : {count}') number_of_zeros(sepsis_data)
Несмотря на то, что в столбцах не было пустых значений, в некоторых столбцах были нули.
# checking the column headers sepsis_data.columns
В приведенной выше ячейке отображаются имена столбцов, однако они недостаточно информативны.
Переименуйте столбцы
sepsis_data = sepsis_data.rename(columns={'PRG': 'Plasma Glucose', 'PL': 'Blood Work Result-1', 'PR': 'Blood Pressure','SK': 'Blood Work Result-2', 'TS': 'Blood Work Result-3', 'M11': 'Body Mass Index', 'BD2': 'Blood Work Result-4', 'Sepssis': 'Sepsis'}) # Checking the rename of columns sepsis_data.head()
Столбцы были успешно переименованы и теперь являются описательными.
# Checking the statistics of the data sepsis_data.describe()
Из сводной статистики мы делаем вывод, что средний возраст захваченных пациентов составляет 33,29 года. Среднее артериальное давление составляет 68,732888 мм рт.ст., что ниже 80 мм рт.ст., следовательно, указывает на то, что у пациентов было в среднем нормальное давление.
Проверьте корреляцию между различными столбцами
# Find the correlation between the columns corr_matrix = sepsis_data.corr() corr_matrix
# Plot a heatmap for the correlations plt.figure(figsize=(8, 5)) sns.heatmap(corr_matrix, annot=True) plt.show();
# get the minimum value min_corr = corr_matrix.min().min() # get the columns involved min_corr_cols = corr_matrix.unstack().idxmin() print('The minimum correlation was:', min_corr) print('The columns involved are:', min_corr_cols) The minimum correlation was: -0.12155329275228288 The columns involved are: ('Blood Work Result-2', 'Age') # get the minimum value max_corr = (corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool_)) .max().max()) # get the maximum value (excluding same columns) # get the columns involved max_corr_cols = (corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool_)) .stack().idxmax()) print('The maximun correlation was:', max_corr) print('The columns involved are:', max_corr_cols) The maximun correlation was: 0.5325446086908504 The columns involved are: ('Plasma Glucose', 'Age')
Самая низкая корреляция между результатом анализа крови-2 и возрастом составляет -0,12155329275228288.
Самая высокая корреляция между уровнем глюкозы в плазме и возрастом составляет 0,5325446086908504.
Проверка гипотезы
# Select the Age and Sepsis columns from the dataset age = sepsis_data['Age'] sepsis = (sepsis_data['Sepsis'] == 'Positive').astype(bool).astype(int) # Perform correlation analysis correlation, p_value = stats.pearsonr(age, sepsis) # Print the correlation coefficient and p-value print("Correlation coefficient:", correlation) print("P-value:", p_value) if p_value > 0.05: print('Fail to reject the null hypothesis. ') else: print('Reject the null hypothesis') Correlation coefficient: 0.2102342858235142 P-value: 2.0718778891887102e-07 Reject the null hypothesis
Одномерный анализ
- Каково распределение пациентов по возрасту в данных?
plt.figure(figsize=(10, 6)) sns.histplot(sepsis_data['Age'], bins=20, kde=True) plt.title("Distribution of Ages for Patients") plt.xlabel("Age") plt.ylabel("Count") plt.show()
На приведенном выше графике показано распределение пациентов по возрасту. Было замечено, что в возрастной группе от 20 до 30 лет больше пациентов, а в возрастной группе от 70 до 80 – меньше.
2. Каково число больных сепсисом?
# Calculate the sepsis counts sepsis_counts = sepsis_data['Sepsis'].value_counts() # Set custom colors colors = ['#fc4f30', '#008fd5'] # Calculate percentages percentages = round(sepsis_counts / sepsis_counts.sum() * 100, 2).astype(str) + '%' # Create a bar plot for sepsis distribution plt.figure(figsize=(8, 5)) ax = sepsis_counts.plot(kind='bar', color=colors, edgecolor='black', linewidth=1.2, figsize=(8, 6), rot=0) ax.set_title('Sepsis Distribution', fontsize=14) ax.set_xlabel('Sepsis', fontsize=12) ax.set_ylabel('Count', fontsize=12) ax.set_ylim(0, 500) # Add count values and percentages on top of each bar for i, p in enumerate(ax.patches): width, height = p.get_width(), p.get_height() x, y = p.get_xy() ax.annotate(f'{height}\n{percentages[i]}', (x + 0.15, y + height + 10)) ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) # Show the plot plt.show()
Распределение сепсиса показывает, что пациентов с отрицательным результатом больше, чем с положительным. В общей сложности 208 пациентов оказались положительными, а 391 – отрицательными.
3. Какова доля пациентов со страховкой?
# Calculate the count of patients with insurance insurance_counts = sepsis_data['Insurance'].value_counts() # Plot the proportion of patients with insurance_counts using a pie chart labels = insurance_counts.index colors = ['#008fd5', '#fc4f30'] explode = (0, 0.1) plt.figure(figsize=(6, 6)) plt.pie(insurance_counts, labels=labels, explode=explode, colors=colors, autopct='%1.1f%%', shadow=True, startangle=90) plt.axis('equal') plt.title('Proportion of Patients With Insurance') plt.legend(loc='upper right', bbox_to_anchor=(1.3, 1)) plt.ylabel('') plt.show()
На круговой диаграмме 1 представляет пациентов со страховкой, а 0 представляет пациентов без страховки. На диаграмме видно, что пациентов со страховкой больше, чем без страховки. Количество пациентов со страховкой составляет 68,6%, что более чем в два раза превышает количество пациентов, не имеющих страховки. Учитывая, что это также наша целевая переменная, мы можем заключить, что набор данных несбалансирован. По сути, переход к моделированию потребует некоторой предварительной обработки, чтобы убедиться, что мы не вносим предвзятость в нашу модель.
Двумерный анализ
4. Влияет ли возраст на давление крови?
plt.figure(figsize=(8, 5)) sns.regplot(x='Blood Pressure', y='Age', data=sepsis_data) plt.title('The correlation between the Age and Blood Pressure') plt.show()
На этом графике показано, что у большинства пациентов нормальное кровяное давление находится в диапазоне от 60 до 80, а у некоторых пациентов наблюдается высокое кровяное давление в диапазоне от 80 до 90, что считается стадией гипертензии 1. Присутствуют некоторые выбросы с низким кровяным давлением ниже 20 и очень высоким кровяным давлением выше 100, что указывает на 2-ю стадию артериальной гипертензии.
5. В какой возрастной группе больше всего случаев сепсиса
# Filter data for patients with sepsis sepsis_age_group = sepsis_data[sepsis_data['Sepsis'] == 'Positive'] # Create an interactive histogram using Plotly fig = px.histogram(sepsis_age_group, x='Age', nbins=20, labels={'Age': 'Age', 'count': 'Count'}, title='Distribution of Ages for Patients With Highest Cases of Sepsis', color_discrete_sequence=['violet']) # Update the layout for better visualization fig.update_layout( xaxis_title="Age", yaxis_title="Count", ) # Show the interactive plot fig.show()
Гистограмма показывает, что возрастная группа с наибольшим количеством случаев сепсиса находится в возрасте от 25 до 29, в то время как возрастная группа с наименьшим количеством случаев составляет 60 лет и старше.
6. Какая связь между возрастом и артериальным давлением?
# Sample data (replace this with your actual data) age_bmi = { 'Blood Pressure': ['Normal', 'Elevated', 'High', 'Normal', 'Elevated'], 'Age': [45, 55, 65, 50, 40] } age_bmi = pd.DataFrame(age_bmi) # Plot the relationship between Age and Blood Pressure using Plotly box plot fig = px.box(age_bmi, x='Blood Pressure', y='Age', color='Blood Pressure', labels={'Blood Pressure': 'Blood Pressure Category', 'Age': 'Age'}, title='Relationship between Age and Blood Pressure') # Update the layout for better visualization fig.update_layout( xaxis_title="Blood Pressure Category", yaxis_title="Age", ) # Show the interactive plot fig.show()
На этой диаграмме показано сравнение между различными категориями крови и их соответствующими возрастами. Пациенты с нормальным и повышенным артериальным давлением имеют средний возраст 47 лет, однако нормальная категория включает пациентов в возрасте от 45 до 50 лет. strong>, в то время как пациенты в повышенной категории находятся в возрасте от 40 до 55 лет. Пациенты с высоким кровяным давлением старше 60 лет.
7. Увеличивает ли отсутствие страховки шансы пациента заболеть сепсисом?
# Plot the impact of having insurance on sepsis occurrence fig = px.histogram(sepsis_data, x='Insurance', color='Sepsis', barmode='group', labels={'Insurance': 'Insurance', 'Sepsis': 'Sepsis'}, color_discrete_map={"Negative": "lightcoral", "Positive": "lightgreen"}) fig.update_layout( title="Impact of Having Insurance on Sepsis Occurrence", xaxis_title="Insurance", yaxis_title="Count", ) # Show the interactive plot fig.show()
1. Наблюдая за графиком, 0 представляет пациентов без страховки, а 1 представляет пациентов с сепсисом. Количество пациентов со страховкой больше, чем без страховки.
2. Для пациентов без страховки отрицательный случай составляет 131, а положительный сепсис - около 57.
3. Пациенты со страховкой также показывают, что отрицательные случаи составляют 260, а положительные случаи сепсиса - 151. Когда мы смотрим на пациентов без страховки, ясно, что отсутствие страховки не влияет на возникновение сепсиса.
4. Однако это может быть результатом других параметров в данных, таких как дисбаланс целевой переменной «сепсис».
Многомерный анализ
8. Сколько пациентов имеют недостаточный вес, нормальный вес, избыточный вес, ожирение и тяжелое ожирение?
Ниже приведены диапазоны ИМТ.
- до 18,5 — это описывается как недостаточный вес.
- между 18,5 и 24,9 — это называется «здоровый диапазон».
- между 25 и 29,9 — это описывается как избыточный вес.
- от 30 до 39,9 — ожирение.
- 40 и более — это описывается как тяжелое ожирение.
# function to create a new column 'BMI def create_bmi_range(row): if (row['Body Mass Index'] <= 18.5): return 'Under Weight' elif (row['Body Mass Index'] > 18.5) and (row['Body Mass Index'] <= 24.9): return 'Healthy Weight' elif (row['Body Mass Index'] > 24.9) and (row['Body Mass Index'] <= 29.9): return 'Overweight' elif (row['Body Mass Index'] > 29.9) and (row['Body Mass Index'] < 40): return 'Obesity' elif row['Body Mass Index'] >= 40: return 'Severe Obesity' weight = sepsis_data.copy() weight['BMI Ranges'] = weight.apply(create_bmi_range, axis=1) range_count = weight['BMI Ranges'].value_counts() # Define custom colors for each BMI range colors = ['lightblue', 'lightgreen', 'orange', 'lightcoral'] # Plot the number of patients in each BMI range using Plotly bar plot fig = px.bar(x=range_count.index, y=range_count.values, labels={'x': 'The BMI Ranges', 'y': 'Number of Patients'}, title='The number of patients in each BMI range', color=range_count.index, # Use BMI range as the color parameter to get different colors for each bar color_discrete_sequence=colors) # Use custom colors defined above # Update the layout for better visualization fig.update_layout( xaxis_title="The BMI Ranges", yaxis_title="Number of Patients", ) # Show the interactive plot fig.show()
На изображении видно, что пациентов с ожирением больше, всего 290 пациентов, и меньше пациентов с недостаточным весом. Также количество пациентов с нормальной массой тела составляет 79, что составляет менее 25% от общего числа пациентов, а количество пациентов с избыточной массой тела (включает избыточную массу тела, ожирение, более тяжелую форму ожирения) составляет более 500 человек.
9. Распределение пациентов с сепсисом в разных категориях ИМТ
# Distribution of patients in different BMI categories data = { 'Body Mass Index': [21.5, 26.3, 19.8, 32.0, 27.5], 'Sepsis': ['Negative', 'Positive', 'Positive', 'Negative', 'Positive'] } sepsis_data = pd.DataFrame(sepsis_data) # Define BMI labels and bins bmi_labels = ['Underweight', 'Healthy weight', 'Overweight', 'Obese', 'Severely Obese'] bmi_bins = [0, 18.5, 24.9, 29.9, 34.9, np.inf] # Create a new column for BMI categories sepsis_data['BMI_Category'] = pd.cut(sepsis_data['Body Mass Index'], bins=bmi_bins, labels=bmi_labels) # Plot the distribution of patients in different BMI categories using Plotly bar plot fig = px.histogram(sepsis_data, x='BMI_Category', color='Sepsis', barmode='group', category_orders={'BMI_Category': bmi_labels}, labels={'BMI_Category': 'BMI Category', 'count': 'Count'}, color_discrete_map={"Negative": "lightcoral", "Positive": "lightgreen"}) # Update the layout for better visualization fig.update_layout( title="Distribution of Patients in Different BMI Categories", xaxis_title="BMI Category", yaxis_title="Count", ) # Show the interactive plot fig.show()
# Multivariate paiplot sns.pairplot(sepsis_data, hue='Sepsis', diag_kind='kde', markers=["o", "s"], palette={"Positive": "lightgreen", "Negative": "lightcoral"}) plt.suptitle("Multivariate Analysis", y=1.02) plt.show()