Приходит мужик в бордель, ведет на поводе осла, а в руках у него пчелиные соты. — Я хотел бы обменять этого осла на ночь с одной из ваших девушек. — Да, конечно, — соглашается мадам. — А в обмен на эти соты вы можете дать мне немного еды? — Могу. Но почему бы вам просто не съесть мед? — недоумевает она. — О, тут та же ситуация, что и с ослом. Понимаете, я пчеловод и держу пасеку. Мне надоело каждый день есть одно и то же.
730 KiB
Лабораторная работа №5. Обучение без учителя.¶
Датасет "Набор данных для анализа и прогнозирования сердечного приступа".¶
Описание датасета¶
Проблемная область: Датасет связан с медицинской статистикой и направлен на анализ факторов, связанных с риском сердечного приступа. Это важно для прогнозирования и разработки стратегий профилактики сердечно-сосудистых заболеваний.
Актуальность: Сердечно-сосудистые заболевания являются одной из ведущих причин смертности во всем мире. Анализ данных об образе жизни, состоянии здоровья и наследственных факторах позволяет выделить ключевые предикторы, влияющие на развитие сердечно-сосудистых заболеваний. Этот датасет предоставляет инструменты для анализа таких факторов и может быть полезен в создании прогнозных моделей, направленных на снижение рисков и своевременную диагностику.
Объекты наблюдения: Каждая запись представляет собой данные о человеке, включая информацию об их состоянии здоровья, образе жизни, демографических характеристиках и наличию определенных заболеваний. Объекты наблюдений — это индивидуальные пациенты.
Атрибуты объектов:
HeartDisease
— наличие сердечного приступа (Yes/No).BMI
— индекс массы тела (Body Mass Index), числовой показатель.Smoking
— курение (Yes/No).AlcoholDrinking
— употребление алкоголя (Yes/No).Stroke
— наличие инсульта (Yes/No).PhysicalHealth
— количество дней в месяц, когда физическое здоровье было неудовлетворительным.MentalHealth
— количество дней в месяц, когда психическое здоровье было неудовлетворительным.DiffWalking
— трудности при ходьбе (Yes/No).Sex
— пол (Male/Female).AgeCategory
— возрастная категория (например, 55-59, 80 or older).Race
— расовая принадлежность (например, White, Black).Diabetic
— наличие диабета (Yes/No/No, borderline diabetes).PhysicalActivity
— физическая активность (Yes/No).GenHealth
— общее состояние здоровья (от Excellent до Poor).SleepTime
— среднее количество часов сна за сутки.Asthma
— наличие астмы (Yes/No).KidneyDisease
— наличие заболеваний почек (Yes/No).SkinCancer
— наличие кожного рака (Yes/No).
import pandas as pd
df = pd.read_csv(".//static//csv//heart_2020_cleaned.csv")
df.head()
Бизнес-цель. Задача кластеризации¶
Описание бизнес-цели¶
Цель: группировка пациентов для выявления скрытых паттернов в их состоянии здоровья. Необходимо кластеризовать пациентов на основе факторов риска (индекс массы тела, физическая активность, возраст, качество сна и т.д.), чтобы лучше понять, какие группы населения требуют большего внимания для профилактики сердечных заболеваний.
Уменьшение объема датасета¶
Так как исходный набор данных слишком объемный (более 300000 наблюдений), для дальнейшего удобства визуализации и скорости обработки снизим размер датасета до 10000 наблюдений:
from sklearn.model_selection import train_test_split
# Уменьшаем размер выборки, сохраняя пропорции классов целевой переменной
df, _ = train_test_split(df, train_size=10000, stratify=df['HeartDisease'], random_state=42)
Удаление целевого признака для кластеризации¶
df = df.drop(columns=['HeartDisease'])
Предобработка данных¶
Построим конвейер для предобработки, включающий стандартизацию, унитарное кодирование и заполнение пропусков, и применим его для набора данных:
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
# Разделение признаков на числовые и категориальные
num_columns = [
column
for column in df.columns
if df[column].dtype != "object"
]
cat_columns = [
column
for column in df.columns
if df[column].dtype == "object"
]
# Числовая обработка: заполнение пропусков медианой и стандартизация
num_imputer = SimpleImputer(strategy="median")
num_scaler = StandardScaler()
preprocessing_num = Pipeline(
[
("imputer", num_imputer),
("scaler", num_scaler),
]
)
# Категориальная обработка: заполнение пропусков значением "unknown" и унитарное кодирование
cat_imputer = SimpleImputer(strategy="constant", fill_value="unknown")
cat_encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False, drop="first")
preprocessing_cat = Pipeline(
[
("imputer", cat_imputer),
("encoder", cat_encoder),
]
)
# Общий конвейер обработки признаков
features_preprocessing = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("prepocessing_num", preprocessing_num, num_columns),
("prepocessing_cat", preprocessing_cat, cat_columns),
],
remainder="passthrough"
)
# Итоговый конвейер
pipeline_end = Pipeline(
[
("features_preprocessing", features_preprocessing),
]
)
preprocessing_result = pipeline_end.fit_transform(df)
preprocessed_df = pd.DataFrame(
preprocessing_result,
columns=pipeline_end.get_feature_names_out(),
)
preprocessed_df
Понижение размерности признаков и визуализация данных¶
Используем алгоритм снижения размерности PCA (метод главных компонент), чтобы уменьшить количество признаков до 2 и отобразить данные на графике:
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
pca = PCA(n_components=2)
data_pca = pca.fit_transform(preprocessed_df)
# Преобразуем результат в DataFrame для визуализации через scatterplot
df_pca = pd.DataFrame(data_pca, columns=['Principal Component 1', 'Principal Component 2'])
# Визуализация данных после PCA
plt.figure(figsize=(10, 6))
sns.scatterplot(
x='Principal Component 1',
y='Principal Component 2',
data=df_pca,
alpha=0.6
)
plt.title('Визуализация данных после PCA', fontsize=14)
plt.xlabel('Главная компонента 1')
plt.ylabel('Главная компонента 2')
plt.grid(True)
plt.show()
Выбор количества кластеров¶
Для определения оптимального числа кластеров используется:
- Инерция (метод локтя): показывает, как внутрикластерная вариация уменьшается с увеличением числа кластеров.
- Коэффициент силуэта: измеряет, насколько хорошо каждый объект кластеризован.
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
inertia = []
silhouette_scores = []
k_range = range(2, 11)
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(data_pca)
inertia.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(data_pca, kmeans.labels_))
# Графики для определения оптимального числа кластеров
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(k_range, inertia, marker='o')
plt.title('Elbow Method')
plt.xlabel('Number of Clusters')
plt.ylabel('Inertia')
plt.subplot(1, 2, 2)
plt.plot(k_range, silhouette_scores, marker='o')
plt.title('Silhouette Scores')
plt.xlabel('Number of Clusters')
plt.ylabel('Silhouette Score')
plt.tight_layout()
plt.show()
Судя по графику инерции, в качестве оптимального числа кластеров можно выбрать k=8, так как в этой точке происходит резкое улучшение метрики (спад инерции) и скорость уменьшения инерции заметно замедляется.
Кластерный анализ¶
Используются два подхода:
- Иерархическая кластеризация - агломеративная кластеризация.
- Неиерархическая кластеризация - алгоритм k-means.
Неиерархическая кластеризация:
# Используем неиерархическую кластеризацию KMeans с оптимальным количеством кластеров 8
kmeans = KMeans(n_clusters=8, random_state=42)
kmeans_labels = kmeans.fit_predict(data_pca)
# Визуализация кластеров KMeans
plt.figure(figsize=(8, 6))
plt.scatter(data_pca[:, 0], data_pca[:, 1], c=kmeans_labels, cmap='viridis', alpha=0.6)
plt.title('KMeans Clustering')
plt.xlabel('PCA 1')
plt.ylabel('PCA 2')
plt.show()
Иерархическая кластеризация:
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
# Иерархическая кластеризация с оптимальным количеством кластеров 8
hierarchical = AgglomerativeClustering(n_clusters=8)
hierarchical_labels = hierarchical.fit_predict(data_pca)
# Визуализация с помощью дендрограммы
plt.figure(figsize=(10, 7))
linkage_matrix = linkage(data_pca, method='ward')
dendrogram(linkage_matrix, truncate_mode='level', p=5)
plt.title('Hierarchical Clustering Dendrogram')
plt.show()
# Визуализация с помощью диаграммы рассеяния
plt.scatter(data_pca[:, 0], data_pca[:, 1], c=hierarchical_labels, cmap='viridis', alpha=0.6)
plt.title('Hierarchical Clustering')
plt.xlabel('PCA 1')
plt.ylabel('PCA 2')
plt.show()
Оценка качества кластеризации¶
Используемая метрика - коэффициент силуэта:
silhouette_kmeans = silhouette_score(data_pca, kmeans_labels)
silhouette_hierarchical = silhouette_score(data_pca, hierarchical_labels)
print(f'Silhouette Score for KMeans: {silhouette_kmeans:.2f}')
print(f'Silhouette Score for Hierarchical Clustering: {silhouette_hierarchical:.2f}')
K-Means показал лучшие результаты и может быть рекомендован для дальнейшей работы с данными. Однако качество кластеризации в целом среднее, что указывает на необходимость дополнительной оптимизации модели или работы с признаками.