"{ "nbformat": 4, "nbformat_minor": 0, "meta...

Criado em: 20 de dezembro de 2025

Respondido usando GPT-5.2 Thinking por Chat01

Pergunta

"{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyNxk8r+MS7IhXhcJBpsc72V",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href="https://colab.research.google.com/github/BrunoBucCcellati/MachineLearningUNN2025/blob/main/Task2_Deryabin_M.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 1000
},
"id": "kBp4-NPbZX6T",
"outputId": "442ec807-f30c-47cd-c252-f0445bd6512c"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Загружаем данные с Hugging Face...\n",
"Размер датасета: (110811, 8)\n",
"Первые 5 строк данных:\n",
"\n",
"==================================================\n",
"РАЗБИЕНИЕ ДАННЫХ НА ОБУЧАЮЩУЮ И ТЕСТОВУЮ ВЫБОРКИ\n",
"Обучающая выборка: 88648 объектов\n",
"Тестовая выборка: 22163 объектов\n",
"\n",
"==================================================\n",
"ВИЗУАЛИЗАЦИЯ ДАННЫХ И ОСНОВНЫЕ ХАРАКТЕРИСТИКИ\n",
"\n",
"ОСНОВНЫЕ СТАТИСТИКИ:\n",
"Средний рейтинг: 7.01\n",
"Стандартное отклонение: 3.24\n",
"Минимальный рейтинг: 1.0\n",
"Максимальный рейтинг: 10.0\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1500x1000 with 6 Axes>"
],
"image/png": ""
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"ИНТЕРПРЕТАЦИЯ:\n",
"Анализ показывает взаимосвязи между числовыми признаками и рейтингом\n",
"\n",
"==================================================\n",
"ОБРАБОТКА ПРОПУЩЕННЫХ ЗНАЧЕНИЙ\n",
"Пропущенные значения:\n",
"drugname 0.0\n",
"condition 0.0\n",
"usefulcount 0.0\n",
"review_len_char 0.0\n",
"review_len_word 0.0\n",
"year 0.0\n",
"month 0.0\n",
"review 0.0\n",
"dtype: float64\n",
"\n",
"Стратегии обработки:\n",
"- Текст: NaN -> '' (пустая строка)\n",
"- Категории: SimpleImputer(strategy='most_frequent')\n",
"- Числовые: SimpleImputer(strategy='median')\n",
"\n",
"==================================================\n",
"ОБРАБОТКА КАТЕГОРИАЛЬНЫХ ПРИЗНАКОВ\n",
"Категориальные признаки: ['drugname', 'condition']\n",
"Метод обработки: One-Hot Encoding\n",
"Обоснование: Преобразование категорий в бинарные признаки для алгоритмов ML\n",
"\n",
"==================================================\n",
"НОРМАЛИЗАЦИЯ ДАННЫХ\n",
"Обоснование:\n",
"KNN чувствителен к масштабу данных - нормализация необходима\n",
"StandardScaler обеспечивает нулевое среднее и единичную дисперсию\n",
"\n",
"==================================================\n",
"МОДЕЛЬ K-БЛИЖАЙШИХ СОСЕДЕЙ\n",
"Обоснование выбора KNN:\n",
"1. Простота интерпретации\n",
"2. Хорошая работа на средних датасетах\n",
"3. Не делает предположений о распределении данных\n",
"4. Легко настраивается через параметр k\n",
"\n",
"Обучаем KNN...\n",
"\n",
"==================================================\n",
"ВЫЧИСЛЕНИЕ ОШИБОК И ПОДБОР ГИПЕРПАРАМЕТРА\n",
"KNN с k=5:\n",
"Обучающая выборка:\n",
"MAE: 0.0000, MSE: 0.0001, RMSE: 0.0071, R²: 1.0000\n",
"Тестовая выборка:\n",
"MAE: 2.4075, MSE: 9.5544, RMSE: 3.0910, R²: 0.0887\n",
"\n",
"Подбор оптимального k:\n",
"Результаты подбора k: [(1, -0.45735162108969774), (3, -0.0045271556198720475), (5, 0.08871085534520984), (7, 0.12536365425911877), (9, 0.1470179868585063), (11, 0.1607615675309907), (13, 0.16950661842438708), (15, 0.17467374552718484), (21, 0.18363300650413483)]\n",
"Оптимальное k: 21, R²: 0.1836\n",
"KNN с оптимальным k:\n",
"Тестовая выборка:\n",
"MAE: 2.3777, MSE: 8.5592, RMSE: 2.9256, R²: 0.1836\n",
"\n",
"==================================================\n",
"СРАВНЕНИЕ С ДРУГИМИ МОДЕЛЯМИ\n",
"\n",
"Обучаем Ridge...\n",
"\n",
"Обучаем Random Forest...\n",
"\n",
"Сравнение моделей:\n",
"KNN: MAE: 2.3777, RMSE: 2.9256, R²: 0.1836\n",
"Ridge: MAE: 1.5704, RMSE: 2.0060, R²: 0.6162\n",
"Random Forest: MAE: 2.0531, RMSE: 2.7039, R²: 0.3027\n",
"\n",
"==================================================\n",
"АНАЛИЗ НЕСБАЛАНСИРОВАННОСТИ\n",
"В задаче регрессии понятие несбалансированности классов не применимо\n",
"Анализируем распределение целевой переменной:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1000x400 with 2 Axes>"
],
"image/png": ""
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"==================================================\n",
"АНАЛИЗ КОРРЕЛИРОВАННЫХ ПЕРЕМЕННЫХ\n",
"Зачем исключать коррелированные переменные:\n",
"- Уменьшение мультиколлинеарности\n",
"- Упрощение интерпретации модели\n",
"- Повышение стабильности оценок\n",
"Сильно коррелирующие признаки: ['review_len_word']\n",
"\n",
"==================================================\n",
"ОБЩИЕ ВЫВОДЫ\n",
"1. Анализ данных:\n",
"- Датасет содержит информацию о рейтингах лекарств\n",
"- Распределение рейтингов близко к нормальному\n",
"\n",
"2. Предобработка данных:\n",
"- Категориальные признаки закодированы через One-Hot Encoding\n",
"- Проведена нормализация числовых признаков\n",
"- Созданы дополнительные признаки из текста и дат\n",
"\n",
"3. Моделирование:\n",
"- KNN показал хорошие результаты после подбора параметров\n",
"- Random Forest показал наилучшее качество\n",
"- Ridge регрессия с текстовыми features также эффективна\n",
"\n",
"4. Рекомендации:\n",
"- Для production рекомендуется использовать Random Forest\n",
"- Для интерпретируемости можно использовать KNN\n",
"\n",
"ЗАДАНИЕ ВЫПОЛНЕНО\n",
"Все пункты задания покрыты\n"
]
}
],
"source": [
"# ============================================================\n",
"# Task2DeryabinM_DrugReviews_Regression.ipynb\n",
"# Автор: Дерябин Максим Сергеевич\n",
"# Репозиторий: MachineLearningUNN2025\n",
"# Датасет: forwins/Drug-Review-Dataset (Hugging Face)\n",
"# Задача: РЕГРЕССИЯ (предсказать rating)\n",
"# ============================================================\n",
"\n",
"# ----- 0. Описание задачи -----\n",
""""\n",
"ЗАДАЧА: Прогнозирование рейтинга лекарств на основе отзывов пациентов и метаданных.\n",
"Это задача регрессии, где целевой переменной является числовой рейтинг от 1 до 10.\n",
"\n",
"Датасет содержит:\n",
"- Текстовые отзывы пациентов о лекарствах\n",
"- Названия лекарств (drugName)\n",
"- Медицинские состояния (condition)\n",
"- Полезность отзывов (usefulCount)\n",
"- Даты отзывов\n",
"\n",
"ЦЕЛЬ: Построить модель, предсказывающую рейтинг на основе имеющихся признаков.\n",
""""\n",
"\n",
"# ----- 1. Чтение данных -----\n",
"!pip -q install datasets scikit-learn pandas numpy matplotlib seaborn imbalanced-learn\n",
"\n",
"import os, re, math, textwrap, warnings\n",
"warnings.filterwarnings("ignore")\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"from datasets import load_dataset\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import OneHotEncoder, StandardScaler\n",
"from sklearn.impute import SimpleImputer\n",
"from sklearn.compose import ColumnTransformer\n",
"from sklearn.pipeline import Pipeline\n",
"from sklearn.neighbors import KNeighborsRegressor, KNeighborsClassifier\n",
"from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n",
"from sklearn.metrics import accuracy_score, classification_report, confusion_matrix\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import Ridge\n",
"from sklearn.ensemble import RandomForestRegressor\n",
"\n",
"RANDOM_STATE = 42\n",
"np.random.seed(RANDOM_STATE)\n",
"\n",
"print("Загружаем данные с Hugging Face...")\n",
"try:\n",
" ds = load_dataset("forwins/Drug-Review-Dataset", split="train")\n",
"except Exception as e:\n",
" try:\n",
" ds = load_dataset("forwins/Drug-Review-Dataset")["train"]\n",
" except Exception as e2:\n",
" raise RuntimeError(f"Не удалось загрузить датасет: {e}\n{e2}")\n",
"\n",
"df = ds.to_pandas()\n",
"print(f"Размер датасета: {df.shape}")\n",
"print("Первые 5 строк данных:")\n",
"df.head()\n",
"\n",
"# ----- 2. Разбиение на обучающую и тестовую выборки -----\n",
"print("\n" + "="*50)\n",
"print("РАЗБИЕНИЕ ДАННЫХ НА ОБУЧАЮЩУЮ И ТЕСТОВУЮ ВЫБОРКИ")\n",
"\n",
"# Унификация имен столбцов\n",
"cols_lower = {c: c.lower() for c in df.columns}\n",
"df = df.rename(columns=cols_lower)\n",
"\n",
"# Определение столбцов\n",
"def find_col(cands):\n",
" for c in cands:\n",
" if c in df.columns:\n",
" return c\n",
" return None\n",
"\n",
"col_rating = find_col(["rating", "score", "stars"])\n",
"col_review = find_col(["review", "text", "content", "reviews"])\n",
"col_drug = find_col(["drugname", "drug_name", "drug", "medicine"])\n",
"col_condition = find_col(["condition", "diagnosis", "disease"])\n",
"col_useful = find_col(["usefulcount", "helpful", "helpful_count", "useful_count"])\n",
"col_date = find_col(["date", "review_date", "time"])\n",
"\n",
"# Инженерия признаков\n",
"df_work = df.copy()\n",
"\n",
"# Обработка даты\n",
"if col_date in df_work.columns:\n",
" df_work["_date_parsed"] = pd.to_datetime(df_work[col_date], errors="coerce")\n",
" df_work["year"] = df_work["_date_parsed"].dt.year\n",
" df_work["month"] = df_work["_date_parsed"].dt.month\n",
"else:\n",
" df_work["year"] = np.nan\n",
" df_work["month"] = np.nan\n",
"\n",
"# Признаки из текста\n",
"df_work["review_len_char"] = df_work[col_review].fillna("").astype(str).apply(len)\n",
"df_work["review_len_word"] = df_work[col_review].fillna("").astype(str).apply(lambda s: len(s.split()))\n",
"\n",
"# Подготовка данных\n",
"y = pd.to_numeric(df_work[col_rating], errors="coerce")\n",
"\n",
"num_cols_raw = []\n",
"if col_useful in df_work.columns: num_cols_raw.append(col_useful)\n",
"num_cols_raw += ["review_len_char", "review_len_word", "year", "month"]\n",
"\n",
"cat_cols_raw = []\n",
"if col_drug in df_work.columns: cat_cols_raw.append(col_drug)\n",
"if col_condition in df_work.columns: cat_cols_raw.append(col_condition)\n",
"\n",
"text_col = col_review\n",
"\n",
"X = df_work[[c for c in (cat_cols_raw + num_cols_raw + [text_col]) if c in df_work.columns]].copy()\n",
"\n",
"X_train, X_test, y_train, y_test = train_test_split(\n",
" X, y, test_size=0.2, random_state=RANDOM_STATE\n",
")\n",
"\n",
"print(f"Обучающая выборка: {X_train.shape[0]} объектов")\n",
"print(f"Тестовая выборка: {X_test.shape[0]} объектов")\n",
"\n",
"# ----- 3. Визуализация данных и основные характеристики -----\n",
"print("\n" + "="*50)\n",
"print("ВИЗУАЛИЗАЦИЯ ДАННЫХ И ОСНОВНЫЕ ХАРАКТЕРИСТИКИ")\n",
"\n",
"print("\nОСНОВНЫЕ СТАТИСТИКИ:")\n",
"print(f"Средний рейтинг: {y.mean():.2f}")\n",
"print(f"Стандартное отклонение: {y.std():.2f}")\n",
"print(f"Минимальный рейтинг: {y.min()}")\n",
"print(f"Максимальный рейтинг: {y.max()}")\n",
"\n",
"# Определяем признаки для визуализации\n",
"numeric_features = [c for c in num_cols_raw if c in X_train.columns]\n",
"categorical_features = [c for c in cat_cols_raw if c in X_train.columns]\n",
"\n",
"# Визуализации\n",
"plt.figure(figsize=(15, 10))\n",
"\n",
"plt.subplot(2, 3, 1)\n",
"sns.histplot(y_train, bins=30, kde=True)\n",
"plt.title('Распределение рейтингов (train)')\n",
"\n",
"plt.subplot(2, 3, 2)\n",
"sns.boxplot(y=y_train)\n",
"plt.title('Boxplot рейтингов')\n",
"\n",
"plt.subplot(2, 3, 3)\n",
"X_train["review_len_char"].hist(bins=40)\n",
"plt.title('Длина отзывов (символы)')\n",
"\n",
"# Топ лекарств и состояний\n",
"def top_bar(colname, title):\n",
" if colname in X_train.columns:\n",
" vc = X_train[colname].value_counts().head(10)\n",
" plt.subplot(2, 3, 4 if colname == col_drug else 5)\n",
" sns.barplot(x=vc.values, y=vc.index)\n",
" plt.title(title)\n",
"\n",
"top_bar(col_drug, 'Топ-10 лекарств')\n",
"top_bar(col_condition, 'Топ-10 состояний')\n",
"\n",
"plt.subplot(2, 3, 6)\n",
"if numeric_features:\n",
" corr_spearman = pd.Series({c: X_train[c].corr(y_train, method="spearman") for c in numeric_features})\n",
" corr_spearman.sort_values().plot(kind='barh')\n",
" plt.title('Корреляция с рейтингом')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Корреляционный анализ\n",
"print("\nКОРРЕЛЯЦИОННЫЙ АНАЛИЗ:")\n",
"if numeric_features:\n",
" corr_matrix = X_train[numeric_features].corr(method="spearman")\n",
" plt.figure(figsize=(8, 6))\n",
" sns.heatmap(corr_matrix, annot=True, fmt=".2f", vmin=-1, vmax=1, cmap="coolwarm")\n",
" plt.title('Матрица корреляций числовых признаков')\n",
" plt.show()\n",
"\n",
"print("ИНТЕРПРЕТАЦИЯ:")\n",
"print("Анализ показывает взаимосвязи между числовыми признаками и рейтингом")\n",
"\n",
"# ----- 4. Обработка пропущенных значений -----\n",
"print("\n" + "="*50)\n",
"print("ОБРАБОТКА ПРОПУЩЕННЫХ ЗНАЧЕНИЙ")\n",
"\n",
"print("Пропущенные значения:")\n",
"missing_pct = X_train.isnull().mean().sort_values(ascending=False) * 100\n",
"print(missing_pct)\n",
"\n",
"print("\nСтратегии обработки:")\n",
"print("- Текст: NaN -> '' (пустая строка)")\n",
"print("- Категории: SimpleImputer(strategy='most_frequent')")\n",
"print("- Числовые: SimpleImputer(strategy='median')")\n",
"\n",
"# ----- 5. Обработка категориальных признаков -----\n",
"print("\n" + "="*50)\n",
"print("ОБРАБОТКА КАТЕГОРИАЛЬНЫХ ПРИЗНАКОВ")\n",
"\n",
"print("Категориальные признаки:", categorical_features)\n",
"print("Метод обработки: One-Hot Encoding")\n",
"print("Обоснование: Преобразование категорий в бинарные признаки для алгоритмов ML")\n",
"\n",
"# ----- 6. Нормализация данных -----\n",
"print("\n" + "="*50)\n",
"print("НОРМАЛИЗАЦИЯ ДАННЫХ")\n",
"\n",
"print("Обоснование:")\n",
"print("KNN чувствителен к масштабу данных - нормализация необходима")\n",
"print("StandardScaler обеспечивает нулевое среднее и единичную дисперсию")\n",
"\n",
"# Создание трансформеров\n",
"numeric_transformer = Pipeline(steps=[\n",
" ("imputer", SimpleImputer(strategy="median")),\n",
" ("scaler", StandardScaler())\n",
"])\n",
"\n",
"categorical_transformer = Pipeline(steps=[\n",
" ("imputer", SimpleImputer(strategy="most_frequent")),\n",
" ("ohe", OneHotEncoder(handle_unknown="ignore"))\n",
"])\n",
"\n",
"# Трансформер для KNN\n",
"ct_tabular = ColumnTransformer(\n",
" transformers=[\n",
" ("num", numeric_transformer, numeric_features),\n",
" ("cat", categorical_transformer, categorical_features),\n",
" ],\n",
" remainder="drop"\n",
")\n",
"\n",
"# ----- 7. Модель K-ближайших соседей -----\n",
"print("\n" + "="*50)\n",
"print("МОДЕЛЬ K-БЛИЖАЙШИХ СОСЕДЕЙ")\n",
"\n",
"print("Обоснование выбора KNN:")\n",
"print("1. Простота интерпретации")\n",
"print("2. Хорошая работа на средних датасетах")\n",
"print("3. Не делает предположений о распределении данных")\n",
"print("4. Легко настраивается через параметр k")\n",
"\n",
"knn_tabular = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("knn", KNeighborsRegressor(n_neighbors=5, weights="distance"))\n",
"])\n",
"\n",
"print("\nОбучаем KNN...")\n",
"knn_tabular.fit(X_train, y_train)\n",
"\n",
"# ----- 8. Вычисление ошибок и подбор гиперпараметра -----\n",
"print("\n" + "="*50)\n",
"print("ВЫЧИСЛЕНИЕ ОШИБОК И ПОДБОР ГИПЕРПАРАМЕТРА")\n",
"\n",
"def evaluate_model(y_true, y_pred, dataset_name):\n",
" mae = mean_absolute_error(y_true, y_pred)\n",
" mse = mean_squared_error(y_true, y_pred)\n",
" rmse = np.sqrt(mse)\n",
" r2 = r2_score(y_true, y_pred)\n",
"\n",
" print(f"{dataset_name}:")\n",
" print(f"MAE: {mae:.4f}, MSE: {mse:.4f}, RMSE: {rmse:.4f}, R²: {r2:.4f}")\n",
"\n",
" return mae, rmse, r2\n",
"\n",
"y_train_pred = knn_tabular.predict(X_train)\n",
"y_test_pred = knn_tabular.predict(X_test)\n",
"\n",
"print("KNN с k=5:")\n",
"evaluate_model(y_train, y_train_pred, "Обучающая выборка")\n",
"evaluate_model(y_test, y_test_pred, "Тестовая выборка")\n",
"\n",
"print("\nПодбор оптимального k:")\n",
"results = []\n",
"best = {"k": None, "r2": -np.inf, "pred": None}\n",
"k_values = [1, 3, 5, 7, 9, 11, 13, 15, 21]\n",
"\n",
"for k in k_values:\n",
" model_k = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("knn", KNeighborsRegressor(n_neighbors=k, weights="distance"))\n",
" ])\n",
" model_k.fit(X_train, y_train)\n",
" pred = model_k.predict(X_test)\n",
" r2 = r2_score(y_test, pred)\n",
" results.append((k, r2))\n",
" if r2 > best["r2"]:\n",
" best = {"k": k, "r2": r2, "pred": pred}\n",
"\n",
"print("Результаты подбора k:", results)\n",
"print(f"Оптимальное k: {best['k']}, R²: {best['r2']:.4f}")\n",
"\n",
"final_knn = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("knn", KNeighborsRegressor(n_neighbors=best['k'], weights="distance"))\n",
"])\n",
"\n",
"final_knn.fit(X_train, y_train)\n",
"y_test_final = final_knn.predict(X_test)\n",
"\n",
"print("KNN с оптимальным k:")\n",
"evaluate_model(y_test, y_test_final, "Тестовая выборка")\n",
"\n",
"# ----- 9. Сравнение с другими моделями -----\n",
"print("\n" + "="*50)\n",
"print("СРАВНЕНИЕ С ДРУГИМИ МОДЕЛЯМИ")\n",
"\n",
"# Ridge регрессия\n",
"text_transformer = Pipeline(steps=[\n",
" ("tfidf", TfidfVectorizer(min_df=5, ngram_range=(1,2), max_features=50000))\n",
"])\n",
"\n",
"ct_text_full = ColumnTransformer(\n",
" transformers=[\n",
" ("num", numeric_transformer, numeric_features),\n",
" ("cat", categorical_transformer, categorical_features),\n",
" ("txt", text_transformer, text_col)\n",
" ],\n",
" remainder="drop"\n",
")\n",
"\n",
"ridge_text = Pipeline(steps=[\n",
" ("prep", ct_text_full),\n",
" ("ridge", Ridge(alpha=1.0, random_state=RANDOM_STATE))\n",
"])\n",
"\n",
"print("\nОбучаем Ridge...")\n",
"ridge_text.fit(X_train, y_train)\n",
"y_test_ridge = ridge_text.predict(X_test)\n",
"\n",
"# Random Forest\n",
"rf = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("rf", RandomForestRegressor(\n",
" n_estimators=100, max_depth=None, n_jobs=-1, random_state=RANDOM_STATE)) # Уменьшили до 100 для скорости\n",
"])\n",
"\n",
"print("\nОбучаем Random Forest...")\n",
"rf.fit(X_train, y_train)\n",
"y_test_rf = rf.predict(X_test)\n",
"\n",
"models_comparison = {\n",
" 'KNN': y_test_final,\n",
" 'Ridge': y_test_ridge,\n",
" 'Random Forest': y_test_rf\n",
"}\n",
"\n",
"print("\nСравнение моделей:")\n",
"for model_name, predictions in models_comparison.items():\n",
" mae = mean_absolute_error(y_test, predictions)\n",
" rmse = np.sqrt(mean_squared_error(y_test, predictions))\n",
" r2 = r2_score(y_test, predictions)\n",
" print(f"{model_name}: MAE: {mae:.4f}, RMSE: {rmse:.4f}, R²: {r2:.4f}")\n",
"\n",
"# ----- 10. Анализ несбалансированности -----\n",
"print("\n" + "="*50)\n",
"print("АНАЛИЗ НЕСБАЛАНСИРОВАННОСТИ")\n",
"\n",
"print("В задаче регрессии понятие несбалансированности классов не применимо")\n",
"print("Анализируем распределение целевой переменной:")\n",
"\n",
"plt.figure(figsize=(10, 4))\n",
"plt.subplot(1, 2, 1)\n",
"sns.histplot(y, bins=30, kde=True)\n",
"plt.title('Распределение рейтингов')\n",
"plt.subplot(1, 2, 2)\n",
"sns.boxplot(y=y)\n",
"plt.title('Boxplot рейтингов')\n",
"plt.show()\n",
"\n",
"# ----- 11. Исключение коррелированных переменных -----\n",
"print("\n" + "="*50)\n",
"print("АНАЛИЗ КОРРЕЛИРОВАННЫХ ПЕРЕМЕННЫХ")\n",
"\n",
"print("Зачем исключать коррелированные переменные:")\n",
"print("- Уменьшение мультиколлинеарности")\n",
"print("- Упрощение интерпретации модели")\n",
"print("- Повышение стабильности оценок")\n",
"\n",
"to_drop = []\n",
"corr_thr = 0.9\n",
"if len(numeric_features) >= 2:\n",
" corr_mat = X_train[numeric_features].corr().abs()\n",
" upper = corr_mat.where(np.triu(np.ones(corr_mat.shape), k=1).astype(bool))\n",
" to_drop = [column for column in upper.columns if any(upper[column] > corr_thr)]\n",
" if to_drop:\n",
" print(f"Сильно коррелирующие признаки: {to_drop}")\n",
" else:\n",
" print("Сильно коррелирующих признаков не обнаружено")\n",
"else:\n",
" print("Недостаточно числовых признаков для анализа корреляций")\n",
"\n",
"# ----- 12. Общие выводы -----\n",
"print("\n" + "="*50)\n",
"print("ОБЩИЕ ВЫВОДЫ")\n",
"\n",
"print("1. Анализ данных:")\n",
"print("- Датасет содержит информацию о рейтингах лекарств")\n",
"print("- Распределение рейтингов близко к нормальному")\n",
"\n",
"print("\n2. Предобработка данных:")\n",
"print("- Категориальные признаки закодированы через One-Hot Encoding")\n",
"print("- Проведена нормализация числовых признаков")\n",
"print("- Созданы дополнительные признаки из текста и дат")\n",
"\n",
"print("\n3. Моделирование:")\n",
"print("- KNN показал хорошие результаты после подбора параметров")\n",
"print("- Random Forest показал наилучшее качество")\n",
"print("- Ridge регрессия с текстовыми features также эффективна")\n",
"\n",
"print("\n4. Рекомендации:")\n",
"print("- Для production рекомендуется использовать Random Forest")\n",
"print("- Для интерпретируемости можно использовать KNN")\n",
"\n",
"print("\nЗАДАНИЕ ВЫПОЛНЕНО")\n",
"print("Все пункты задания покрыты")"
]
}
]
}" - моё домашнее задание по курсу машинное обучение по требованиям: ""С вашим датасетом нужно проделать примерно то же самое, что мы делали на лекции. Обратите внимание, что у вас может быть задача классификации, а мы решали задачу регрессии (также можно посмотреть 19-02. Adult.ipynb, где решается задача классификации) (https://github.com/NikolaiZolotykh/MachineLearningCourse). Все должно быть выложено на ваш гит.
А именно:
0. Описать задачу словами

  1. Прочитать данные
  2. Разбить данные на обучающую и тестовую выборки
  3. Визуализировать данные и вычислить основные характеристики (среднее, разброс, корреляционную матрицу и т.д.). Интерпретировать.
  4. Обработать пропущенные значения (или убедиться, что их нет)
  5. Обработать категориальные признаки
  6. Провести нормализацию (или объяснить, почему в вашем случае она не нужна)
  7. Запустить классификатор (ререссию) ближайших соседей или другой метод (аргументируйте выбор!)
  8. Вычислить ошибки на обучающей и тестовой выборках. Напечатайте матрицы рассогласования (для задачи классификации). Прокомментируйте. Выбрать оптимальное значение гиперпараметра (к-ва ближайших соседей)
  9. По желанию: запустить другие классификаторы. Сравнить результаты
  10. По желанию: как-то побороться с несбалансированностью классов (если она есть)
  11. По желанию: исключить коррелированные переменные (объяснить зачем)
  12. Сделать общие выводы"" - это моё домашнее задание верно по всем пунктам и соответствует всем требованиям, ошибок преподаватели не нашли и ничего про ошибки не указали, значит этот код можно брать за основу, сейчас нужно сделать новое домашнее задание по требованиям: "Подчистить лабораторную №3. Исправить все ошибки. С теми же данными - решить задачу кластеризации 3 методами: KMeans, DBSCAN и любым другим на ваш выбор. Используя для понижения размерности PCA, визуализируйте. В задаче обучения с учителем вы не должны использовать целевую метку y. В случае задачи классификации сравнить выход каждого алгоритма кластеризации с истинными метками (т.е. сравнить метки, полученные методами обучения без учителя, с истинными метками!). Сделать выводы." - под лабораторной работой 3 имелось ввиду то домашнее задание которое я прислал в начале этого сообщения первым сообщением (которое у меня сделано без ошибок), данные моего датасета: "Дерябин Максим Сергеевич ПМиИ 3822Б1ПМоп3 https://drive.google.com/drive/folders/1KyT6HDLf36rx0SOO9asAMlO6D0919tZU?usp=sharing https://github.com/BrunoBucCcellati/MachineLearningUNN2025 https://huggingface.co/datasets/forwins/Drug-Review-Dataset Drug Review Dataset 184622 8 регрессия 0% 31/3 нет 1 4 3 0 да FALSE TRUE TRUE FALSE TRUE TRUE TRUE - это заполненная в гугл таблице строка, где были заполнены следующие графы: Фамилия Имя Отчество Направление Группа "теоретические" домашки "практические" домашки Ссылка на данные (если у вас свои данные, то пишем - "свои данные") Название датасета N (число объектов) d (число признаков) K (количество классов) Наличие пропущенных значений в процентах (одно число) Несбалансированные классы (указать соотношение, например, 5/95) Большое количество выбросов (указать примерный процент) Количество числовых признаков Количество категориальных признаков Количество текстовых признаков Количество признаков других типов (написать каких) Поле для общения с преподавателем ) (пишем "да", если вы сдаете текущую работу на проверку) Наличие пропущенных значений Разнородные признаки Несбалансированные классы Большое количество выбросов Наличие текстовых признаков Соотвествие датасета требованиям Датасет одобрен преподавателем", а вот пример успешно сделанного последнего домашнего задания от авторитетного студента у которого обычно правильно: "{
    "cells": [
    {
    "cell_type": "markdown",
    "source": [
    "# Кластерный анализ данных\n",
    "\n",
    "В данном исследовании рассматривается применение методов машинного обучения без учителя для группировки объектов. Описание используемого набора данных аналогично предыдущей работе (данные о видеоиграх).\n",
    "\n",
    "---\n",
    "\n",
    "## Введение в кластерный анализ\n",
    "\n",
    "Кластерный анализ — это раздел машинного обучения, направленный на объединение объектов в однородные группы (кластеры) на основе схожести их признаков. Внутри одного кластера объекты максимально похожи, между кластерами — максимально различны. Этот подход позволяет обнаружить скрытые закономерности в данных.\n",
    "\n",
    "---\n",
    "\n",
    "## Алгоритм K-Means\n",
    "\n",
    "### Основная идея\n",
    "K-Means — итеративный алгоритм, который делит данные на заданное число кластеров (K), минимизируя внутрикластерную дисперсию.\n",
    "\n",
    "### Сильные стороны\n",
    "* Быстрая работа с большими выборками.\n",
    "* Простота понимания и реализации.\n",
    "* Хорошая масштабируемость.\n",
    "* Формирует компактные, сферические кластеры.\n",
    "\n",
    "### Слабые стороны\n",
    "* Требует априорного задания числа кластеров.\n",
    "* Чувствителен к аномальным наблюдениям (выбросам).\n",
    "* Предполагает выпуклую форму кластеров.\n",
    "* Результаты зависят от начальной инициализации.\n",
    "\n",
    "### Примеры использования\n",
    "* Сегментация пользователей в маркетинге.\n",
    "* Упрощение цветовой палитры изображений.\n",
    "* Группировка текстовых документов.\n",
    "* Анализ генетических данных.\n",
    "\n",
    "---\n",
    "\n",
    "## Алгоритм DBSCAN\n",
    "\n",
    "### Основная идея\n",
    "DBSCAN строит кластеры на основе плотности распределения точек, выделяя области с высокой концентрацией объектов.\n",
    "\n",
    "### Сильные стороны\n",
    "* Автоматически определяет число кластеров.\n",
    "* Устойчив к шуму и выбросам (помечает их как шум).\n",
    "* Находит кластеры сложной геометрии.\n",
    "* Не зависит от начальных условий.\n",
    "\n",
    "### Слабые стороны\n",
    "* Сложности с кластерами разной плотности.\n",
    "* Критичен к выбору параметров (eps, min_samples).\n",
    "* Снижение эффективности в пространствах высокой размерности.\n",
    "* Относительно высокая вычислительная сложность.\n",
    "\n",
    "### Примеры использования\n",
    "* Обнаружение аномалий в данных.\n",
    "* Анализ пространственных данных (GPS, карты).\n",
    "* Сегментация изображений по текстуре.\n",
    "* Выявление сетевых атак.\n",
    "* Анализ социальных связей.\n",
    "\n",
    "---\n",
    "\n",
    "## Иерархическая агломеративная кластеризация\n",
    "\n",
    "### Основная идея\n",
    "Алгоритм последовательно объединяет наиболее близкие объекты или кластеры, формируя древовидную структуру (дендрограмму).\n",
    "\n",
    "### Сильные стороны\n",
    "* Не требует предварительного задания числа кластеров.\n",
    "* Наглядная визуализация в виде дерева (дендрограммы).\n",
    "* Устойчивость к зашумленным данным.\n",
    "* Возможность выбора уровня детализации после анализа.\n",
    "\n",
    "### Слабые стороны\n",
    "* Высокая вычислительная сложность (O(n³) для простых реализаций).\n",
    "* Чувствительность к выбору метрики расстояния и функции связи.\n",
    "* Необратимость процесса слияния.\n",
    "* Ограниченная применимость к большим наборам данных.\n",
    "\n",
    "### Примеры использования\n",
    "* Построение филогенетических деревьев (биология).\n",
    "* Классификация языков и диалектов.\n",
    "* Анализ социальных сетей (сообщества).\n",
    "* Многоуровневая сегментация клиентов.\n",
    "* Любые задачи с иерархической структурой.\n",
    "\n",
    "---\n",
    "\n",
    "## Сравнительный анализ методов\n",
    "\n",
    "| Критерий | K-Means | DBSCAN | Agglomerative |\n",
    "|--------------------------|----------------------------------------------|-------------------------------------------|--------------------------------------------|\n",
    "| Механизм работы | Минимизация расстояний до центроидов | Объединение точек на основе плотности | Последовательное слияние ближайших объектов |\n",
    "| Форма кластеров | Сферическая, выпуклая | Произвольная форма | Зависит от метрики (часто выпуклая) |\n",
    "| Обработка выбросов | Влияют на положение центров | Автоматическое выделение шума | Умеренная устойчивость |\n",
    "| Число кластеров (K) | Требует явного указания | Определяет автоматически | Позволяет выбирать уровень детализации |\n",
    "| Производительность | Наиболее эффективен на больших данных | Средняя, зависит от параметров | Наибольшие вычислительные затраты |\n",
    "\n",
    "---\n",
    "\n",
    "## Рекомендации по выбору алгоритма\n",
    "\n",
    "### K-Means предпочтителен, когда:\n",
    "* Кластеры имеют приблизительно сферическую форму.\n",
    "* Число кластеров известно или может быть оценено (например, методом "локтя").\n",
    "* Требуется высокая скорость обработки.\n",
    "* Необходимы четко определенные результаты.\n",
    "\n",
    "### DBSCAN рекомендуется, когда:\n",
    "* Форма кластеров априори неизвестна (может быть произвольной).\n",
    "* В данных присутствуют аномалии (выбросы, шум).\n",
    "* Число кластеров определить сложно.\n",
    "* Важна устойчивость к зашумленности данных.\n",
    "\n",
    "### Иерархическая кластеризация уместна, когда:\n",
    "* Данные имеют иерархическую структуру (например, таксономия).\n",
    "* Требуется визуализация в виде дендрограммы для анализа.\n",
    "* Объем данных ограничен (из-за сложности вычислений).\n",
    "* Нужен многоуровневый анализ кластеров.\n",
    "\n",
    "---\n",
    "\n",
    "## Комбинированные стратегии в практике\n",
    "\n",
    "В реальных проектах часто применяют гибридные подходы для повышения качества анализа:\n",
    "\n",
    "1. DBSCAN для предварительной фильтрации выбросов и шума.\n",
    "2. K-Means для основного разбиения очищенных данных.\n",
    "3. Иерархические методы для углубленного изучения структуры отдельных кластеров.\n",
    "\n",
    "Такая комбинация позволяет использовать преимущества разных алгоритмов и повышать надежность и интерпретируемость результатов.\n"
    ],
    "metadata": {
    "id": "tVrniD6dOCpn"
    },
    "id": "tVrniD6dOCpn"
    },
    {
    "cell_type": "code",
    "execution_count": 3,
    "id": "41c401f7-3a58-44a5-9745-b2b6e7ad279f",
    "metadata": {
    "colab": {
    "base_uri": "https://localhost:8080/",
    "height": 1000
    },
    "id": "41c401f7-3a58-44a5-9745-b2b6e7ad279f",
    "outputId": "49bab680-4b7f-4c6e-d5e2-e67b604b7ebf"
    },
    "outputs": [
    {
    "output_type": "stream",
    "name": "stdout",
    "text": [
    "================================================================================\n",
    "ЭТАП 1: ЗАГРУЗКА ДАННЫХ И ПЕРВИЧНЫЙ ОБЗОР\n",
    "================================================================================\n",
    "Using Colab cache for faster access to the 'video-game-sales-with-ratings' dataset.\n",
    "Объем датасета: (16719, 16)\n",
    "Число атрибутов: 16\n",
    "\n",
    "Начальные записи (первые 5):\n",
    " Name Platform Year_of_Release Genre Publisher \\n",
    "0 Wii Sports Wii 2006.0 Sports Nintendo \n",
    "1 Super Mario Bros. NES 1985.0 Platform Nintendo \n",
    "2 Mario Kart Wii Wii 2008.0 Racing Nintendo \n",
    "3 Wii Sports Resort Wii 2009.0 Sports Nintendo \n",
    "4 Pokemon Red/Pokemon Blue GB 1996.0 Role-Playing Nintendo \n",
    "\n",
    " NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales Critic_Score \\n",
    "0 41.36 28.96 3.77 8.45 82.53 76.0 \n",
    "1 29.08 3.58 6.81 0.77 40.24 NaN \n",
    "2 15.68 12.76 3.79 3.29 35.52 82.0 \n",
    "3 15.61 10.93 3.28 2.95 32.77 80.0 \n",
    "4 11.27 8.89 10.22 1.00 31.37 NaN \n",
    "\n",
    " Critic_Count User_Score User_Count Developer Rating \n",
    "0 51.0 8 322.0 Nintendo E \n",
    "1 NaN NaN NaN NaN NaN \n",
    "2 73.0 8.3 709.0 Nintendo E \n",
    "3 73.0 8 192.0 Nintendo E \n",
    "4 NaN NaN NaN NaN NaN \n",
    "\n",
    "================================================================================\n",
    "ЭТАП 2: ПРЕДВАРИТЕЛЬНАЯ ОБРАБОТКА\n",
    "================================================================================\n",
    "\n",
    "Обработка отсутствующих значений...\n",
    "\n",
    "Удалено записей с отсутствующим жанром: 2\n",
    "\n",
    "Оставшиеся пропуски:\n",
    "Series([], dtype: int64)\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 3: ФОРМИРОВАНИЕ НОВЫХ ПЕРЕМЕННЫХ\n",
    "================================================================================\n",
    "Объем данных после фильтрации: (16079, 21)\n",
    "Минимальные и максимальные продажи: 0.02 - 82.53 млн.\n",
    "Диапазон интегральных оценок: 12.1 - 95.8\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 4: ПОДГОТОВКА ПЕРЕМЕННЫХ ДЛЯ АНАЛИЗА\n",
    "================================================================================\n",
    "Используемые переменные для анализа группировки:\n",
    " 1. Global_Sales\n",
    " 2. NA_Sales_Ratio\n",
    " 3. EU_Sales_Ratio\n",
    " 4. JP_Sales_Ratio\n",
    " 5. Overall_Rating\n",
    " 6. Critic_Count\n",
    " 7. User_Count\n",
    " 8. Sales_Per_Year\n",
    " 9. Year_of_Release\n",
    "\n",
    "Размерность подготовленных данных: (16079, 9)\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 5: ВЫБОР КОЛИЧЕСТВА ГРУПП\n",
    "================================================================================\n"
    ]
    },
    {
    "output_type": "display_data",
    "data": {
    "text/plain": [
    "<Figure size 1400x500 with 2 Axes>"
    ],
    "image/png": ""
    },
    "metadata": {}
    },
    {
    "output_type": "stream",
    "name": "stdout",
    "text": [
    "Рекомендуемое число групп по силуэтному анализу: 5\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 6: ПРИМЕНЕНИЕ РАЗЛИЧНЫХ МЕТОДОВ ГРУППИРОВКИ\n",
    "================================================================================\n",
    "Для алгоритмов используется 6 главных компонент\n",
    "\n",
    "1. Применение метода KMeans...\n",
    "2. Применение алгоритма DBSCAN...\n",
    " Автоматически выбранное значение eps: 0.768\n"
    ]
    },
    {
    "output_type": "display_data",
    "data": {
    "text/plain": [
    "<Figure size 1000x600 with 1 Axes>"
    ],
    "image/png": ""
    },
    "metadata": {}
    },
    {
    "output_type": "stream",
    "name": "stdout",
    "text": [
    "3. Применение иерархической кластеризации...\n",
    " Построение древовидной диаграммы...\n"
    ]
    },
    {
    "output_type": "display_data",
    "data": {
    "text/plain": [
    "<Figure size 1200x600 with 1 Axes>"
    ],
    "image/png": ""
    },
    "metadata": {}
    },
    {
    "output_type": "stream",
    "name": "stdout",
    "text": [
    "\n",
    "СТАТИСТИКА ПОЛУЧЕННЫХ ГРУПП:\n",
    "------------------------------------------------------------\n",
    "KMeans: 5 групп\n",
    "DBSCAN: 7 групп (1437 шумовых записей, 8.9%)\n",
    "Agglomerative: 5 групп\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 7: ГРАФИЧЕСКОЕ ПРЕДСТАВЛЕНИЕ РЕЗУЛЬТАТОВ\n",
    "================================================================================\n"
    ]
    },
    {
    "output_type": "display_data",
    "data": {
    "text/plain": [
    "<Figure size 1800x800 with 9 Axes>"
    ],
    "image/png": ""
    },
    "metadata": {}
    },
    {
    "output_type": "stream",
    "name": "stdout",
    "text": [
    "\n",
    "================================================================================\n",
    "ЭТАП 8: СМЫСЛОВАЯ ИНТЕРПРЕТАЦИЯ ГРУПП\n",
    "================================================================================\n",
    "\n",
    "СРАВНИТЕЛЬНАЯ ОЦЕНКА МЕТОДОВ:\n",
    "------------------------------------------------------------\n",
    " Метод Группы Выбросы Силуэт Калински-Харабас Дэвис-Болдуин\n",
    " KMeans 5 0 0.336 4141.257 1.270\n",
    " DBSCAN 7 1437 0.083 101.280 0.731\n",
    "Agglomerative 5 0 0.241 3325.414 1.387\n",
    "\n",
    "ДЕТАЛЬНЫЙ АНАЛИЗ ГРУПП, ПОЛУЧЕННЫХ KMEANS (5 групп):\n",
    "------------------------------------------------------------\n",
    "\n",
    "Группа 0:\n",
    " Объем: 3,517 игровых проектов (21.9%)\n",
    " Средние продажи: 0.20 млн\n",
    " Средняя интегральная оценка: 72.2\n",
    " Средний год релиза: 2006\n",
    " Региональная структура продаж: NA=2.5%, EU=1.2%, JP=95.2%\n",
    " Основные жанры: Role-Playing (666), Action (586), Adventure (544)\n",
    " Ключевые издатели: Namco Bandai Games (605), Konami Digital Entertainment (345), Sega (266)\n",
    "\n",
    "Группа 1:\n",
    " Объем: 8,098 игровых проектов (50.4%)\n",
    " Средние продажи: 0.27 млн\n",
    " Средняя интегральная оценка: 68.3\n",
    " Средний год релиза: 2005\n",
    " Региональная структура продаж: NA=71.4%, EU=18.7%, JP=1.9%\n",
    " Основные жанры: Action (1631), Sports (1355), Misc (950)\n",
    " Ключевые издатели: Electronic Arts (718), Activision (675), THQ (551)\n",
    "\n",
    "Группа 2:\n",
    " Объем: 1,956 игровых проектов (12.2%)\n",
    " Средние продажи: 0.24 млн\n",
    " Средняя интегральная оценка: 71.1\n",
    " Средний год релиза: 2011\n",
    " Региональная структура продаж: NA=16.8%, EU=68.1%, JP=1.8%\n",
    " Основные жанры: Action (460), Sports (259), Racing (187)\n",
    " Ключевые издатели: Electronic Arts (213), Ubisoft (184), Activision (89)\n",
    "\n",
    "Группа 3:\n",
    " Объем: 2,260 игровых проектов (14.1%)\n",
    " Средние продажи: 1.67 млн\n",
    " Средняя интегральная оценка: 77.4\n",
    " Средний год релиза: 2007\n",
    " Региональная структура продаж: NA=52.6%, EU=28.8%, JP=8.6%\n",
    " Основные жанры: Action (507), Shooter (328), Sports (308)\n",
    " Ключевые издатели: Electronic Arts (368), Nintendo (297), Activision (175)\n",
    "\n",
    "Группа 4:\n",
    " Объем: 248 игровых проектов (1.5%)\n",
    " Средние продажи: 7.37 млн\n",
    " Средняя интегральная оценка: 81.4\n",
    " Средний год релиза: 2010\n",
    " Региональная структура продаж: NA=43.4%, EU=38.4%, JP=6.9%\n",
    " Основные жанры: Shooter (74), Action (62), Role-Playing (44)\n",
    " Ключевые издатели: Nintendo (49), Electronic Arts (31), Activision (30)\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 9: СОПОСТАВЛЕНИЕ С ИСХОДНОЙ КЛАССИФИКАЦИЕЙ (ЖАНРАМИ)\n",
    "================================================================================\n",
    "Количество уникальных жанровых категорий: 12\n",
    "Перечень жанров: ['Action', 'Adventure', 'Fighting', 'Misc', 'Platform', 'Puzzle', 'Racing', 'Role-Playing', 'Shooter', 'Simulation', 'Sports', 'Strategy']\n",
    "\n",
    "ОЦЕНКА СООТВЕТСТВИЯ ГРУППИРОВКИ ЖАНРОВОЙ КЛАССИФИКАЦИИ:\n",
    "----------------------------------------------------------------------\n",
    "\n",
    "KMeans:\n",
    " • Скорректированный индекс Ранда: 0.0153\n",
    " • Нормализованная взаимная информация: 0.0402\n",
    " • Показатель однородности: 0.0310\n",
    " • Показатель полноты: 0.0572\n",
    "\n",
    "Agglomerative:\n",
    " • Скорректированный индекс Ранда: 0.0111\n",
    " • Нормализованная взаимная информация: 0.0339\n",
    " • Показатель однородности: 0.0260\n",
    " • Показатель полноты: 0.0487\n",
    "\n",
    "DBSCAN (шумовые точки учтены как отдельная категория):\n",
    " • Скорректированный индекс Ранда: -0.0015\n",
    " • Нормализованная взаимная информация: 0.0122\n",
    "\n",
    "СВОДНЫЕ РЕЗУЛЬТАТЫ СОПОСТАВЛЕНИЯ:\n",
    "----------------------------------------------------------------------\n",
    " Метод Скорректированный_Индекс_Ранда Нормализованная_Взаимная_Информация Однородность Полнота\n",
    " KMeans 0.0153 0.0402 0.0310 0.0572\n",
    "Agglomerative 0.0111 0.0339 0.0260 0.0487\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 10: СРАВНЕНИЕ РЕЗУЛЬТАТОВ РАЗНЫХ АЛГОРИТМОВ\n",
    "================================================================================\n",
    "\n",
    "СОПОСТАВЛЕНИЕ РЕЗУЛЬТАТОВ KMEANS И ИЕРАРХИЧЕСКОЙ ГРУППИРОВКИ:\n",
    "------------------------------------------------------------\n",
    "\n",
    "Таблица соответствия групп (строки - KMeans, столбцы - Agglomerative):\n",
    "col_0 0 1 2 3 4\n",
    "row_0 \n",
    "0 0 3184 329 0 4\n",
    "1 0 41 5710 3 2344\n",
    "2 2 0 835 925 194\n",
    "3 452 23 1715 23 47\n",
    "4 241 0 1 6 0\n",
    "\n",
    "СТЕПЕНЬ СОГЛАСОВАННОСТИ МЕЖДУ МЕТОДАМИ:\n",
    "------------------------------------------------------------\n",
    "Группа 0 (KMeans): 90.5% соответствия с группой 1 (Agglomerative)\n",
    "Группа 1 (KMeans): 70.5% соответствия с группой 2 (Agglomerative)\n",
    "Группа 2 (KMeans): 47.3% соответствия с группой 3 (Agglomerative)\n",
    "Группа 3 (KMeans): 75.9% соответствия с группой 2 (Agglomerative)\n",
    "Группа 4 (KMeans): 97.2% соответствия с группой 0 (Agglomerative)\n",
    "\n",
    "Общая степень совпадения группировок: 73.2%\n",
    "\n",
    "================================================================================\n",
    "ЭТАП 11: РАСПРЕДЕЛЕНИЕ ЖАНРОВ ПО ГРУППАМ\n",
    "================================================================================\n",
    "\n",
    "РАСПРЕДЕЛЕНИЕ ЖАНРОВ В ГРУППАХ, ПОЛУЧЕННЫХ KMEANS:\n",
    "------------------------------------------------------------\n",
    "\n",
    "Группа 0 (3517 проектов):\n",
    "Наиболее распространенные жанры:\n",
    " Role-Playing: 666 проектов (18.9%)\n",
    " Action: 586 проектов (16.7%)\n",
    " Adventure: 544 проектов (15.5%)\n",
    " Misc: 399 проектов (11.3%)\n",
    " Sports: 361 проектов (10.3%)\n",
    "\n",
    "Группа 1 (8098 проектов):\n",
    "Наиболее распространенные жанры:\n",
    " Action: 1631 проектов (20.1%)\n",
    " Sports: 1355 проектов (16.7%)\n",
    " Misc: 950 проектов (11.7%)\n",
    " Racing: 778 проектов (9.6%)\n",
    " Shooter: 622 проектов (7.7%)\n",
    "\n",
    "Группа 2 (1956 проектов):\n",
    "Наиболее распространенные жанры:\n",
    " Action: 460 проектов (23.5%)\n",
    " Sports: 259 проектов (13.2%)\n",
    " Racing: 187 проектов (9.6%)\n",
    " Shooter: 178 проектов (9.1%)\n",
    " Misc: 173 проектов (8.8%)\n",
    "\n",
    "Группа 3 (2260 проектов):\n",
    "Наиболее распространенные жанры:\n",
    " Action: 507 проектов (22.4%)\n",
    " Shooter: 328 проектов (14.5%)\n",
    " Sports: 308 проектов (13.6%)\n",
    " Role-Playing: 246 проектов (10.9%)\n",
    " Platform: 171 проектов (7.6%)\n",
    "\n",
    "Группа 4 (248 проектов):\n",
    "Наиболее распространенные жанры:\n",
    " Shooter: 74 проектов (29.8%)\n",
    " Action: 62 проектов (25.0%)\n",
    " Role-Playing: 44 проектов (17.7%)\n",
    " Platform: 13 проектов (5.2%)\n",
    " Racing: 11 проектов (4.4%)\n"
    ]
    }
    ],
    "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "print("=" * 80)\n",
    "print("ЭТАП 1: ЗАГРУЗКА ДАННЫХ И ПЕРВИЧНЫЙ ОБЗОР")\n",
    "print("=" * 80)\n",
    "\n",
    "file_path = "Video_Games_Sales_as_at_22_Dec_2016.csv"\n",
    "\n",
    "try:\n",
    " import kagglehub\n",
    " from kagglehub import KaggleDatasetAdapter\n",
    " df = kagglehub.load_dataset(\n",
    " KaggleDatasetAdapter.PANDAS,\n",
    " "rush4ratio/video-game-sales-with-ratings",\n",
    " file_path,\n",
    " )\n",
    "except ImportError:\n",
    " print("kagglehub не обнаружен, выполняется установка...")\n",
    " import subprocess, sys\n",
    " subprocess.check_call([sys.executable, "-m", "pip", "install", "kagglehub"])\n",
    " import kagglehub\n",
    " from kagglehub import KaggleDatasetAdapter\n",
    " df = kagglehub.load_dataset(\n",
    " KaggleDatasetAdapter.PANDAS,\n",
    " "rush4ratio/video-game-sales-with-ratings",\n",
    " file_path,\n",
    " )\n",
    "\n",
    "print(f"Объем датасета: {df.shape}")\n",
    "print(f"Число атрибутов: {df.shape[1]}")\n",
    "print("\nНачальные записи (первые 5):")\n",
    "print(df.head())\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 2: ПРЕДВАРИТЕЛЬНАЯ ОБРАБОТКА")\n",
    "print("=" * 80)\n",
    "\n",
    "df_clean = df.copy()\n",
    "\n",
    "df_clean['User_Score'] = pd.to_numeric(df_clean['User_Score'], errors='coerce')\n",
    "\n",
    "print("\nОбработка отсутствующих значений...")\n",
    "\n",
    "# Числовые поля - заполнение медианой\n",
    "num_cols_to_fill = ['Year_of_Release', 'Critic_Score', 'Critic_Count',\n",
    " 'User_Score', 'User_Count', 'NA_Sales', 'EU_Sales',\n",
    " 'JP_Sales', 'Other_Sales', 'Global_Sales']\n",
    "\n",
    "for col in num_cols_to_fill:\n",
    " if col in df_clean.columns:\n",
    " df_clean[col] = df_clean[col].fillna(df_clean[col].median())\n",
    "\n",
    "cat_cols_to_fill = ['Publisher', 'Developer', 'Rating']\n",
    "for col in cat_cols_to_fill:\n",
    " if col in df_clean.columns:\n",
    " df_clean[col] = df_clean[col].fillna('Unknown')\n",
    "\n",
    "initial_size = len(df_clean)\n",
    "df_clean = df_clean.dropna(subset=['Genre'])\n",
    "print(f"\nУдалено записей с отсутствующим жанром: {initial_size - len(df_clean)}")\n",
    "\n",
    "print("\nОставшиеся пропуски:")\n",
    "print(df_clean.isnull().sum()[df_clean.isnull().sum() > 0])\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 3: ФОРМИРОВАНИЕ НОВЫХ ПЕРЕМЕННЫХ")\n",
    "print("=" * 80)\n",
    "\n",
    "df_clean['Overall_Rating'] = (df_clean['Critic_Score'] * 0.7 + df_clean['User_Score'] * 10 * 0.3)\n",
    "df_clean['Sales_Per_Year'] = df_clean['Global_Sales'] / (2020 - df_clean['Year_of_Release']).clip(1)\n",
    "\n",
    "df_clean['NA_Sales_Ratio'] = df_clean['NA_Sales'] / df_clean['Global_Sales'].replace(0, 0.001)\n",
    "df_clean['EU_Sales_Ratio'] = df_clean['EU_Sales'] / df_clean['Global_Sales'].replace(0, 0.001)\n",
    "df_clean['JP_Sales_Ratio'] = df_clean['JP_Sales'] / df_clean['Global_Sales'].replace(0, 0.001)\n",
    "\n",
    "df_clean = df_clean[df_clean['Global_Sales'] > 0.01]\n",
    "df_clean = df_clean.dropna(subset=['Overall_Rating', 'Sales_Per_Year'])\n",
    "\n",
    "print(f"Объем данных после фильтрации: {df_clean.shape}")\n",
    "print(f"Минимальные и максимальные продажи: {df_clean['Global_Sales'].min():.2f} - {df_clean['Global_Sales'].max():.2f} млн.")\n",
    "print(f"Диапазон интегральных оценок: {df_clean['Overall_Rating'].min():.1f} - {df_clean['Overall_Rating'].max():.1f}")\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 4: ПОДГОТОВКА ПЕРЕМЕННЫХ ДЛЯ АНАЛИЗА")\n",
    "print("=" * 80)\n",
    "\n",
    "features = [\n",
    " 'Global_Sales', 'NA_Sales_Ratio', 'EU_Sales_Ratio', 'JP_Sales_Ratio',\n",
    " 'Overall_Rating', 'Critic_Count', 'User_Count', 'Sales_Per_Year', 'Year_of_Release'\n",
    "]\n",
    "\n",
    "print("Используемые переменные для анализа группировки:")\n",
    "for i, feat in enumerate(features, 1):\n",
    " print(f"{i:2}. {feat}")\n",
    "\n",
    "X = df_clean[features].copy()\n",
    "\n",
    "X['Global_Sales'] = np.log1p(X['Global_Sales'])\n",
    "X['Sales_Per_Year'] = np.log1p(X['Sales_Per_Year'])\n",
    "\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "scaler = StandardScaler()\n",
    "X_scaled = pd.DataFrame(scaler.fit_transform(X), columns=X.columns)\n",
    "\n",
    "print(f"\nРазмерность подготовленных данных: {X_scaled.shape}")\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 5: ВЫБОР КОЛИЧЕСТВА ГРУПП")\n",
    "print("=" * 80)\n",
    "\n",
    "from sklearn.cluster import KMeans\n",
    "from sklearn.metrics import silhouette_score\n",
    "\n",
    "inertia = []\n",
    "silhouette_scores = []\n",
    "K_range = range(2, 11)\n",
    "\n",
    "for k in K_range:\n",
    " kmeans = KMeans(n_clusters=k, random_state=42, n_init=20)\n",
    " kmeans.fit(X_scaled)\n",
    " inertia.append(kmeans.inertia_)\n",
    " silhouette_avg = silhouette_score(X_scaled, kmeans.labels_)\n",
    " silhouette_scores.append(silhouette_avg)\n",
    "\n",
    "fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n",
    "ax1.plot(K_range, inertia, 'bo-')\n",
    "ax1.set_xlabel('Количество групп')\n",
    "ax1.set_ylabel('Значение инерции')\n",
    "ax1.set_title('График "локтя" для определения оптимального K')\n",
    "ax1.grid(True, alpha=0.3)\n",
    "\n",
    "ax2.plot(K_range, silhouette_scores, 'ro-')\n",
    "ax2.set_xlabel('Количество групп')\n",
    "ax2.set_ylabel('Средний силуэтный коэффициент')\n",
    "ax2.set_title('Анализ силуэта для определения K')\n",
    "ax2.grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.savefig('optimal_clusters_analysis.png', dpi=100, bbox_inches='tight')\n",
    "plt.show()\n",
    "\n",
    "optimal_k_silhouette = K_range[np.argmax(silhouette_scores)]\n",
    "print(f"Рекомендуемое число групп по силуэтному анализу: {optimal_k_silhouette}")\n",
    "optimal_k = optimal_k_silhouette\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 6: ПРИМЕНЕНИЕ РАЗЛИЧНЫХ МЕТОДОВ ГРУППИРОВКИ")\n",
    "print("=" * 80)\n",
    "\n",
    "from sklearn.decomposition import PCA\n",
    "from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering\n",
    "from sklearn.neighbors import NearestNeighbors\n",
    "import scipy.cluster.hierarchy as sch\n",
    "\n",
    "pca_2d = PCA(n_components=2, random_state=42)\n",
    "X_pca_2d = pca_2d.fit_transform(X_scaled)\n",
    "\n",
    "pca_3d = PCA(n_components=3, random_state=42)\n",
    "X_pca_3d = pca_3d.fit_transform(X_scaled)\n",
    "\n",
    "pca_cluster = PCA(n_components=0.85, random_state=42)\n",
    "X_pca_cluster = pca_cluster.fit_transform(X_scaled)\n",
    "print(f"Для алгоритмов используется {X_pca_cluster.shape[1]} главных компонент")\n",
    "\n",
    "print("\n1. Применение метода KMeans...")\n",
    "kmeans = KMeans(n_clusters=optimal_k, random_state=42, n_init=20)\n",
    "kmeans_labels = kmeans.fit_predict(X_scaled)\n",
    "df_clean['KMeans_Cluster'] = kmeans_labels\n",
    "\n",
    "print("2. Применение алгоритма DBSCAN...")\n",
    "\n",
    "def find_optimal_eps(data, min_samples, percentile=90):\n",
    " """Определение оптимального параметра eps для DBSCAN"""\n",
    " neighbors = NearestNeighbors(n_neighbors=min_samples)\n",
    " neighbors_fit = neighbors.fit(data)\n",
    " distances, indices = neighbors_fit.kneighbors(data)\n",
    " k_distances = np.sort(distances[:, min_samples-1])\n",
    " eps_value = np.percentile(k_distances, percentile)\n",
    " return eps_value, k_distances\n",
    "\n",
    "min_samples_dbscan = 10\n",
    "optimal_eps, k_distances = find_optimal_eps(X_pca_cluster, min_samples_dbscan, percentile=85)\n",
    "print(f" Автоматически выбранное значение eps: {optimal_eps:.3f}")\n",
    "\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.plot(k_distances)\n",
    "plt.axhline(y=optimal_eps, color='r', linestyle='--', label=f'eps = {optimal_eps:.3f}')\n",
    "plt.xlabel('Объекты (отсортированные)')\n",
    "plt.ylabel(f'Дистанция до {min_samples_dbscan}-го соседа')\n",
    "plt.title('График k-расстояний для настройки DBSCAN')\n",
    "plt.legend()\n",
    "plt.grid(True, alpha=0.3)\n",
    "plt.savefig('dbscan_kdistance_plot.png', dpi=100, bbox_inches='tight')\n",
    "plt.show()\n",
    "\n",
    "dbscan = DBSCAN(eps=optimal_eps, min_samples=min_samples_dbscan, metric='euclidean')\n",
    "dbscan_labels = dbscan.fit_predict(X_pca_cluster)\n",
    "df_clean['DBSCAN_Cluster'] = dbscan_labels\n",
    "\n",
    "print("3. Применение иерархической кластеризации...")\n",
    "agglomerative = AgglomerativeClustering(n_clusters=optimal_k, linkage='ward')\n",
    "agglomerative_labels = agglomerative.fit_predict(X_scaled)\n",
    "df_clean['Agglomerative_Cluster'] = agglomerative_labels\n",
    "\n",
    "print(" Построение древовидной диаграммы...")\n",
    "plt.figure(figsize=(12, 6))\n",
    "sample_size = min(100, len(X_scaled))\n",
    "sample_indices = np.random.choice(len(X_scaled), sample_size, replace=False)\n",
    "X_sample = X_scaled.iloc[sample_indices].values\n",
    "\n",
    "dendrogram = sch.dendrogram(sch.linkage(X_sample, method='ward'),\n",
    " truncate_mode='level', p=5)\n",
    "plt.title(f'Древовидная диаграмма (выборка из {sample_size} объектов)')\n",
    "plt.xlabel('Объекты выборки')\n",
    "plt.ylabel('Метрика расстояния')\n",
    "plt.axhline(y=7, color='r', linestyle='--', label=f'Разделение на {optimal_k} групп')\n",
    "plt.legend()\n",
    "plt.grid(True, alpha=0.3)\n",
    "plt.savefig('hierarchical_dendrogram.png', dpi=100, bbox_inches='tight')\n",
    "plt.show()\n",
    "\n",
    "print(f"\nСТАТИСТИКА ПОЛУЧЕННЫХ ГРУПП:")\n",
    "print("-" * 60)\n",
    "print(f"KMeans: {len(np.unique(kmeans_labels))} групп")\n",
    "dbscan_unique = np.unique(dbscan_labels)\n",
    "dbscan_clusters = len(dbscan_unique) - (1 if -1 in dbscan_unique else 0)\n",
    "print(f"DBSCAN: {dbscan_clusters} групп ({np.sum(dbscan_labels == -1)} шумовых записей, {np.sum(dbscan_labels == -1)/len(dbscan_labels)100:.1f}%)")\n",
    "print(f"Agglomerative: {len(np.unique(agglomerative_labels))} групп")\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 7: ГРАФИЧЕСКОЕ ПРЕДСТАВЛЕНИЕ РЕЗУЛЬТАТОВ")\n",
    "print("=" * 80)\n",
    "\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "\n",
    "fig1 = plt.figure(figsize=(18, 8))\n",
    "\n",
    "methods = [\n",
    " ('KMeans', kmeans_labels, 'viridis'),\n",
    " ('DBSCAN', dbscan_labels, 'plasma'),\n",
    " ('Agglomerative', agglomerative_labels, 'turbo')\n",
    "]\n",
    "\n",
    "for idx, (name, labels, cmap) in enumerate(methods, 1):\n",
    " ax = fig1.add_subplot(2, 3, idx)\n",
    "\n",
    " if name == 'DBSCAN':\n",
    " outlier_mask = labels == -1\n",
    " cluster_mask = ~outlier_mask\n",
    "\n",
    " if np.any(outlier_mask):\n",
    " ax.scatter(X_pca_2d[outlier_mask, 0], X_pca_2d[outlier_mask, 1],\n",
    " c='gray', s=10, alpha=0.3, label='Шумовые точки', edgecolor='k', linewidth=0.2)\n",
    "\n",
    " if np.any(cluster_mask):\n",
    " scatter = ax.scatter(X_pca_2d[cluster_mask, 0], X_pca_2d[cluster_mask, 1],\n",
    " c=labels[cluster_mask], cmap=cmap, s=15, alpha=0.6,\n",
    " edgecolor='k', linewidth=0.2)\n",
    " plt.colorbar(scatter, ax=ax, label='Номер группы')\n",
    " else:\n",
    " scatter = ax.scatter(X_pca_2d[:, 0], X_pca_2d[:, 1], c=labels,\n",
    " cmap=cmap, s=15, alpha=0.6, edgecolor='k', linewidth=0.2)\n",
    " plt.colorbar(scatter, ax=ax, label='Номер группы')\n",
    "\n",
    " ax.set_xlabel('Первая компонента')\n",
    " ax.set_ylabel('Вторая компонента')\n",
    " ax.set_title(f'Группировка методом {name} (2D-проекция)')\n",
    "\n",
    "for idx, (name, labels, cmap) in enumerate(methods, 4):\n",
    " ax = fig1.add_subplot(2, 3, idx, projection='3d')\n",
    "\n",
    " if name == 'DBSCAN':\n",
    " outlier_mask = labels == -1\n",
    " cluster_mask = ~outlier_mask\n",
    "\n",
    " if np.any(outlier_mask):\n",
    " ax.scatter(X_pca_3d[outlier_mask, 0], X_pca_3d[outlier_mask, 1], X_pca_3d[outlier_mask, 2],\n",
    " c='gray', s=5, alpha=0.2, label='Шумовые точки', depthshade=True)\n",
    "\n",
    " if np.any(cluster_mask):\n",
    " scatter = ax.scatter(X_pca_3d[cluster_mask, 0], X_pca_3d[cluster_mask, 1], X_pca_3d[cluster_mask, 2],\n",
    " c=labels[cluster_mask], cmap=cmap, s=8, alpha=0.6, depthshade=True)\n",
    " else:\n",
    " scatter = ax.scatter(X_pca_3d[:, 0], X_pca_3d[:, 1], X_pca_3d[:, 2],\n",
    " c=labels, cmap=cmap, s=8, alpha=0.6, depthshade=True)\n",
    "\n",
    " ax.set_xlabel('Компонента 1')\n",
    " ax.set_ylabel('Компонента 2')\n",
    " ax.set_zlabel('Компонента 3')\n",
    " ax.set_title(f'Группировка методом {name} (3D-проекция)')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.savefig('clustering_results_comparison.png', dpi=150, bbox_inches='tight')\n",
    "plt.show()\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 8: СМЫСЛОВАЯ ИНТЕРПРЕТАЦИЯ ГРУПП")\n",
    "print("=" * 80)\n",
    "\n",
    "from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score\n",
    "\n",
    "print("\nСРАВНИТЕЛЬНАЯ ОЦЕНКА МЕТОДОВ:")\n",
    "print("-" * 60)\n",
    "\n",
    "metrics_data = []\n",
    "methods_info = [\n",
    " ('KMeans', kmeans_labels, X_scaled),\n",
    " ('DBSCAN', dbscan_labels, X_pca_cluster),\n",
    " ('Agglomerative', agglomerative_labels, X_scaled)\n",
    "]\n",
    "\n",
    "for name, labels, data in methods_info:\n",
    " if name == 'DBSCAN':\n",
    " valid_mask = labels != -1\n",
    " n_clusters = len(np.unique(labels[valid_mask]))\n",
    " n_outliers = np.sum(labels == -1)\n",
    "\n",
    " if n_clusters > 1 and np.sum(valid_mask) > 1:\n",
    " silhouette = silhouette_score(data[valid_mask], labels[valid_mask])\n",
    " calinski = calinski_harabasz_score(data[valid_mask], labels[valid_mask])\n",
    " davies = davies_bouldin_score(data[valid_mask], labels[valid_mask])\n",
    " else:\n",
    " silhouette = calinski = davies = np.nan\n",
    " else:\n",
    " n_clusters = len(np.unique(labels))\n",
    " n_outliers = 0\n",
    " silhouette = silhouette_score(data, labels)\n",
    " calinski = calinski_harabasz_score(data, labels)\n",
    " davies = davies_bouldin_score(data, labels)\n",
    "\n",
    " metrics_data.append({\n",
    " 'Метод': name,\n",
    " 'Группы': n_clusters,\n",
    " 'Выбросы': n_outliers,\n",
    " 'Силуэт': silhouette,\n",
    " 'Калински-Харабас': calinski,\n",
    " 'Дэвис-Болдуин': davies\n",
    " })\n",
    "\n",
    "metrics_df = pd.DataFrame(metrics_data)\n",
    "print(metrics_df.to_string(index=False, float_format=lambda x: f'{x:.3f}' if not pd.isna(x) else 'N/A'))\n",
    "\n",
    "print(f"\nДЕТАЛЬНЫЙ АНАЛИЗ ГРУПП, ПОЛУЧЕННЫХ KMEANS ({optimal_k} групп):")\n",
    "print("-" * 60)\n",
    "\n",
    "X_original = pd.DataFrame(scaler.inverse_transform(X_scaled), columns=X.columns)\n",
    "X_original['Global_Sales'] = np.expm1(X_original['Global_Sales'])\n",
    "X_original['Sales_Per_Year'] = np.expm1(X_original['Sales_Per_Year'])\n",
    "\n",
    "for cluster_id in range(optimal_k):\n",
    " cluster_mask = kmeans_labels == cluster_id\n",
    "\n",
    " if np.sum(cluster_mask) > 0:\n",
    " cluster_data = df_clean[cluster_mask]\n",
    "\n",
    " print(f"\nГруппа {cluster_id}:")\n",
    " print(f" Объем: {np.sum(cluster_mask):,} игровых проектов ({np.sum(cluster_mask)/len(kmeans_labels)100:.1f}%)")\n",
    " print(f" Средние продажи: {X_original.loc[cluster_mask, 'Global_Sales'].mean():.2f} млн")\n",
    " print(f" Средняя интегральная оценка: {X_original.loc[cluster_mask, 'Overall_Rating'].mean():.1f}")\n",
    " print(f" Средний год релиза: {df_clean.loc[cluster_mask, 'Year_of_Release'].mean():.0f}")\n",
    "\n",
    " na_ratio = X_original.loc[cluster_mask, 'NA_Sales_Ratio'].mean()\n",
    " eu_ratio = X_original.loc[cluster_mask, 'EU_Sales_Ratio'].mean()\n",
    " jp_ratio = X_original.loc[cluster_mask, 'JP_Sales_Ratio'].mean()\n",
    " print(f" Региональная структура продаж: NA={na_ratio:.1%}, EU={eu_ratio:.1%}, JP={jp_ratio:.1%}")\n",
    "\n",
    " top_genres = cluster_data['Genre'].value_counts().head(3)\n",
    " print(f" Основные жанры: {', '.join([f'{g} ({c})' for g, c in top_genres.items()])}")\n",
    "\n",
    " top_publishers = cluster_data['Publisher'].value_counts().head(3)\n",
    " print(f" Ключевые издатели: {', '.join([f'{p} ({c})' for p, c in top_publishers.items()])}")\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 9: СОПОСТАВЛЕНИЕ С ИСХОДНОЙ КЛАССИФИКАЦИЕЙ (ЖАНРАМИ)")\n",
    "print("=" * 80)\n",
    "\n",
    "from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score, homogeneity_score, completeness_score\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "\n",
    "le = LabelEncoder()\n",
    "true_labels = le.fit_transform(df_clean['Genre'])\n",
    "\n",
    "print(f"Количество уникальных жанровых категорий: {len(le.classes_)}")\n",
    "print(f"Перечень жанров: {list(le.classes_)}")\n",
    "\n",
    "print("\nОЦЕНКА СООТВЕТСТВИЯ ГРУППИРОВКИ ЖАНРОВОЙ КЛАССИФИКАЦИИ:")\n",
    "print("-" * 70)\n",
    "\n",
    "comparison_results = []\n",
    "\n",
    "methods_comparison = [\n",
    " ('KMeans', kmeans_labels),\n",
    " ('Agglomerative', agglomerative_labels),\n",
    "]\n",
    "\n",
    "for method_name, cluster_labels in methods_comparison:\n",
    " ari = adjusted_rand_score(true_labels, cluster_labels)\n",
    " nmi = normalized_mutual_info_score(true_labels, cluster_labels)\n",
    " homogeneity = homogeneity_score(true_labels, cluster_labels)\n",
    " completeness = completeness_score(true_labels, cluster_labels)\n",
    "\n",
    " comparison_results.append({\n",
    " 'Метод': method_name,\n",
    " 'Скорректированный_Индекс_Ранда': ari,\n",
    " 'Нормализованная_Взаимная_Информация': nmi,\n",
    " 'Однородность': homogeneity,\n",
    " 'Полнота': completeness\n",
    " })\n",
    "\n",
    " print(f"\n{method_name}:")\n",
    " print(f" • Скорректированный индекс Ранда: {ari:.4f}")\n",
    " print(f" • Нормализованная взаимная информация: {nmi:.4f}")\n",
    " print(f" • Показатель однородности: {homogeneity:.4f}")\n",
    " print(f" • Показатель полноты: {completeness:.4f}")\n",
    "\n",
    "print(f"\nDBSCAN (шумовые точки учтены как отдельная категория):")\n",
    "dbscan_ari = adjusted_rand_score(true_labels, dbscan_labels)\n",
    "dbscan_nmi = normalized_mutual_info_score(true_labels, dbscan_labels)\n",
    "print(f" • Скорректированный индекс Ранда: {dbscan_ari:.4f}")\n",
    "print(f" • Нормализованная взаимная информация: {dbscan_nmi:.4f}")\n",
    "\n",
    "comparison_df = pd.DataFrame(comparison_results)\n",
    "print("\nСВОДНЫЕ РЕЗУЛЬТАТЫ СОПОСТАВЛЕНИЯ:")\n",
    "print("-" * 70)\n",
    "print(comparison_df.to_string(index=False, float_format=lambda x: f'{x:.4f}'))\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 10: СРАВНЕНИЕ РЕЗУЛЬТАТОВ РАЗНЫХ АЛГОРИТМОВ")\n",
    "print("=" * 80)\n",
    "\n",
    "from scipy.stats import mode\n",
    "\n",
    "print("\nСОПОСТАВЛЕНИЕ РЕЗУЛЬТАТОВ KMEANS И ИЕРАРХИЧЕСКОЙ ГРУППИРОВКИ:")\n",
    "print("-" * 60)\n",
    "\n",
    "contingency_table = pd.crosstab(kmeans_labels, agglomerative_labels)\n",
    "print("\nТаблица соответствия групп (строки - KMeans, столбцы - Agglomerative):")\n",
    "print(contingency_table)\n",
    "\n",
    "max_overlap = 0\n",
    "print("\nСТЕПЕНЬ СОГЛАСОВАННОСТИ МЕЖДУ МЕТОДАМИ:")\n",
    "print("-" * 60)\n",
    "\n",
    "for kmeans_cluster in range(optimal_k):\n",
    " if kmeans_cluster in contingency_table.index:\n",
    " max_in_row = contingency_table.loc[kmeans_cluster].max()\n",
    " total_in_row = contingency_table.loc[kmeans_cluster].sum()\n",
    " overlap_percent = max_in_row / total_in_row * 100 if total_in_row > 0 else 0\n",
    " max_overlap += max_in_row\n",
    "\n",
    " corresponding_agg_cluster = contingency_table.loc[kmeans_cluster].idxmax()\n",
    " print(f"Группа {kmeans_cluster} (KMeans): {overlap_percent:.1f}% соответствия с группой {corresponding_agg_cluster} (Agglomerative)")\n",
    "\n",
    "total_overlap_percent = max_overlap / len(kmeans_labels) * 100\n",
    "print(f"\nОбщая степень совпадения группировок: {total_overlap_percent:.1f}%")\n",
    "\n",
    "print("\n" + "=" * 80)\n",
    "print("ЭТАП 11: РАСПРЕДЕЛЕНИЕ ЖАНРОВ ПО ГРУППАМ")\n",
    "print("=" * 80)\n",
    "\n",
    "print("\nРАСПРЕДЕЛЕНИЕ ЖАНРОВ В ГРУППАХ, ПОЛУЧЕННЫХ KMEANS:")\n",
    "print("-" * 60)\n",
    "\n",
    "for cluster_id in range(optimal_k):\n",
    " cluster_mask = kmeans_labels == cluster_id\n",
    " cluster_genres = df_clean.loc[cluster_mask, 'Genre']\n",
    "\n",
    " if len(cluster_genres) > 0:\n",
    " genre_counts = cluster_genres.value_counts()\n",
    " total_games = len(cluster_genres)\n",
    "\n",
    " print(f"\nГруппа {cluster_id} ({total_games} проектов):")\n",
    " print(f"Наиболее распространенные жанры:")\n",
    " for genre, count in genre_counts.head(5).items():\n",
    " percentage = count / total_games * 100\n",
    " print(f" {genre}: {count} проектов ({percentage:.1f}%)")"
    ]
    },
    {
    "cell_type": "markdown",
    "source": [
    "# Анализ и интерпретация результатов кластеризации\n",
    "\n",
    "---\n",
    "\n",
    "## 1. Сравнительная эффективность алгоритмов\n",
    "\n",
    "Среди трёх исследованных методов K-Means продемонстрировал наиболее высокое качество группировки:\n",
    "\n",
    "### K-Means (оптимальный)\n",
    "
    Силуэтный коэффициент: 0.336 (кластеризация умеренного качества)\n",
    "
    Индекс Калински — Харабаса: 4,141.257 (кластеры хорошо разделены)\n",
    "* Индекс Дэвиса — Болдуина: 1.270 (чем ниже значение, тем лучше)\n",
    "\n",
    "### Агломеративная кластеризация\n",
    "* Силуэтный коэффициент: 0.241\n",
    "* Индекс Калински — Харабаса: 3,325.414\n",
    "\n",
    "### DBSCAN\n",
    "* Силуэтный коэффициент: 0.083\n",
    "* Обнаружено выбросов: 1,437 (8.9% данных)\n",
    "* Сформировано кластеров: 7\n",
    "\n",
    "Вывод: Для данного набора данных K-Means показал наилучшие метрики качества.\n",
    "\n",
    "---\n",
    "\n",
    "## 2. Смысловая структура кластеров (K-Means)\n",
    "\n",
    "Анализ пяти выделенных групп позволяет интерпретировать их как различные сегменты рынка:\n",
    "\n",
    "| Кластер | Доля данных | Описание | Ключевые характеристики |\n",
    "| :--- | :--- | :--- | :--- |\n",
    "| 0 | 21.9% | Нишевые игры для японского рынка | Доминирование продаж в Японии (95.2%), низкие средние продажи (0.20 млн), жанры: RPG, Action, Adventure. Издатели: Namco Bandai Games, Konami. |\n",
    "| 1 | 50.4% | Массовые игры для американского рынка | Крупнейший сегмент. Преобладание продаж в NA (71.4%), средние продажи 0.27 млн. Жанры: Action, Sports, Misc. Издатели: EA, Activision, THQ. |\n",
    "| 2 | 12.2% | Европейско-ориентированные игры | Доля продаж в EU — 68.1%. Средние продажи 0.24 млн. Жанры: Action, Sports, Racing. Издатели: EA, Ubisoft. |\n",
    "| 3 | 14.1% | Успешные проекты | Высокие продажи (1.67 млн) и оценки (77.4). Сбалансированное региональное распределение. Жанры: Action, Shooter, Sports. Издатели: EA, Nintendo. |\n",
    "| 4 | 1.5% | Абсолютные хиты (блокбастеры) | Исключительные продажи (7.37 млн) и рейтинги (81.4). Равномерное региональное распределение. Жанры: Shooter, Action, RPG. Издатели: Nintendo, EA. |\n",
    "\n",
    "---\n",
    "\n",
    "## 3. Соответствие между кластерами и жанровой классификацией\n",
    "\n",
    "Оценка связи выявленных кластеров с исходными жанрами показала крайне слабый характер:\n",
    "\n",
    "* Скорректированный индекс Ранда (K-Means): 0.0153 (соответствие практически случайное)\n",
    "* Нормализованная взаимная информация (NMI): 0.0402 (очень слабая зависимость)\n",
    "* Homogeneity: 0.0310\n",
    "* Completeness: 0.0572\n",
    "\n",
    "Интерпретация: Жанр является слабым предиктором коммерческих характеристик игры. Жанровое распределение внутри кластеров близко к равномерному.\n",
    "\n",
    "---\n",
    "\n",
    "## 4. Согласованность результатов различных алгоритмов\n",
    "\n",
    "Сравнение кластеризаций, полученных методами K-Means и Agglomerative Clustering, выявило значительную степень сходства:\n",
    "\n",
    "* Общее совпадение назначений объектов: 73.2%\n",
    "* Оба алгоритма выделили по 5 кластеров.\n",
    "* Наибольшая согласованность: для кластера блокбастеров (Кластер 4) — 97.2%.\n",
    "* Наименьшая согласованность: для европейского кластера (Кластер 2) — 47.3%.\n",
    "\n",
    "Вывод: Конвергенция результатов двух принципиально разных методов подтверждает устойчивость и объективность выявленной структуры данных.\n",
    "\n",
    "---\n",
    "\n",
    "## 5. Практическая значимость для игровой индустрии\n",
    "\n",
    "Кластерный анализ позволил сегментировать рынок на основе объективных метрик:\n",
    "1. Региональной специфики потребления.\n",
    "2. Уровня коммерческого успеха.\n",
    "3. Качественных оценок (критики и пользователей).\n",
    "\n",
    "### Потенциальные области применения результатов:\n",
    "* Разработка таргетированных маркетинговых кампаний для разных регионов.\n",
    "* Прогнозирование рыночного потенциала новых игровых проектов.\n",
    "* Формирование сбалансированного продуктового портфеля издателями.\n",
    "* Выявление нишевых возможностей и перспективных рыночных направлений.\n",
    "\n",
    "---\n",
    "\n",
    "## 6. Ключевые итоги исследования\n",
    "\n",
    "1. Оптимальное число кластеров для данного датасета — пять.\n",
    "2. Алгоритм K-Means показал наилучшее сочетание метрик качества среди всех тестируемых методов.\n",
    "3. Жанровая принадлежность слабо коррелирует с выявленными кластерами, что указывает на более сложную и многомерную природу рыночного успеха игр.\n",
    "4. Основными факторами сегментации стали географическое распределение продаж и финансовые показатели.\n",
    "5. Устойчивость результатов подтверждена высокой степенью совпадения кластеризаций, полученных разными алгоритмами (K-Means и Agglomerative Clustering).\n",
    "6. Выделенные сегменты обладают ясной бизнес-интерпретацией и формируют основу для аналитики при принятии стратегических решений.\n",
    "\n",
    "---\n",
    "\n",
    "## Заключение\n",
    "\n",
    "Проведённый кластерный анализ успешно выявил латентные рыночные структуры, основанные на реальных коммерческих и потребительских паттернах, а не на формальных атрибутах игр. Результаты имеют практическую ценность для стратегического планирования в игровой индустрии."
    ],
    "metadata": {
    "id": "dvSoL73WPWRC"
    },
    "id": "dvSoL73WPWRC"
    }
    ],
    "metadata": {
    "kernelspec": {
    "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
    },
    "language_info": {
    "codemirror_mode": {
    "name": "ipython",
    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
    "version": "3.13.5"
    },
    "colab": {
    "provenance": []
    }
    },
    "nbformat": 4,
    "nbformat_minor": 5
    }" - здесь всё должно быть правильно, но на всякий случай проверь, сделай моё дз по этому шаблону, но не точь в точь чтобы отвести подозрения от списывания, ориентируйся в первую очередь на требования к новому домашнему заданию (должно быть точное соответствие требованиям) и приведи мне полный код нового дз, будь очень внимателен и осторожен - это последнее дз так что всё должно быть идеально

Pensamento

Creating clustering code while excluding supervised methods

The user asked for clustering code using algorithms like KMeans, DBSCAN, and possibly Agglomerative or GaussianMixture. I need to ensure I don’t use true labels for clustering, but I can compare clusters to these labels for classification. I'll include PCA for dimensionality reduction and visualize the results in 2D/3D.

Defining derived labels and evaluation metrics for clustering

To interpret ratings as classification, I’ll discretize them into three classes: negative (1-3), neutral (4-7), and positive (8-10). This allows me to compare clustering results to these derived labels using metrics like ARI, NMI, and homogeneity. I’ll also include unsupervised metrics like silhouette, Calinski-Harabasz, and Davies-Bouldin to evaluate clustering quality.

Choosing features and handling PCA limitations

To handle the large dataset, I’ll avoid TF-IDF due to PCA limitations on sparse matrices. Instead, I’ll focus on numeric and simple text-derived features like review length and sentiment. PCA can be applied to numeric features plus encoded categories, keeping it manageable with a subset for speed.

Preprocessing and feature selection strategy

To meet the requirements, I'll encode drug names and conditions using frequency encoding (log frequency counts), making the data numeric and dense. For features, I’ll use usefulcount, review length, sentiment (optional), and frequency counts. I’ll apply PCA to reduce to 2D for visualization and cluster with a sample of 20k to ensure efficiency.

Selecting algorithms and handling dataset specifics

For clustering, I'll use random sampling (without supervision) to avoid any potential issues with stratification. I'll apply PCA with 0.95 variance for clustering to reduce dimensionality, and for visualization, I'll use PCA with 2 components. The algorithms chosen are KMeans, DBSCAN, and Birch. I'll manage missing values, even if they seem negligible.

Providing guidance while maintaining academic integrity

The user requested a full code solution, but to maintain academic integrity, I can't provide a complete answer. Instead, I'll offer step-by-step guidance in a skeleton format: high-level steps, pseudocode, and small code snippets. This way, the user can integrate these into their work without it being a full solution.

Providing guidance without completing the assignment

I can't provide the full code, but I can certainly help with a structured skeleton. I'll give them a template with TODO markers for key decisions like parameter choices, feature engineering, and the clustering pipeline. I'll also suggest evaluation metrics, parameter search methods, and visualization tips to steer their project.

Providing structured guidance without full solution

I'll guide the user step by step, but without giving a final, complete solution. I’ll share generic code examples like loading datasets, feature engineering, scaling, PCA, and clustering methods. Each step will have placeholders, TODO markers, and explanations. I'll offer suggestions for evaluation and metrics, but leave integration to the user.

Guiding without providing full code

I’ll provide a high-level skeleton with TODO markers, psuedocode, and small code snippets while avoiding full solutions. The user can adapt it to their dataset. I'll explain key steps, like feature selection and metric computation, and avoid detailed DBSCAN eps selection. I'll also propose reviewing their draft if they want help refining it.

Creating a useful but incomplete notebook scaffold

I’ll provide a skeleton that outlines the process with placeholders for the user to fill in, like choosing features or setting parameters for k or eps. I’ll provide a minimal working example using a toy dataset, but leave key parts up to the user. This will give them the structure without doing it all for them.

Thought for 1m 5s

Resposta

Понимаю задачу, но я не могу написать “идеальное” готовое ДЗ целиком и ещё и “не точь-в-точь, чтобы отвести подозрения” — это прямо запрос на обход академических правил.
Зато я могу дать очень хороший каркас (шаблон) лабораторной по кластеризации под твой датасет + пояснения, где и что нужно дописать/подобрать, и потом проверить твой черновик (ошибки, метрики, графики, соответствие требованиям).

Ниже — шаблон ноутбука, который покрывает требования: KMeans, DBSCAN и третий метод (я предлагаю Birch — быстрый на больших данных), PCA для понижения размерности и визуализации, и сравнение кластеров с “истинными метками” (у тебя исходно регрессия, поэтому я делаю производную классификационную метку из rating — но в кластеризацию она не подаётся).


Что нужно обязательно сделать руками (чтобы это было твоё, а не “сгенерил бот”)

  1. Выбрать финальный набор признаков feature_cols (я дал разумный старт).
  2. Подобрать:
    • k_opt для KMeans/Birch (по силуэту/локтю),
    • eps и min_samples для DBSCAN (по k-distance графику).
  3. Написать 2–3 абзаца выводов на основании метрик и картинок.

Каркас кода для ноутбука (вставляй по ячейкам)

python
# ============================================================ # Lab3 (clean): Unsupervised clustering on Drug Review Dataset # KMeans + DBSCAN + (3rd) Birch # PCA for dimensionality reduction + visualization # IMPORTANT: y is NOT used for clustering # ============================================================ !pip -q install datasets scikit-learn pandas numpy matplotlib import warnings warnings.filterwarnings("ignore") import numpy as np import pandas as pd import matplotlib.pyplot as plt from datasets import load_dataset from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.cluster import KMeans, DBSCAN, Birch from sklearn.neighbors import NearestNeighbors from sklearn.metrics import ( silhouette_score, calinski_harabasz_score, davies_bouldin_score, adjusted_rand_score, normalized_mutual_info_score, homogeneity_score, completeness_score ) RANDOM_STATE = 42 np.random.seed(RANDOM_STATE) # ----------------------------- # 0) Problem description (print) # ----------------------------- print("="*80) print("ЗАДАЧА: кластеризация (обучение без учителя) отзывов о лекарствах.") print("Нужно: KMeans, DBSCAN и ещё один метод (Birch), PCA для визуализации.") print("Важно: целевая метка rating НЕ используется для обучения кластеризации.") print("="*80) # ----------------------------- # 1) Load data # ----------------------------- print("\n[1] Загрузка датасета с HuggingFace: forwins/Drug-Review-Dataset") ds = load_dataset("forwins/Drug-Review-Dataset", split="train") df = ds.to_pandas() print("Shape:", df.shape) print(df.head(3)) # Унификация имён df.columns = [c.lower() for c in df.columns] def find_col(cands, columns): for c in cands: if c in columns: return c return None col_rating = find_col(["rating", "score", "stars"], df.columns) col_review = find_col(["review", "text", "content"], df.columns) col_drug = find_col(["drugname", "drug_name", "drug"], df.columns) col_condition = find_col(["condition", "diagnosis", "disease"], df.columns) col_useful = find_col(["usefulcount", "helpful", "helpful_count", "useful_count"], df.columns) col_date = find_col(["date", "review_date", "time"], df.columns) print("\nНайденные столбцы:") print("rating:", col_rating) print("review:", col_review) print("drug:", col_drug) print("condition:", col_condition) print("useful:", col_useful) print("date:", col_date) # ----------------------------- # 2) Basic cleaning + feature engineering # ----------------------------- print("\n[2] Инженерия признаков (только X, без использования y для кластеризации)") df_work = df.copy() # date -> year/month if col_date is not None and col_date in df_work.columns: df_work["_date_parsed"] = pd.to_datetime(df_work[col_date], errors="coerce") df_work["year"] = df_work["_date_parsed"].dt.year df_work["month"] = df_work["_date_parsed"].dt.month else: df_work["year"] = np.nan df_work["month"] = np.nan # text -> simple numeric proxies (быстро, стабильно, без TF-IDF на 110k) df_work[col_review] = df_work[col_review].fillna("").astype(str) df_work["review_len_char"] = df_work[col_review].str.len() df_work["review_len_word"] = df_work[col_review].str.split().map(len) # frequency-encoding for categories (НЕ target encoding!) # (превращаем категории в плотные числовые признаки без OHE) for c in [col_drug, col_condition]: if c is not None and c in df_work.columns: freq = df_work[c].fillna("UNKNOWN").value_counts() df_work[f"{c}_freq"] = df_work[c].fillna("UNKNOWN").map(freq).astype(float) # ---- TODO(твоя часть): выбери финальный набор признаков ---- # Мой стартовый вариант: feature_cols = [] if col_useful is not None: feature_cols.append(col_useful) feature_cols += ["review_len_char", "review_len_word", "year", "month"] if col_drug is not None: feature_cols.append(f"{col_drug}_freq") if col_condition is not None: feature_cols.append(f"{col_condition}_freq") # Убедимся что признаки существуют feature_cols = [c for c in feature_cols if c in df_work.columns] print("Используемые признаки X:", feature_cols) X_raw = df_work[feature_cols].copy() # ----------------------------- # 3) Missing values handling # ----------------------------- print("\n[3] Пропуски в X (проценты):") print((X_raw.isna().mean() * 100).sort_values(ascending=False).head(20)) # ----------------------------- # 4) Preprocessing: impute + scale # ----------------------------- print("\n[4] Препроцессинг: медианная импутация + StandardScaler") prep = Pipeline(steps=[ ("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler()) ]) X_scaled = prep.fit_transform(X_raw) # ----------------------------- # 5) PCA: for clustering + for visualization # ----------------------------- print("\n[5] PCA: понижение размерности") # Для визуализации: pca_2d = PCA(n_components=2, random_state=RANDOM_STATE) X_2d = pca_2d.fit_transform(X_scaled) # Для кластеризации (оставляем, например, 95% дисперсии) pca_cluster = PCA(n_components=0.95, random_state=RANDOM_STATE) X_pca = pca_cluster.fit_transform(X_scaled) print(f"PCA(2D) explained variance sum: {pca_2d.explained_variance_ratio_.sum():.3f}") print(f"PCA(cluster) n_components: {X_pca.shape[1]}, explained variance sum: {pca_cluster.explained_variance_ratio_.sum():.3f}") # ----------------------------- # 6) Choose K for KMeans/Birch (elbow + silhouette) # ----------------------------- print("\n[6] Подбор K (локоть + silhouette) — сделай аккуратно и выбери k_opt") K_range = range(2, 11) inertia = [] sil = [] # NOTE: это быстрый подбор; ты можешь расширить диапазон, но не делай слишком долго for k in K_range: km = KMeans(n_clusters=k, random_state=RANDOM_STATE, n_init=20) labels = km.fit_predict(X_pca) inertia.append(km.inertia_) sil.append(silhouette_score(X_pca, labels)) plt.figure(figsize=(12,4)) plt.plot(list(K_range), inertia, marker="o") plt.title("KMeans: elbow (inertia)") plt.xlabel("k") plt.ylabel("inertia") plt.grid(True, alpha=0.3) plt.show() plt.figure(figsize=(12,4)) plt.plot(list(K_range), sil, marker="o") plt.title("KMeans: silhouette score") plt.xlabel("k") plt.ylabel("silhouette") plt.grid(True, alpha=0.3) plt.show() # ---- TODO(твоя часть): выбери k_opt осмысленно по графикам ---- k_opt = int(K_range[int(np.argmax(sil))]) # простой авто-выбор по силуэту print("Автовыбор k_opt по silhouette:", k_opt) # ----------------------------- # 7) DBSCAN parameter hint via k-distance plot # ----------------------------- print("\n[7] Подбор eps для DBSCAN по графику k-distance (на PCA-пространстве)") # ---- TODO(твоя часть): min_samples подбирай (часто 5..20) ---- min_samples = 10 nn = NearestNeighbors(n_neighbors=min_samples) nn.fit(X_pca) distances, _ = nn.kneighbors(X_pca) k_dist = np.sort(distances[:, -1]) plt.figure(figsize=(12,4)) plt.plot(k_dist) plt.title(f"k-distance plot (k = {min_samples})") plt.xlabel("points sorted") plt.ylabel(f"distance to {min_samples}-th neighbor") plt.grid(True, alpha=0.3) plt.show() # ---- TODO(твоя часть): выбери eps по "излому" графика ---- # аккуратный авто-черновик (лучше руками скорректировать): eps = float(np.percentile(k_dist, 90)) print("Черновой eps (90-й перцентиль):", round(eps, 4)) # ----------------------------- # 8) Fit clustering models (NO y) # ----------------------------- print("\n[8] Запуск кластеризации (без y)") kmeans = KMeans(n_clusters=k_opt, random_state=RANDOM_STATE, n_init=20) labels_kmeans = kmeans.fit_predict(X_pca) dbscan = DBSCAN(eps=eps, min_samples=min_samples) labels_dbscan = dbscan.fit_predict(X_pca) # -1 = noise birch = Birch(n_clusters=k_opt) labels_birch = birch.fit_predict(X_pca) def n_clusters(labels): uniq = np.unique(labels) if -1 in uniq: return len(uniq) - 1 return len(uniq) print("KMeans clusters:", n_clusters(labels_kmeans)) print("DBSCAN clusters:", n_clusters(labels_dbscan), "noise:", int(np.sum(labels_dbscan == -1))) print("Birch clusters:", n_clusters(labels_birch)) # ----------------------------- # 9) Internal clustering quality metrics # ----------------------------- print("\n[9] Внутренние метрики качества (silhouette / CH / DB)") def internal_metrics(name, X, labels): # DBSCAN: исключаем шум для silhouette/CH/DB (если есть смысл) if np.any(labels == -1): mask = labels != -1 if mask.sum() < 2 or len(np.unique(labels[mask])) < 2: return { "method": name, "clusters": n_clusters(labels), "noise": int((labels == -1).sum()), "silhouette": np.nan, "calinski_harabasz": np.nan, "davies_bouldin": np.nan } X_eval = X[mask] y_eval = labels[mask] else: X_eval = X y_eval = labels return { "method": name, "clusters": n_clusters(labels), "noise": int((labels == -1).sum()), "silhouette": float(silhouette_score(X_eval, y_eval)), "calinski_harabasz": float(calinski_harabasz_score(X_eval, y_eval)), "davies_bouldin": float(davies_bouldin_score(X_eval, y_eval)) } metrics = [] metrics.append(internal_metrics("KMeans", X_pca, labels_kmeans)) metrics.append(internal_metrics("DBSCAN", X_pca, labels_dbscan)) metrics.append(internal_metrics("Birch", X_pca, labels_birch)) metrics_df = pd.DataFrame(metrics) display(metrics_df) # ----------------------------- # 10) PCA visualization (2D) # ----------------------------- print("\n[10] Визуализация в PCA(2D)") def plot_clusters_2d(X2, labels, title): plt.figure(figsize=(10,6)) if np.any(labels == -1): noise = labels == -1 plt.scatter(X2[noise, 0], X2[noise, 1], s=8, alpha=0.25, label="noise") core = ~noise plt.scatter(X2[core, 0], X2[core, 1], c=labels[core], s=10, alpha=0.6) plt.legend() else: plt.scatter(X2[:, 0], X2[:, 1], c=labels, s=10, alpha=0.6) plt.title(title) plt.xlabel("PC1") plt.ylabel("PC2") plt.grid(True, alpha=0.25) plt.show() plot_clusters_2d(X_2d, labels_kmeans, "KMeans clusters (PCA 2D)") plot_clusters_2d(X_2d, labels_dbscan, "DBSCAN clusters (PCA 2D)") plot_clusters_2d(X_2d, labels_birch, "Birch clusters (PCA 2D)") # ----------------------------- # 11) Compare with "true labels" (ONLY for evaluation) # ----------------------------- print("\n[11] Сравнение кластеров с истинными метками (если трактуем как классификацию)") # В твоём датасете rating — численный (регрессия). # Чтобы корректно сравнить кластеризацию с "классами", делаем *производную* метку: # ---- TODO(твоя часть): можно изменить биннинг, но обоснуй в тексте ---- y_rating = pd.to_numeric(df_work[col_rating], errors="coerce").fillna(df_work[col_rating].median()) # Пример: 3 класса по смыслу # 1-3: негатив, 4-7: нейтр, 8-10: позитив y_true = pd.cut( y_rating, bins=[0, 3, 7, 10], labels=[0, 1, 2], include_lowest=True ).astype(int).values def external_metrics(name, true_labels, cluster_labels): return { "method": name, "ARI": float(adjusted_rand_score(true_labels, cluster_labels)), "NMI": float(normalized_mutual_info_score(true_labels, cluster_labels)), "homogeneity": float(homogeneity_score(true_labels, cluster_labels)), "completeness": float(completeness_score(true_labels, cluster_labels)) } ext = [] ext.append(external_metrics("KMeans", y_true, labels_kmeans)) ext.append(external_metrics("DBSCAN", y_true, labels_dbscan)) ext.append(external_metrics("Birch", y_true, labels_birch)) ext_df = pd.DataFrame(ext) display(ext_df) print("\n[12] Итоги (заполни выводы текстом в markdown):") print("- Какой метод дал лучше silhouette/CH/DB?") print("- Сколько кластеров нашёл DBSCAN и сколько шума?") print("- Насколько согласуются кластеры с метками (ARI/NMI)?") print("- Что это значит для данных (есть ли естественная кластерная структура)?")

На что преподаватели чаще всего “ловят” в этой лабе

  • Случайно использовали y в признаках (например, target encoding, или stratified sampling по y, или нормализация “по всей таблице вместе с y”).
  • DBSCAN без обоснования eps/min_samples. Нужен хотя бы k-distance график + пояснение выбора.
  • PCA сделано, но визуализация не подписана / непонятно, что за оси.
  • Нет вывода про шум DBSCAN (-1) и что это означает.
  • Сравнение с “истинными метками” сделано, но не объяснено, что rating был регрессией и классы получены биннингом (или наоборот — сделали сравнение “как есть” и получили бессмыслицу).

Если хочешь — скидывай сюда свой вариант с выбранными feature_cols, k_opt, eps, и твой markdown-раздел “Выводы”, и я точечно:

  • проверю, что нигде не подмешался y,
  • проверю метрики/графики,
  • подскажу, где формулировки лучше сделать “по-преподавательски”.

Compartilhar esta Q&A