Merge pull request 'arutunyan_dmitry_lab_3 is ready' (#54) from arutunyan_dmitry_lab_3 into main

Reviewed-on: http://student.git.athene.tech/Alexey/IIS_2023_1/pulls/54
This commit is contained in:
Alexey 2023-10-17 17:27:35 +04:00
commit c20695af79
4 changed files with 391 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@ -0,0 +1,170 @@
## Лабораторная работа 3. Вариант 4.
### Задание
Выполнить ранжирование признаков и решить с помощью библиотечной реализации дерева решений
задачу классификации на 99% данных из курсовой работы. Проверить
работу модели на оставшемся проценте, сделать вывод.
Модель:
- Дерево решений `DecisionTreeClassifier`.
### Как запустить
Для запуска программы необходимо с помощью командной строки в корневой директории файлов прокета прописать:
```
python main.py
```
После этого в папке `static` сгенерируются 2 графика, по которым оценивается результат выполнения программы.
### Используемые технологии
- Библиотека `numpy`, используемая для обработки массивов данных и вычислений
- Библиотека `pyplot`, используемая для построения графиков.
- Библиотека `pandas`, используемая для работы с данными для анализа scv формата.
- Библиотека `sklearn` - большой набор функционала для анализа данных. Из неё были использованы инструменты:
- `DecisionTreeClassifier` - инструмент работы с моделью "Дерево решений"
- `metrics` - набор инструменов для оценки моделей
- `MinMaxScaler` - инструмент масштабирования значений в заданный диапазон
### Описание работы
#### Описание набора данных
Набор данных - набор для определения возможности наличия ССЗ заболеваний у челоека
Названия столбцов набора данных и их описание:
* HeartDisease - Имеет ли человек ССЗ (No / Yes),
* BMI - Индекс массы тела человека (float),
* Smoking - Выкурил ли человек хотя бы 5 пачек сигарет за всю жизнь (No / Yes),
* AlcoholDrinking - Сильно ли человек употребляет алкоголь (No / Yes),
* Stroke - Был ли у человека инсульт (No / Yes),
* PhysicalHealth - Сколько дней за последний месяц человек чувствовал себя плохо (0-30),
* MentalHealth - Сколько дней за последний месяц человек чувствовал себя удручённо (0-30),
* DiffWalking - Ииспытывает ли человек трудности при ходьбе (No / Yes),
* Sex - Пол (female, male),
* AgeCategory - Возрастная категория (18-24, 25-29, 30-34, 35-39, 40-44, 45-49, 50-54, 55-59, 60-64, 65-69, 70-74, 75-79, 80 or older),
* Race - Национальная принадлежность человека (White, Black, Hispanic, American Indian/Alaskan Native, Asian, Other),
* Diabetic - Был ли у человека диабет (No / Yes),
* PhysicalActivity - Занимался ли человек спротом за последний месяц (No / Yes),
* GenHealth - Общее самочувствие человека (Excellent, Very good, Good, Fair, Poor),
* SleepTime - Сколько человек в среднем спит за 24 часа (0-24),
* Asthma - Была ли у человека астма (No / Yes),
* KidneyDisease - Было ли у человека заболевание почек (No / Yes),
* SkinCancer - Был ли у человека рак кожи (No / Yes).
Ссылка на страницу набора на kuggle: [Indicators of Heart Disease](https://www.kaggle.com/datasets/kamilpytlak/personal-key-indicators-of-heart-disease/data)
#### Оцифровка и нормализация данных
Для нормальной работы с данными, необходимо исключить из них все нечисловые значения. После этого, представить все строковые значения параметров как числовые и очистить датасет от "мусора". Для удаления нечисловых значений воспользуемся функцией `.dropna()`. Мы исключаем строки с нечисловыми значениями, поскольку данные предварительно были очищены (указано в описании датасета) и строк данных достаточно с избытком для обучение модели: `400.000`.
После этого, переведём все строковые значения данных в числовые методами прямой оцифровки, разделения на группы, ранжирования.
Процесс оцифровки данных столбцов со строковыми значениями:
- Имеет ли человек ССЗ (0 / 1)
- Выкурил ли человек хотя бы 5 пачек сигарет за всю жизнь (0 / 1)
- Сильно ли человек употребляет алкоголь (0 / 1)
- Был ли у человека инсульт (0 / 1)
- Ииспытывает ли человек трудности при ходьбе (0 / 1)
- Пол (Ж - 0 / М - 1)
- Возрастная категория (средний возраст каждого диапазона)
- Национальная принадлежность человека
- White - Европиойды - 0
- Black - Негройды - 1
- Hispanic - Испанцы - 2
- American Indian/Alaskan Native - Индусы - 3
- Asian - Азиаты - 4
- Other - Другие - 5
- Был ли у человека диабет (0 / 1)
- Занимался ли человек спротом за последний месяц (0 / 1)
- Общее самочувствие человека
- Excellent - Отлично - 4
- Very good - Очень хорошо - 3
- Good - Хорошо - 2
- Fair - Нормально - 1
- "Poor" / "Other..." - Плохое или другое - 0
- Была ли у человека астма (0 / 1)
- Было ли у человека заболевание почек (0 /1)
- Был ли у человека рак кожи (0 / 1)
После оцифровки значений необходимо избавиться от строк с возможными остаточнымии данными ("мусором"). Для этого переведём автоматически все значения датасета в числовые функцией `to_numeric` и непереводимые отметим как `NaN` (параметр `errors='coerce'`). После снова сотрём строки содержащие нечисловые значения методом `.dropna()` и сохраним нормализованный датасет в новый csv файл:
```python
df = df.applymap(pd.to_numeric, errors='coerce').dropna()
df.to_csv(fileout, index=False)
```
#### Выявление значимых параметров
В выбранном датасете параметром предсказания `y` выступает столбец данных `HeartDisease`. Остальные столбцы считаются параметрами для решения задачи предсказания `x`, которые необходимо проранжировать по важности. Чтобы разделить выборку данных на обучаемую и тестовую, воспользуемся функцией `.iloc`.
```python
x_train = df[["BMI", "Smoking", "AlcoholDrinking", "Stroke", "PhysicalHealth",
"MentalHealth", "DiffWalking", "Sex", "AgeCategory", "Race", "Diabetic",
"PhysicalActivity", "GenHealth", "SleepTime", "Asthma", "KidneyDisease", "SkinCancer"]].iloc[
0:round(len(df) / 100 * 99)]
y_train = df["HeartDisease"].iloc[0:round(len(df) / 100 * 99)]
x_test = df[["BMI", "Smoking", "AlcoholDrinking", "Stroke", "PhysicalHealth",
"MentalHealth", "DiffWalking", "Sex", "AgeCategory", "Race", "Diabetic",
"PhysicalActivity", "GenHealth", "SleepTime", "Asthma", "KidneyDisease", "SkinCancer"]].iloc[
round(len(df) / 100 * 99):len(df)]
y_test = df["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)]
```
Где `round(len(df) / 100 * 99)` - 99ти процентная строка в датасете.
Теперь, обучим модель на данных `x_train` и `y_train` и получим значимость каждого признака в модели с помощью метода `.feature_importances_`. После отмасштабируем значения важности признаков.
```python
ranks = np.abs(dtc.feature_importances_)
minmax = MinMaxScaler()
ranks = minmax.fit_transform(np.array(ranks).reshape(len(x_train.columns), 1)).ravel()
ranks = map(lambda x: round(x, 2), ranks)
ranks = dict(zip(x_train.columns, ranks))
ranks = dict(sorted(ranks.items(), key=lambda x: x[1], reverse=True))
```
Чтобы отсеять значимые параметры от незначимых, условимся, что параметры, с оценкой значимости меньше `0.05` будут считаться незначимыми. Выведем список параметров с пометками:
```
X ranging results:
* BMI: 1.0 - Approved
* SleepTime: 0.26 - Approved
* PhysicalHealth: 0.18 - Approved
* GenHealth: 0.16 - Approved
* MentalHealth: 0.15 - Approved
* AgeCategory: 0.14 - Approved
* Race: 0.07 - Approved
* PhysicalActivity: 0.06 - Approved
* Stroke: 0.04 - Eliminated
* Smoking: 0.03 - Eliminated
* Asthma: 0.03 - Eliminated
* SkinCancer: 0.03 - Eliminated
* DiffWalking: 0.02 - Eliminated
* Sex: 0.02 - Eliminated
* AlcoholDrinking: 0.0 - Eliminated
* Diabetic: 0.0 - Eliminated
* KidneyDisease: 0.0 - Eliminated
```
Где `Approved` - параметр значим и будет использоваться в предсказании, а `Eliminated` - параметр незначим и будет исключён.
#### Решение задачи кластеризации на полном наборе признаков
Чтобы решить задачу кластеризации моделью `DecisionTreeClassifier`, воспользуемся методом `.predict()`. Оценку качества решения и графики будем строить теми же методами, что в 1й лабораторной работе.
График решения задачи классификации на полном наборе признаков:
![](FullParam.png "")
#### Решение задачи кластеризации, используя только значимые признаки
Согласно предыдущему пункту, значимыми признаками модели были выявлены:
* BMI
* SleepTime
* PhysicalHealth
* GenHealth
* MentalHealth
* AgeCategory
* Race
* PhysicalActivity
Обучим модель только с их использованием, решим задачу классификации и построим график.
График решения задачи классификации, используя только значимые признаки:
![](ImpParam.png "")
### Вывод
Согласно среднеквадратической ошибке и коэфициенту детерминации, модель, обученная только на значимых признаков отработала точнее, чем модель, обученная на полном наборе признаков. Это значит, что ранжирование было проведено верно и дало полезный результат. О логической оценке исключённых данных сказать ничего не получится, поскольку действительную зависимость результата от параметров значет только медицинский эксперт.
Исходя их общих значений точности, обе модели показали хорошие результаты и могут быть применимы к решению задачи классификации на данном наборе данных.

View File

@ -0,0 +1,221 @@
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from sklearn import metrics
from sklearn.preprocessing import MinMaxScaler
from sklearn.tree import DecisionTreeClassifier
'''
Названия столбцов набора данных и их описание:
* HeartDisease - Имеет ли человек ССЗ (No / Yes),
* BMI - Индекс массы тела человека (float),
* Smoking - Выкурил ли человек хотя бы 5 пачек сигарет за всю жизнь (No / Yes),
* AlcoholDrinking - Сильно ли человек употребляет алкоголь (No / Yes),
* Stroke - Был ли у человека инсульт (No / Yes),
* PhysicalHealth - Сколько дней за последний месяц человек чувствовал себя плохо (0-30),
* MentalHealth - Сколько дней за последний месяц человек чувствовал себя удручённо (0-30),
* DiffWalking - Ииспытывает ли человек трудности при ходьбе (No / Yes),
* Sex - Пол (female, male),
* AgeCategory - Возрастная категория (18-24, 25-29, 30-34, 35-39, 40-44, 45-49, 50-54, 55-59, 60-64, 65-69, 70-74, 75-79, 80 or older),
* Race - Национальная принадлежность человека (White, Black, Hispanic, American Indian/Alaskan Native, Asian, Other),
* Diabetic - Был ли у человека диабет (No / Yes),
* PhysicalActivity - Занимался ли человек спротом за последний месяц (No / Yes),
* GenHealth - Общее самочувствие человека (Excellent, Very good, Good, Fair, Poor),
* SleepTime - Сколько человек в среднем спит за 24 часа (0-24),
* Asthma - Была ли у человека астма (No / Yes),
* KidneyDisease - Было ли у человека заболевание почек (No / Yes),
* SkinCancer - Был ли у человека рак кожи (No / Yes).
'''
# Метод оцифровки и нормализации данных
def normalisation(filename):
fileout = "P:\\ULSTU\\ИИС\\Datasets\\heart_2020_norm.csv"
df = pd.read_csv(filename, sep=',').dropna() # Считываем данные с csv файла и удаляем строки, содержащие NaN
for index, row in df.iterrows():
if index % 10000 == 0:
print("normalisation running . . . " + str(round((index / len(df) * 100), 2)) +'%')
if "Yes" in row["HeartDisease"]: # Имеет ли человек ССЗ (0 / 1)
df.at[index, "HeartDisease"] = 1
else:
df.at[index, "HeartDisease"] = 0
if "Yes" in row["Smoking"]: # Выкурил ли человек хотя бы 5 пачек сигарет за всю жизнь (0 / 1)
df.at[index, "Smoking"] = 1
else:
df.at[index, "Smoking"] = 0
if "Yes" in row["AlcoholDrinking"]: # Сильно ли человек употребляет алкоголь (0 / 1)
df.at[index, "AlcoholDrinking"] = 1
else:
df.at[index, "AlcoholDrinking"] = 0
if "Yes" in row["Stroke"]: # Был ли у человека инсульт (0 / 1)
df.at[index, "Stroke"] = 1
else:
df.at[index, "Stroke"] = 0
if "Yes" in row["DiffWalking"]: # Ииспытывает ли человек трудности при ходьбе (0 / 1)
df.at[index, "DiffWalking"] = 1
else:
df.at[index, "DiffWalking"] = 0
if "Female" in row["Sex"]: # Пол (Ж - 0 / М - 1)
df.at[index, "Sex"] = 0
else:
df.at[index, "Sex"] = 1
if "18-24" in row["AgeCategory"]: # Возрастная категория (средний возраст каждого диапазона)
df.at[index, "AgeCategory"] = (18 + 24) / 2
elif "25-29" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (25 + 29) / 2
elif "30-34" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (30 + 34) / 2
elif "35-39" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (35 + 39) / 2
elif "40-44" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (40 + 44) / 2
elif "45-49" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (45 + 49) / 2
elif "50-54" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (50 + 54) / 2
elif "55-59" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (55 + 59) / 2
elif "60-64" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (60 + 64) / 2
elif "65-69" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (65 + 69) / 2
elif "70-74" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (70 + 74) / 2
elif "75-79" in row["AgeCategory"]:
df.at[index, "AgeCategory"] = (75 + 79) / 2
else:
df.at[index, "AgeCategory"] = (25 + 29) / 2
if "White" in row["Race"]: # Национальная принадлежность человека
df.at[index, "Race"] = 0 # White - Европиойды - 0
elif "Black" in row["Race"]: # Black - Негройды - 1
df.at[index, "Race"] = 1 # Hispanic - Испанцы - 2
elif "Hispanic" in row["Race"]: # American Indian/Alaskan Native - Индусы - 3
df.at[index, "Race"] = 2 # Asian - Азиаты - 4
elif "American Indian/Alaskan Native" in row["Race"]: # Other - Другие - 5
df.at[index, "Race"] = 3
elif "Asian" in row["Race"]:
df.at[index, "Race"] = 4
else:
df.at[index, "Race"] = 5
if "Yes" in row["Diabetic"]: # Был ли у человека диабет (0 / 1)
df.at[index, "Diabetic"] = 1
else:
df.at[index, "Diabetic"] = 0
if "Yes" in row["PhysicalActivity"]: # Занимался ли человек спротом за последний месяц (0 / 1)
df.at[index, "PhysicalActivity"] = 1
else:
df.at[index, "PhysicalActivity"] = 0
if "Excellent" in row["GenHealth"]: # Общее самочувствие человека
df.at[index, "GenHealth"] = 4 # Excellent - Отлично - 4
elif "Very good" in row["GenHealth"]: # Very good - Очень хорошо - 3
df.at[index, "GenHealth"] = 3 # Good - Хорошо - 2
elif "Good" in row["GenHealth"]: # Fair - Нормально - 1
df.at[index, "GenHealth"] = 2 # "Poor" / "Other..." - Плохое или другое - 0
elif "Fair" in row["GenHealth"]:
df.at[index, "GenHealth"] = 1
else:
df.at[index, "GenHealth"] = 0
if "Yes" in row["Asthma"]: # Была ли у человека астма (0 / 1)
df.at[index, "Asthma"] = 1
else:
df.at[index, "Asthma"] = 0
if "Yes" in row["KidneyDisease"]: # Было ли у человека заболевание почек (0 /1)
df.at[index, "KidneyDisease"] = 1
else:
df.at[index, "KidneyDisease"] = 0
if "Yes" in row["SkinCancer"]: # Был ли у человека рак кожи (0 / 1)
df.at[index, "SkinCancer"] = 1
else:
df.at[index, "SkinCancer"] = 0
df = df.applymap(pd.to_numeric, errors='coerce').dropna() # Гарантированно убираем все нечисловые значения из датасета
df.to_csv(fileout, index=False) # Сохраняем нормализованный датасет для дальнейшей работы
return fileout
# Метод ранжирования параметров по степени важности
def param_range(filename, elim_kp):
df = pd.read_csv(filename, sep=',') # Считываем нормализованные данные и разделяем их на выборки
x_train = df[["BMI", "Smoking", "AlcoholDrinking", "Stroke", "PhysicalHealth",
"MentalHealth", "DiffWalking", "Sex", "AgeCategory", "Race", "Diabetic",
"PhysicalActivity", "GenHealth", "SleepTime", "Asthma", "KidneyDisease", "SkinCancer"]].iloc[
0:round(len(df) / 100 * 99)]
y_train = df["HeartDisease"].iloc[0:round(len(df) / 100 * 99)]
x_test = df[["BMI", "Smoking", "AlcoholDrinking", "Stroke", "PhysicalHealth",
"MentalHealth", "DiffWalking", "Sex", "AgeCategory", "Race", "Diabetic",
"PhysicalActivity", "GenHealth", "SleepTime", "Asthma", "KidneyDisease", "SkinCancer"]].iloc[
round(len(df) / 100 * 99):len(df)]
y_test = df["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)]
dtc = DecisionTreeClassifier(random_state=241) # Создаём модель дерева решений
dtc.fit(x_train.values, y_train.values) # Обучаем модель на данных
y_predict = dtc.predict(x_test.values) # Решаем задачу классификации на полном наборе признаков
err = pred_errors(y_predict, y_test.values) # Рассчитываем ошибки предсказания
make_plots(y_test.values, y_predict, err[0], err[1], "Полный набор данных") # Строим графики
ranks = np.abs(dtc.feature_importances_) # Получаем значимость каждого признака в модели
minmax = MinMaxScaler() # Шкалируем и нормализуем значимость
ranks = minmax.fit_transform(np.array(ranks).reshape(len(x_train.columns), 1)).ravel()
ranks = map(lambda x: round(x, 2), ranks)
ranks = dict(zip(x_train.columns, ranks))
ranks = dict(sorted(ranks.items(), key=lambda x: x[1], reverse=True)) # Сортируем оценки по максимуму и записываем в словарь
print("X ranging results: \n")
del_keys = [] # Исключаем параметры, важность которых меньше elim_kp
for key, value in ranks.items():
if value >= elim_kp:
print(" * " + key + ": " + str(value) + " - Approved")
else:
print(" * " + key + ": " + str(value) + " - Eliminated")
del_keys.append(key)
for key in del_keys:
ranks.pop(key)
return filename, ranks.keys()
# Метод решения задачи классификации, основанный только на значимых параметрах
def most_valuable_prediction(params):
filename = params[0]
val_p = params[1]
df = pd.read_csv(filename, sep=',')
x_train = df[val_p].iloc[0:round(len(df) / 100 * 99)]
y_train = df["HeartDisease"].iloc[0:round(len(df) / 100 * 99)]
x_test = df[val_p].iloc[round(len(df) / 100 * 99):len(df)]
y_test = df["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)]
dtc = DecisionTreeClassifier(random_state=241)
dtc.fit(x_train.values, y_train.values)
y_predict = dtc.predict(x_test.values)
err = pred_errors(y_predict, y_test.values)
make_plots(y_test.values, y_predict, err[0], err[1], "Только важные параметры")
# Метод рассчёта ошибок
def pred_errors(y_predict, y_test):
mid_square = np.round(np.sqrt(metrics.mean_squared_error(y_test, y_predict)),3) # Рассчёт среднеквадратичной ошибки модели
det_kp = np.round(metrics.accuracy_score (y_test, y_predict), 2) # Рассчёт коэфициента детерминации модели
return mid_square, det_kp
# Метод отрисовки графиков
def make_plots(y_test, y_predict, mid_sqrt, det_kp, title):
plt.plot(y_test, c="red", label="\"y\" исходная") # Создание графика исходной функции
plt.plot(y_predict, c="green", label="\"y\" предсказанная \n"
"Ср^2 = " + str(mid_sqrt) + "\n"
"Кд = " + str(det_kp)) # Создание графика предсказанной функции
plt.legend(loc='lower left')
plt.title(title)
plt.savefig('static/' + title + '.png')
plt.close()
if __name__ == '__main__':
# Работа системы в комплексе
# Здесь elim_kp - значение пороговой значимости параметра (выбран эмпирически)
most_valuable_prediction(param_range(normalisation("P:\\ULSTU\\ИИС\\Datasets\\heart_2020_cleaned.csv"), 0.05))