191 KiB
191 KiB
Вариант 4. Данные по инсультам¶
In [3]:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import RandomOverSampler
from sklearn.preprocessing import StandardScaler
import featuretools as ft
import time
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
df = pd.read_csv("../data/healthcare-dataset-stroke-data.csv")
print(df.columns)
df.head()
Out[3]:
Бизнес цели и цели технического проекта.¶
Бизнес цели:¶
1. Предсказание инсульта: Разработать систему, которая сможет предсказать вероятность инсульта у пациентов на основе их медицинских и социальных данных. Это может помочь медицинским учреждениям и специалистам в более раннем выявлении пациентов с высоким риском.¶
2. Снижение затрат на лечение: Предупреждение инсультов у пациентов позволит снизить затраты на лечение и реабилитацию. Это также поможет улучшить качество медицинских услуг и повысить удовлетворенность пациентов.¶
3. Повышение эффективности профилактики: Выявление факторов риска инсульта на ранней стадии может способствовать более эффективному проведению профилактических мероприятий.¶
Цели технического проекта:¶
1. Создание и обучение модели машинного обучения: Разработка модели, способной предсказать вероятность инсульта на основе данных о пациентах (например, возраст, уровень глюкозы, наличие сердечно-сосудистых заболеваний, тип работы, индекс массы тела и т.д.).¶
2. Анализ и обработка данных: Провести предобработку данных (очистка, заполнение пропущенных значений, кодирование категориальных признаков), чтобы улучшить качество и надежность модели.¶
3. Оценка модели: Использовать метрики, такие как точность, полнота и F1-мера, чтобы оценить эффективность модели и минимизировать риск ложных положительных и ложных отрицательных предсказаний.¶
In [5]:
print(df.isnull().sum())
print()
print(df.isnull().any())
print()
for i in df.columns:
null_rate = df[i].isnull().sum() / len(df) * 100
if null_rate > 0:
print(f"{i} процент пустых значений: %{null_rate:.2f}")
Видим пустые значения в bmi, заменяем их¶
In [6]:
df["bmi"] = df["bmi"].fillna(df["bmi"].median())
missing_values = df.isnull().sum()
print("Количество пустых значений в каждом столбце после замены:")
print(missing_values)
In [7]:
df = df.drop('id', axis = 1)
print(df.columns)
Создаем выборки¶
In [8]:
# Разделим данные на признак (X) и переменую (Y)
# Начнем со stroke
X = df.drop(columns=['stroke'])
y = df['stroke']
# Разбиваем на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# Разбиваем на обучающую и контрольную выборки
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.3)
print("Размер обучающей выборки: ", X_train.shape)
print("Размер контрольной выборки: ", X_val.shape)
print("Размер тестовой выборки: ", X_test.shape)
Оценим сбалансированность сборок¶
In [9]:
from locale import normalize
def analyze_balance(y_train, y_val, y_test, y_name):
print("Распределение классов в обучающей выборке:")
print(y_train.value_counts(normalize=True))
print("\nРаспределение классов в контрольной выборке:")
print(y_val.value_counts(normalize=True))
print("\nРаспределение классов в тестовой выборке:")
print(y_test.value_counts(normalize=True))
fig, axes = plt.subplots(1, 3, figsize=(18,5), sharey=True)
fig.suptitle('Распределение в различных выборках')
sns.barplot(x=y_train.value_counts().index, y=y_train.value_counts(normalize=True), ax=axes[0])
axes[0].set_title('Обучающая выборка')
axes[0].set_xlabel(y_name)
axes[0].set_ylabel('Доля')
sns.barplot(x=y_val.value_counts().index, y=y_val.value_counts(normalize=True), ax=axes[1])
axes[1].set_title('Контрольная выборка')
axes[1].set_xlabel(y_name)
sns.barplot(x=y_test.value_counts().index, y=y_test.value_counts(normalize=True), ax=axes[2])
axes[2].set_title('Тестовая выборка')
axes[2].set_xlabel(y_name)
plt.show()
analyze_balance(y_train, y_val, y_test, 'stroke')
Заметим, что выборки не сбалансированы. Для балансировки будем использовать RandomOverSampler
In [10]:
randoversamp = RandomOverSampler(random_state=42)
# Применение RandomOverSampler для балансировки выборок
X_train_resampled, y_train_resampled = randoversamp.fit_resample(X_train, y_train)
X_val_resampled, y_val_resampled = randoversamp.fit_resample(X_val, y_val)
# Проверка сбалансированности после RandomOverSampler
analyze_balance(y_train_resampled, y_val_resampled, y_test, "stroke")
Выборки сбалансированы¶
Применим унитарное кодирование категориальных признаков (one-hot encoding), переведя их в бинарные вектора.¶
In [11]:
# Определение категориальных признаков
categorical_features = [
"gender",
"ever_married",
"work_type",
"Residence_type",
"smoking_status",
]
# Применение one-hot encoding к обучающей выборке
X_train_encoded = pd.get_dummies(
X_train_resampled, columns=categorical_features, drop_first=True
)
# Применение one-hot encoding к контрольной выборке
X_val_encoded = pd.get_dummies(
X_val_resampled, columns=categorical_features, drop_first=True
)
# Применение one-hot encoding к тестовой выборке
X_test_encoded = pd.get_dummies(X_test, columns=categorical_features, drop_first=True)
print(X_train_encoded.head())
Перейдем к числовым признакам, а именно к колонке age, применим дискретизацию (позволяет преобразовать данные из числового представления в категориальное):¶
In [12]:
# Определение числовых признаков для дискретизации
numerical_features = ["age"]
# Функция для дискретизации числовых признаков
def discretize_features(df, features, bins, labels):
for feature in features:
df[f"{feature}_bin"] = pd.cut(df[feature], bins=bins, labels=labels)
df.drop(columns=[feature], inplace=True)
return df
# Заданные интервалы и метки
age_bins = [0, 25, 55, 100]
age_labels = ["young", "middle-aged", "old"]
# Применение дискретизации к обучающей, контрольной и тестовой выборкам
X_train_encoded = discretize_features(
X_train_encoded, numerical_features, bins=age_bins, labels=age_labels
)
X_val_encoded = discretize_features(
X_val_encoded, numerical_features, bins=age_bins, labels=age_labels
)
X_test_encoded = discretize_features(
X_test_encoded, numerical_features, bins=age_bins, labels=age_labels
)
print(X_train_encoded.head())
Применим ручной синтез признаков. Например, в этом случае создадим признак, в котором вычисляется отклонение уровня глюкозы от среднего для определенной возрастной группы. Вышеуказанный признак может быть полезен для определения пациентов с аномальными данными.¶
In [13]:
age_glucose_mean = X_train_encoded.groupby("age_bin", observed=False)[
"avg_glucose_level"
].transform("mean")
X_train_encoded["glucose_age_deviation"] = (
X_train_encoded["avg_glucose_level"] - age_glucose_mean
)
age_glucose_mean = X_val_encoded.groupby("age_bin", observed=False)[
"avg_glucose_level"
].transform("mean")
X_val_encoded["glucose_age_deviation"] = (
X_val_encoded["avg_glucose_level"] - age_glucose_mean
)
age_glucose_mean = X_test_encoded.groupby("age_bin", observed=False)[
"avg_glucose_level"
].transform("mean")
X_test_encoded["glucose_age_deviation"] = (
X_test_encoded["avg_glucose_level"] - age_glucose_mean
)
print(X_train_encoded.head())
In [14]:
numerical_features = ["avg_glucose_level", "bmi", "glucose_age_deviation"]
scaler = StandardScaler()
X_train_encoded[numerical_features] = scaler.fit_transform(
X_train_encoded[numerical_features]
)
X_val_encoded[numerical_features] = scaler.transform(X_val_encoded[numerical_features])
X_test_encoded[numerical_features] = scaler.transform(
X_test_encoded[numerical_features]
)
print(X_train_encoded.head())
Сконструируем признаки, используя фреймворк Featuretools:
In [15]:
data = X_train_encoded.copy() # Используем предобработанные данные
es = ft.EntitySet(id="patients")
es = es.add_dataframe(
dataframe_name="strokes_data", dataframe=data, index="index", make_index=True
)
feature_matrix, feature_defs = ft.dfs(
entityset=es, target_dataframe_name="strokes_data", max_depth=1
)
print(feature_matrix.head())
Оценим качество набора признаков.¶
- Предсказательная способность (для задачи классификации)
- Метрики: Accuracy, Precision, Recall, F1-Score, ROC AUC
- Методы: Обучение модели на обучающей выборке и оценка на валидационной и тестовой выборках.
- Вычислительная эффективность
- Методы: Измерение времени, затраченного на генерацию признаков и обучение модели.
- Надежность
- Методы: Кросс-валидация и анализ чувствительности модели к изменениям в данных.
- Корреляция
- Методы: Анализ корреляционной матрицы признаков и исключение мультиколлинеарных признаков.
- Логическая согласованность
- Методы: Проверка логической связи признаков с целевой переменной и интерпретация результатов модели.
In [16]:
X_train_encoded = pd.get_dummies(X_train_encoded, drop_first=True)
X_val_encoded = pd.get_dummies(X_val_encoded, drop_first=True)
X_test_encoded = pd.get_dummies(X_test_encoded, drop_first=True)
all_columns = X_train_encoded.columns
X_train_encoded = X_train_encoded.reindex(columns=all_columns, fill_value=0)
X_val_encoded = X_val_encoded.reindex(columns=all_columns, fill_value=0)
X_test_encoded = X_test_encoded.reindex(columns=all_columns, fill_value=0)
# Выбор модели
model = RandomForestClassifier(n_estimators=100, random_state=42)
# Начинаем отсчет времени
start_time = time.time()
model.fit(X_train_encoded, y_train_resampled)
# Время обучения модели
train_time = time.time() - start_time
print(f"Время обучения модели: {train_time:.2f} секунд")
In [17]:
# Получение важности признаков
importances = model.feature_importances_
feature_names = X_train_encoded.columns
# Сортировка признаков по важности
feature_importance = pd.DataFrame({"feature": feature_names, "importance": importances})
feature_importance = feature_importance.sort_values(by="importance", ascending=False)
print("Feature Importance:")
print(feature_importance)
In [18]:
# Предсказание и оценка
y_pred = model.predict(X_test_encoded)
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
roc_auc = roc_auc_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")
print(f"Precision: {precision}")
print(f"Recall: {recall}")
print(f"F1 Score: {f1}")
print(f"ROC AUC: {roc_auc}")
# Кросс-валидация
scores = cross_val_score(
model, X_train_encoded, y_train_resampled, cv=5, scoring="accuracy"
)
accuracy_cv = scores.mean()
print(f"Cross-validated Accuracy: {accuracy_cv}")
# Анализ важности признаков
feature_importances = model.feature_importances_
feature_names = X_train_encoded.columns
importance_df = pd.DataFrame(
{"Feature": feature_names, "Importance": feature_importances}
)
importance_df = importance_df.sort_values(by="Importance", ascending=False)
plt.figure(figsize=(10, 6))
sns.barplot(x="Importance", y="Feature", data=importance_df)
plt.title("Feature Importance")
plt.show()
# Проверка на переобучение
y_train_pred = model.predict(X_train_encoded)
accuracy_train = accuracy_score(y_train_resampled, y_train_pred)
precision_train = precision_score(y_train_resampled, y_train_pred)
recall_train = recall_score(y_train_resampled, y_train_pred)
f1_train = f1_score(y_train_resampled, y_train_pred)
roc_auc_train = roc_auc_score(y_train_resampled, y_train_pred)
print(f"Train Accuracy: {accuracy_train}")
print(f"Train Precision: {precision_train}")
print(f"Train Recall: {recall_train}")
print(f"Train F1 Score: {f1_train}")
print(f"Train ROC AUC: {roc_auc_train}")