{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4da9f8d6-b42c-4155-903f-b6d27291f421",
   "metadata": {},
   "source": [
    "**Цель задачи:**\n",
    "Продемонстрировать применение метода **K-Nearest Neighbors (KNN)** для классификации данных.\n",
    "\n",
    "1. Использовать алгоритм KNN из scikit-learn (`sklearn.neighbors.KNeighborsClassifier`).\n",
    "2. Протестировать на двух типах данных:\n",
    "   - Встроенный датасет: `make_classification` (синтетические данные с двумя классами).\n",
    "   - Реальный датасет: (будет загружен из openml.org.).\n",
    "3. Сравнить результаты, визуализировать границы решений и интерпретировать, почему KNN классифицирует объекты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "394082d8-55cd-451e-b682-3d839e21868e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import openml\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.inspection import DecisionBoundaryDisplay"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a796ba9b-c325-4143-be8b-76aa47e80e05",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Получение данных из openml.org: Alcohol Consumption in Russia\n",
    "dataset = openml.datasets.get_dataset(43425)\n",
    "# Получаем данные\n",
    "X, _, _, _ = dataset.get_data(target=None)\n",
    "# Оставляем только числовые признаки\n",
    "X = X.select_dtypes(include=['float64', 'int64'])\n",
    "# ПРЕОБРАЗУЕМ ВСЕ ЗНАЧЕНИЯ В ЧИСЛА, А ? В NaN\n",
    "X = X.apply(pd.to_numeric, errors='coerce')\n",
    "# Удаляем строки с любыми пропусками\n",
    "X = X.dropna()\n",
    "# Создаём целевую переменную (сумма всего алкоголя)\n",
    "total_alcohol = X['wine'] + X['beer'] + X['vodka'] + X['champagne'] + X['brandy']\n",
    "# Удаляем строки, где total_alcohol = NaN (на всякий случай)\n",
    "mask = ~total_alcohol.isnull()\n",
    "X = X[mask]\n",
    "total_alcohol = total_alcohol[mask]\n",
    "# Создаём категории (0 - low, 1 - medium, 2 - high)\n",
    "y = pd.cut(total_alcohol, bins=[0, 20, 40, 200], labels=[0, 1, 2])\n",
    "# ПРЕОБРАЗУЕМ В int И УБЕЖДАЕМСЯ, ЧТО НЕТ NaN\n",
    "y = y.astype('Int64')  # Int64 допускает NaN, но мы их удалим\n",
    "y = y.dropna()\n",
    "# Синхронизируем X и y\n",
    "X = X.loc[y.index]\n",
    "# Преобразуем в numpy int\n",
    "y = y.astype(int).values\n",
    "# Берём только два признака для 2D-визуализации\n",
    "X_vis = X[['vodka', 'beer']].values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5923b3d7-803f-4065-ad90-eeb4d32afd73",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 219496 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(8, 6))\n",
    "colors = ['blue', 'green', 'red']\n",
    "labels = ['Low (<20 l/year)', 'Medium (20-40 l/year)', 'High (>40 l/year)']\n",
    "for i in range(3):\n",
    "    mask = y == i\n",
    "    plt.scatter(X_vis[mask, 0], X_vis[mask, 1], \n",
    "                c=colors[i], label=labels[i], alpha=0.7, edgecolors='k')\n",
    "plt.xlabel(\"Vodka consumption (liters per capita)\")\n",
    "plt.ylabel(\"Beer consumption (liters per capita)\")\n",
    "plt.title(\"Исходные данные (потребление алкоголя)\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "b2c9fbf1-54e3-4667-9b6a-3e45f42bec22",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Разделяем на обучающую и тестовую выборки\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X_vis, y, stratify=y, random_state=0\n",
    ")\n",
    "# Создаём пайплайн с масштабированием и KNN\n",
    "clf = Pipeline([\n",
    "    (\"scaler\", StandardScaler()),\n",
    "    (\"knn\", KNeighborsClassifier(n_neighbors=11))\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b8cd88c3-5d75-4c08-87d7-9ff7951e1537",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 445384 characters]",
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "_, axs = plt.subplots(ncols=2, figsize=(12, 5))\n",
    "for ax, weights in zip(axs, (\"uniform\", \"distance\")):\n",
    "    clf.set_params(knn__weights=weights).fit(X_train, y_train)\n",
    "    \n",
    "    disp = DecisionBoundaryDisplay.from_estimator(\n",
    "        clf,\n",
    "        X_test,\n",
    "        response_method=\"predict\",\n",
    "        plot_method=\"pcolormesh\",\n",
    "        xlabel=\"Vodka consumption (liters per capita)\",\n",
    "        ylabel=\"Beer consumption (liters per capita)\",\n",
    "        shading=\"auto\",\n",
    "        alpha=0.5,\n",
    "        ax=ax,\n",
    "    )\n",
    "    scatter = disp.ax_.scatter(X_vis[:, 0], X_vis[:, 1], c=y, edgecolors=\"k\", cmap='viridis')\n",
    "    disp.ax_.legend(\n",
    "        scatter.legend_elements()[0],\n",
    "        ['Low', 'Medium', 'High'],\n",
    "        loc=\"lower left\",\n",
    "        title=\"Consumption level\",\n",
    "    )\n",
    "    _ = disp.ax_.set_title(\n",
    "        f\"Classification of alcohol consumption\\n(k={clf[-1].n_neighbors}, weights={weights!r})\"\n",
    "    )\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4601660-81de-4fed-b93c-7bef64c03586",
   "metadata": {},
   "source": [
    "**5. Интерпретация результатов**\n",
    "\n",
    "- **Визуальный анализ**\n",
    "  - KNN успешно разделяет три уровня потребления алкоголя.\n",
    "  - Границы решений зависят от выбранных признаков (vodka, beer).\n",
    "\n",
    "- **Почему KNN нашел эти границы?**\n",
    "  - KNN вычисляет расстояния до 11 ближайших соседей.\n",
    "  - Класс новой точки (уровень потребления) определяется голосованием соседей.\n",
    "\n",
    "- **Как улучшить?**\n",
    "  - Добавить больше признаков (wine, champagne, brandy).\n",
    "  - Подобрать оптимальное `n_neighbors`.\n",
    "\n",
    "**Вывод:**  \n",
    "KNN — эффективный алгоритм для классификации уровня потребления алкоголя на основе двух ключевых признаков. Параметр `weights` позволяет гибко настраивать влияние соседей на итоговое решение."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dccb0620-395d-4a3d-b43b-1c6ea6c73e66",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7fce398f-2d61-4af9-b872-5eee4c8941a2",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.14.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
