{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# Урок 7. Практика: Визуализация для риск-комитета\n\n**Цель**: научиться создавать визуализации, которые понимают бизнес-руководители и которые приводят к принятию решений.\n\n**Что понадобится:**\n- `pandas` и `numpy` — для работы с данными\n- `matplotlib` и `seaborn` — для построения графиков\n- `sklearn` — для ROC-кривой\n\n**Задачи:**\n1. Построить \"плохую\" визуализацию (с антипаттернами)\n2. Переделать её в \"хорошую\" (по 5 принципам)\n3. Создать слайд с vintage-анализом\n4. Создать слайд с WOE/IV анализом\n5. Создать слайд с результатами модели\n6. Собрать итоговую презентацию из 3 слайдов\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Импортируем pandas для работы с таблицами\nimport pandas as pd\n\n# Импортируем numpy для числовых расчётов\nimport numpy as np\n\n# Импортируем matplotlib для построения графиков\nimport matplotlib.pyplot as plt\n\n# Импортируем seaborn для красивых графиков\nimport seaborn as sns\n\n# Импортируем функции для ROC-кривой\nfrom sklearn.metrics import roc_curve, auc\n\n# Фиксируем seed для воспроизводимости результатов\nnp.random.seed(42)\n\n# Устанавливаем стиль seaborn (белая сетка)\nsns.set_style('whitegrid')\n\n# Задаём размер фигур по умолчанию: 10x6 дюймов\nplt.rcParams['figure.figsize'] = (10, 6)\n\n# Задаём размер шрифта по умолчанию: 11 pt\nplt.rcParams['font.size'] = 11\n\n# Включаем русский шрифт (если доступен)\n# Для Colab можно использовать DejaVu Sans, который поддерживает кириллицу\nplt.rcParams['font.family'] = 'DejaVu Sans'\n\n# Печатаем сообщение о готовности\nprint(\"Библиотеки загружены\")\n# Печатаем следующую строку отчёта\nprint(\"Готовы создавать визуализации для бизнеса\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 1. Создай данные для визуализации\n\nПодготовим данные о vintage-анализе, IV признаков и результатах модели.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# === ДАННЫЕ 1: Vintage-анализ ===\n\n# Задаём список когорт (кварталы)\ncohorts = ['2023-Q1', '2023-Q2', '2023-Q3', '2023-Q4',\n           '2024-Q1', '2024-Q2', '2024-Q3', '2024-Q4']\n\n# Задаём MOB (Month on Book) — 3, 6, 9, 12 месяцев\nmob_values = [3, 6, 9, 12]\n\n# Создаём словарь с накопленными дефолтами для каждой когорты\n# Нормальные когорты: 2-7% на MOB 12\n# Проблемная когорта 2024-Q1: 4-12% (аномально высокий дефолт)\nvintage_data = {\n    # 2023-Q1: нормальная когорта, \"созрела\"\n    '2023-Q1': [1.5, 3.2, 5.0, 6.2],\n\n    # 2023-Q2: нормальная когорта\n    '2023-Q2': [1.8, 3.5, 5.2, 6.5],\n\n    # 2023-Q3: нормальная когорта\n    '2023-Q3': [1.6, 3.0, 4.8, 6.0],\n\n    # 2023-Q4: нормальная когорта\n    '2023-Q4': [2.0, 3.8, 5.5, 6.8],\n\n    # 2024-Q1: ПРОБЛЕМНАЯ когорта (высокий дефолт!)\n    '2024-Q1': [4.5, 8.5, 10.8, 12.0],\n\n    # 2024-Q2: вернулись к норме после исправления политики\n    '2024-Q2': [1.8, 3.5, 5.0, 6.2],\n\n    # 2024-Q3: норма\n    '2024-Q3': [1.5, 3.0, 4.5, 5.8],\n\n    # 2024-Q4: молодая когорта, только MOB 3 и 6\n    '2024-Q4': [1.2, 2.8, None, None]\n}\n\n# Создаём DataFrame для удобства\nvintage_df = pd.DataFrame(vintage_data, index=mob_values)\n\n# Печатаем информацию\nprint(\"=== Данные vintage-анализа ===\")\n# Печатаем следующую строку отчёта\nprint(f\"Количество когорт: {len(cohorts)}\")\n# Печатаем следующую строку отчёта\nprint(f\"MOB значения: {mob_values}\")\n# Печатаем следующую строку отчёта\nprint(f\"\\nТаблица накопленных дефолтов (%):\")\n# Печатаем следующую строку отчёта\nprint(vintage_df.round(1))\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# === ДАННЫЕ 2: IV признаков ===\n\n# Создаём список признаков для скоринга\nfeatures = [\n    'Кредитный балл',      # самый сильный признак\n    'Количество просрочек', # сильный признак\n    'Долговая нагрузка',   # средний признак\n    'Доход',               # средний признак\n    'Возраст',             # средний признак\n    'Срок занятости',      # слабый признак\n    'Регион',              # слабый признак\n    'Пол',                 # бесполезный признак\n    'Семейное положение',  # бесполезный признак\n    'Образование'          # бесполезный признак\n]\n\n# Задаём значения IV для каждого признака\n# Сильные (>0.1): 5 признаков\n# Слабые (0.02-0.1): 2 признака\n# Бесполезные (<0.02): 3 признака\niv_values = [0.45, 0.38, 0.28, 0.22, 0.18, 0.08, 0.06, 0.01, 0.015, 0.012]\n\n# Создаём DataFrame\niv_df = pd.DataFrame({\n    'Признак': features,\n    'IV': iv_values\n})\n\n# Сортируем по убыванию IV\niv_df = iv_df.sort_values('IV', ascending=False).reset_index(drop=True)\n\n# Добавляем колонку с оценкой силы признака\ndef classify_iv(iv):\n    \"\"\"Классифицирует силу признака по IV.\"\"\"\n    if iv >= 0.3:\n        # Возвращаем значение для этой ветки\n        return 'Сильный'\n    elif iv >= 0.1:\n        # Возвращаем значение для этой ветки\n        return 'Средний'\n    elif iv >= 0.02:\n        # Возвращаем значение для этой ветки\n        return 'Слабый'\n    else:\n        # Возвращаем значение для этой ветки\n        return 'Бесполезный'\n\n# Применяем функцию к каждой строке\niv_df['Сила'] = iv_df['IV'].apply(classify_iv)\n\n# Печатаем таблицу\nprint(\"=== Информативность признаков ===\")\n# Печатаем следующую строку отчёта\nprint(iv_df.to_string(index=False))\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# === ДАННЫЕ 3: Результаты модели ===\n\n# Генерируем искусственные данные для ROC-кривой\n# 10 000 клиентов, 10% дефолтов\nn_clients = 10000\n# Доля дефолтов в искусственной выборке\ndefault_rate = 0.10\n\n# Генерируем истинные метки (0 = не дефолт, 1 = дефолт)\ny_true = np.random.binomial(1, default_rate, n_clients)\n\n# Генерируем предсказанные вероятности\n# Для недефолтных: ниже (beta 3, 5); для дефолтных: выше (beta 4, 3)\n# Параметры подобраны так, чтобы AUC был около 0.78, как в уроке\ny_scores = np.zeros(n_clients)\n\n# Заполняем вероятности для недефолтных клиентов\nnon_default_mask = (y_true == 0)\n# Заполняем предсказанные вероятности\ny_scores[non_default_mask] = np.random.beta(3, 5, non_default_mask.sum())\n\n# Заполняем вероятности для дефолтных клиентов\ndefault_mask = (y_true == 1)\n# Заполняем предсказанные вероятности\ny_scores[default_mask] = np.random.beta(4, 3, default_mask.sum())\n\n# Вычисляем ROC-кривую\n# fpr — False Positive Rate\n# tpr — True Positive Rate\n# thresholds — пороги классификации\nfpr, tpr, thresholds = roc_curve(y_true, y_scores)\n\n# Вычисляем AUC (площадь под ROC-кривой)\nroc_auc = auc(fpr, tpr)\n\n# Печатаем результаты\nprint(\"=== Результаты модели ===\")\n# Печатаем следующую строку отчёта\nprint(f\"Количество клиентов: {n_clients:,}\")\n# Печатаем следующую строку отчёта\nprint(f\"Доля дефолтов: {y_true.mean():.1%}\")\n# Печатаем следующую строку отчёта\nprint(f\"AUC-ROC: {roc_auc:.3f}\")\n# Печатаем следующую строку отчёта\nprint(f\"\\nИнтерпретация: AUC {roc_auc:.2f} — хорошее качество модели\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 2. Построй \"ПЛОХУЮ\" визуализацию (с антипаттернами)\n\nСоздадим график vintage-кривых с типичными ошибками DS:\n- Нет вывода в заголовке\n- Все линии одного цвета\n- Нет контекста (нормы)\n- Легенда перегружена\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Создаём фигуру\nplt.figure(figsize=(10, 6))\n\n# ОШИБКА 1: Заголовок без вывода\nplt.title('Vintage-анализ кредитного портфеля')\n\n# Рисуем все vintage-кривые\n# ОШИБКА 2: Все линии одного цвета (синие)\n# ОШИБКА 3: Все линии одной толщины\nfor cohort in cohorts:\n    # Получаем данные для когорты\n    values = vintage_df[cohort].values\n\n    # Рисуем линию\n    # marker='o' — точки на линии\n    # label=cohort — подпись для легенды\n    plt.plot(mob_values[:len(values)], values, marker='o',\n             label=cohort, linewidth=1.5)\n\n# Подписываем оси\n# ОШИБКА 4: Нет единиц измерения\nplt.xlabel('MOB')\n# Подписываем ось Y\nplt.ylabel('Дефолт')\n\n# ОШИБКА 5: Перегруженная легенда (8 линий!)\nplt.legend(loc='upper left', fontsize=9)\n\n# Включаем сетку\nplt.grid(True, alpha=0.3)\n\n# Автоматически подгоняем размеры\nplt.tight_layout()\n\n# Показываем график\nplt.show()\n\n# Печатаем следующую строку отчёта\nprint(\"Это ПЛОХАЯ визуализация:\")\n# Печатаем следующую строку отчёта\nprint(\"   • Заголовок не содержит вывод\")\n# Печатаем следующую строку отчёта\nprint(\"   • Все линии одинаковые — не видно проблему\")\n# Печатаем следующую строку отчёта\nprint(\"   • Нет контекста (нормы)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Легенда перегружена (8 пунктов)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Комитет не поймёт, что делать\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 3. Переделай в \"ХОРОШУЮ\" визуализацию\n\nПрименим 5 принципов бизнес-визуализации:\n1. Вывод в заголовке\n2. Выделение цветом проблемы\n3. Контекст (линия нормы)\n4. Минимум чернил\n5. Упрощённая легенда\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Создаём фигуру\nplt.figure(figsize=(12, 7))\n\n# ПРИНЦИП 1: Заголовок с выводом\ntitle = 'Когорта 2024-Q1 имеет дефолт 12% — в 2 раза выше нормы'\n# Задаём заголовок графика\nplt.title(title, fontsize=16, fontweight='bold', pad=20)\n\n# Определяем проблемную когорту\nproblem_cohort = '2024-Q1'\n\n# ПРИНЦИП 2: Выделение цветом проблемы\n# Рисуем нормальные когорты серым цветом (тонкие линии)\nfor cohort in cohorts:\n    # Пропускаем проблемную когорту (нарисуем отдельно)\n    if cohort == problem_cohort:\n        # Пропускаем этот элемент цикла\n        continue\n\n    # Получаем данные для когорты\n    values = vintage_df[cohort].dropna().values\n\n    # Рисуем серую тонкую линию с прозрачностью\n    # color='gray' — серый цвет\n    # alpha=0.4 — прозрачность 40%\n    # linewidth=1 — тонкая линия\n    # label показываем только для первой линии (для легенды)\n    label = 'Нормальные когорты (2023-2024)' if cohort == cohorts[0] else None\n    # Рисуем линию на графике\n    plt.plot(mob_values[:len(values)], values, marker='o', markersize=4,\n             color='gray', alpha=0.4, linewidth=1, label=label)\n\n# ПРИНЦИП 2: Выделяем проблемную когорту красным цветом\nproblem_values = vintage_df[problem_cohort].dropna().values\n# Рисуем линию на графике\nplt.plot(mob_values[:len(problem_values)], problem_values,\n         marker='o', markersize=8, color='red', linewidth=3,\n         label=f'{problem_cohort} (ПРОБЛЕМА)')\n\n# ПРИНЦИП 3: Добавляем контекст — линию нормы (6%)\n# color='green' — зелёный (норма)\n# linestyle='--' — пунктирная линия\n# linewidth=2 — средняя толщина\nplt.axhline(y=6, color='green', linestyle='--', linewidth=2,\n            label='Норма (6%)')\n\n# ПРИНЦИП 4: Подписываем оси с единицами измерения\nplt.xlabel('MOB (месяцев с момента выдачи)', fontsize=12)\n# Подписываем ось Y\nplt.ylabel('Накопленный дефолт, %', fontsize=12)\n\n# ПРИНЦИП 4: Добавляем аннотацию для проблемной точки\n# Аннотация показывает конкретное значение\nplt.annotate(f'{problem_values[-1]:.1f}%',  # текст аннотации\n             xy=(mob_values[-1], problem_values[-1]),  # точка, куда указывает\n             xytext=(mob_values[-2], problem_values[-1] + 1.5),  # где разместить текст\n             fontsize=12, fontweight='bold', color='red',\n             arrowprops=dict(arrowstyle='->', color='red', lw=2))\n\n# ПРИНЦИП 5: Упрощённая легенда (только 3 пункта)\nplt.legend(loc='upper left', fontsize=11, framealpha=0.9)\n\n# ПРИНЦИП 4: Добавляем подпись с выводом внизу\nsubtitle = ('Вывод: В Q1 2024 снизили скоринговый порог с 650 до 600. '\n            'Это привело к росту дефолтов. Дополнительные убытки: 355 млн руб.')\n# Добавляем подпись под графиком\nplt.figtext(0.5, 0.02, subtitle, ha='center', fontsize=10,\n            style='italic', color='darkred',\n            bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.7))\n\n# Включаем сетку\nplt.grid(True, alpha=0.3)\n\n# Автоматически подгоняем размеры\nplt.tight_layout()\n# Освобождаем место под подписью\nplt.subplots_adjust(bottom=0.15)  # освобождаем место для подписи\n\n# Сохраняем график в файл\nplt.savefig('good_vintage_chart.png', dpi=300, bbox_inches='tight')\n\n# Показываем график\nplt.show()\n\n# Печатаем следующую строку отчёта\nprint(\"Это ХОРОШАЯ визуализация:\")\n# Печатаем следующую строку отчёта\nprint(\"   • Заголовок содержит вывод (12% vs 6%)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Проблема выделена красным\")\n# Печатаем следующую строку отчёта\nprint(\"   • Норма показана зелёным пунктиром\")\n# Печатаем следующую строку отчёта\nprint(\"   • Легенда содержит только 3 пункта\")\n# Печатаем следующую строку отчёта\nprint(\"   • Подпись объясняет причину и эффект\")\n# Печатаем следующую строку отчёта\nprint(\"   • Комитет поймёт проблему за 5 секунд\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 4. Построй \"ПЛОХУЮ\" столбчатую диаграмму IV\n\nСоздадим график IV признаков с типичными ошибками.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Создаём фигуру\nplt.figure(figsize=(10, 6))\n\n# ОШИБКА 1: Заголовок без вывода\nplt.title('Information Value признаков')\n\n# ОШИБКА 2: Радужная палитра (все цвета разные)\n# Генерируем 10 разных цветов через colormap rainbow\ncolors_bad = plt.cm.rainbow(np.linspace(0, 1, len(iv_df)))\n\n# Рисуем столбчатую диаграмму\n# ОШИБКА 3: Вертикальные столбцы (длинным названиям тесно)\n# ОШИБКА 4: Ось Y начинается не с нуля (обман!)\nbars = plt.bar(range(len(iv_df)), iv_df['IV'], color=colors_bad,\n               edgecolor='black', linewidth=0.5)\n\n# Устанавливаем метки признаков на оси X\n# ОШИБКА 5: Подписи перекрываются\nplt.xticks(range(len(iv_df)), iv_df['Признак'], rotation=45, ha='right')\n\n# ОШИБКА 3: Обманчивая ось Y (начинается с 0.05, а не с 0)\n# Это преувеличивает различия между признаками\nplt.ylim(0.05, 0.5)\n\n# Подписываем оси\nplt.xlabel('Признаки')\n# Подписываем ось Y\nplt.ylabel('IV')\n\n# Автоматически подгоняем размеры\nplt.tight_layout()\n\n# Показываем график\nplt.show()\n\n# Печатаем следующую строку отчёта\nprint(\"Это ПЛОХАЯ визуализация:\")\n# Печатаем следующую строку отчёта\nprint(\"   • Заголовок не содержит вывод\")\n# Печатаем следующую строку отчёта\nprint(\"   • Радужная палитра (цвета не несут смысла)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Ось Y начинается с 0.05, а не с 0 (обман!)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Подписи признаков перекрываются\")\n# Печатаем следующую строку отчёта\nprint(\"   • Непонятно, какие признаки брать в модель\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 5. Переделай в \"ХОРОШУЮ\" столбчатую диаграмму\n\nПрименим принципы:\n- Горизонтальные столбцы\n- Ось начинается с 0\n- Цвет несёт смысл\n- Порог IV=0.1 показан явно\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Создаём фигуру (выше, чтобы поместились все подписи)\nplt.figure(figsize=(12, 8))\n\n# ПРИНЦИП 1: Заголовок с выводом\ntitle = 'Отобрано 5 сильных признаков с IV > 0.1 для скоринга'\n# Задаём заголовок графика\nplt.title(title, fontsize=16, fontweight='bold', pad=20)\n\n# ПРИНЦИП 2: Цвет несёт смысл\n# Определяем цвета по силе признака\ndef get_color(iv):\n    \"\"\"Возвращает цвет в зависимости от IV.\"\"\"\n    if iv >= 0.3:\n        # Возвращаем значение для этой ветки\n        return '#2e7d32'  # тёмно-зелёный (сильный)\n    elif iv >= 0.1:\n        # Возвращаем значение для этой ветки\n        return '#66bb6a'  # светло-зелёный (средний)\n    elif iv >= 0.02:\n        # Возвращаем значение для этой ветки\n        return '#ffb74d'  # оранжевый (слабый)\n    else:\n        # Возвращаем значение для этой ветки\n        return '#e0e0e0'  # серый (бесполезный)\n\n# Применяем функцию к каждому IV\ncolors_good = [get_color(iv) for iv in iv_df['IV']]\n\n# ПРИНЦИП 3: Горизонтальные столбцы (длинным названиям удобно)\nbars = plt.barh(range(len(iv_df)), iv_df['IV'], color=colors_good,\n                edgecolor='black', linewidth=0.5, height=0.7)\n\n# ПРИНЦИП 4: Ось X начинается с 0 (честно!)\nplt.xlim(0, max(iv_df['IV']) * 1.15)\n\n# Устанавливаем метки признаков на оси Y\nplt.yticks(range(len(iv_df)), iv_df['Признак'], fontsize=11)\n\n# Инвертируем ось Y, чтобы самый важный признак был сверху\nplt.gca().invert_yaxis()\n\n# ПРИНЦИП 5: Добавляем значения IV справа от столбцов\nfor i, (idx, row) in enumerate(iv_df.iterrows()):\n    # Определяем цвет текста (тёмный для сильных, серый для бесполезных)\n    text_color = 'black' if row['IV'] >= 0.1 else 'gray'\n\n    # Добавляем текст с значением IV и силой\n    plt.text(row['IV'] + 0.01, i, f\"{row['IV']:.2f} ({row['Сила']})\",\n             va='center', fontsize=10, color=text_color, fontweight='bold')\n\n# ПРИНЦИП 6: Добавляем вертикальную линию порога (IV = 0.1)\nplt.axvline(x=0.1, color='red', linestyle='--', linewidth=2,\n            label='Порог отбора (IV = 0.1)')\n\n# Подписываем ось X\nplt.xlabel('Information Value (IV)', fontsize=12)\n\n# Показываем легенду\nplt.legend(loc='lower right', fontsize=11)\n\n# Включаем сетку (только по оси X)\nplt.grid(True, alpha=0.3, axis='x')\n\n# Автоматически подгоняем размеры\nplt.tight_layout()\n\n# Сохраняем график\nplt.savefig('good_iv_chart.png', dpi=300, bbox_inches='tight')\n\n# Показываем график\nplt.show()\n\n# Печатаем следующую строку отчёта\nprint(\"Это ХОРОШАЯ визуализация:\")\n# Печатаем следующую строку отчёта\nprint(\"   • Заголовок содержит вывод (5 признаков с IV > 0.1)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Цвет несёт смысл (зелёный = сильный)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Горизонтальные столбцы (подписи читаются)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Ось X начинается с 0 (честно)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Порог IV=0.1 показан явно\")\n# Печатаем следующую строку отчёта\nprint(\"   • Комитет сразу видит, какие признаки брать\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 6. Построй ROC-кривую с контекстом\n\nROC-кривая — технический график. Для комитета нужно добавить бизнес-контекст.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Создаём фигуру\nplt.figure(figsize=(12, 7))\n\n# ПРИНЦИП 1: Заголовок с выводом на языке бизнеса\ntitle = 'Модель предсказывает дефолты с точностью 78% (AUC 0.78)'\n# Задаём заголовок графика\nplt.title(title, fontsize=16, fontweight='bold', pad=20)\n\n# Рисуем ROC-кривую модели\n# color='blue' — синий цвет\n# linewidth=2 — средняя толщина\n# label содержит AUC\nplt.plot(fpr, tpr, color='#1976d2', linewidth=2.5,\n         label=f'Наша модель (AUC = {roc_auc:.2f})')\n\n# Рисуем диагональ (случайный классификатор)\n# color='red' — красный (плохо)\n# linestyle='--' — пунктир\nplt.plot([0, 1], [0, 1], color='#d32f2f', linestyle='--', linewidth=1.5,\n         label='Случайное угадывание (AUC = 0.50)')\n\n# ПРИНЦИП 2: Добавляем контекст — эталонную модель\n# Генерируем ROC-кривую для \"эталонной\" модели с AUC 0.70\n# Используем более слабые предсказания\ny_scores_baseline = np.zeros(n_clients)\n# Заполняем предсказанные вероятности\ny_scores_baseline[non_default_mask] = np.random.beta(3, 4, non_default_mask.sum())\n# Заполняем предсказанные вероятности\ny_scores_baseline[default_mask] = np.random.beta(3.5, 2.5, default_mask.sum())\n\n# Вычисляем ROC-кривую для baseline\nfpr_base, tpr_base, _ = roc_curve(y_true, y_scores_baseline)\n# Считаем AUC эталонной модели\nauc_base = auc(fpr_base, tpr_base)\n\n# Рисуем baseline модель серым цветом\nplt.plot(fpr_base, tpr_base, color='gray', linewidth=1.5, alpha=0.7,\n         label=f'Предыдущая модель (AUC = {auc_base:.2f})')\n\n# Подписываем оси\nplt.xlabel('False Positive Rate (доля ложных тревог)', fontsize=12)\n# Подписываем ось Y\nplt.ylabel('True Positive Rate (доля пойманных дефолтов)', fontsize=12)\n\n# ПРИНЦИП 3: Добавляем область интерпретации\n# Заливаем область под нашей кривой светло-синим\nplt.fill_between(fpr, tpr, alpha=0.1, color='#1976d2')\n\n# ПРИНЦИП 4: Добавляем аннотацию с бизнес-интерпретацией\n# Аннотация объясняет, что значит AUC 0.78\nannotation_text = ('Интерпретация для бизнеса:\\n'\n                   '• Модель ловит 75% дефолтов\\n'\n                   '• При этом 25% ложных тревог\\n'\n                   '• Эффект: 7.6 млрд руб/год')\n\n# Размещаем аннотацию в правом нижнем углу\nplt.annotate(annotation_text,\n             xy=(0.65, 0.05),  # координаты размещения\n             xycoords='axes fraction',  # координаты относительно осей\n             fontsize=10,\n             bbox=dict(boxstyle='round', facecolor='lightyellow',\n                      alpha=0.8, edgecolor='orange'),\n             verticalalignment='bottom')\n\n# ПРИНЦИП 5: Упрощённая легенда\nplt.legend(loc='lower right', fontsize=11, framealpha=0.9)\n\n# Включаем сетку\nplt.grid(True, alpha=0.3)\n\n# Устанавливаем пределы осей\nplt.xlim([0, 1])\n# Задаём пределы оси Y\nplt.ylim([0, 1.02])\n\n# Автоматически подгоняем размеры\nplt.tight_layout()\n\n# Сохраняем график\nplt.savefig('good_roc_chart.png', dpi=300, bbox_inches='tight')\n\n# Показываем график\nplt.show()\n\n# Печатаем следующую строку отчёта\nprint(\"Это ХОРОШАЯ визуализация:\")\n# Печатаем следующую строку отчёта\nprint(\"   • Заголовок содержит вывод (точность 78%)\")\n# Печатаем следующую строку отчёта\nprint(\"   • Есть сравнение с предыдущей моделью\")\n# Печатаем следующую строку отчёта\nprint(\"   • Бизнес-интерпретация прямо на графике\")\n# Печатаем следующую строку отчёта\nprint(\"   • Легенда содержит только 3 пункта\")\n# Печатаем следующую строку отчёта\nprint(\"   • Комитет понимает эффект в деньгах\")\n"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Задача 7 (финальная). Собери итоговый слайд для комитета\n\nСоздадим итоговый слайд, который объединяет все выводы в одно решение.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Создаём фигуру для итогового слайда\n# Формат 16:9 (стандарт для презентаций)\nfig = plt.figure(figsize=(16, 9))\n\n# ПРИНЦИП 1: Заголовок-вывод сверху\nfig.suptitle('РЕШЕНИЕ: Внедрить скоринговую модель с 5 признаками',\n             fontsize=24, fontweight='bold', y=0.98, color='#1a237e')\n\n# Создаём сетку из 3 подграфиков (1 ряд, 3 колонки)\n# gridspec_kw задаёт промежутки между графиками\ngs = fig.add_gridspec(2, 3, hspace=0.4, wspace=0.3,\n                       height_ratios=[1, 0.3])\n\n# === ПОДГРАФИК 1: Проблема (vintage) ===\nax1 = fig.add_subplot(gs[0, 0])\n\n# Рисуем нормальные когорты серым\nfor cohort in cohorts:\n    # Проверяем условие\n    if cohort == problem_cohort:\n        # Пропускаем этот элемент цикла\n        continue\n    # Берём значения когорты без пропусков\n    values = vintage_df[cohort].dropna().values\n    # Настраиваем подграфик\n    ax1.plot(mob_values[:len(values)], values, color='gray',\n             alpha=0.4, linewidth=1)\n\n# Рисуем проблемную когорту красным\nproblem_values = vintage_df[problem_cohort].dropna().values\n# Настраиваем подграфик\nax1.plot(mob_values[:len(problem_values)], problem_values,\n         color='red', linewidth=3, marker='o', markersize=6)\n\n# Линия нормы\nax1.axhline(y=6, color='green', linestyle='--', linewidth=1.5)\n\n# Заголовок подграфика\nax1.set_title('ПРОБЛЕМА\\nДефолт Q1 2024: 12%',\n              fontsize=12, fontweight='bold', color='red')\n\n# Подписи осей\nax1.set_xlabel('MOB', fontsize=9)\n# Настраиваем подграфик\nax1.set_ylabel('Дефолт, %', fontsize=9)\n# Настраиваем подграфик\nax1.grid(True, alpha=0.3)\n\n# === ПОДГРАФИК 2: Решение (IV) ===\nax2 = fig.add_subplot(gs[0, 1])\n\n# Берём топ-5 признаков\ntop_5 = iv_df.head(5)\n\n# Определяем цвета\ncolors_top = ['#2e7d32' if iv >= 0.3 else '#66bb6a' for iv in top_5['IV']]\n\n# Рисуем горизонтальные столбцы\nax2.barh(range(len(top_5)), top_5['IV'], color=colors_top,\n         edgecolor='black', linewidth=0.5)\n\n# Подписи\nax2.set_yticks(range(len(top_5)))\n# Настраиваем подграфик\nax2.set_yticklabels(top_5['Признак'], fontsize=9)\n# Настраиваем подграфик\nax2.invert_yaxis()\n\n# Порог\nax2.axvline(x=0.1, color='red', linestyle='--', linewidth=1.5)\n\n# Заголовок\nax2.set_title('РЕШЕНИЕ\\n5 признаков с IV > 0.1',\n              fontsize=12, fontweight='bold', color='green')\n\n# Подписи осей\nax2.set_xlabel('IV', fontsize=9)\n# Настраиваем подграфик\nax2.grid(True, alpha=0.3, axis='x')\n\n# === ПОДГРАФИК 3: Эффект (ROC) ===\nax3 = fig.add_subplot(gs[0, 2])\n\n# Рисуем ROC-кривую\nax3.plot(fpr, tpr, color='#1976d2', linewidth=2.5)\n# Настраиваем подграфик\nax3.plot([0, 1], [0, 1], color='red', linestyle='--', linewidth=1)\n\n# Заливка\nax3.fill_between(fpr, tpr, alpha=0.1, color='#1976d2')\n\n# Заголовок\nax3.set_title(f'ЭФФЕКТ\\nAUC = {roc_auc:.2f}',\n              fontsize=12, fontweight='bold', color='#1976d2')\n\n# Подписи осей\nax3.set_xlabel('FPR', fontsize=9)\n# Настраиваем подграфик\nax3.set_ylabel('TPR', fontsize=9)\n# Настраиваем подграфик\nax3.grid(True, alpha=0.3)\n# Настраиваем подграфик\nax3.set_xlim([0, 1])\n# Настраиваем подграфик\nax3.set_ylim([0, 1])\n\n# === НИЖНЯЯ ЧАСТЬ: Финансовый эффект и рекомендация ===\nax_bottom = fig.add_subplot(gs[1, :])\n# Настраиваем подграфик\nax_bottom.axis('off')  # отключаем оси\n\n# Создаём таблицу с ключевыми метриками\ntable_data = [\n    ['Инвестиции', 'ROI', 'Окупаемость', 'Экономия/год', 'NPV 3 года'],\n    ['7.95 млн руб', '95 500%', '0.4 дня', '7.6 млрд руб', '17.4 млрд руб']\n]\n\n# Рисуем таблицу\ntable = ax_bottom.table(cellText=table_data,\n                         cellLoc='center',\n                         loc='center',\n                         bbox=[0.1, 0.1, 0.8, 0.8])\n\n# Настраиваем стиль таблицы\ntable.auto_set_font_size(False)\n# Настраиваем таблицу метрик\ntable.set_fontsize(14)\n\n# Красим заголовки таблицы\nfor i in range(5):\n    # Настраиваем ячейку таблицы\n    cell = table[(0, i)]\n    # Настраиваем ячейку таблицы\n    cell.set_facecolor('#1976d2')\n    # Настраиваем ячейку таблицы\n    cell.set_text_props(color='white', fontweight='bold')\n\n    # Красим значения зелёным\n    cell_value = table[(1, i)]\n    # Настраиваем ячейку таблицы\n    cell_value.set_facecolor('#c8e6c9')\n    # Настраиваем ячейку таблицы\n    cell_value.set_text_props(fontweight='bold', fontsize=16)\n\n# Добавляем итоговую рекомендацию\nrecommendation = ('РЕКОМЕНДАЦИЯ: Внедрить модель с 1 мая 2026. '\n                  'Пилот на 10% клиентов в феврале. '\n                  'Ожидаемый эффект: 7.6 млрд руб/год.')\n\n# Добавляем рекомендацию внизу слайда\nfig.text(0.5, 0.02, recommendation, ha='center', fontsize=14,\n         fontweight='bold', color='#2e7d32',\n         bbox=dict(boxstyle='round', facecolor='#c8e6c9',\n                  alpha=0.8, edgecolor='#2e7d32', linewidth=2))\n\n# Сохраняем итоговый слайд\nplt.savefig('final_slide.png', dpi=300, bbox_inches='tight')\n\n# Показываем слайд\nplt.show()\n\n# Печатаем следующую строку отчёта\nprint(\"ИТОГОВЫЙ СЛАЙД ГОТОВ\")\n# Печатаем следующую строку отчёта\nprint()\n# Печатаем следующую строку отчёта\nprint(\"Структура слайда:\")\n# Печатаем следующую строку отчёта\nprint(\"  1. Заголовок с решением (вверху)\")\n# Печатаем следующую строку отчёта\nprint(\"  2. Три графика: Проблема → Решение → Эффект\")\n# Печатаем следующую строку отчёта\nprint(\"  3. Финансовые метрики в таблице\")\n# Печатаем следующую строку отчёта\nprint(\"  4. Итоговая рекомендация (внизу)\")\n# Печатаем следующую строку отчёта\nprint()\n# Печатаем следующую строку отчёта\nprint(\"Время чтения слайда: 15-20 секунд\")\n# Печатаем следующую строку отчёта\nprint(\"Комитет может принять решение, посмотрев только этот слайд\")\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# ПРОВЕРКА финального слайда\n\n# Проверяем, что все графики построены\nassert len(cohorts) == 8, \"Должно быть 8 когорт\"\n# Проверяем инвариант практики\nassert len(iv_df) == 10, \"Должно быть 10 признаков\"\n# Проверяем инвариант практики\nassert 0.7 < roc_auc < 0.9, f\"AUC должен быть 0.7-0.9, получено {roc_auc:.3f}\"\n\n# Печатаем итоговую статистику\nprint(\"=\" * 70)\n# Печатаем следующую строку отчёта\nprint(\"ПРАКТИКА ЗАВЕРШЕНА УСПЕШНО\")\n# Печатаем следующую строку отчёта\nprint(\"=\" * 70)\n# Печатаем следующую строку отчёта\nprint()\n# Печатаем следующую строку отчёта\nprint(\"Что ты научился делать:\")\n# Печатаем следующую строку отчёта\nprint(\"  • Создавать визуализации с выводом в заголовке\")\n# Печатаем следующую строку отчёта\nprint(\"  • Выделять проблему цветом (красный = опасность)\")\n# Печатаем следующую строку отчёта\nprint(\"  • Добавлять контекст (норма, бенчмарк)\")\n# Печатаем следующую строку отчёта\nprint(\"  • Избегать антипаттернов (3D, радуга, обманчивые оси)\")\n# Печатаем следующую строку отчёта\nprint(\"  • Собирать слайды по принципу 'один вывод - один слайд'\")\n# Печатаем следующую строку отчёта\nprint()\n# Печатаем следующую строку отчёта\nprint(\"Сохранённые файлы:\")\n# Печатаем следующую строку отчёта\nprint(\"  • good_vintage_chart.png — vintage-анализ\")\n# Печатаем следующую строку отчёта\nprint(\"  • good_iv_chart.png — IV признаков\")\n# Печатаем следующую строку отчёта\nprint(\"  • good_roc_chart.png — ROC-кривая\")\n# Печатаем следующую строку отчёта\nprint(\"  • final_slide.png — итоговый слайд для комитета\")\n# Печатаем следующую строку отчёта\nprint()\n# Печатаем следующую строку отчёта\nprint(\"Готов к презентации риск-комитету\")\n"
  }
 ]
}