Индийский прогноз диабета PIMA

Прогнозирование начала диабета

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

Набор данных

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

#Importing basic packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pandas_profiling import ProfileReport
#Importing the Dataset
diabetes = pd.read_csv(“diabetes.csv”)
dataset = diabetes
#EDA using Pandas Profiling
file = ProfileReport(dataset)
file.to_file(output_file=’output.html’)

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

Мы можем видеть основную информацию о наборе данных, такую ​​как размер, отсутствующие значения и т. Д. В правом верхнем углу мы видим 8 числовых столбцов и 1 логический столбец (который является нашей зависимой переменной). На нижней панели в каждом столбце указано (%) нулей, что будет для нас полезной информацией позже. У нас нет категориальной переменной как независимой переменной.

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

Наблюдая за основными характеристиками набора данных, мы переходим к изучению характеристик переменных, участвующих в исследовании. И снова нам на помощь приходит Профилирование Pandas. Тот же отчет HTML дает информацию о переменных.

Давайте посмотрим на Возраст. Быстро взглянув на гистограмму в правом верхнем углу, мы можем увидеть многие характеристики. Переменная не подчиняется нормальному распределению, так как она смещена вправо (или положительно смещена). Средний возраст - 33 года, тогда как средний возраст - 29. Это дополнительно подтверждает наш анализ (как и в случае нормального распределения, среднее значение должно быть примерно равно медиане). Самое главное, что ни одно из значений не кажется ненормальным, то есть минимальный возраст 21 и максимальный возраст 81 возможны для исследования. Давайте посмотрим на другие переменные.

Проделав то же самое с кровяным давлением, мы увидим, что переменная может приближаться к нормальному распределению. Однако мы не можем подтвердить это визуально. Таким образом, мы проводим тест нормальности Шапиро-Уилка. Нулевая гипотеза (Ho) заключается в том, что данные нормальные.

from scipy.stats import shapiro
stat, p = shapiro(dataset['BloodPressure'])
print('Statistics=%.3f, p=%.3f' % (stat, p))
Statistics=0.819, p=0.000

p-значение меньше 0,001, что означает, что на уровне значимости 5% (LOS) мы отклоняем нашу нулевую гипотезу (Ho). Следовательно, переменная не подчиняется нормальному распределению.

Во-вторых, если мы наблюдаем минимальное значение артериального давления, оно будет равно 0 (что невозможно). Следовательно, дана неверная информация. Теперь у нас есть два варианта. Один из них - отказаться от таких наблюдений (что приводит к потере 4,6% данных) или мы можем заменить такие значения медианой (вменение). Я предпочитаю вменение, поскольку у нас небольшой набор данных (только 768 наблюдений). Следовательно, важна каждая информация.

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

#Classifying the Blood Pressure based on class
ax = sns.violinplot(x=”Outcome”, y=”BloodPressure”, data=dataset, palette=”muted”, split=True)

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

#Replacing the zero-values for Blood Pressure
df1 = dataset.loc[dataset['Outcome'] == 1]
df2 = dataset.loc[dataset['Outcome'] == 0]
df1 = df1.replace({'BloodPressure':0}, np.median(df1['BloodPressure']))
df2 = df2.replace({'BloodPressure':0}, np.median(df2['BloodPressure']))
dataframe = [df1, df2]
dataset = pd.concat(dataframe)

В столбце BloodPressure не будет нулевых значений. Перейдем к следующей переменной.

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

Мы можем заметить, что 1 соответствует нормальному распределению, а 0 - нет. Кроме того, ИМТ для диабетиков больше, чем ИМТ для недиабетиков (можно наблюдать с помощью прямоугольной диаграммы). Мы делаем то же самое для нулевых значений ИМТ, что и для нулевых значений артериального давления. В этом случае мы также можем заменить на среднее, однако я буду придерживаться медианы. Мы запускаем тот же код после изменения имени столбца. Переходим к следующей переменной.

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

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

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

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

Как указано в словаре данных, доступном вместе с набором данных, инсулин - это 2-часовой сывороточный инсулин (мЕд / мл). Переменная имеет положительный перекос. Однако встречаемость нулевых значений в этом случае высока, составляя 48,7% данных. Эти данные должны быть исчислены. Сначала мы лечим их.

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

Переменная имеет положительный перекос с нулевыми значениями 14,5%. Нам не нужно рассматривать нулевые значения как ненормальное явление. Однако, учитывая тот факт, что 85,5% значений отличны от нуля, мы можем сделать вывод, что это исследование проводится только для женщин. Глядя на поведение, используя сюжеты для скрипки, мы наблюдаем, что у женщин с диабетом было больше беременностей, чем у женщин без диабета.

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

Толщина кожи у диабетиков больше, чем у недиабетиков.

Наша зависимая переменная «Результат» принимает логические значения 0 и 1. 0 указывает на отсутствие диабета, а 1 указывает на диабетика. Чтобы исследовать возникновение, мы используем простую гистограмму.

Несбалансированность данных четко видна с модальным классом 0 (без диабета). Мы рассмотрим этот дисбаланс позже.

Проверка на мультиколлинеарность

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

from scipy.stats import pearsonr
corr, _ = pearsonr(dataset[‘Age’], dataset[‘Pregnancies’])
print(‘Pearsons correlation: %.3f’ % corr)
Pearsons correlation: 0.544

Коэффициент корреляции (r) равен 0,544. Как показывает практика, в случае r выше 0,70 ожидается мультиколлинеарность. Следовательно, значимого случая мультиколлинеарности не наблюдается.

Обработка выбросов и ненормальности

Выбросы - это экстремальные значения, существующие в наборе данных. Необходимо обрабатывать выбросы, если к набору данных применяется алгоритм, основанный на расстоянии (логистическая регрессия, SVM и т. Д.). Выбросы не влияют на древовидный алгоритм. Поскольку мы будем использовать как дистанционные, так и древовидные алгоритмы, мы будем масштабировать наши данные для обработки выбросов. Для этого мы используем Standard Scaler. Стандартный масштабатор преобразует объект, вычитая среднее значение и деля на стандартное отклонение. Таким образом, функция также приближается к стандартному нормальному распределению со средним значением 0.

#Splitting the data into dependent and independent variables
Y = dataset.Outcome
x = dataset.drop(‘Outcome’, axis = 1)
columns = x.columns
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X = scaler.fit_transform(x)
data_x = pd.DataFrame(X, columns = columns)

Мы масштабировали наши значения X.

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

Теперь мы разделим наш обработанный набор данных на данные обучения и тестирования. Размер тестовых данных должен составлять 15% от всех данных (что означает 115 наблюдений), и модель будет обучена на 653 наблюдениях.

#Splitting the data into training and test
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(data_x, Y, test_size = 0.15, random_state = 45)

В значениях y_train наблюдается огромный дисбаланс. Чтобы решить эту проблему, мы используем метод SMOTE.

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

from imblearn.over_sampling import SMOTE
smt = SMOTE()
x_train, y_train = smt.fit_sample(x_train, y_train)
np.bincount(y_train)
Out[74]: array([430, 430])

Теперь у нас есть сбалансированные данные обучения.

Теперь наши данные готовы к модели.

Подбор модели: логистическая регрессия

Первая модель, которую мы подбираем для обучающих данных, - это логистическая регрессия.

from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
logreg.fit(x_train, y_train)
y_pred = logreg.predict(x_test)
print('Accuracy of logistic regression classifier on test set: {:.2f}'.format(logreg.score(x_test, y_test)))
Out[76]: Accuracy of logistic regression classifier on test set: 0.73

Мы получили оценку 73% точности по тестовым данным.

print(f1_score(y_test, y_pred, average=”macro”))
print(precision_score(y_test, y_pred, average=”macro”))
print(recall_score(y_test, y_pred, average=”macro”))
0.723703419131771
0.7220530003045994
0.7263975155279503

Наша точность для модели составляет 0,722. Это означает, что в 72% случаев наша модель относила пациентов к категории высокого риска, тогда как у них действительно был высокий риск развития диабета.

Отзыв / чувствительность составляет 0,726, что означает, что 72% случаев, когда люди, действительно имеющие высокий риск, были правильно классифицированы нашей моделью.

Подгонка модели: машина опорных векторов (ядро: rbf)

Первая модель, которую мы подбираем для обучающих данных, - это машина опорных векторов (SVM). SVM использует множество ядер для классификации данных. Мы используем ядро ​​rbf / Gaussian, чтобы соответствовать первой модели.

from sklearn.svm import SVC
classifier_rbf = SVC(kernel = ‘rbf’)
classifier_rbf.fit(x_train, y_train)
y_pred = classifier_rbf.predict(x_test)
print('Accuracy of SVC (RBF) classifier on test set: {:.2f}'.format(classifier_rbf.score(x_test, y_test)))
Out[76]: Accuracy of SVC (RBF) classifier on test set: 0.75
print(f1_score(y_test, y_pred, average="macro"))
print(precision_score(y_test, y_pred, average="macro"))
print(recall_score(y_test, y_pred, average="macro"))
0.7431080565101182
0.7410256410256411
0.7481366459627329

У нас улучшена точность при использовании SVM с ядром rbf. Точность модели составляет 75% с улучшенными значениями точности и отзыва по сравнению с логистической регрессией.

Примерка модели: случайный лес

Мы используем классификатор случайного леса с 300 деревьями (полученными после настройки модели), чтобы соответствовать модели на данных.

from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=300, bootstrap = True, max_features = ‘sqrt’)
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print('Accuracy of Random Forest on test set: {:.2f}'.format(model.score(x_test, y_test)))
Out[95]: Accuracy of Random Forest on test set: 0.88
print(f1_score(y_test, y_pred, average="macro"))
print(precision_score(y_test, y_pred, average="macro"))
print(recall_score(y_test, y_pred, average="macro"))
0.8729264475743349
0.8762626262626263
0.8701863354037267

Мы получаем самую высокую точность для случайного леса, достигая 88%. Это означает, что наша модель правильно предсказала классификацию в 88% случаев.

Показатель точности составил 0,876, что означает, что наша модель правильно классифицировала наблюдения с высоким риском в категории высокого риска 87,6% случаев. Отзыв составил 0,870.

У нас также есть оценка F1 0,872. Оценка F1 - это среднее гармоническое значение точности и запоминания. Он присваивает одинаковый вес обоим показателям. Однако для нашего анализа относительно более важно, чтобы модель имела низкий уровень ложноотрицательных случаев (поскольку будет опасно отнести пациентов с высоким риском к категории низкого риска). Поэтому мы индивидуально смотрим на точность и отзыв.

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

Заключение

Таким образом, мы выбрали классификатор случайного леса в качестве правильной модели из-за высокой точности, точности и отзыва. Одна из причин, по которой классификатор случайного леса показал улучшенную производительность, заключалась в наличии выбросов. Как упоминалось ранее, поскольку случайный лес не является алгоритмом, основанным на расстоянии, на него не сильно влияют выбросы, тогда как алгоритм, основанный на расстоянии, такой как логистическая регрессия и опорный вектор, показал более низкую производительность.

В зависимости от важности функции:

  1. Глюкоза - самый важный фактор в определении начала диабета, за которым следуют ИМТ и возраст.
  2. Другие факторы, такие как диабетическая родословная, беременность, артериальное давление, толщина кожи и инсулин, также влияют на прогноз.

Как мы видим, результаты, полученные с помощью функции «Важность функций», имеют смысл, поскольку одна из первых вещей, которые фактически отслеживаются у пациентов с высоким риском, - это уровень глюкозы. Повышенный ИМТ также может указывать на риск развития диабета II типа. Обычно, особенно в случае диабета II типа, существует высокий риск развития по мере увеличения возраста человека (с учетом других факторов).

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

Как всегда, приветствую конструктивную критику, отзывы и обсуждения. Со мной можно связаться через Gmail: [email protected]

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

Набор данных и информация о данных: https://www.kaggle.com/uciml/pima-indians-diabetes-database

Профилирование Pandas: https://towardsdatascience.com/speed-up-your-exploratory-data-analysis-with-pandas-profiling-88b33dc53625

Сюжеты для скрипки: https://seaborn.pydata.org/generated/seaborn.violinplot.html

Тест Шапиро-Уилка на нормальность: https://machinelearningmastery.com/a-gentle-introduction-to-normality-tests-in-python/

Масштабирование / обработка выбросов: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html

Обработка выбросов: https://towardsdatascience.com/ways-to-detect-and-remove-the-outliers-404d16608dba

SMOTE: https://machinelearningmastery.com/smote-oversampling-for-imbalanced-classification/

Точность и отзывчивость: https://towardsdatascience.com/beyond-accuracy-precision-and-recall-3da06bea9f6c