1 범주형 변수

1.1 One Hot Encoding - ifelse

data(package="ggplot2") 명령어를 통해서 ggplot2 팩키지에 내장된 데이터셋을 확인할 수 있다. dataset 팩키지 mtcars 데이터셋을 새롭게 만든 mpg 데이터셋을 활용하여 One Hot Encoding 기법을 통해서 범주형 변수를 기계학습 예측모형에 활용할 수 있는 Feature로 만들어 보자.

One Hot Encoding을 통계학에서는 가변수(dummy variable)을 만든다고 한다. 즉, 변수에 특성이 있느냐 없느냐를 가지고 두가지 수준을 갖는 범주의 차이를 계량화한다. 예를 들어 1999년 양산차와 2008년 양산차간의 연비향상이 얼마나 있었는지를 하나의 Feature로 넣는 것을 상정할 수 있다.

library(tidyverse)

mpg <- mpg %>% tbl_df

mpg %>% 
  select(year) %>% 
  table()
.
1999 2008 
 117  117 

시내주행연비(cty)와 고속도로연비(hwy)에 영향을 주는 변수는 많다. 그중 10년이라는 시간이라는 요인이 연비향상에 얼마나 기여를 했는지 요약통계량을 다음과 같이 구할 수 있다.

mpg %>% 
  group_by(year) %>% 
  summarise(mean_cty = mean(cty),
            mean_hwy = mean(hwy))
# A tibble: 2 x 3
   year mean_cty mean_hwy
  <int>    <dbl>    <dbl>
1  1999     17.0     23.4
2  2008     16.7     23.5

ifelse 문을 사용해서 1999년을 기준으로 회귀계수(가중치, w)를 통해 연비에 주는 효과를 계량화해서 넣을 수 있다.

mpg %>% 
  mutate(decade = ifelse(year == 1999, 0, 1)) %>% 
  select(cty, year, decade)
# A tibble: 234 x 3
     cty  year decade
   <int> <int>  <dbl>
 1    18  1999      0
 2    21  1999      0
 3    20  2008      1
 4    21  2008      1
 5    16  1999      0
 6    18  1999      0
 7    18  2008      1
 8    18  1999      0
 9    16  1999      0
10    20  2008      1
# ... with 224 more rows

1.2 유의미한 범주 - case_when

case_when()을 사용하여 ifelse를 여러번 중첩시켜 사용하는 대신 범주를 깔끔하게 정리할 수 있다.

mpg %>% 
  select(class) %>% 
  table()
.
   2seater    compact    midsize    minivan     pickup subcompact 
         5         47         41         11         33         35 
       suv 
        62 

자동차를 소형, 중형, 중소형 등 다양하게 구분할 수 있는데 이를 다음과 같이 범주를 나눠서 정리하는 것이 예측모형의 유의미한 Feature로 탈바꿈시킬 수도 있다.

  • minivan, pickup - 영업용
  • subcompact, compact - 소형
  • midsize - 중형
  • 2seater, suv - 여가용
mpg %>% 
  mutate(`차량범주` = case_when(str_detect( class, pattern="minivan|pickup") ~ "영업용",
                              str_detect( class, pattern="compact") ~ "소형",
                              str_detect( class, pattern="midsize") ~ "중형",
                              TRUE ~ "여가용")) %>% 
  select(`차량범주`) %>% 
  table()
.
  소형 여가용 영업용   중형 
    82     67     44     41 

1.3 범주가 많은 경우 - 비율(prop.table)

시내주행연비를 평균보다 높은 경우 “high”, 낮은 경우 “low”로 두고 prop.table() 함수를 통해 제조사(manufacturer) 비율을 계산한다. 이를 hl_prop 변수로 결합시켜 너무 범주가 많아서 사용하기 어려웠던 범수형 변수를 새로운 feature로 만들어 냈다.

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()

2 연속형 변수 → 범주형 변수

2.1 연속형 변수 → 범주형 변수 - 절대값(cut)

연속형 변수의 경우 히스토그램을 통해서 분포를 확인할 수 있고, 비선형적인 특성을 적절한 범주로 표현하여 잡아내는 것이 가능하다.

mpg %>% 
  ggplot(aes(x=displ)) +
    geom_histogram(bins = 30)

cut() 함수를 사용해서 배기량(displ)을 2000cc 기준으로 나눠본다. 배기량에 대한 자세한 사항은 나무위키 배기량을 참고한다.

mpg <- mpg %>% 
  mutate(displ_cat = cut(displ, breaks = seq(1, 7.0, by=2)))

mpg %>% 
  select(displ_cat) %>% 
  table()
.
(1,3] (3,5] (5,7] 
  108    90    36 

model.matrix 함수를 사용해서 가변수화한 후에 이를 범주형 변수로 변환하여 cbind() 함수로 결합시킨다.

mpg <- cbind(mpg, model.matrix(~ displ_cat -1, data = mpg))

mpg %>% 
  select(contains("disp")) %>% 
  head()
  displ displ_cat displ_cat(1,3] displ_cat(3,5] displ_cat(5,7]
1   1.8     (1,3]              1              0              0
2   1.8     (1,3]              1              0              0
3   2.0     (1,3]              1              0              0
4   2.0     (1,3]              1              0              0
5   2.8     (1,3]              1              0              0
6   2.8     (1,3]              1              0              0

2.2 연속형 변수 → 범주형 변수 - 분위수(quantile)

연속형 변수를 범주형 변수로 변환시킬 때 앞서 cut() 함수의 절대값을 기준으로 나누는 대신에 분위수(quantile)를 사용해서 나누는 것이 적절할 때가 있다. 대표적으로 일일이 사람이 보는 대신에 기계적으로 자동화를 할 경우 도움이 된다.

이런 경우 ntile() 함수를 사용하면 관측점을 예를 들어 배기량(displ) 기준으로 3개 범주집단으로 동일하게 나눠준다.

mpg %>% 
  mutate(displ_tile = ntile(displ, 3) %>% as.factor) %>% 
  select(displ_tile) %>% 
  table()
.
 1  2  3 
78 78 78 

ntile() 함수로 연속형 변수 배기량(displ)을 범주형 변수로 변환시킨 후에 이를 model.matrix() 함수로 가변수화하여 예측모형을 위한 basetable에 일원으로 편입시킨다. 역행렬 변환이 가능한 full rank를 맞추고자 할 경우 -1을 빼서 넣어준다.

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()
  displ displ_cat displ_cat(1,3] displ_cat(3,5] displ_cat(5,7] displ_tile
1   1.8     (1,3]              1              0              0          1
2   1.8     (1,3]              1              0              0          1
3   2.0     (1,3]              1              0              0          1
4   2.0     (1,3]              1              0              0          1
5   2.8     (1,3]              1              0              0          2
6   2.8     (1,3]              1              0              0          2
  displ_tile2 displ_tile3
1           0           0
2           0           0
3           0           0
4           0           0
5           1           0
6           1           0

3 변수 변환 (Transformation)

연속형 변수 중 치우침이 심한 변수가 많다. 이를 변수변환하여 정규분포에 가까운 형태로 맞추게 되면 예측모형의 성능 향상과 안정성을 기대할 수 있다. 1

멱변환(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 변환을 통해 치우친 분포를 바로잡을 수 있게 된다.

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)

4 정규화(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)를 단위 분산으로 조정함.

4.1 정규화 - 범위(range)

가장 먼저 range를 통해 배기량(displ) 척도를 0 – 1 사이로 조정시킨다.

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()
     displ            displ1     
 Min.   :0.0000   Min.   :1.600  
 1st Qu.:0.1481   1st Qu.:2.400  
 Median :0.3148   Median :3.300  
 Mean   :0.3466   Mean   :3.472  
 3rd Qu.:0.5556   3rd Qu.:4.600  
 Max.   :1.0000   Max.   :7.000  

4.2 정규화 - 평균 중심화(center)

평균 중심화(mean centering)을 preProcess 함수 center를 통해 배기량(displ) 변수를 평균이 0을 중심으로 값들을 변환시킨다.

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()
     displ             displ1     
 Min.   :-1.8718   Min.   :1.600  
 1st Qu.:-1.0718   1st Qu.:2.400  
 Median :-0.1718   Median :3.300  
 Mean   : 0.0000   Mean   :3.472  
 3rd Qu.: 1.1282   3rd Qu.:4.600  
 Max.   : 3.5282   Max.   :7.000  

4.3 정규화 - Z-변환

z-점수 표준화를 preProcess 함수 c("center", "scale") 콤보를 통해 배기량(displ) 변수를 평균이 0, 분산이 1인 값들로 변환시킨다.

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()
     displ             displ1     
 Min.   :-1.4488   Min.   :1.600  
 1st Qu.:-0.8296   1st Qu.:2.400  
 Median :-0.1330   Median :3.300  
 Mean   : 0.0000   Mean   :3.472  
 3rd Qu.: 0.8733   3rd Qu.:4.600  
 Max.   : 2.7309   Max.   :7.000