{ "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-09-04T01:10:16+00:00", "source_file": "2-Regression/3-Linear/solution/R/lesson_3-R.ipynb", "language_code": "ko" } }, "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", "Lesson 1에서 배운 것처럼, 선형 회귀의 목표는 *최적의 적합선*을 그리는 것입니다. 이를 통해:\n", "\n", "- **변수 간의 관계를 보여줍니다**. 변수들 간의 관계를 시각적으로 나타냅니다.\n", "\n", "- **예측을 수행합니다**. 새로운 데이터 포인트가 이 선과의 관계에서 어디에 위치할지 정확히 예측합니다.\n", "\n", "이러한 유형의 선을 그리기 위해 **최소제곱 회귀법(Least-Squares Regression)**이라는 통계 기법을 사용합니다. `최소제곱`이라는 용어는 회귀선을 둘러싼 모든 데이터 포인트의 거리를 제곱한 후 이를 합산하는 것을 의미합니다. 이상적으로는 이 최종 합계가 가능한 한 작아야 합니다. 왜냐하면 우리는 오류가 적은, 즉 `최소제곱`을 원하기 때문입니다. 따라서 최적의 적합선은 제곱된 오류 합계가 가장 낮은 값을 제공하는 선입니다. 이것이 *최소제곱 회귀법*이라는 이름의 유래입니다.\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절편으로, 이는 `X = 0`일 때의 `Y` 값을 나타냅니다.\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.ko.png)\n", " Jen Looper 제작 인포그래픽\n", "> \n", "> `Y` 값을 계산합니다. 만약 약 \\$4를 지불하고 있다면, 이는 4월일 것입니다!\n", ">\n", "> 이 선을 계산하는 수학은 선의 기울기를 보여주어야 하며, 이는 또한 절편, 즉 `X = 0`일 때 `Y`의 위치에 따라 달라집니다.\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 변수 간의 **상관계수(Correlation Coefficient)**입니다. 산점도를 사용하면 이 계수를 빠르게 시각화할 수 있습니다. 데이터 포인트가 깔끔한 선으로 흩어져 있는 플롯은 높은 상관관계를 가지지만, 데이터 포인트가 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 패키지`](../../../../../../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": [ "🤔 우리는 더 잘할 수 있습니다. 이 열 이름들을 `janitor::clean_names`를 사용하여 [snake_case](https://en.wikipedia.org/wiki/Snake_case) 규칙으로 변환하여 `friendR`로 만들어 봅시다. 이 함수에 대해 더 알아보려면: `?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": [ "더 깔끔하게 정리하기 🧹! 이제 이전 강의에서처럼 `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": [ "산점도를 보면 우리가 8월부터 12월까지의 월별 데이터만 가지고 있다는 것을 상기시켜줍니다. 선형적으로 결론을 내리기 위해서는 더 많은 데이터가 필요할 가능성이 높습니다.\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. 모델링을 위한 데이터 전처리 with recipes 👩‍🍳👨‍🍳\n", "\n", "모델이 데이터를 효과적으로 활용할 수 있도록 예측 변수 값을 재구성하는 작업을 `특징 공학(feature engineering)`이라고 합니다.\n", "\n", "모델마다 요구하는 전처리 방식이 다릅니다. 예를 들어, 최소제곱법(least squares)은 `월(month)`, `품종(variety)`, `도시 이름(city_name)`과 같은 `범주형 변수`를 `인코딩`해야 합니다. 이는 단순히 `범주형 값`이 포함된 열을 하나 이상의 `숫자 열`로 변환하여 원래 열을 대체하는 작업을 의미합니다.\n", "\n", "예를 들어, 데이터에 다음과 같은 범주형 특징이 포함되어 있다고 가정해 봅시다:\n", "\n", "| city |\n", "|:-------:|\n", "| Denver |\n", "| Nairobi |\n", "| Tokyo |\n", "\n", "여기에 *순서 인코딩(ordinal encoding)*을 적용하여 각 범주에 고유한 정수 값을 대체할 수 있습니다. 결과는 다음과 같습니다:\n", "\n", "| city |\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()` 호출 시 공식(formula)을 사용하면 `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()`. 그리고 항상 그렇듯이, [`Allison Horst`](https://github.com/allisonhorst/stats-illustrations)의 작은 R 친구들이 이를 더 잘 이해할 수 있도록 도와줄 거예요!\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", "> Note: **`new_data = NULL`**로 **`pumpkins_prep`** 레시피를 **`bake()`**하면, 처리된(즉, 인코딩된) 훈련 데이터를 추출할 수 있습니다. 예를 들어 테스트 세트와 같은 다른 데이터 세트가 있고 레시피가 이를 어떻게 전처리할지 확인하고 싶다면, **`new_data = test_set`**로 **`pumpkins_prep`**을 간단히 bake하면 됩니다.\n", "\n", "## 4. 선형 회귀 모델 구축하기\n", "\n", "

\n", " \n", "

Dasani Madipalli의 인포그래픽
\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": [ "좋아요! 이제 레시피와 모델 사양을 준비했으니, 이를 하나의 객체로 묶어 데이터를 먼저 전처리(백그라운드에서 prep+bake 수행), 전처리된 데이터로 모델을 학습시키고, 추가적인 후처리 작업도 가능하게 만들어야 합니다. 마음이 한결 편안해지지 않나요!🤩\n", "\n", "Tidymodels에서는 이 편리한 객체를 [`workflow`](https://workflows.tidymodels.org/)라고 부르며, 모델링 구성 요소를 간편하게 담을 수 있습니다! 이는 *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", "모델을 학습시켰으니, 이제 `parsnip::predict()`를 사용하여 test_set에 대한 예측을 수행할 수 있습니다. 그런 다음, 이 예측값을 실제 레이블 값과 비교하여 모델이 얼마나 잘 작동하는지 (혹은 잘 작동하지 않는지!) 평가할 수 있습니다.\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-squared 또는 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", "> 이전 그래프에서 품종(Variety)과 가격(Price) 간의 관계를 다시 살펴보세요. 이 산점도가 반드시 직선으로 분석되어야 할 것처럼 보이나요? 아마도 그렇지 않을 겁니다. 이 경우, 다항 회귀를 시도해볼 수 있습니다.\n", ">\n", "> ✅ 다항식은 하나 이상의 변수와 계수로 구성될 수 있는 수학적 표현식입니다.\n", "\n", "#### 훈련 세트를 사용하여 다항 회귀 모델 훈련하기\n", "\n", "다항 회귀는 비선형 데이터를 더 잘 맞추기 위해 *곡선*을 생성합니다.\n", "\n", "다항 모델이 예측 성능을 더 잘 발휘할 수 있는지 확인해봅시다. 이전에 했던 절차와 약간 유사한 과정을 따를 것입니다:\n", "\n", "- 데이터를 모델링에 적합하도록 준비하기 위해 수행해야 할 전처리 단계를 지정하는 레시피를 생성합니다. 예: 예측 변수를 인코딩하고 차수 *n*의 다항식을 계산하기\n", "\n", "- 모델 사양을 작성합니다.\n", "\n", "- 레시피와 모델 사양을 워크플로우에 묶습니다.\n", "\n", "- 워크플로우를 맞춤(fitting)으로써 모델을 생성합니다.\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": [ "우후, `yardstick::metrics()`를 사용하여 test_set에서 모델 성능을 평가해 봅시다.\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": [ "`polynomial model` 예측은 `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 기법에 대해 읽어보세요. 더 배우고 싶다면 [Stanford Statistical Learning course](https://online.stanford.edu/courses/sohs-ystatslearning-statistical-learning)가 좋은 과정입니다.\n", "\n", "Tidymodels 프레임워크를 사용하는 방법에 대해 더 배우고 싶다면 다음 리소스를 확인하세요:\n", "\n", "- Tidymodels 웹사이트: [Tidymodels 시작하기](https://www.tidymodels.org/start/)\n", "\n", "- Max Kuhn과 Julia Silge, [*Tidy Modeling with R*](https://www.tmwr.org/)*.*\n", "\n", "###### **감사합니다:**\n", "\n", "[R의 놀라운 삽화를 만들어 R을 더 친근하고 매력적으로 만들어 주신 Allison Horst](https://twitter.com/allison_horst?lang=en)에게 감사드립니다. 그녀의 [갤러리](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이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문 번역가에 의한 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다. \n" ] } ] }