olshab ddc6524b6c Приходит мужик в бордель, ведет на поводе осла, а в руках у него пчелиные соты...
Приходит мужик в бордель, ведет на поводе осла, а в руках у него пчелиные соты.
— Я хотел бы обменять этого осла на ночь с одной из ваших девушек.
— Да, конечно, — соглашается мадам.
— А в обмен на эти соты вы можете дать мне немного еды?
— Могу. Но почему бы вам просто не съесть мед? — недоумевает она.
— О, тут та же ситуация, что и с ослом. Понимаете, я пчеловод и держу пасеку. Мне надоело каждый день есть одно и то же.
2024-12-20 23:03:20 +04:00

730 KiB
Raw Blame History

Лабораторная работа №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).
In [1]:
import pandas as pd
df = pd.read_csv(".//static//csv//heart_2020_cleaned.csv")
df.head()
Out[1]:
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
0 No 16.60 Yes No No 3.0 30.0 No Female 55-59 White Yes Yes Very good 5.0 Yes No Yes
1 No 20.34 No No Yes 0.0 0.0 No Female 80 or older White No Yes Very good 7.0 No No No
2 No 26.58 Yes No No 20.0 30.0 No Male 65-69 White Yes Yes Fair 8.0 Yes No No
3 No 24.21 No No No 0.0 0.0 No Female 75-79 White No No Good 6.0 No No Yes
4 No 23.71 No No No 28.0 0.0 Yes Female 40-44 White No Yes Very good 8.0 No No No

Бизнес-цель. Задача кластеризации

Описание бизнес-цели

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

Уменьшение объема датасета

Так как исходный набор данных слишком объемный (более 300000 наблюдений), для дальнейшего удобства визуализации и скорости обработки снизим размер датасета до 10000 наблюдений:

In [2]:
from sklearn.model_selection import train_test_split

# Уменьшаем размер выборки, сохраняя пропорции классов целевой переменной
df, _ = train_test_split(df, train_size=10000, stratify=df['HeartDisease'], random_state=42)

Удаление целевого признака для кластеризации

In [3]:
df = df.drop(columns=['HeartDisease'])

Предобработка данных

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

In [4]:
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
Out[4]:
BMI PhysicalHealth MentalHealth SleepTime Smoking_Yes AlcoholDrinking_Yes Stroke_Yes DiffWalking_Yes Sex_Male AgeCategory_25-29 ... Diabetic_Yes Diabetic_Yes (during pregnancy) PhysicalActivity_Yes GenHealth_Fair GenHealth_Good GenHealth_Poor GenHealth_Very good Asthma_Yes KidneyDisease_Yes SkinCancer_Yes
0 -0.392085 1.326284 -0.492347 1.348988 1.0 0.0 0.0 0.0 1.0 0.0 ... 1.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 1.0 1.0
1 0.253157 -0.425660 -0.366659 0.646792 1.0 0.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
2 -0.961511 -0.425660 -0.492347 -0.055403 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
3 0.749993 -0.175382 -0.492347 -0.757599 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
4 -1.435763 -0.425660 -0.492347 0.646792 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
9995 -0.959898 -0.425660 -0.492347 -0.055403 0.0 0.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
9996 0.348330 -0.425660 -0.492347 1.348988 0.0 0.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
9997 -0.517907 -0.425660 -0.492347 -0.055403 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
9998 0.646754 0.200034 0.387473 0.646792 0.0 0.0 0.0 1.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0
9999 0.441890 -0.425660 -0.492347 -0.055403 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0

10000 rows × 37 columns

Понижение размерности признаков и визуализация данных

Используем алгоритм снижения размерности PCA (метод главных компонент), чтобы уменьшить количество признаков до 2 и отобразить данные на графике:

In [5]:
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()
No description has been provided for this image

Выбор количества кластеров

Для определения оптимального числа кластеров используется:

  1. Инерция (метод локтя): показывает, как внутрикластерная вариация уменьшается с увеличением числа кластеров.
  2. Коэффициент силуэта: измеряет, насколько хорошо каждый объект кластеризован.
In [6]:
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()
No description has been provided for this image

Судя по графику инерции, в качестве оптимального числа кластеров можно выбрать k=8, так как в этой точке происходит резкое улучшение метрики (спад инерции) и скорость уменьшения инерции заметно замедляется.

Кластерный анализ

Используются два подхода:

  1. Иерархическая кластеризация - агломеративная кластеризация.
  2. Неиерархическая кластеризация - алгоритм k-means.

Неиерархическая кластеризация:

In [7]:
# Используем неиерархическую кластеризацию 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()
No description has been provided for this image

Иерархическая кластеризация:

In [9]:
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()
No description has been provided for this image
In [11]:
# Визуализация с помощью диаграммы рассеяния

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()
No description has been provided for this image

Оценка качества кластеризации

Используемая метрика - коэффициент силуэта:

In [12]:
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}')
Silhouette Score for KMeans: 0.33
Silhouette Score for Hierarchical Clustering: 0.26

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