{ "nbformat": 4, "nbformat_minor": 2, "metadata": { "colab": { "name": "lesson_2-R.ipynb", "provenance": [], "collapsed_sections": [], "toc_visible": true }, "kernelspec": { "name": "ir", "display_name": "R" }, "language_info": { "name": "R" }, "coopTranslator": { "original_hash": "f3c335f9940cfd76528b3ef918b9b342", "translation_date": "2025-09-04T01:40:56+00:00", "source_file": "2-Regression/2-Data/solution/R/lesson_2-R.ipynb", "language_code": "ko" } }, "cells": [ { "cell_type": "markdown", "source": [ "# 회귀 모델 구축: 데이터 준비 및 시각화\n", "\n", "## **호박을 위한 선형 회귀 - 2강**\n", "#### 소개\n", "\n", "이제 Tidymodels와 Tidyverse를 사용하여 머신러닝 모델을 구축할 준비가 되었으니, 데이터를 탐구하며 질문을 시작할 차례입니다. 데이터를 다루고 머신러닝 솔루션을 적용할 때, 데이터셋의 잠재력을 제대로 발휘하기 위해 올바른 질문을 하는 방법을 이해하는 것이 매우 중요합니다.\n", "\n", "이 강의에서 배우게 될 내용:\n", "\n", "- 모델 구축을 위해 데이터를 준비하는 방법.\n", "\n", "- `ggplot2`를 사용하여 데이터를 시각화하는 방법.\n", "\n", "답을 얻고자 하는 질문은 어떤 유형의 머신러닝 알고리즘을 사용할지 결정하게 됩니다. 그리고 얻는 답변의 품질은 데이터의 특성에 크게 좌우됩니다.\n", "\n", "실제 연습을 통해 이를 살펴보겠습니다.\n", "\n", "
\n",
" \n",
"
\n",
"\n",
"> 복습: 파이프 연산자 (`%>%`)는 객체를 함수나 호출 표현식으로 전달하여 논리적 순서로 작업을 수행합니다. 코드에서 파이프 연산자는 \"그리고 나서\"라고 말하는 것과 같은 역할을 한다고 생각할 수 있습니다.\n"
],
"metadata": {
"id": "REWcIv9yX29v"
}
},
{
"cell_type": "markdown",
"source": [
"## 2. 누락된 데이터 확인하기\n",
"\n",
"데이터 과학자들이 자주 직면하는 문제 중 하나는 불완전하거나 누락된 데이터입니다. R은 누락되었거나 알 수 없는 값을 특별한 센티널 값인 `NA`(Not Available)로 나타냅니다.\n",
"\n",
"그렇다면 데이터 프레임에 누락된 값이 포함되어 있는지 어떻게 알 수 있을까요? \n",
"
\n",
"- 가장 간단한 방법은 기본 R 함수인 `anyNA`를 사용하는 것입니다. 이 함수는 논리 객체 `TRUE` 또는 `FALSE`를 반환합니다.\n"
],
"metadata": {
"id": "Zxfb3AM5YbUe"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"pumpkins %>% \n",
" anyNA()"
],
"outputs": [],
"metadata": {
"id": "G--DQutAYltj"
}
},
{
"cell_type": "markdown",
"source": [
"좋아요, 일부 데이터가 누락된 것 같네요! 그것부터 시작하면 좋을 것 같습니다.\n",
"\n",
"- 또 다른 방법은 `is.na()` 함수를 사용하는 것으로, 이 함수는 각 열 요소 중 누락된 데이터를 논리값 `TRUE`로 표시합니다.\n"
],
"metadata": {
"id": "mU-7-SB6YokF"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"pumpkins %>% \n",
" is.na() %>% \n",
" head(n = 7)"
],
"outputs": [],
"metadata": {
"id": "W-DxDOR4YxSW"
}
},
{
"cell_type": "markdown",
"source": [
"이와 같은 큰 데이터 프레임에서는 모든 행과 열을 개별적으로 검토하는 것은 비효율적이고 사실상 불가능합니다😴.\n",
"\n",
"- 더 직관적인 방법은 각 열의 결측값 합계를 계산하는 것입니다:\n"
],
"metadata": {
"id": "xUWxipKYY0o7"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"pumpkins %>% \n",
" is.na() %>% \n",
" colSums()"
],
"outputs": [],
"metadata": {
"id": "ZRBWV6P9ZArL"
}
},
{
"cell_type": "markdown",
"source": [
"훨씬 좋아졌습니다! 데이터가 일부 누락되었지만, 작업에는 큰 영향을 미치지 않을 수도 있습니다. 추가 분석 결과를 지켜보죠.\n",
"\n",
"> R은 훌륭한 패키지와 함수 세트뿐만 아니라 매우 좋은 문서를 제공합니다. 예를 들어, `help(colSums)` 또는 `?colSums`를 사용하여 해당 함수에 대해 더 알아볼 수 있습니다.\n"
],
"metadata": {
"id": "9gv-crB6ZD1Y"
}
},
{
"cell_type": "markdown",
"source": [
"## 3. Dplyr: 데이터 조작을 위한 문법\n",
"\n",
"
\n",
" \n",
"
\n"
],
"metadata": {
"id": "i5o33MQBZWWw"
}
},
{
"cell_type": "markdown",
"source": [
"#### dplyr::select()\n",
"\n",
"`select()`는 `dplyr` 패키지에 포함된 함수로, 유지하거나 제외할 열을 선택하는 데 도움을 줍니다.\n",
"\n",
"데이터 프레임을 더 다루기 쉽게 만들기 위해, `select()`를 사용하여 필요한 열만 남기고 여러 열을 제거할 수 있습니다.\n",
"\n",
"예를 들어, 이번 연습에서는 `Package`, `Low Price`, `High Price`, `Date` 열을 분석에 사용할 것입니다. 이 열들을 선택해 봅시다.\n"
],
"metadata": {
"id": "x3VGMAGBZiUr"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Select desired columns\n",
"pumpkins <- pumpkins %>% \n",
" select(Package, `Low Price`, `High Price`, Date)\n",
"\n",
"\n",
"# Print data set\n",
"pumpkins %>% \n",
" slice_head(n = 5)"
],
"outputs": [],
"metadata": {
"id": "F_FgxQnVZnM0"
}
},
{
"cell_type": "markdown",
"source": [
"#### dplyr::mutate()\n",
"\n",
"`mutate()`는 `dplyr` 패키지에 있는 함수로, 기존 열을 유지하면서 새로운 열을 생성하거나 기존 열을 수정할 수 있도록 도와줍니다.\n",
"\n",
"`mutate`의 일반적인 구조는 다음과 같습니다:\n",
"\n",
"`data %>% mutate(new_column_name = what_it_contains)`\n",
"\n",
"`Date` 열을 사용하여 다음 작업을 수행하며 `mutate`를 직접 사용해 보겠습니다:\n",
"\n",
"1. 날짜(현재 문자형)를 월 형식으로 변환합니다(이 날짜는 미국 형식이므로 `MM/DD/YYYY` 형식입니다).\n",
"\n",
"2. 날짜에서 월을 추출하여 새로운 열에 저장합니다.\n",
"\n",
"R에서는 [lubridate](https://lubridate.tidyverse.org/) 패키지가 날짜-시간 데이터를 더 쉽게 다룰 수 있도록 도와줍니다. 따라서 `dplyr::mutate()`, `lubridate::mdy()`, `lubridate::month()`를 사용하여 위의 목표를 달성하는 방법을 살펴보겠습니다. 이후 작업에서 더 이상 필요하지 않으므로 Date 열은 삭제할 수 있습니다.\n"
],
"metadata": {
"id": "2KKo0Ed9Z1VB"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Load lubridate\n",
"library(lubridate)\n",
"\n",
"pumpkins <- pumpkins %>% \n",
" # Convert the Date column to a date object\n",
" mutate(Date = mdy(Date)) %>% \n",
" # Extract month from Date\n",
" mutate(Month = month(Date)) %>% \n",
" # Drop Date column\n",
" select(-Date)\n",
"\n",
"# View the first few rows\n",
"pumpkins %>% \n",
" slice_head(n = 7)"
],
"outputs": [],
"metadata": {
"id": "5joszIVSZ6xe"
}
},
{
"cell_type": "markdown",
"source": [
"우와! 🤩\n",
"\n",
"다음으로, 호박의 평균 가격을 나타내는 새로운 열 `Price`를 만들어 봅시다. 이제 `Low Price`와 `High Price` 열의 평균을 계산하여 새로운 Price 열을 채워보세요.\n",
"
\n"
],
"metadata": {
"id": "nIgLjNMCZ-6Y"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Create a new column Price\n",
"pumpkins <- pumpkins %>% \n",
" mutate(Price = (`Low Price` + `High Price`)/2)\n",
"\n",
"# View the first few rows of the data\n",
"pumpkins %>% \n",
" slice_head(n = 5)"
],
"outputs": [],
"metadata": {
"id": "Zo0BsqqtaJw2"
}
},
{
"cell_type": "markdown",
"source": [
"예스!💪\n",
"\n",
"\"잠깐만요!\", 당신은 `View(pumpkins)`로 전체 데이터 세트를 훑어본 후 말할 겁니다. \"여기 뭔가 이상한 게 있어요!\"🤔\n",
"\n",
"`Package` 열을 보면, 호박은 여러 가지 다른 구성으로 판매됩니다. 일부는 `1 1/9 bushel` 단위로, 일부는 `1/2 bushel` 단위로, 일부는 호박 개수로, 일부는 무게 단위로, 그리고 일부는 폭이 다양한 큰 상자에 담겨 판매됩니다.\n",
"\n",
"이것을 확인해 봅시다:\n"
],
"metadata": {
"id": "p77WZr-9aQAR"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Verify the distinct observations in Package column\n",
"pumpkins %>% \n",
" distinct(Package)"
],
"outputs": [],
"metadata": {
"id": "XISGfh0IaUy6"
}
},
{
"cell_type": "markdown",
"source": [
"놀라워요!👏\n",
"\n",
"호박은 일관되게 무게를 재기가 매우 어려운 것 같으니, `Package` 열에 *bushel*이라는 문자열이 포함된 호박만 선택하여 새로운 데이터 프레임 `new_pumpkins`에 넣어 봅시다.\n",
"
\n"
],
"metadata": {
"id": "7sMjiVujaZxY"
}
},
{
"cell_type": "markdown",
"source": [
"#### dplyr::filter()와 stringr::str_detect()\n",
"\n",
"[`dplyr::filter()`](https://dplyr.tidyverse.org/reference/filter.html): 조건을 만족하는 **행**만 포함하는 데이터의 부분 집합을 생성합니다. 이 경우, `Package` 열에 *bushel* 문자열이 포함된 호박 데이터를 필터링합니다.\n",
"\n",
"[stringr::str_detect()](https://stringr.tidyverse.org/reference/str_detect.html): 문자열에서 특정 패턴의 존재 여부를 감지합니다.\n",
"\n",
"[`stringr`](https://github.com/tidyverse/stringr) 패키지는 일반적인 문자열 작업을 위한 간단한 함수들을 제공합니다.\n"
],
"metadata": {
"id": "L8Qfcs92ageF"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Retain only pumpkins with \"bushel\"\n",
"new_pumpkins <- pumpkins %>% \n",
" filter(str_detect(Package, \"bushel\"))\n",
"\n",
"# Get the dimensions of the new data\n",
"dim(new_pumpkins)\n",
"\n",
"# View a few rows of the new data\n",
"new_pumpkins %>% \n",
" slice_head(n = 5)"
],
"outputs": [],
"metadata": {
"id": "hy_SGYREampd"
}
},
{
"cell_type": "markdown",
"source": [
"우리는 약 415줄 정도의 데이터를 좁혀서 한 바구니에 담긴 호박 데이터를 확인할 수 있습니다.🤩 \n",
"
\n"
],
"metadata": {
"id": "VrDwF031avlR"
}
},
{
"cell_type": "markdown",
"source": [
"#### dplyr::case_when()\n",
"\n",
"**하지만 잠깐! 해야 할 일이 하나 더 있습니다**\n",
"\n",
"행마다 부셸 양이 다르다는 것을 눈치채셨나요? 가격을 1 1/9 부셸이나 1/2 부셸이 아닌, 부셸당 가격으로 표준화해야 합니다. 이제 수학을 활용해 이를 표준화할 시간입니다.\n",
"\n",
"[`case_when()`](https://dplyr.tidyverse.org/reference/case_when.html) 함수를 사용하여 조건에 따라 Price 열을 *변경(mutate)*할 것입니다. `case_when`은 여러 `if_else()` 문을 벡터화하여 처리할 수 있도록 도와줍니다.\n"
],
"metadata": {
"id": "mLpw2jH4a0tx"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Convert the price if the Package contains fractional bushel values\n",
"new_pumpkins <- new_pumpkins %>% \n",
" mutate(Price = case_when(\n",
" str_detect(Package, \"1 1/9\") ~ Price/(1 + 1/9),\n",
" str_detect(Package, \"1/2\") ~ Price/(1/2),\n",
" TRUE ~ Price))\n",
"\n",
"# View the first few rows of the data\n",
"new_pumpkins %>% \n",
" slice_head(n = 30)"
],
"outputs": [],
"metadata": {
"id": "P68kLVQmbM6I"
}
},
{
"cell_type": "markdown",
"source": [
"이제 우리는 부셸 측정을 기반으로 단위당 가격을 분석할 수 있습니다. 하지만 이 모든 호박 부셸에 대한 연구는 `데이터의 본질을 이해하는 것`이 얼마나 `중요한지`를 보여줍니다!\n",
"\n",
"> ✅ [The Spruce Eats](https://www.thespruceeats.com/how-much-is-a-bushel-1389308)에 따르면, 부셸의 무게는 부피 측정 단위이기 때문에 농산물의 종류에 따라 달라집니다. \"예를 들어, 토마토 한 부셸은 56파운드로 간주됩니다... 잎채소와 같은 경우는 공간을 더 많이 차지하지만 무게는 적기 때문에 시금치 한 부셸은 20파운드에 불과합니다.\" 꽤 복잡하죠! 부셸을 파운드로 변환하려고 애쓰지 말고, 부셸 단위로 가격을 매기는 게 낫습니다. 하지만 이 모든 호박 부셸에 대한 연구는 데이터의 본질을 이해하는 것이 얼마나 중요한지 다시 한번 보여줍니다!\n",
">\n",
"> ✅ 반 부셸 단위로 판매되는 호박이 매우 비싸다는 것을 눈치채셨나요? 왜 그런지 알아낼 수 있나요? 힌트: 작은 호박이 큰 호박보다 훨씬 비쌉니다. 아마도 큰 속이 빈 파이 호박 하나가 차지하는 공간 때문에, 부셸당 훨씬 더 많은 작은 호박이 들어가기 때문일 것입니다.\n"
],
"metadata": {
"id": "pS2GNPagbSdb"
}
},
{
"cell_type": "markdown",
"source": [
"이제 마지막으로, 모험심을 발휘해 💁♀️, Month 열을 첫 번째 위치로 옮겨봅시다. 즉, `Package` 열 `앞`으로 이동시키는 것입니다.\n",
"\n",
"`dplyr::relocate()`는 열의 위치를 변경하는 데 사용됩니다.\n"
],
"metadata": {
"id": "qql1SowfbdnP"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Create a new data frame new_pumpkins\n",
"new_pumpkins <- new_pumpkins %>% \n",
" relocate(Month, .before = Package)\n",
"\n",
"new_pumpkins %>% \n",
" slice_head(n = 7)"
],
"outputs": [],
"metadata": {
"id": "JJ1x6kw8bixF"
}
},
{
"cell_type": "markdown",
"source": [
"좋아요!👌 이제 새로운 회귀 모델을 구축할 수 있는 깔끔하고 정돈된 데이터셋이 준비되었습니다! \n",
"
\n"
],
"metadata": {
"id": "y8TJ0Za_bn5Y"
}
},
{
"cell_type": "markdown",
"source": [
"## 4. ggplot2를 활용한 데이터 시각화\n",
"\n",
"
\n",
" \n",
"
\n"
],
"metadata": {
"id": "Ml7SDCLQcPvE"
}
},
{
"cell_type": "markdown",
"source": [
"### **어떻게 유용하게 만들 수 있을까요?**\n",
"\n",
"차트에 유용한 데이터를 표시하려면 데이터를 어떤 방식으로든 그룹화해야 하는 경우가 많습니다. 예를 들어, 우리의 경우 각 달의 호박 평균 가격을 찾으면 데이터의 기본 패턴에 대한 더 많은 통찰력을 제공할 수 있습니다. 이는 또 하나의 **dplyr** 기능을 사용하게 만듭니다:\n",
"\n",
"#### `dplyr::group_by() %>% summarize()`\n",
"\n",
"R에서 그룹화된 집계는 다음을 사용하여 쉽게 계산할 수 있습니다:\n",
"\n",
"`dplyr::group_by() %>% summarize()`\n",
"\n",
"- `dplyr::group_by()`는 분석 단위를 전체 데이터셋에서 개별 그룹(예: 월별)으로 변경합니다.\n",
"\n",
"- `dplyr::summarize()`는 그룹화 변수마다 하나의 열과 지정한 요약 통계마다 하나의 열을 포함하는 새로운 데이터 프레임을 생성합니다.\n",
"\n",
"예를 들어, `dplyr::group_by() %>% summarize()`를 사용하여 **Month** 열을 기준으로 호박을 그룹화한 다음 각 달의 **평균 가격**을 찾을 수 있습니다.\n"
],
"metadata": {
"id": "jMakvJZIcVkh"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Find the average price of pumpkins per month\r\n",
"new_pumpkins %>%\r\n",
" group_by(Month) %>% \r\n",
" summarise(mean_price = mean(Price))"
],
"outputs": [],
"metadata": {
"id": "6kVSUa2Bcilf"
}
},
{
"cell_type": "markdown",
"source": [
"간단하게!✨\n",
"\n",
"월과 같은 범주형 특징은 막대 그래프 📊로 표현하는 것이 더 적합합니다. 막대 그래프를 그리는 데 사용되는 레이어는 `geom_bar()`와 `geom_col()`입니다. 자세한 내용은 `?geom_bar`를 참고하세요.\n",
"\n",
"하나 만들어볼까요!\n"
],
"metadata": {
"id": "Kds48GUBcj3W"
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Find the average price of pumpkins per month then plot a bar chart\r\n",
"new_pumpkins %>%\r\n",
" group_by(Month) %>% \r\n",
" summarise(mean_price = mean(Price)) %>% \r\n",
" ggplot(aes(x = Month, y = mean_price)) +\r\n",
" geom_col(fill = \"midnightblue\", alpha = 0.7) +\r\n",
" ylab(\"Pumpkin Price\")"
],
"outputs": [],
"metadata": {
"id": "VNbU1S3BcrxO"
}
},
{
"cell_type": "markdown",
"source": [
"🤩🤩이것은 훨씬 더 유용한 데이터 시각화입니다! 이는 호박의 최고 가격이 9월과 10월에 발생한다는 것을 나타내는 것 같습니다. 이것이 당신의 예상과 일치하나요? 왜 그런가요, 아니면 왜 그렇지 않은가요?\n",
"\n",
"두 번째 강의를 마친 것을 축하합니다 👏! 데이터를 모델 구축을 위해 준비한 후, 시각화를 통해 더 많은 인사이트를 발견했네요!\n"
],
"metadata": {
"id": "zDm0VOzzcuzR"
}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다. \n"
]
}
]
}