Представьте себе: хрустальный шар, который может предсказывать сердечные приступы до того, как они разразятся. Звучит как что-то из научно-фантастического фильма, верно? Что ж, благодаря чудесам машинного обучения и волшебству программирования на Python мы недалеки от того, чтобы превратить это воображение в реальность.

Добро пожаловать в область прогнозного моделирования, где сила данных и передовые технологии объединяются для решения одной из самых важных проблем в области сердечно-сосудистых заболеваний: прогнозирования сердечных приступов. В этой статье мы углубимся в увлекательный мир предсказания сердечных приступов, используя возможности прогностического моделирования с помощью Python.

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

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

Сбор данных

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

import pandas as pd

data = pd.read_csv("/content/heart.csv")
data.head()

Это чистый и понятный набор данных с Shape — 1025 * 14. Однако значение некоторых заголовков столбцов не очевидно. Вот что они означают,

  • age: возраст человека в годах
  • пол: Пол человека (1 = мужской, 0 = женский)
  • cp: Испытываемая боль в груди (значение 0: типичная стенокардия, значение 1: атипичная стенокардия, значение 2: неангинозная боль, значение 3: бессимптомное течение)
  • trestbps: артериальное давление человека в покое (мм рт. ст. при поступлении в больницу).
  • chol: Измерение холестерина человека в мг/дл
  • fbs: уровень сахара в крови человека натощак (> 120 мг/дл, 1 = правда; 0 = ложь)
  • restecg: электрокардиографическое измерение в состоянии покоя (0 = нормальное, 1 = наличие аномалии ST-T, 2 = выявление вероятной или достоверной гипертрофии левого желудочка по критериям Эстеса)
  • Талах: максимальная достигнутая частота сердечных сокращений человека
  • exang: стенокардия, вызванная физической нагрузкой (1 = да; 0 = нет)
  • oldpeak: депрессия ST, вызванная физической нагрузкой, по сравнению с состоянием покоя («ST» относится к положениям на графике ЭКГ. Подробнее см. здесь)
  • наклон: наклон сегмента ST пикового упражнения (значение 1: восходящий, значение 2: плоский, значение 3: нисходящий)
  • ca: количество крупных сосудов (0–3)
  • thal: заболевание крови, называемое талассемией (3 = норма, 6 = постоянный дефект, 7 = обратимый дефект)
  • цель: болезнь сердца (0 = нет, 1 = да)

Давайте погрузимся в предварительную обработку этих данных.

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

С недавним улучшением, как правило, я бы предпочел использовать PandasAI или pandas_profiling для предварительной обработки данных.

Пример -

import pandas_profiling as df_report
df_report.ProfileReport(data)

Попробуйте это и увидите чудеса. Он суммирует все данные и предоставляет вам всю связанную информацию. Но для этой статьи давайте следовать ручному процессу —

Начнем с обнаружения пропущенных значений —

data.isnull().sum()

К счастью, в данных нет пропущенных значений. Проверим избыточность данных —

data.duplicated().sum()

К сожалению, у нас уже было меньше данных, и из них 723 записи дублируются. Но я думаю, что оставшихся записей будет достаточно для нашей цели, потому что получить эти конфиденциальные данные очень сложно. Итак, давайте удалим эти записи и двинемся дальше.

data.drop_duplicates(inplace=True)

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

from plotly.subplots import make_subplots
import plotly.graph_objects as go

df=data[['sex','age','target']]
df['sex'].replace({1:'Male',0:'Female'},inplace=True)
df['target'].replace({1:'Heart Patient',0:'Healthy'},inplace=True)

fig = make_subplots(rows=1, cols=2,specs=[[{"type": "histogram"}, {"type": "histogram"}]])
fig.add_trace(
    go.Histogram(
             x=df['age'].where(df['target']=='Heart Patient'),
             name='Heart Patient',
             nbinsx=20,
             showlegend=False,
             marker={"color": '#f84242'}
             ),
    row=1,col=1
)
fig.add_trace(
     go.Histogram(
             x=df['age'].where(df['target']=='Healthy'),
             name='Healthy',
             nbinsx=20,
             showlegend=False,
             marker={"color": 'white'}
             ),
    row=1,col=1
)
fig.add_trace(
    go.Histogram(
             x=df['sex'].where(df['target']=='Heart Patient'),
             name='Heart Patient',
             nbinsx=20,
             marker={"color": '#f84242'}
             ),
    row=1,col=2
)
fig.add_trace(
     go.Histogram(
             x=df['sex'].where(df['target']=='Healthy'),
             name='Healthy',
             nbinsx=20,
             marker={"color": 'white'}
             ),
    row=1,col=2
)

fig.update_layout(height=500,
                  title_text="<b>Age & Gender Distribution<b>",
                  title_font_size=30,
                  bargap=0.1,
                  template='plotly_dark',
                 )
fig.update_xaxes(title_text="Age", row=1, col=1)
fig.update_yaxes(title_text="Count", row=1, col=1)

fig.update_xaxes(title_text="Gender", row=1, col=2)
fig.update_yaxes(title_text="Count", row=1, col=2)

fig.show()

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

Как мы можем, женщины более склонны к сердечным приступам, чем мужчины. По этим данным, почти 75% женщин являются сердечными больными, а люди в возрастной группе 35–55 лет более склонны к сердечным заболеваниям.

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

cp1=data.where(data['target']==0).groupby(by=["cp"]).size().reset_index(name="Count")
cp0=data.where(data['target']==1).groupby(by=["cp"]).size().reset_index(name="Count")

cp0['cp'].replace({0:'Type 1',1:'Type 2',2:'Type 3',3:'Type 4'},inplace=True)
cp1['cp'].replace({0:'Type 1',1:'Type 2',2:'Type 3',3:'Type 4'},inplace=True)

df1=data[['thalach','chol','target','age','trestbps']]
df1['targetname']=df1['target'].replace({1:'Heart Patient',0:'Healthy'})

fig = make_subplots(rows=1, cols=2,specs=[[{"type": "histogram"}, {"type": "scatter"}]])
fig.add_trace(
    go.Bar(
             x=cp0['cp'],y=cp0.Count,marker={"color": 'white'},name='Healthy'
             ),
    row=1,col=1
)
fig.add_trace(
    go.Bar(
             x=cp1['cp'],y=cp1.Count,marker={"color": '#f84242'},name='Heart Patient'
             ),
    row=1,col=1
)
fig.update_layout(height=500,
                  title_text="<b>Chest Pain & Max Heart Rate<b>",
                  title_font_size=30,
                  bargap=0.1,
                  template='plotly_dark',
                 )
fig.add_trace(
    go.Scatter(x=df1.thalach, y=df1.age, mode='markers', text=df1['targetname'],showlegend=False,
               marker=dict(
               color=df1.target,
               colorscale=['white','#f84242'],
               line_width=1)
              ),
    row=1,col=2
)
fig.update_xaxes(title_text="Chest Pain Type", row=1, col=1)
fig.update_yaxes(title_text="Count", row=1, col=1)

fig.update_xaxes(title_text="Max. Heart Rate", row=1, col=2)
fig.update_yaxes(title_text="Age", row=1, col=2)

fig.show()

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

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

import seaborn as sb
import matplotlib.pyplot as plt

sb.set(style="white")
plt.rcParams['figure.figsize']=(15,15) 
sb.heatmap(data.corr(),annot= True, linewidth=0.5)
plt.title("Correlation between variables")

Здесь мы сопоставляем поля на основе всего набора данных. Основываясь на этой корреляции, мы можем сделать вывод, что боль в груди и максимальная частота сердечных сокращений коррелируют с нашей целевой переменной (сердечный пациент или нет). Я также предпочитаю сравнивать данные на основе интервалов данных, но мы не будем рассматривать это в этой статье, но позвольте мне дать вам снимок кода, если кто-то захочет попробовать —

for seq_columns in ["age","trestbps","thalach","oldpeak","chol"]:
  print(data.groupby([pd.cut(data[seq_columns],5)])['target'].mean())
for categ_columns in list(set(data.columns) - set(["age","trestbps","thalach","oldpeak","chol","target"])):
  print(data.groupby(categ_columns)['target'].mean())

В зависимости от анализа результатов мы можем сделать следующие выводы:

  • Чем выше частота сердечных сокращений, тем больше шансов быть сердечным больным.
  • Боль в груди 1 типа имеет высокий риск высокого заболевания по сравнению с другими типами боли в груди.
  • Если уровень холестерина превышает 475, то вероятность сердечного приступа намного выше.
  • Женщины более склонны к сердечно-сосудистым заболеваниям, чем мужчины
  • Стенокардия, вызванная физической нагрузкой, является основной причиной сердечных приступов у молодых людей.

Мы можем получить больше информации, закрыв каждое поле. Но для целей статьи давайте углубимся в часть «Моделирование», где мы можем предсказывать сердечные приступы с помощью машинного обучения.

from sklearn.preprocessing import StandardScaler

data=pd.get_dummies(data,columns=['sex','cp','restecg','exang','slope','ca','thal','fbs'])
scaler=StandardScaler()
scaledColumns=['age','trestbps','thalach','oldpeak','chol']
data[scaledColumns]=scaler.fit_transform(data[scaledColumns])
data.head()

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

Разработка модели

Разделим данные для обучения и тестирования —

from sklearn.model_selection import train_test_split

x = data.drop(columns='target',axis=1)
y = data['target']
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,stratify=y)
print(x_train.shape,x_test.shape)

Мы сохраняем 20% данных (61 строка) для тестирования и 80% данных (241 строка) для обучения. Чтобы не переобучать нашу модель, я учел это соотношение. Кроме того, мы будем вычислять 10-кратную перекрестную проверку в качестве нашего параметра оценки, чтобы убедиться, что данные подобраны правильно.

Начнем с логистической регрессии —

from sklearn.metrics import confusion_matrix,accuracy_score,roc_curve,classification_report
from sklearn.model_selection import cross_val_score,GridSearchCV
from sklearn.linear_model import LogisticRegression

model=LogisticRegression()
model.fit(x_train,y_train)
train_pred=model.predict(x_train)
score=accuracy_score(y_train,train_pred)

print("Train Accuracy Score : ",score*100)
test_pred=model.predict(x_test)
test_score=accuracy_score(y_test,test_pred)
print("Test Accuracy Score : ",test_score*100,'\n\n')

lrScore=cross_val_score(model,x,y,cv=10).mean()*100
print("10-Fold CV Score : ",lrScore,'\n')

print("Confusion Matrix : \n",confusion_matrix(y_test,test_pred),'\n\n')
print(classification_report(y_test,test_pred))

Мы достигли 85% — 10-кратной перекрестной проверки.

10-кратная оценка перекрестной проверки — это метод, который делит данные на 10 подмножеств, обучает и тестирует модель 10 раз, используя разные подмножества, и вычисляет средний показатель производительности как оценку способности модели к обобщению.

Давайте попробуем разные алгоритмы и посчитаем оценку, и воспользуемся тем, который точно предсказывает —

---------------------------------------------------------------------------
#KNN

from sklearn.neighbors import KNeighborsClassifier

knn=KNeighborsClassifier(n_neighbors=1,n_jobs=-1)
knn.fit(x_train,y_train)
train_pred=knn.predict(x_train)
score=accuracy_score(y_train,train_pred)

---------------------------------------------------------------------------
#SVC

from sklearn.svm import SVC

svm = SVC(C=4, degree=8, kernel='poly',max_iter=1000)
svm.fit(x_train,y_train)

train_pred=svm.predict(x_train)
score=accuracy_score(y_train,train_pred)

---------------------------------------------------------------------------
#Navie Bayes or Guassian

from sklearn.naive_bayes import GaussianNB

nb=GaussianNB()
nb.fit(x_train,y_train)
train_pred=nb.predict(x_train)
score=accuracy_score(y_train,train_pred)

---------------------------------------------------------------------------
# Decision Tree

from sklearn.tree import DecisionTreeClassifier

dt=DecisionTreeClassifier(criterion = 'entropy',random_state=0,max_depth = 12)
dt.fit(x_train,y_train)
train_pred=dt.predict(x_train)
score=accuracy_score(y_train,train_pred)
---------------------------------------------------------------------------
# Random Forest

from sklearn.ensemble import RandomForestClassifier
randFor = RandomForestClassifier(n_estimators=1000, random_state = 35)
randFor.fit(x_train, y_train)
train_pred=randFor.predict(x_train)
score=accuracy_score(y_train,train_pred)
---------------------------------------------------------------------------
# XGBoost

from xgboost import XGBClassifier

xgb = XGBClassifier(n_estimators=200)
xgb.fit(x_train, y_train)
train_pred=xgb.predict(x_train)
score=accuracy_score(y_train,train_pred)
---------------------------------------------------------------------------

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

print("Train Accuracy Score : ",score*100)
test_pred=xgb.predict(x_test)
test_score=accuracy_score(y_test,test_pred)
print("Test Accuracy Score : ",test_score*100,'\n\n')

xgbScore = cross_val_score(xgb,x,y,cv=10).mean()*100
print("10-Fold CV Score : ",xgbScore,'\n')

print("Confusion Matrix : \n",confusion_matrix(y_test,test_pred),'\n\n')
print(classification_report(y_test,test_pred))

Сравнение моделей

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

Помните, хотя мы выбрали 10-кратный CV Score в качестве основного параметра оценки, в HealthCare точность также становится важным параметром, на котором следует сосредоточиться. Высокая точность означает, что положительные прогнозы (пациенты, отнесенные к группе высокого риска) с большей вероятностью окажутся точными. В этом случае вы хотите убедиться, что люди, идентифицированные как люди с высоким риском, действительно имеют более высокую вероятность сердечного приступа.

Однако также важно учитывать влияние ложноотрицательных результатов (пропущенных пациентов с высоким риском). Если ваша цель состоит в том, чтобы выявить как можно больше пациентов с высоким риском, с целью раннего вмешательства или тщательного мониторинга, тогда отзыв становится решающим. Высокий отзыв указывает на то, что вы фиксируете большую долю истинно положительных случаев.

Надеюсь, вы найдете эту статью полезной.

Удачи в обучении…..