{ "nbformat": 4, "nbformat_minor": 2, "metadata": { "colab": { "name": "lesson_3-R.ipynb", "provenance": [], "collapsed_sections": [], "toc_visible": true }, "kernelspec": { "name": "ir", "display_name": "R" }, "language_info": { "name": "R" }, "coopTranslator": { "original_hash": "5015d65d61ba75a223bfc56c273aa174", "translation_date": "2025-08-29T22:51:49+00:00", "source_file": "2-Regression/3-Linear/solution/R/lesson_3-R.ipynb", "language_code": "ru" } }, "cells": [ { "cell_type": "markdown", "source": [], "metadata": { "id": "EgQw8osnsUV-" } }, { "cell_type": "markdown", "source": [ "## Линейная и полиномиальная регрессия для определения цен на тыквы - Урок 3\n", "

\n", " \n", "

Инфографика от Дасани Мадипалли
\n", "\n", "\n", "\n", "\n", "#### Введение\n", "\n", "До этого момента вы изучили, что такое регрессия, используя пример данных из набора данных о ценах на тыквы, который мы будем использовать на протяжении всего урока. Вы также визуализировали данные с помощью `ggplot2`.💪\n", "\n", "Теперь вы готовы углубиться в регрессию для машинного обучения. В этом уроке вы узнаете больше о двух типах регрессии: *основной линейной регрессии* и *полиномиальной регрессии*, а также о некоторых математических основах этих методов.\n", "\n", "> На протяжении всей программы мы предполагаем минимальные знания математики и стремимся сделать материал доступным для студентов из других областей. Следите за заметками, 🧮 подсказками, диаграммами и другими инструментами обучения, которые помогут вам понять материал.\n", "\n", "#### Подготовка\n", "\n", "Напомним, что вы загружаете эти данные, чтобы задавать вопросы.\n", "\n", "- Когда лучше всего покупать тыквы?\n", "\n", "- Какую цену можно ожидать за ящик миниатюрных тыкв?\n", "\n", "- Стоит ли покупать их в корзинах на пол-бушеля или в коробках на 1 1/9 бушеля? Давайте продолжим исследовать эти данные.\n", "\n", "В предыдущем уроке вы создали `tibble` (современное переосмысление фрейма данных) и заполнили его частью исходного набора данных, стандартизировав цены по бушелю. Однако, таким образом, вы смогли собрать только около 400 точек данных и только за осенние месяцы. Может быть, мы сможем получить немного больше информации о природе данных, если очистим их лучше? Посмотрим... 🕵️‍♀️\n", "\n", "Для выполнения этой задачи нам понадобятся следующие пакеты:\n", "\n", "- `tidyverse`: [tidyverse](https://www.tidyverse.org/) — это [коллекция пакетов для R](https://www.tidyverse.org/packages), разработанная для того, чтобы сделать работу с данными быстрее, проще и увлекательнее!\n", "\n", "- `tidymodels`: [tidymodels](https://www.tidymodels.org/) — это [фреймворк](https://www.tidymodels.org/packages), состоящий из пакетов для моделирования и машинного обучения.\n", "\n", "- `janitor`: [пакет janitor](https://github.com/sfirke/janitor) предоставляет простые инструменты для анализа и очистки \"грязных\" данных.\n", "\n", "- `corrplot`: [пакет corrplot](https://cran.r-project.org/web/packages/corrplot/vignettes/corrplot-intro.html) предлагает визуальный инструмент для исследования матрицы корреляции, поддерживающий автоматическую перестановку переменных для выявления скрытых закономерностей между ними.\n", "\n", "Вы можете установить их следующим образом:\n", "\n", "`install.packages(c(\"tidyverse\", \"tidymodels\", \"janitor\", \"corrplot\"))`\n", "\n", "Скрипт ниже проверяет, установлены ли у вас необходимые пакеты для выполнения этого модуля, и устанавливает их, если они отсутствуют.\n" ], "metadata": { "id": "WqQPS1OAsg3H" } }, { "cell_type": "code", "execution_count": null, "source": [ "suppressWarnings(if (!require(\"pacman\")) install.packages(\"pacman\"))\n", "\n", "pacman::p_load(tidyverse, tidymodels, janitor, corrplot)" ], "outputs": [], "metadata": { "id": "tA4C2WN3skCf", "colab": { "base_uri": "https://localhost:8080/" }, "outputId": "c06cd805-5534-4edc-f72b-d0d1dab96ac0" } }, { "cell_type": "markdown", "source": [ "Мы позже загрузим эти замечательные пакеты и сделаем их доступными в нашей текущей R-сессии. (Это просто для иллюстрации, `pacman::p_load()` уже сделал это за вас)\n", "\n", "## 1. Линия линейной регрессии\n", "\n", "Как вы узнали в Уроке 1, цель упражнения по линейной регрессии — построить *линию* *наилучшего соответствия*, чтобы:\n", "\n", "- **Показать взаимосвязь переменных**. Показать связь между переменными.\n", "\n", "- **Сделать прогнозы**. Делать точные прогнозы о том, где новая точка данных окажется относительно этой линии.\n", "\n", "Для построения такой линии мы используем статистический метод, называемый **регрессией наименьших квадратов**. Термин `наименьшие квадраты` означает, что все точки данных вокруг линии регрессии возводятся в квадрат, а затем складываются. В идеале, итоговая сумма должна быть как можно меньше, потому что мы хотим минимизировать количество ошибок, или `наименьшие квадраты`. Таким образом, линия наилучшего соответствия — это линия, которая дает нам наименьшее значение суммы квадратов ошибок — отсюда и название *регрессия наименьших квадратов*.\n", "\n", "Мы делаем это, потому что хотим смоделировать линию, которая имеет минимальное суммарное расстояние от всех наших точек данных. Мы также возводим значения в квадрат перед их сложением, так как нас интересует их величина, а не направление.\n", "\n", "> **🧮 Покажите мне математику**\n", ">\n", "> Эта линия, называемая *линией наилучшего соответствия*, может быть выражена [уравнением](https://en.wikipedia.org/wiki/Simple_linear_regression):\n", ">\n", "> Y = a + bX\n", ">\n", "> `X` — это '`объясняющая переменная` или `предиктор`'. `Y` — это '`зависимая переменная` или `результат`'. Наклон линии обозначается как `b`, а `a` — это точка пересечения с осью Y, которая указывает значение `Y`, когда `X = 0`.\n", ">\n", "\n", "> ![](../../../../../../2-Regression/3-Linear/solution/images/slope.png \"наклон = $y/x$\")\n", " Инфографика от Jen Looper\n", ">\n", "> Сначала вычислите наклон `b`.\n", ">\n", "> Другими словами, если обратиться к исходному вопросу о данных по тыквам: \"предсказать цену тыквы за бушель по месяцам\", `X` будет означать цену, а `Y` — месяц продажи.\n", ">\n", "> ![](../../../../../../translated_images/calculation.989aa7822020d9d0ba9fc781f1ab5192f3421be86ebb88026528aef33c37b0d8.ru.png)\n", " Инфографика от Jen Looper\n", "> \n", "> Вычислите значение Y. Если вы платите около \\$4, значит, это апрель!\n", ">\n", "> Математика, которая рассчитывает линию, должна показать наклон линии, который также зависит от точки пересечения, или от того, где находится `Y`, когда `X = 0`.\n", ">\n", "> Вы можете ознакомиться с методом расчета этих значений на сайте [Math is Fun](https://www.mathsisfun.com/data/least-squares-regression.html). Также посетите [этот калькулятор наименьших квадратов](https://www.mathsisfun.com/data/least-squares-calculator.html), чтобы увидеть, как значения чисел влияют на линию.\n", "\n", "Не так уж страшно, правда? 🤓\n", "\n", "#### Корреляция\n", "\n", "Еще один термин, который нужно понять, — это **коэффициент корреляции** между заданными переменными X и Y. Используя диаграмму рассеяния, вы можете быстро визуализировать этот коэффициент. График с точками данных, расположенными в аккуратной линии, имеет высокую корреляцию, а график с точками данных, разбросанными повсюду между X и Y, имеет низкую корреляцию.\n", "\n", "Хорошая модель линейной регрессии — это та, которая имеет высокий коэффициент корреляции (ближе к 1, чем к 0) с использованием метода регрессии наименьших квадратов и линии регрессии.\n" ], "metadata": { "id": "cdX5FRpvsoP5" } }, { "cell_type": "markdown", "source": [ "## **2. Танец с данными: создание датафрейма для моделирования**\n", "\n", "

\n", " \n", "

Иллюстрация @allison_horst
\n", "\n", "\n", "\n" ], "metadata": { "id": "WdUKXk7Bs8-V" } }, { "cell_type": "markdown", "source": [ "Загрузите необходимые библиотеки и набор данных. Преобразуйте данные в датафрейм, содержащий подмножество данных:\n", "\n", "- Оставьте только тыквы, цена которых указана за бушель\n", "\n", "- Преобразуйте дату в месяц\n", "\n", "- Рассчитайте цену как среднее значение между высокой и низкой ценами\n", "\n", "- Преобразуйте цену, чтобы она отражала стоимость за количество в бушелях\n", "\n", "> Мы рассмотрели эти шаги в [предыдущем уроке](https://github.com/microsoft/ML-For-Beginners/blob/main/2-Regression/2-Data/solution/lesson_2-R.ipynb).\n" ], "metadata": { "id": "fMCtu2G2s-p8" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Load the core Tidyverse packages\n", "library(tidyverse)\n", "library(lubridate)\n", "\n", "# Import the pumpkins data\n", "pumpkins <- read_csv(file = \"https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/2-Regression/data/US-pumpkins.csv\")\n", "\n", "\n", "# Get a glimpse and dimensions of the data\n", "glimpse(pumpkins)\n", "\n", "\n", "# Print the first 50 rows of the data set\n", "pumpkins %>% \n", " slice_head(n = 5)" ], "outputs": [], "metadata": { "id": "ryMVZEEPtERn" } }, { "cell_type": "markdown", "source": [ "В духе чистого приключения давайте изучим [`janitor package`](../../../../../../2-Regression/3-Linear/solution/R/github.com/sfirke/janitor), который предоставляет простые функции для анализа и очистки грязных данных. Например, давайте посмотрим на названия столбцов в наших данных:\n" ], "metadata": { "id": "xcNxM70EtJjb" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Return column names\n", "pumpkins %>% \n", " names()" ], "outputs": [], "metadata": { "id": "5XtpaIigtPfW" } }, { "cell_type": "markdown", "source": [ "🤔 Мы можем сделать лучше. Давайте сделаем эти названия столбцов `friendR`, преобразовав их в [snake_case](https://en.wikipedia.org/wiki/Snake_case) с помощью `janitor::clean_names`. Чтобы узнать больше об этой функции: `?clean_names`\n" ], "metadata": { "id": "IbIqrMINtSHe" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Clean names to the snake_case convention\n", "pumpkins <- pumpkins %>% \n", " clean_names(case = \"snake\")\n", "\n", "# Return column names\n", "pumpkins %>% \n", " names()" ], "outputs": [], "metadata": { "id": "a2uYvclYtWvX" } }, { "cell_type": "markdown", "source": [ "Много tidyR 🧹! Теперь танец с данными, используя `dplyr`, как в предыдущем уроке! 💃\n" ], "metadata": { "id": "HfhnuzDDtaDd" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Select desired columns\n", "pumpkins <- pumpkins %>% \n", " select(variety, city_name, package, low_price, high_price, date)\n", "\n", "\n", "\n", "# Extract the month from the dates to a new column\n", "pumpkins <- pumpkins %>%\n", " mutate(date = mdy(date),\n", " month = month(date)) %>% \n", " select(-date)\n", "\n", "\n", "\n", "# Create a new column for average Price\n", "pumpkins <- pumpkins %>% \n", " mutate(price = (low_price + high_price)/2)\n", "\n", "\n", "# Retain only pumpkins with the string \"bushel\"\n", "new_pumpkins <- pumpkins %>% \n", " filter(str_detect(string = package, pattern = \"bushel\"))\n", "\n", "\n", "# Normalize the pricing so that you show the pricing per bushel, not per 1 1/9 or 1/2 bushel\n", "new_pumpkins <- new_pumpkins %>% \n", " mutate(price = case_when(\n", " str_detect(package, \"1 1/9\") ~ price/(1.1),\n", " str_detect(package, \"1/2\") ~ price*2,\n", " TRUE ~ price))\n", "\n", "# Relocate column positions\n", "new_pumpkins <- new_pumpkins %>% \n", " relocate(month, .before = variety)\n", "\n", "\n", "# Display the first 5 rows\n", "new_pumpkins %>% \n", " slice_head(n = 5)" ], "outputs": [], "metadata": { "id": "X0wU3gQvtd9f" } }, { "cell_type": "markdown", "source": [ "Отличная работа!👌 Теперь у вас есть чистый, аккуратный набор данных, на основе которого можно построить новую регрессионную модель!\n", "\n", "Как насчет диаграммы рассеяния?\n" ], "metadata": { "id": "UpaIwaxqth82" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Set theme\n", "theme_set(theme_light())\n", "\n", "# Make a scatter plot of month and price\n", "new_pumpkins %>% \n", " ggplot(mapping = aes(x = month, y = price)) +\n", " geom_point(size = 1.6)\n" ], "outputs": [], "metadata": { "id": "DXgU-j37tl5K" } }, { "cell_type": "markdown", "source": [ "Диаграмма рассеяния напоминает нам, что у нас есть данные только за месяцы с августа по декабрь. Вероятно, нам нужно больше данных, чтобы сделать выводы в линейной форме.\n", "\n", "Давайте снова посмотрим на наши данные для моделирования:\n" ], "metadata": { "id": "Ve64wVbwtobI" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Display first 5 rows\n", "new_pumpkins %>% \n", " slice_head(n = 5)" ], "outputs": [], "metadata": { "id": "HFQX2ng1tuSJ" } }, { "cell_type": "markdown", "source": [ "Что, если мы захотим предсказать `price` тыквы на основе столбцов `city` или `package`, которые имеют тип \"символы\"? Или, еще проще, как мы могли бы найти корреляцию (которая требует, чтобы оба входных значения были числовыми) между, например, `package` и `price`? 🤷🤷\n", "\n", "Модели машинного обучения лучше работают с числовыми признаками, чем с текстовыми значениями, поэтому обычно необходимо преобразовать категориальные признаки в числовые представления.\n", "\n", "Это означает, что нам нужно найти способ преобразовать наши предикторы, чтобы модель могла эффективно их использовать. Этот процесс называется `feature engineering`.\n" ], "metadata": { "id": "7hsHoxsStyjJ" } }, { "cell_type": "markdown", "source": [ "## 3. Предобработка данных для моделирования с помощью recipes 👩‍🍳👨‍🍳\n", "\n", "Действия, которые преобразуют значения предикторов, чтобы сделать их более удобными для использования моделью, называются `feature engineering` (инженерия признаков).\n", "\n", "Разные модели имеют разные требования к предобработке. Например, метод наименьших квадратов требует `кодирования категориальных переменных`, таких как месяц, сорт и название города. Это просто означает `преобразование` столбца с `категориальными значениями` в один или несколько `числовых столбцов`, которые заменяют оригинальный.\n", "\n", "Например, предположим, что ваши данные включают следующую категориальную переменную:\n", "\n", "| город |\n", "|:--------:|\n", "| Денвер |\n", "| Найроби |\n", "| Токио |\n", "\n", "Вы можете применить *порядковое кодирование*, чтобы заменить каждую категорию уникальным целым числом, например так:\n", "\n", "| город |\n", "|:-----:|\n", "| 0 |\n", "| 1 |\n", "| 2 |\n", "\n", "Именно это мы и сделаем с нашими данными!\n", "\n", "В этом разделе мы изучим еще один удивительный пакет из экосистемы Tidymodels: [recipes](https://tidymodels.github.io/recipes/) - который предназначен для помощи в предобработке данных **до** обучения модели. В своей основе рецепт (recipe) - это объект, который определяет, какие шаги должны быть применены к набору данных, чтобы подготовить его для моделирования.\n", "\n", "Теперь давайте создадим рецепт, который подготовит наши данные для моделирования, заменяя уникальным целым числом все наблюдения в столбцах предикторов:\n" ], "metadata": { "id": "AD5kQbcvt3Xl" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Specify a recipe\n", "pumpkins_recipe <- recipe(price ~ ., data = new_pumpkins) %>% \n", " step_integer(all_predictors(), zero_based = TRUE)\n", "\n", "\n", "# Print out the recipe\n", "pumpkins_recipe" ], "outputs": [], "metadata": { "id": "BNaFKXfRt9TU" } }, { "cell_type": "markdown", "source": [ "Отлично! 👏 Мы только что создали наш первый рецепт, который задает результат (цена) и соответствующие предикторы, а также указывает, что все столбцы предикторов должны быть закодированы в набор целых чисел 🙌! Давайте быстро разберем, как это работает:\n", "\n", "- Вызов `recipe()` с формулой указывает рецепту *роли* переменных, используя данные `new_pumpkins` в качестве эталона. Например, столбец `price` был назначен как `outcome` (результат), в то время как остальные столбцы были назначены как `predictor` (предикторы).\n", "\n", "- `step_integer(all_predictors(), zero_based = TRUE)` указывает, что все предикторы должны быть преобразованы в набор целых чисел, начиная с 0.\n", "\n", "Мы уверены, что у вас могут возникнуть мысли вроде: \"Это так круто!! Но что, если мне нужно убедиться, что рецепты действительно делают то, что я от них ожидаю? 🤔\"\n", "\n", "Это отличная мысль! Видите ли, как только ваш рецепт определен, вы можете оценить параметры, необходимые для предварительной обработки данных, а затем извлечь обработанные данные. Обычно вам не нужно делать это при использовании Tidymodels (мы скоро увидим стандартный подход —\\> `workflows`), но это может быть полезно, если вы хотите провести проверку, чтобы убедиться, что рецепты работают так, как вы ожидаете.\n", "\n", "Для этого вам понадобятся еще два действия: `prep()` и `bake()`. И, как всегда, наши маленькие друзья из R от [`Allison Horst`](https://github.com/allisonhorst/stats-illustrations) помогут вам лучше понять это!\n", "\n", "

\n", " \n", "

Иллюстрация от @allison_horst
\n" ], "metadata": { "id": "KEiO0v7kuC9O" } }, { "cell_type": "markdown", "source": [ "[`prep()`](https://recipes.tidymodels.org/reference/prep.html): оценивает необходимые параметры на основе обучающего набора данных, которые затем могут быть применены к другим наборам данных. Например, для заданного столбца предиктора, какое наблюдение будет назначено целому числу 0, 1, 2 и т.д.\n", "\n", "[`bake()`](https://recipes.tidymodels.org/reference/bake.html): берет подготовленный рецепт и применяет операции к любому набору данных.\n", "\n", "Итак, давайте подготовим и применим наши рецепты, чтобы действительно убедиться, что \"под капотом\" столбцы предикторов сначала будут закодированы, прежде чем модель будет обучена.\n" ], "metadata": { "id": "Q1xtzebuuTCP" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Prep the recipe\n", "pumpkins_prep <- prep(pumpkins_recipe)\n", "\n", "# Bake the recipe to extract a preprocessed new_pumpkins data\n", "baked_pumpkins <- bake(pumpkins_prep, new_data = NULL)\n", "\n", "# Print out the baked data set\n", "baked_pumpkins %>% \n", " slice_head(n = 10)" ], "outputs": [], "metadata": { "id": "FGBbJbP_uUUn" } }, { "cell_type": "markdown", "source": [ "Ура! 🥳 Обработанные данные `baked_pumpkins` имеют все предикторы закодированными, что подтверждает, что этапы предварительной обработки, определенные в нашем рецепте, действительно работают как задумано. Это делает данные сложнее для чтения, но гораздо более понятными для Tidymodels! Потратьте немного времени, чтобы выяснить, какое наблюдение было сопоставлено с соответствующим целым числом.\n", "\n", "Также стоит упомянуть, что `baked_pumpkins` — это фрейм данных, с которым мы можем выполнять вычисления.\n", "\n", "Например, давайте попробуем найти хорошую корреляцию между двумя точками ваших данных, чтобы потенциально построить хорошую предсказательную модель. Для этого мы воспользуемся функцией `cor()`. Введите `?cor()`, чтобы узнать больше об этой функции.\n" ], "metadata": { "id": "1dvP0LBUueAW" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Find the correlation between the city_name and the price\n", "cor(baked_pumpkins$city_name, baked_pumpkins$price)\n", "\n", "# Find the correlation between the package and the price\n", "cor(baked_pumpkins$package, baked_pumpkins$price)\n" ], "outputs": [], "metadata": { "id": "3bQzXCjFuiSV" } }, { "cell_type": "markdown", "source": [ "Как оказалось, между Городом и Ценой существует лишь слабая корреляция. Однако между Упаковкой и её Ценой корреляция немного сильнее. Логично, правда? Обычно, чем больше коробка с продуктами, тем выше цена.\n", "\n", "Раз уж мы этим занимаемся, давайте также попробуем визуализировать матрицу корреляции всех столбцов с помощью пакета `corrplot`.\n" ], "metadata": { "id": "BToPWbgjuoZw" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Load the corrplot package\n", "library(corrplot)\n", "\n", "# Obtain correlation matrix\n", "corr_mat <- cor(baked_pumpkins %>% \n", " # Drop columns that are not really informative\n", " select(-c(low_price, high_price)))\n", "\n", "# Make a correlation plot between the variables\n", "corrplot(corr_mat, method = \"shade\", shade.col = NA, tl.col = \"black\", tl.srt = 45, addCoef.col = \"black\", cl.pos = \"n\", order = \"original\")" ], "outputs": [], "metadata": { "id": "ZwAL3ksmutVR" } }, { "cell_type": "markdown", "source": [ "🤩🤩 Намного лучше.\n", "\n", "Хороший вопрос, который теперь можно задать этим данным: '`Какую цену я могу ожидать для заданного пакета тыкв?`' Давайте разберемся!\n", "\n", "> Примечание: Когда вы **`bake()`** подготовленный рецепт **`pumpkins_prep`** с параметром **`new_data = NULL`**, вы извлекаете обработанные (т.е. закодированные) тренировочные данные. Если у вас был бы другой набор данных, например, тестовый набор, и вы хотели бы увидеть, как рецепт его предварительно обработает, вы просто запекаете **`pumpkins_prep`** с параметром **`new_data = test_set`**.\n", "\n", "## 4. Построение модели линейной регрессии\n", "\n", "

\n", " \n", "

Инфографика от Дасани Мадипалли
\n", "\n", "\n", "\n" ], "metadata": { "id": "YqXjLuWavNxW" } }, { "cell_type": "markdown", "source": [ "Теперь, когда мы создали рецепт и убедились, что данные будут предварительно обработаны должным образом, давайте построим регрессионную модель, чтобы ответить на вопрос: `Какую цену я могу ожидать для данного пакета тыквы?`\n", "\n", "#### Обучение модели линейной регрессии с использованием обучающего набора данных\n", "\n", "Как вы, возможно, уже догадались, колонка *price* является переменной `результата`, а колонка *package* — переменной `предиктором`.\n", "\n", "Для этого мы сначала разделим данные так, чтобы 80% попало в обучающий набор, а 20% — в тестовый. Затем определим рецепт, который закодирует колонку предиктора в набор целых чисел, и создадим спецификацию модели. Мы не будем подготавливать и применять наш рецепт, так как уже знаем, что он обработает данные должным образом.\n" ], "metadata": { "id": "Pq0bSzCevW-h" } }, { "cell_type": "code", "execution_count": null, "source": [ "set.seed(2056)\n", "# Split the data into training and test sets\n", "pumpkins_split <- new_pumpkins %>% \n", " initial_split(prop = 0.8)\n", "\n", "\n", "# Extract training and test data\n", "pumpkins_train <- training(pumpkins_split)\n", "pumpkins_test <- testing(pumpkins_split)\n", "\n", "\n", "\n", "# Create a recipe for preprocessing the data\n", "lm_pumpkins_recipe <- recipe(price ~ package, data = pumpkins_train) %>% \n", " step_integer(all_predictors(), zero_based = TRUE)\n", "\n", "\n", "\n", "# Create a linear model specification\n", "lm_spec <- linear_reg() %>% \n", " set_engine(\"lm\") %>% \n", " set_mode(\"regression\")" ], "outputs": [], "metadata": { "id": "CyoEh_wuvcLv" } }, { "cell_type": "markdown", "source": [ "Отличная работа! Теперь, когда у нас есть рецепт и спецификация модели, нам нужно найти способ объединить их в один объект, который сначала будет предварительно обрабатывать данные (подготовка + выпекание за кулисами), обучать модель на предварительно обработанных данных, а также предоставлять возможность для потенциальной постобработки. Как вам такое для спокойствия?🤩\n", "\n", "В Tidymodels этот удобный объект называется [`workflow`](https://workflows.tidymodels.org/) и удобно хранит ваши компоненты моделирования! Это то, что мы бы назвали *конвейерами* (*pipelines*) в *Python*.\n", "\n", "Итак, давайте объединим всё в workflow!📦\n" ], "metadata": { "id": "G3zF_3DqviFJ" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Hold modelling components in a workflow\n", "lm_wf <- workflow() %>% \n", " add_recipe(lm_pumpkins_recipe) %>% \n", " add_model(lm_spec)\n", "\n", "# Print out the workflow\n", "lm_wf" ], "outputs": [], "metadata": { "id": "T3olroU3v-WX" } }, { "cell_type": "markdown", "source": [ "Вдобавок, рабочий процесс может быть настроен/обучен почти так же, как и модель.\n" ], "metadata": { "id": "zd1A5tgOwEPX" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Train the model\n", "lm_wf_fit <- lm_wf %>% \n", " fit(data = pumpkins_train)\n", "\n", "# Print the model coefficients learned \n", "lm_wf_fit" ], "outputs": [], "metadata": { "id": "NhJagFumwFHf" } }, { "cell_type": "markdown", "source": [ "Из результатов модели мы можем увидеть коэффициенты, полученные в процессе обучения. Они представляют собой коэффициенты линии наилучшего соответствия, которая минимизирует общую ошибку между фактической и предсказанной переменной.\n", "\n", "#### Оценка производительности модели с использованием тестового набора\n", "\n", "Пришло время проверить, как справилась модель 📏! Как это сделать?\n", "\n", "Теперь, когда мы обучили модель, мы можем использовать её для предсказаний на `test_set` с помощью `parsnip::predict()`. Затем мы сравним эти предсказания с фактическими значениями меток, чтобы оценить, насколько хорошо (или не очень!) работает модель.\n", "\n", "Начнем с предсказаний для тестового набора, а затем объединим столбцы с тестовым набором.\n" ], "metadata": { "id": "_4QkGtBTwItF" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Make predictions for the test set\n", "predictions <- lm_wf_fit %>% \n", " predict(new_data = pumpkins_test)\n", "\n", "\n", "# Bind predictions to the test set\n", "lm_results <- pumpkins_test %>% \n", " select(c(package, price)) %>% \n", " bind_cols(predictions)\n", "\n", "\n", "# Print the first ten rows of the tibble\n", "lm_results %>% \n", " slice_head(n = 10)" ], "outputs": [], "metadata": { "id": "UFZzTG0gwTs9" } }, { "cell_type": "markdown", "source": [ "Да, вы только что обучили модель и использовали её для предсказаний! 🔮 Хороша ли она? Давайте оценим производительность модели!\n", "\n", "В Tidymodels мы делаем это с помощью `yardstick::metrics()`! Для линейной регрессии давайте сосредоточимся на следующих метриках:\n", "\n", "- `Root Mean Square Error (RMSE)` (Среднеквадратичная ошибка): Квадратный корень из [MSE](https://en.wikipedia.org/wiki/Mean_squared_error). Это абсолютная метрика в тех же единицах измерения, что и целевая переменная (в данном случае, цена тыквы). Чем меньше значение, тем лучше модель (в упрощённом смысле, это средняя цена, на которую предсказания ошибаются!).\n", "\n", "- `Coefficient of Determination` (обычно известен как R-квадрат или R2): Относительная метрика, где чем выше значение, тем лучше модель соответствует данным. По сути, эта метрика показывает, какую долю дисперсии между предсказанными и фактическими значениями целевой переменной модель способна объяснить.\n" ], "metadata": { "id": "0A5MjzM7wW9M" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Evaluate performance of linear regression\n", "metrics(data = lm_results,\n", " truth = price,\n", " estimate = .pred)" ], "outputs": [], "metadata": { "id": "reJ0UIhQwcEH" } }, { "cell_type": "markdown", "source": [ "Там идет производительность модели. Давайте посмотрим, сможем ли мы получить лучшее представление, визуализировав диаграмму рассеяния с пакетом и ценой, а затем наложив линию наилучшего соответствия, используя сделанные прогнозы.\n", "\n", "Это означает, что нам нужно подготовить и обработать тестовый набор, чтобы закодировать столбец пакета, а затем связать его с прогнозами, сделанными нашей моделью.\n" ], "metadata": { "id": "fdgjzjkBwfWt" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Encode package column\n", "package_encode <- lm_pumpkins_recipe %>% \n", " prep() %>% \n", " bake(new_data = pumpkins_test) %>% \n", " select(package)\n", "\n", "\n", "# Bind encoded package column to the results\n", "lm_results <- lm_results %>% \n", " bind_cols(package_encode %>% \n", " rename(package_integer = package)) %>% \n", " relocate(package_integer, .after = package)\n", "\n", "\n", "# Print new results data frame\n", "lm_results %>% \n", " slice_head(n = 5)\n", "\n", "\n", "# Make a scatter plot\n", "lm_results %>% \n", " ggplot(mapping = aes(x = package_integer, y = price)) +\n", " geom_point(size = 1.6) +\n", " # Overlay a line of best fit\n", " geom_line(aes(y = .pred), color = \"orange\", size = 1.2) +\n", " xlab(\"package\")\n", " \n" ], "outputs": [], "metadata": { "id": "R0nw719lwkHE" } }, { "cell_type": "markdown", "source": [ "Отлично! Как вы можете видеть, модель линейной регрессии не очень хорошо обобщает связь между упаковкой и её соответствующей ценой.\n", "\n", "🎃 Поздравляем, вы только что создали модель, которая может помочь предсказать цену нескольких видов тыкв. Ваш праздничный тыквенный огород будет великолепным. Но, вероятно, вы можете создать модель получше!\n", "\n", "## 5. Построение модели полиномиальной регрессии\n", "\n", "

\n", " \n", "

Инфографика от Дасани Мадипалли
\n", "\n", "\n", "\n" ], "metadata": { "id": "HOCqJXLTwtWI" } }, { "cell_type": "markdown", "source": [ "Иногда наши данные могут не иметь линейной зависимости, но мы все равно хотим предсказать результат. Полиномиальная регрессия может помочь нам делать прогнозы для более сложных нелинейных зависимостей.\n", "\n", "Возьмем, например, зависимость между упаковкой и ценой в нашем наборе данных о тыквах. Хотя иногда между переменными существует линейная зависимость — чем больше объем тыквы, тем выше цена — иногда такие зависимости нельзя изобразить в виде плоскости или прямой линии.\n", "\n", "> ✅ Вот [несколько примеров](https://online.stat.psu.edu/stat501/lesson/9/9.8) данных, для которых может быть полезна полиномиальная регрессия\n", ">\n", "> Взгляните еще раз на зависимость между сортом и ценой на предыдущем графике. Кажется ли, что этот диаграмма рассеяния обязательно должна быть проанализирована с помощью прямой линии? Возможно, нет. В таком случае можно попробовать полиномиальную регрессию.\n", ">\n", "> ✅ Полиномы — это математические выражения, которые могут состоять из одной или нескольких переменных и коэффициентов.\n", "\n", "#### Обучение модели полиномиальной регрессии с использованием обучающего набора данных\n", "\n", "Полиномиальная регрессия создает *изогнутую линию*, чтобы лучше описать нелинейные данные.\n", "\n", "Давайте посмотрим, сможет ли полиномиальная модель лучше справиться с прогнозированием. Мы будем следовать примерно той же процедуре, что и раньше:\n", "\n", "- Создать рецепт, который определяет шаги предварительной обработки данных, чтобы подготовить их для моделирования, например: кодирование предикторов и вычисление полиномов степени *n*\n", "\n", "- Построить спецификацию модели\n", "\n", "- Объединить рецепт и спецификацию модели в рабочий процесс\n", "\n", "- Создать модель, подогнав рабочий процесс\n", "\n", "- Оценить, насколько хорошо модель работает на тестовых данных\n", "\n", "Давайте начнем!\n" ], "metadata": { "id": "VcEIpRV9wzYr" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Specify a recipe\r\n", "poly_pumpkins_recipe <-\r\n", " recipe(price ~ package, data = pumpkins_train) %>%\r\n", " step_integer(all_predictors(), zero_based = TRUE) %>% \r\n", " step_poly(all_predictors(), degree = 4)\r\n", "\r\n", "\r\n", "# Create a model specification\r\n", "poly_spec <- linear_reg() %>% \r\n", " set_engine(\"lm\") %>% \r\n", " set_mode(\"regression\")\r\n", "\r\n", "\r\n", "# Bundle recipe and model spec into a workflow\r\n", "poly_wf <- workflow() %>% \r\n", " add_recipe(poly_pumpkins_recipe) %>% \r\n", " add_model(poly_spec)\r\n", "\r\n", "\r\n", "# Create a model\r\n", "poly_wf_fit <- poly_wf %>% \r\n", " fit(data = pumpkins_train)\r\n", "\r\n", "\r\n", "# Print learned model coefficients\r\n", "poly_wf_fit\r\n", "\r\n", " " ], "outputs": [], "metadata": { "id": "63n_YyRXw3CC" } }, { "cell_type": "markdown", "source": [ "#### Оценка производительности модели\n", "\n", "👏👏Вы создали полиномиальную модель, давайте сделаем прогнозы на тестовом наборе данных!\n" ], "metadata": { "id": "-LHZtztSxDP0" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Make price predictions on test data\r\n", "poly_results <- poly_wf_fit %>% predict(new_data = pumpkins_test) %>% \r\n", " bind_cols(pumpkins_test %>% select(c(package, price))) %>% \r\n", " relocate(.pred, .after = last_col())\r\n", "\r\n", "\r\n", "# Print the results\r\n", "poly_results %>% \r\n", " slice_head(n = 10)" ], "outputs": [], "metadata": { "id": "YUFpQ_dKxJGx" } }, { "cell_type": "markdown", "source": [ "Ура, давайте оценим, как модель справилась с test_set, используя `yardstick::metrics()`.\n" ], "metadata": { "id": "qxdyj86bxNGZ" } }, { "cell_type": "code", "execution_count": null, "source": [ "metrics(data = poly_results, truth = price, estimate = .pred)" ], "outputs": [], "metadata": { "id": "8AW5ltkBxXDm" } }, { "cell_type": "markdown", "source": [ "🤩🤩 Намного лучшее качество работы.\n", "\n", "`rmse` снизился с примерно 7 до примерно 3, что указывает на уменьшение ошибки между фактической ценой и предсказанной ценой. Можно *условно* интерпретировать это как среднюю ошибку в прогнозах около \\$3. `rsq` увеличился с примерно 0.4 до 0.8.\n", "\n", "Все эти метрики показывают, что полиномиальная модель работает значительно лучше, чем линейная. Отличная работа!\n", "\n", "Давайте посмотрим, сможем ли мы это визуализировать!\n" ], "metadata": { "id": "6gLHNZDwxYaS" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Bind encoded package column to the results\r\n", "poly_results <- poly_results %>% \r\n", " bind_cols(package_encode %>% \r\n", " rename(package_integer = package)) %>% \r\n", " relocate(package_integer, .after = package)\r\n", "\r\n", "\r\n", "# Print new results data frame\r\n", "poly_results %>% \r\n", " slice_head(n = 5)\r\n", "\r\n", "\r\n", "# Make a scatter plot\r\n", "poly_results %>% \r\n", " ggplot(mapping = aes(x = package_integer, y = price)) +\r\n", " geom_point(size = 1.6) +\r\n", " # Overlay a line of best fit\r\n", " geom_line(aes(y = .pred), color = \"midnightblue\", size = 1.2) +\r\n", " xlab(\"package\")\r\n" ], "outputs": [], "metadata": { "id": "A83U16frxdF1" } }, { "cell_type": "markdown", "source": [ "Вы можете увидеть кривую, которая лучше подходит к вашим данным! 🤩\n", "\n", "Вы можете сделать её более плавной, передав полиномиальную формулу в `geom_smooth`, например так:\n" ], "metadata": { "id": "4U-7aHOVxlGU" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Make a scatter plot\r\n", "poly_results %>% \r\n", " ggplot(mapping = aes(x = package_integer, y = price)) +\r\n", " geom_point(size = 1.6) +\r\n", " # Overlay a line of best fit\r\n", " geom_smooth(method = lm, formula = y ~ poly(x, degree = 4), color = \"midnightblue\", size = 1.2, se = FALSE) +\r\n", " xlab(\"package\")" ], "outputs": [], "metadata": { "id": "5vzNT0Uexm-w" } }, { "cell_type": "markdown", "source": [ "Похоже на плавную кривую!🤩\n", "\n", "Вот как можно сделать новый прогноз:\n" ], "metadata": { "id": "v9u-wwyLxq4G" } }, { "cell_type": "code", "execution_count": null, "source": [ "# Make a hypothetical data frame\r\n", "hypo_tibble <- tibble(package = \"bushel baskets\")\r\n", "\r\n", "# Make predictions using linear model\r\n", "lm_pred <- lm_wf_fit %>% predict(new_data = hypo_tibble)\r\n", "\r\n", "# Make predictions using polynomial model\r\n", "poly_pred <- poly_wf_fit %>% predict(new_data = hypo_tibble)\r\n", "\r\n", "# Return predictions in a list\r\n", "list(\"linear model prediction\" = lm_pred, \r\n", " \"polynomial model prediction\" = poly_pred)\r\n" ], "outputs": [], "metadata": { "id": "jRPSyfQGxuQv" } }, { "cell_type": "markdown", "source": [ "Прогнозирование с использованием `полиномиальной модели` действительно имеет смысл, учитывая диаграммы рассеяния для `price` и `package`! И если эта модель лучше предыдущей, глядя на те же данные, вам нужно заложить бюджет на эти более дорогие тыквы!\n", "\n", "🏆 Отличная работа! Вы создали две регрессионные модели за один урок. В последнем разделе о регрессии вы узнаете о логистической регрессии для определения категорий.\n", "\n", "## **🚀Задание**\n", "\n", "Протестируйте несколько различных переменных в этом ноутбуке, чтобы увидеть, как корреляция влияет на точность модели.\n", "\n", "## [**Викторина после лекции**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/14/)\n", "\n", "## **Обзор и самостоятельное изучение**\n", "\n", "На этом уроке мы изучили линейную регрессию. Существуют и другие важные типы регрессии. Прочитайте о методах Stepwise, Ridge, Lasso и Elasticnet. Хороший курс для изучения этих методов — [курс Стэнфорда по статистическому обучению](https://online.stanford.edu/courses/sohs-ystatslearning-statistical-learning).\n", "\n", "Если вы хотите узнать больше о том, как использовать удивительный фреймворк Tidymodels, ознакомьтесь со следующими ресурсами:\n", "\n", "- Сайт Tidymodels: [Начните с Tidymodels](https://www.tidymodels.org/start/)\n", "\n", "- Макс Кун и Джулия Силге, [*Tidy Modeling with R*](https://www.tmwr.org/)*.*\n", "\n", "###### **СПАСИБО:**\n", "\n", "[Эллисон Хорст](https://twitter.com/allison_horst?lang=en) за создание удивительных иллюстраций, которые делают R более доступным и увлекательным. Найдите больше иллюстраций в её [галерее](https://www.google.com/url?q=https://github.com/allisonhorst/stats-illustrations&sa=D&source=editors&ust=1626380772530000&usg=AOvVaw3zcfyCizFQZpkSLzxiiQEM).\n" ], "metadata": { "id": "8zOLOWqMxzk5" } }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Отказ от ответственности**: \nЭтот документ был переведен с помощью сервиса автоматического перевода [Co-op Translator](https://github.com/Azure/co-op-translator). Хотя мы стремимся к точности, пожалуйста, учитывайте, что автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его родном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникающие в результате использования данного перевода.\n" ] } ] }