-
Notifications
You must be signed in to change notification settings - Fork 7
/
model-feature-engineering-tech.Rmd
276 lines (190 loc) · 10.5 KB
/
model-feature-engineering-tech.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
---
layout: page
title: xwMOOC 모형
subtitle: 피처 공학 기법 - 직사각형 데이터프레임
output:
html_document:
toc: yes
toc_float: true
highlight: tango
code_folding: show
number_section: true
self_contained: true
editor_options:
chunk_output_type: console
---
```{r, include=FALSE}
source("tools/chunk-options.R")
knitr::opts_chunk$set(echo = TRUE, message=FALSE, warning=FALSE,
comment="", digits = 3, tidy = FALSE, prompt = TRUE, fig.align = 'center')
```
# 범주형 변수 {#categorical-one-hot-encoding}
## One Hot Encoding - `ifelse` {#categorical-one-hot-encoding-ifelse}
`data(package="ggplot2")` 명령어를 통해서 `ggplot2` 팩키지에 내장된 데이터셋을 확인할 수 있다. `dataset` 팩키지 `mtcars` 데이터셋을 새롭게 만든 `mpg` 데이터셋을 활용하여 **One Hot Encoding** 기법을 통해서 범주형 변수를 기계학습 예측모형에 활용할 수 있는 Feature로 만들어 보자.
One Hot Encoding을 통계학에서는 가변수(dummy variable)을 만든다고 한다. 즉, 변수에 특성이 있느냐 없느냐를 가지고 두가지 수준을 갖는 범주의 차이를 계량화한다. 예를 들어 1999년 양산차와 2008년 양산차간의 연비향상이 얼마나 있었는지를 하나의 Feature로 넣는 것을 상정할 수 있다.
```{r mtcars-one-hot-encoding}
library(tidyverse)
mpg <- mpg %>% tbl_df
mpg %>%
select(year) %>%
table()
```
시내주행연비(`cty`)와 고속도로연비(`hwy`)에 영향을 주는 변수는 많다. 그중 10년이라는 시간이라는 요인이 연비향상에 얼마나 기여를 했는지 요약통계량을 다음과 같이 구할 수 있다.
```{r mtcars-one-hot-encoding-summary}
mpg %>%
group_by(year) %>%
summarise(mean_cty = mean(cty),
mean_hwy = mean(hwy))
```
`ifelse` 문을 사용해서 1999년을 기준으로 회귀계수(가중치, `w`)를 통해 연비에 주는 효과를 계량화해서 넣을 수 있다.
```{r mtcars-one-hot-encoding-summary-encode}
mpg %>%
mutate(decade = ifelse(year == 1999, 0, 1)) %>%
select(cty, year, decade)
```
## 유의미한 범주 - `case_when` {#categorical-one-hot-encoding-case_when}
`case_when()`을 사용하여 `ifelse`를 여러번 중첩시켜 사용하는 대신 범주를 깔끔하게 정리할 수 있다.
```{r mtcars-case-when}
mpg %>%
select(class) %>%
table()
```
자동차를 소형, 중형, 중소형 등 다양하게 구분할 수 있는데 이를 다음과 같이 범주를 나눠서 정리하는 것이 예측모형의 유의미한 Feature로 탈바꿈시킬 수도 있다.
- minivan, pickup - 영업용
- subcompact, compact - 소형
- midsize - 중형
- 2seater, suv - 여가용
```{r mtcars-case-when-class}
mpg %>%
mutate(`차량범주` = case_when(str_detect( class, pattern="minivan|pickup") ~ "영업용",
str_detect( class, pattern="compact") ~ "소형",
str_detect( class, pattern="midsize") ~ "중형",
TRUE ~ "여가용")) %>%
select(`차량범주`) %>%
table()
```
## 범주가 많은 경우 - 비율(`prop.table`) {#categorical-one-hot-encoding-prop-table}
시내주행연비를 평균보다 높은 경우 "high", 낮은 경우 "low"로 두고 `prop.table()` 함수를 통해 제조사(`manufacturer`) 비율을 계산한다. 이를 `hl_prop` 변수로 결합시켜 너무 범주가 많아서 사용하기 어려웠던 범수형 변수를 새로운 feature로 만들어 냈다.
```{r mtcars-prop-table}
manufacturer_tbl <- mpg %>%
mutate(hl_mpg = ifelse(cty >17, "high", "low")) %>%
select(manufacturer, hl_mpg) %>%
table
manufacturer_prop_tbl <- prop.table(manufacturer_tbl, 1) %>% tbl_df %>%
filter(hl_mpg == "high") %>%
rename(hl_prop = n)
mpg <- inner_join(mpg, manufacturer_prop_tbl, by="manufacturer")
mpg %>%
DT::datatable()
```
# 연속형 변수 → 범주형 변수 {#categorical-numerical-to-categorical}
## 연속형 변수 → 범주형 변수 - 절대값(`cut`) {#categorical-numerical-to-categorical-cut}
연속형 변수의 경우 히스토그램을 통해서 분포를 확인할 수 있고, 비선형적인 특성을 적절한 범주로 표현하여 잡아내는 것이 가능하다.
```{r mtcars-numerical-categorical-histogram}
mpg %>%
ggplot(aes(x=displ)) +
geom_histogram(bins = 30)
```
`cut()` 함수를 사용해서 배기량(`displ`)을 2000cc 기준으로 나눠본다. 배기량에 대한 자세한 사항은 [나무위키 배기량](https://namu.wiki/w/%EB%B0%B0%EA%B8%B0%EB%9F%89)을 참고한다.
```{r mtcars-numerical-categorical-bins}
mpg <- mpg %>%
mutate(displ_cat = cut(displ, breaks = seq(1, 7.0, by=2)))
mpg %>%
select(displ_cat) %>%
table()
```
`model.matrix` 함수를 사용해서 가변수화한 후에 이를 범주형 변수로 변환하여 `cbind()` 함수로 결합시킨다.
```{r mtcars-numerical-categorical-bins-matrix}
mpg <- cbind(mpg, model.matrix(~ displ_cat -1, data = mpg))
mpg %>%
select(contains("disp")) %>%
head()
```
## 연속형 변수 → 범주형 변수 - 분위수(`quantile`) {#categorical-numerical-to-categorical-quantile}
연속형 변수를 범주형 변수로 변환시킬 때 앞서 `cut()` 함수의 절대값을 기준으로 나누는 대신에 **분위수(quantile)**를 사용해서 나누는 것이 적절할 때가 있다. 대표적으로 일일이 사람이 보는 대신에 기계적으로 자동화를 할 경우 도움이 된다.
이런 경우 `ntile()` 함수를 사용하면 관측점을 예를 들어 배기량(`displ`) 기준으로 3개 범주집단으로 동일하게 나눠준다.
```{r mtcars-numerical-categorical-quantile}
mpg %>%
mutate(displ_tile = ntile(displ, 3) %>% as.factor) %>%
select(displ_tile) %>%
table()
```
`ntile()` 함수로 연속형 변수 배기량(`displ`)을 범주형 변수로 변환시킨 후에 이를 `model.matrix()` 함수로 가변수화하여 예측모형을 위한 `basetable`에 일원으로 편입시킨다.
역행렬 변환이 가능한 `full rank`를 맞추고자 할 경우 `-1`을 빼서 넣어준다.
```{r mtcars-numerical-categorical-quantile-dummy}
mpg <- mpg %>%
mutate(displ_tile = ntile(displ, 3) %>% as.factor)
# mpg <- cbind(mpg, model.matrix(~ displ_tile -1, data = mpg))
mpg <- cbind(mpg, model.matrix(~ displ_tile, data = mpg)) # full rank
mpg %>%
select(contains("disp")) %>%
head()
```
# 변수 변환 (Transformation) {#categorical-numerical-to-categorical}
연속형 변수 중 치우침이 심한 변수가 많다. 이를 변수변환하여 정규분포에 가까운 형태로 맞추게 되면 예측모형의 성능 향상과 안정성을 기대할 수 있다. [^power-transformation]
[^power-transformation]: [Wikipedia, "Power Transform"](https://en.wikipedia.org/wiki/Power_transform)
멱변환(Power transformation)은 멱함수(power function)를 사용하여 데이터를 단조변환시키는데 이를 통해서 분산을 안정화시키고, 정규분포에 가까운 형태로 만들 수 있어 통계학에서 유용한 도구 중 하나다. 박스-콕스 변환(Box-Cox Transformation)은 Yeo–Johnson 변환과 비교하여 0 혹은 음수인 경우에 적용에 한계가 있다.
$$y_i^{(\lambda)} = \begin{cases} ((y_i+1)^\lambda-1)/\lambda & \text{if }\lambda \neq 0, y \geq 0 \\
\log(y_i + 1) & \text{if }\lambda = 0, y \geq 0 \\
-[(-y_i + 1)^{(2-\lambda)} - 1] / (2 - \lambda) & \text{if }\lambda \neq 2, y < 0 \\
-\log(-y_i + 1) & \text{if }\lambda = 2, y < 0
\end{cases}
$$
`caret` 팩키지 `preProcess()` 함수에 `method="YeoJohnson"`을 통해서 한쪽으로 치우친 배기량 변수를 Yeo-Johnson 변환을 통해 치우친 분포를 바로잡을 수 있게 된다.
```{r mtcars-power-transformation}
library(caret)
mpg_before <- mpg %>%
ggplot(aes(x=displ)) +
geom_density() +
labs(title="변환 전 배기량")
## 변수변환
mpg_displ <- mpg %>%
select(displ)
mpg_displ_transformed <- preProcess(mpg_displ, method="YeoJohnson")
mpg_df <- predict(mpg_displ_transformed, mpg)
mpg_after <- mpg_df %>%
ggplot(aes(x=displ)) +
geom_density() +
labs(title="변환 후 배기량")
cowplot::plot_grid(mpg_before, mpg_after)
```
# 정규화(Normalization) {#normalization}
정규화는 변수의 특성에 따라 다음과 같이 크게 세가지 경우로 정규화를 통해 변환시킨다.
- 0 ~ 1 사이 범위(range): $\text{신규 생성 변수} = \frac{x - min(x)}{max(x) - min(x)}$
- 변수가 하한과 상한을 갖고 있고, 이상점(outlier)가 많지 않고, 일양균등분포를 갖는 경우 적절한 변환
- 평균 중심화(Mean centering): $\text{신규 생성 변수} = x - mean(x)$
- 이상점이 있을 경우에도 유용하고 특히, 모형 설명에 장점이 있음.
- 평균 중심화는 값들을 평균을 중심으로 이동시키지만 척도(scale)는 변경시키지 않음.
- z-점수 표준화: $\text{신규 생성 변수} = \frac{x - mean(x)}{\sigma}$
- 이상점이 존재하는데 변수 척도가 상이한 경우
- 척도(scale)를 단위 분산으로 조정함.
## 정규화 - 범위(`range`) {#normalization-range}
가장 먼저 `range`를 통해 배기량(`displ`) 척도를 0 -- 1 사이로 조정시킨다.
```{r mtcars-scale-range}
mpg_displ_range <- preProcess(mpg_displ, method="range")
mpg_df <- predict(mpg_displ_range, mpg)
mpg_df %>%
select(displ) %>%
bind_cols(mpg %>% select(displ)) %>%
summary()
```
## 정규화 - 평균 중심화(`center`) {#normalization-center}
평균 중심화(mean centering)을 `preProcess` 함수 `center`를 통해 배기량(`displ`) 변수를 평균이 0을 중심으로 값들을 변환시킨다.
```{r mtcars-scale-center}
mpg_displ_center <- preProcess(mpg_displ, method="center")
mpg_df <- predict(mpg_displ_center, mpg)
mpg_df %>%
select(displ) %>%
bind_cols(mpg %>% select(displ)) %>%
summary()
```
## 정규화 - Z-변환 {#normalization-z-변환}
z-점수 표준화를 `preProcess` 함수 `c("center", "scale")` 콤보를 통해 배기량(`displ`) 변수를 평균이 0, 분산이 1인 값들로 변환시킨다.
```{r mtcars-scale-center-unit}
mpg_displ_z_tranform <- preProcess(mpg_displ, c("center", "scale"))
mpg_df <- predict(mpg_displ_z_tranform, mpg)
mpg_df %>%
select(displ) %>%
bind_cols(mpg %>% select(displ)) %>%
summary()
```