You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
18 KiB
448 lines
18 KiB
{
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2,
|
|
"metadata": {
|
|
"colab": {
|
|
"name": "lesson_1-R.ipynb",
|
|
"provenance": [],
|
|
"collapsed_sections": [],
|
|
"toc_visible": true
|
|
},
|
|
"kernelspec": {
|
|
"name": "ir",
|
|
"display_name": "R"
|
|
},
|
|
"language_info": {
|
|
"name": "R"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "c18d3bd0bd8ae3878597e89dcd1fa5c1",
|
|
"translation_date": "2025-09-04T01:32:53+00:00",
|
|
"source_file": "2-Regression/1-Tools/solution/R/lesson_1-R.ipynb",
|
|
"language_code": "ko"
|
|
}
|
|
},
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [],
|
|
"metadata": {
|
|
"id": "YJUHCXqK57yz"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## 회귀 분석 소개 - 1강\n",
|
|
"\n",
|
|
"#### 관점을 넓혀보기\n",
|
|
"\n",
|
|
"✅ 회귀 방법에는 여러 가지가 있으며, 어떤 방법을 선택할지는 여러분이 찾고자 하는 답에 따라 달라집니다. 예를 들어, 특정 나이에 따른 예상 키를 예측하고 싶다면, **숫자 값**을 찾는 것이므로 `선형 회귀`를 사용할 것입니다. 반면, 특정 요리가 비건으로 간주되어야 하는지 여부를 알고 싶다면, 이는 **카테고리 할당**을 찾는 것이므로 `로지스틱 회귀`를 사용할 것입니다. 로지스틱 회귀에 대해서는 나중에 더 배우게 될 것입니다. 데이터를 통해 어떤 질문을 할 수 있을지, 그리고 이 방법들 중 어떤 것이 더 적합할지에 대해 잠시 생각해 보세요.\n",
|
|
"\n",
|
|
"이 섹션에서는 [당뇨병에 관한 작은 데이터셋](https://www4.stat.ncsu.edu/~boos/var.select/diabetes.html)을 다룰 것입니다. 당뇨병 환자를 위한 치료법을 테스트하고 싶다고 상상해 보세요. 머신러닝 모델은 변수들의 조합을 기반으로 어떤 환자가 치료에 더 잘 반응할지 결정하는 데 도움을 줄 수 있습니다. 심지어 아주 기본적인 회귀 모델이라도 시각화하면 이론적인 임상 시험을 구성하는 데 도움이 되는 변수에 대한 정보를 보여줄 수 있습니다.\n",
|
|
"\n",
|
|
"그럼, 이 작업을 시작해 봅시다!\n",
|
|
"\n",
|
|
"<p >\n",
|
|
" <img src=\"../../images/encouRage.jpg\"\n",
|
|
" width=\"630\"/>\n",
|
|
" <figcaption>@allison_horst의 작품</figcaption>\n",
|
|
"\n",
|
|
"<!--<br>@allison_horst의 작품-->\n"
|
|
],
|
|
"metadata": {
|
|
"id": "LWNNzfqd6feZ"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## 1. 도구 세트 준비하기\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",
|
|
"다음 명령어로 패키지를 설치할 수 있습니다:\n",
|
|
"\n",
|
|
"`install.packages(c(\"tidyverse\", \"tidymodels\"))`\n",
|
|
"\n",
|
|
"아래 스크립트는 이 모듈을 완료하는 데 필요한 패키지가 설치되어 있는지 확인하고, 누락된 경우 자동으로 설치해줍니다.\n"
|
|
],
|
|
"metadata": {
|
|
"id": "FIo2YhO26wI9"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"source": [
|
|
"suppressWarnings(if(!require(\"pacman\")) install.packages(\"pacman\"))\n",
|
|
"pacman::p_load(tidyverse, tidymodels)"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"output_type": "stream",
|
|
"name": "stderr",
|
|
"text": [
|
|
"Loading required package: pacman\n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"id": "cIA9fz9v7Dss",
|
|
"colab": {
|
|
"base_uri": "https://localhost:8080/"
|
|
},
|
|
"outputId": "2df7073b-86b2-4b32-cb86-0da605a0dc11"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"이제 이 멋진 패키지들을 로드하고 현재 R 세션에서 사용할 수 있도록 합시다. (이는 단순한 예시일 뿐이며, `pacman::p_load()`가 이미 이를 수행했습니다)\n"
|
|
],
|
|
"metadata": {
|
|
"id": "gpO_P_6f9WUG"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# load the core Tidyverse packages\r\n",
|
|
"library(tidyverse)\r\n",
|
|
"\r\n",
|
|
"# load the core Tidymodels packages\r\n",
|
|
"library(tidymodels)\r\n"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "NLMycgG-9ezO"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## 2. 당뇨병 데이터셋\n",
|
|
"\n",
|
|
"이번 연습에서는 당뇨병 데이터셋을 사용하여 회귀 분석 기술을 활용해 예측을 수행해 보겠습니다. [당뇨병 데이터셋](https://www4.stat.ncsu.edu/~boos/var.select/diabetes.rwrite1.txt)은 당뇨병과 관련된 `442개의 샘플` 데이터를 포함하고 있으며, 10개의 예측 변수(`나이`, `성별`, `체질량지수`, `평균 혈압`, `6가지 혈청 측정값`)와 결과 변수 `y`를 포함하고 있습니다. 결과 변수 `y`는 기준 시점으로부터 1년 후 질병 진행 정도를 정량적으로 측정한 값입니다.\n",
|
|
"\n",
|
|
"|관측치 수|442|\n",
|
|
"|----------------------|:---|\n",
|
|
"|예측 변수 수|처음 10개의 열은 숫자로 된 예측 변수|\n",
|
|
"|결과/목표|11번째 열은 기준 시점으로부터 1년 후 질병 진행 정도를 정량적으로 측정한 값|\n",
|
|
"|예측 변수 정보|- 나이 (연령)\n",
|
|
"||- 성별\n",
|
|
"||- bmi 체질량지수\n",
|
|
"||- bp 평균 혈압\n",
|
|
"||- s1 tc, 총 혈청 콜레스테롤\n",
|
|
"||- s2 ldl, 저밀도 지단백\n",
|
|
"||- s3 hdl, 고밀도 지단백\n",
|
|
"||- s4 tch, 총 콜레스테롤 / HDL\n",
|
|
"||- s5 ltg, 아마도 혈청 중성지방 수치의 로그값\n",
|
|
"||- s6 glu, 혈당 수치|\n",
|
|
"\n",
|
|
"\n",
|
|
"> 🎓 기억하세요, 이는 지도 학습(supervised learning)이며, 'y'라는 이름의 목표 변수가 필요합니다.\n",
|
|
"\n",
|
|
"R로 데이터를 조작하기 전에, 데이터를 R의 메모리로 가져오거나, R이 원격으로 데이터를 액세스할 수 있도록 데이터와 연결을 설정해야 합니다.\n",
|
|
"\n",
|
|
"> [readr](https://readr.tidyverse.org/) 패키지는 Tidyverse의 일부로, 직사각형 형태의 데이터를 R로 빠르고 간편하게 읽어오는 방법을 제공합니다.\n",
|
|
"\n",
|
|
"이제 다음 소스 URL에서 제공된 당뇨병 데이터셋을 불러옵시다: <https://www4.stat.ncsu.edu/~boos/var.select/diabetes.html>\n",
|
|
"\n",
|
|
"또한, `glimpse()`를 사용하여 데이터에 대한 간단한 검사를 수행하고, `slice()`를 사용해 처음 5개의 행을 표시해 보겠습니다.\n",
|
|
"\n",
|
|
"진행하기 전에, R 코드에서 자주 접하게 될 🥁🥁 파이프 연산자 `%>%`를 소개하겠습니다.\n",
|
|
"\n",
|
|
"파이프 연산자 (`%>%`)는 객체를 함수나 호출 표현식으로 전달하여 논리적인 순서로 작업을 수행합니다. 코드에서 파이프 연산자는 \"그리고 나서\"라고 생각하면 됩니다.\n"
|
|
],
|
|
"metadata": {
|
|
"id": "KM6iXLH996Cl"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Import the data set\r\n",
|
|
"diabetes <- read_table2(file = \"https://www4.stat.ncsu.edu/~boos/var.select/diabetes.rwrite1.txt\")\r\n",
|
|
"\r\n",
|
|
"\r\n",
|
|
"# Get a glimpse and dimensions of the data\r\n",
|
|
"glimpse(diabetes)\r\n",
|
|
"\r\n",
|
|
"\r\n",
|
|
"# Select the first 5 rows of the data\r\n",
|
|
"diabetes %>% \r\n",
|
|
" slice(1:5)"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "Z1geAMhM-bSP"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"`glimpse()`를 사용하면 이 데이터가 442개의 행과 11개의 열로 구성되어 있으며, 모든 열의 데이터 타입이 `double`임을 알 수 있습니다.\n",
|
|
"\n",
|
|
"<br>\n",
|
|
"\n",
|
|
"> `glimpse()`와 `slice()`는 [`dplyr`](https://dplyr.tidyverse.org/) 패키지의 함수입니다. Tidyverse의 일부인 dplyr은 데이터 조작을 위한 문법으로, 가장 일반적인 데이터 조작 문제를 해결하는 데 도움을 주는 일관된 동사 세트를 제공합니다.\n",
|
|
"\n",
|
|
"<br>\n",
|
|
"\n",
|
|
"이제 데이터를 확보했으니, 이번 연습에서는 특정 특징(`bmi`)에 초점을 맞춰 보겠습니다. 이를 위해 원하는 열을 선택해야 합니다. 그렇다면, 어떻게 해야 할까요?\n",
|
|
"\n",
|
|
"[`dplyr::select()`](https://dplyr.tidyverse.org/reference/select.html)는 데이터 프레임에서 열을 *선택*하고 (선택적으로 이름을 변경할 수도 있음) 사용할 수 있도록 해줍니다.\n"
|
|
],
|
|
"metadata": {
|
|
"id": "UwjVT1Hz-c3Z"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Select predictor feature `bmi` and outcome `y`\r\n",
|
|
"diabetes_select <- diabetes %>% \r\n",
|
|
" select(c(bmi, y))\r\n",
|
|
"\r\n",
|
|
"# Print the first 5 rows\r\n",
|
|
"diabetes_select %>% \r\n",
|
|
" slice(1:10)"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "RDY1oAKI-m80"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## 3. 학습 및 테스트 데이터\n",
|
|
"\n",
|
|
"지도 학습에서는 데이터를 두 개의 하위 집합으로 *분할*하는 것이 일반적인 관행입니다. 하나는 모델을 학습시키는 데 사용하는 (일반적으로 더 큰) 데이터 세트이고, 다른 하나는 모델의 성능을 확인하기 위해 사용하는 더 작은 \"보류\" 데이터 세트입니다.\n",
|
|
"\n",
|
|
"이제 데이터를 준비했으니, 이 데이터 세트에서 숫자들을 논리적으로 분할하는 데 머신이 도움을 줄 수 있는지 확인해 봅시다. 이를 위해 Tidymodels 프레임워크의 일부인 [rsample](https://tidymodels.github.io/rsample/) 패키지를 사용할 수 있습니다. 이 패키지를 사용하면 데이터를 *어떻게* 분할할지에 대한 정보를 포함하는 객체를 생성할 수 있으며, 이후 두 개의 rsample 함수를 사용하여 생성된 학습 및 테스트 세트를 추출할 수 있습니다:\n"
|
|
],
|
|
"metadata": {
|
|
"id": "SDk668xK-tc3"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"set.seed(2056)\r\n",
|
|
"# Split 67% of the data for training and the rest for tesing\r\n",
|
|
"diabetes_split <- diabetes_select %>% \r\n",
|
|
" initial_split(prop = 0.67)\r\n",
|
|
"\r\n",
|
|
"# Extract the resulting train and test sets\r\n",
|
|
"diabetes_train <- training(diabetes_split)\r\n",
|
|
"diabetes_test <- testing(diabetes_split)\r\n",
|
|
"\r\n",
|
|
"# Print the first 3 rows of the training set\r\n",
|
|
"diabetes_train %>% \r\n",
|
|
" slice(1:10)"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "EqtHx129-1h-"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## 4. Tidymodels로 선형 회귀 모델 학습하기\n",
|
|
"\n",
|
|
"이제 모델을 학습할 준비가 되었습니다!\n",
|
|
"\n",
|
|
"Tidymodels에서는 `parsnip()`을 사용하여 세 가지 개념을 지정함으로써 모델을 정의합니다:\n",
|
|
"\n",
|
|
"- 모델 **유형(type)**: 선형 회귀, 로지스틱 회귀, 결정 트리 모델 등과 같은 모델을 구분합니다.\n",
|
|
"\n",
|
|
"- 모델 **모드(mode)**: 회귀(regression)와 분류(classification)와 같은 일반적인 옵션을 포함하며, 일부 모델 유형은 둘 중 하나를 지원하거나 하나의 모드만 가질 수 있습니다.\n",
|
|
"\n",
|
|
"- 모델 **엔진(engine)**: 모델을 학습시키는 데 사용되는 계산 도구입니다. 종종 **`\"lm\"`** 또는 **`\"ranger\"`**와 같은 R 패키지가 사용됩니다.\n",
|
|
"\n",
|
|
"이러한 모델링 정보는 모델 사양에 캡처되므로, 이제 하나를 만들어 봅시다!\n"
|
|
],
|
|
"metadata": {
|
|
"id": "sBOS-XhB-6v7"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Build a linear model specification\r\n",
|
|
"lm_spec <- \r\n",
|
|
" # Type\r\n",
|
|
" linear_reg() %>% \r\n",
|
|
" # Engine\r\n",
|
|
" set_engine(\"lm\") %>% \r\n",
|
|
" # Mode\r\n",
|
|
" set_mode(\"regression\")\r\n",
|
|
"\r\n",
|
|
"\r\n",
|
|
"# Print the model specification\r\n",
|
|
"lm_spec"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "20OwEw20--t3"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"모델이 *지정*된 후, [`fit()`](https://parsnip.tidymodels.org/reference/fit.html) 함수를 사용하여 모델을 `추정`하거나 `훈련`할 수 있습니다. 일반적으로 공식과 일부 데이터를 사용합니다.\n",
|
|
"\n",
|
|
"`y ~ .`은 `y`를 예측 값/목표로 설정하고, 모든 예측 변수/특징(즉, `.`)에 의해 설명된다는 것을 의미합니다. (이 경우, 우리는 하나의 예측 변수인 `bmi`만 가지고 있습니다.)\n"
|
|
],
|
|
"metadata": {
|
|
"id": "_oDHs89k_CJj"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Build a linear model specification\r\n",
|
|
"lm_spec <- linear_reg() %>% \r\n",
|
|
" set_engine(\"lm\") %>%\r\n",
|
|
" set_mode(\"regression\")\r\n",
|
|
"\r\n",
|
|
"\r\n",
|
|
"# Train a linear regression model\r\n",
|
|
"lm_mod <- lm_spec %>% \r\n",
|
|
" fit(y ~ ., data = diabetes_train)\r\n",
|
|
"\r\n",
|
|
"# Print the model\r\n",
|
|
"lm_mod"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "YlsHqd-q_GJQ"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"모델 출력에서 우리는 훈련 중 학습된 계수를 확인할 수 있습니다. 이 계수들은 실제 변수와 예측 변수 간의 전체 오류를 가장 낮게 만드는 최적의 선형 적합 계수를 나타냅니다.\n",
|
|
"<br>\n",
|
|
"\n",
|
|
"## 5. 테스트 세트에 대한 예측 수행\n",
|
|
"\n",
|
|
"이제 모델을 훈련했으니, 이를 사용하여 테스트 데이터셋의 질병 진행 y를 [parsnip::predict()](https://parsnip.tidymodels.org/reference/predict.model_fit.html)를 통해 예측할 수 있습니다. 이는 데이터 그룹 간의 선을 그리는 데 사용됩니다.\n"
|
|
],
|
|
"metadata": {
|
|
"id": "kGZ22RQj_Olu"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Make predictions for the test set\r\n",
|
|
"predictions <- lm_mod %>% \r\n",
|
|
" predict(new_data = diabetes_test)\r\n",
|
|
"\r\n",
|
|
"# Print out some of the predictions\r\n",
|
|
"predictions %>% \r\n",
|
|
" slice(1:5)"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "nXHbY7M2_aao"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"우와! 💃🕺 우리는 방금 모델을 훈련시키고 이를 사용해 예측을 했어요!\n",
|
|
"\n",
|
|
"예측을 할 때, tidymodels의 관례는 항상 표준화된 열 이름을 가진 tibble/데이터 프레임 형태로 결과를 생성하는 것입니다. 이렇게 하면 원본 데이터와 예측 결과를 결합하여 이후의 작업(예: 시각화)에서 사용하기 쉬운 형식으로 만들 수 있습니다.\n",
|
|
"\n",
|
|
"`dplyr::bind_cols()`는 여러 데이터 프레임의 열을 효율적으로 결합합니다.\n"
|
|
],
|
|
"metadata": {
|
|
"id": "R_JstwUY_bIs"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Combine the predictions and the original test set\r\n",
|
|
"results <- diabetes_test %>% \r\n",
|
|
" bind_cols(predictions)\r\n",
|
|
"\r\n",
|
|
"\r\n",
|
|
"results %>% \r\n",
|
|
" slice(1:5)"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "RybsMJR7_iI8"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## 6. 모델링 결과 시각화\n",
|
|
"\n",
|
|
"이제 결과를 시각적으로 확인해볼 시간입니다 📈. 테스트 세트의 모든 `y`와 `bmi` 값을 산점도로 표시한 다음, 예측값을 사용해 모델의 데이터 그룹 사이에 가장 적절한 위치에 선을 그려보겠습니다.\n",
|
|
"\n",
|
|
"R에는 그래프를 만드는 여러 가지 시스템이 있지만, `ggplot2`는 가장 우아하고 다재다능한 도구 중 하나입니다. 이 도구를 사용하면 **독립적인 구성 요소를 결합하여** 그래프를 만들 수 있습니다.\n"
|
|
],
|
|
"metadata": {
|
|
"id": "XJbYbMZW_n_s"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"source": [
|
|
"# Set a theme for the plot\r\n",
|
|
"theme_set(theme_light())\r\n",
|
|
"# Create a scatter plot\r\n",
|
|
"results %>% \r\n",
|
|
" ggplot(aes(x = bmi)) +\r\n",
|
|
" # Add a scatter plot\r\n",
|
|
" geom_point(aes(y = y), size = 1.6) +\r\n",
|
|
" # Add a line plot\r\n",
|
|
" geom_line(aes(y = .pred), color = \"blue\", size = 1.5)"
|
|
],
|
|
"outputs": [],
|
|
"metadata": {
|
|
"id": "R9tYp3VW_sTn"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"✅ 여기서 무슨 일이 일어나고 있는지 잠시 생각해 보세요. 직선 하나가 많은 작은 데이터 점들 사이를 지나가고 있습니다. 그런데 이 직선이 정확히 무엇을 하고 있는 걸까요? 이 직선을 사용해서 새로운, 보지 못한 데이터 포인트가 플롯의 y축과 어떤 관계를 가지는지 예측할 수 있는 방법을 이해할 수 있나요? 이 모델의 실질적인 활용 방법을 말로 표현해 보세요.\n",
|
|
"\n",
|
|
"축하합니다! 첫 번째 선형 회귀 모델을 만들고, 이를 사용해 예측을 생성한 뒤, 플롯에 표시했습니다!\n"
|
|
],
|
|
"metadata": {
|
|
"id": "zrPtHIxx_tNI"
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\n"
|
|
]
|
|
}
|
|
]
|
|
} |