본문 바로가기

R/R을 이용한 데이터 처리&분석 실무

전처리-1

반응형

분류 알고리즘을 적용하기에 앞서 모델링에 알맞은 형태로 데이터를 처리해주어야 한다. 이를 전처리(Preprocessing)라 한다. 전처리의 예에는 데이터를 정규화하거나, 다른 형태로 재표현하거나, 결측치NA를 다른 값으로 대치해주는 작업 등이 있다.

 

데이터 변환

데이터 정규화(Feature Scaling)

데이터 정규화는 변숫값의 분포를 표준화하는 것을 의미한다. 표준화는 변수에서 데이터의 평균을 빼거나 변수를 전체 데이터의 표준 편차로 나누는 작업을 포함한다. 이렇게 하면 변숫값의 평균이 0이 되고 값의 퍼짐 정도(분포) 또한 일정해진다.

 

데이터 정규화는 k 최근 이웃 분류 알고리즘(kNN, k-Nearest Neighbor), 서포트 벡터 머신(SVM, Support Vector Machine), 신경망(Neural Network) 등 많은 분류 알고리즘에서 유용하게 사용된다. R에서 데이터를 정규화하는 함수는 scale( )이다.

 

-scale : 행렬 유형의 데이터를 정규화한다.

scale(
  x,            # 숫자 벡터 유형의 객체
  center=TRUE,  # TRUE면 모든 데이터에서 전체 데이터의 평균을 뺀다.
  # scale이 TRUE일 때 center도 TRUE면 모든 데이터를 전체 데이터의 표준 편차로 나눈다.
  # scale이 TRUE지만 center는 FALSE면 모든 데이터를 전체 데이터의 제곱 평균 제곱근으로 나눈다.
  # scale이 FALSE면 데이터를 어떤 값으로도 나누지 않는다.
  scale=TRUE
)

 

다음은 아이리스 데이터의 값을 정규화한 예다. scale()이 행렬을 반환하므로 이를 다시 데이터 프레임으로 변환하기 위해 as.data.frame()을 사용했으며, Species는 정규화에서 제외했다가 후에 cbind()로 합쳤다.

> cbind(as.data.frame(scale(iris[1:4])), iris$Species)
    Sepal.Length Sepal.Width Petal.Length   Petal.Width iris$Species
1    -0.89767388  1.01560199  -1.33575163 -1.3110521482       setosa
2    -1.13920048 -0.13153881  -1.33575163 -1.3110521482       setosa
3    -1.38072709  0.32731751  -1.39239929 -1.3110521482       setosa
4    -1.50149039  0.09788935  -1.27910398 -1.3110521482       setosa
5    -1.01843718  1.24503015  -1.33575163 -1.3110521482       setosa
...

 

주성분 분석(PCA, Principal Component Analysis)

 

주성분 분석은 데이터에 많은 변수가 있을 때 변수의 수를 줄이는 차원 감소(Dimensionality Reduction) 기법 중 하나다. PCA는 변수들을 주성분(Principal Component)이라 부르는 선형적인 상관관계가 없는 다른 변수들로 재표현한다.

 

주성분들은 원 데이터의 분산(퍼짐 정도)을 최대한 보존하는 방법으로 구한다.

 

princomp( )는 PCA를 위해 사용하는 함수다. princomp( ) 수행 뒤 결과의 scores에는 재표현된 좌표가 저장되어 있다.

 

-princomp : 주성분 분석을 수행한다.

princomp(
  x,         # 행렬 또는 데이터 프레임
  cor=FALSE  # cor=FALSE면 공분산 행렬, TRUE면 상관 행렬을 사용한 주성분 분석을 한다.
)

 

1:10을 저장한 x, 여기에 약간의 노이즈를 추가한 y, x + y에 약간의 노이즈를 추가한 z가 있을 때 이 데이터에 주성분 분석을 수행해보자.

 

> x<-1:10
> y<-x+runif(10,min=-.5,max=.5)
> z<-x+y+runif(10,min=-10,max=.10)
> (data<-data.frame(x,y,z))
    x          y          z
1   1  0.9701918 -2.6261391
2   2  2.3340587 -3.9818311
3   3  2.6712888  0.5580181
4   4  4.1494362  7.3614161
5   5  4.6743576  1.5698966
6   6  6.4568781  2.9118993
7   7  6.5662380 13.1144860
8   8  7.6649556  9.5287878
9   9  9.3146536 16.8249596
10 10 10.2275449 19.1806151
> pr<-princomp(data)
> summary(pr)
Importance of components:
                          Comp.1    Comp.2       Comp.3
Standard deviation     8.5593550 1.4409549 0.2196993159
Proportion of Variance 0.9718172 0.0275425 0.0006402653
Cumulative Proportion  0.9718172 0.9993597 1.0000000000

 

주성분 분석 수행 후 summary()를 보면 주성분들이 원 데이터의 분산 중 얼마만큼을 설명해주는지를 알 수 있다. Proportion of Variance 행을 보면 첫 번째 주성분은 데이터의 분산 중 92.86%를 설명해주며, 두 번째 주성분은 데이터의 분산 중 7.1%를 설명함을 알 수 있다. 세 번째 주성분은 가장 작은 0.03%의 분산을 설명한다. 마지막 행의 Cumulative Proportion은 Proportion of Variance의 누적 값이다.

 

결국 원 데이터의 분산은 첫 번째와 두 번째 주성분에 의해 99.97%가 표현됨을 알 수 있다. 이들 두 주성분상의 좌표는 scores를 보고 구하면 된다. 다음 코드에서 Comp.1, Comp.2 두 컬럼은 x, y, z를 2개 차원으로 축약한 결과다.

> pr$scores[, 1:2]
            Comp.1      Comp.2
 [1,] -10.97931678 -1.58742032
 [2,] -11.43084237  0.52380072
 [3,]  -6.95563585 -0.70295480
 [4,]  -0.09563178 -2.21003420
 [5,]  -4.77178121  1.36165620
 [6,]  -2.68347566  2.52495734
 [7,]   6.76753019 -1.41340773
 [8,]   4.24249414  1.53417924
 [9,]  11.59698385 -0.08403716
[10,]  14.30967547  0.05326071

 

원 핫 인코딩

지금까지 하나의 범주형 데이터는 하나의 팩터(Factor) 데이터 변수로만 표현해왔다. 범주형 변수의 레벨(수준)(level) 수가 적다면 하나의 팩터 변수로 여러 레벨을 표현해도 문제가 없지만, 범주 레벨의 수가 많다면 경우에 따라 표현 방식을 다르게 해야 한다.

 

예를 들어, 랜덤 포레스트 알고리즘("랜덤 포레스트" 절 참고)의 경우 범주형 변수의 레벨 수를 32로 제한하고 있다. 이를 확인해보기 위해 많은 수의 레벨을 가진 범주형 변수를 포함한 데이터를 작성해보자. 아래 코드에서는 all에 총 52개 레벨을 가진 범주형 변수를 저장하고, value에는 임의의 수를 저장했다.

> (all <- factor(c(paste0(LETTERS, "0"), paste0(LETTERS, "1"))))
 [1] A0 B0 C0 D0 E0 F0 G0 H0 I0 J0 K0 L0 M0 N0 O0 P0 Q0 R0 S0 T0 U0 V0 W0
[24] X0 Y0 Z0 A1 B1 C1 D1 E1 F1 G1 H1 I1 J1 K1 L1 M1 N1 O1 P1 Q1 R1 S1 T1
[47] U1 V1 W1 X1 Y1 Z1
52 Levels: A0 A1 B0 B1 C0 C1 D0 D1 E0 E1 F0 F1 G0 G1 H0 H1 I0 I1 ... Z1
> (data <- data.frame(lvl=all, value=rnorm(length(all))))
   lvl       value
1   A0 -0.81681250
2   B0  0.28331815
3   C0  0.20341997
4   D0 -2.27739479
5   E0  2.23239985

 

이 데이터를 랜덤 포레스트 알고리즘에 입력으로 주면 다음과 같이 32개 이상의 레벨은 처리할 수 없다는 에러 메시지가 출력된다. 32개 이상의 레벨을 한 번의 가지치기로 나누는 경우의 수는 약 2^32에 달해 지나치게 계산양이 많기 때문이다.

> install.packages("randomForest")
> library(randomForest)
> m <- randomForest(value ~ lvl, data=data)
Error in randomForest.default(m, y, ...) :
  Can not handle categorical predictors with more than 32 categories.

 

이를 해결하기 위해 발생 빈도가 낮은 레벨들을 하나로 묶거나, 범주형 변수의 레벨을 숫자로 취급하는 방법을 생각해볼 수 있다. 또 다른 방법은 여러 개의 가변수(dummy variables)를 사용해 범주형 변수를 재표현하는 것으로, 원 핫 인코딩(One Hot Encoding)이라고 부른다. 원 핫 인코딩은 model.matrix( )를 사용해 구할 수 있다.

 

-model.matrix : 디자인 행렬(모델 행렬)을 생성한다.

model.matrix(
  object,  # formula 객체
  data     # formula를 적용할 객체
)

 

다음은 A, B, C 3개 레벨을 저장한 lvl이라는 팩터를 3개의 변수로 재표현한 예다. model.matrix( )의 결과를 보면 A는 (0, 0), B는 (1, 0), C는 (0, 1)로 변환된 것을 알 수 있다.

> (x <- data.frame(lvl=factor(c("A", "B", "A", "A", "C")),
+                  value=c(1, 3, 2, 4, 5)))
  lvl value
1   A     1
2   B     3
3   A     2
4   A     4
5   C     5
> model.matrix(~ lvl, data=x)[, -1]
  lvlB lvlC
1    0    0
2    1    0
3    0    0
4    0    0
5    0    1

 

결측치의 처리

데이터에 결측치(NA)가 있는 경우 값이 존재하지 않으므로 해당 변숫값을 사용한 계산을 수행할 수 없다. 결측치가 있는 경우 rpart(10.3.2절 참조)는 NA를 대신하는 변수인 surrogate 변수를 사용하여 결측치 문제를 해결한다. 반면 랜덤 포레스트(10.3.4절 참조) 모델은 곧바로 에러를 발생시킨다. 이 경우 NA를 다른 값으로 대체해주는 randomForest::rfImpute( ) 함수를 사용할 수 있다.

 

이처럼 데이터에 결측치가 있는 경우 모델 자체에서 제공하는 알고리즘을 사용하거나 결측치를 다른 값으로 대체해주는 함수를 활용하게 된다. 결측치를 다른 값으로 바꾸는 것을 대체(대치)(imputation)라 한다.

 

이 절에서는 결측치를 대치하는 일반적인 방법에 대해서 살펴본다.

 

-complete.cases : 관측값에 결측치가 없는지를 테스트한다.

complete.cases(
  ...  # 벡터, 행렬 또는 데이터 프레임
)

 

-is.na : 결측치인 NA인지 여부를 반환한다.

is.na(
  x  # 벡터, 리스트 등의 R 객체
)

 

-DMwR::centralInputation : NA를 가운데 값(central value)으로 대체한다.

DMwR::centralInputation(

)

 

-DMwR::knnImputation : NA를 k 최근 이웃 분류 알고리즘을 사용해 대체한다.

DMwR::knnImputation(
  data,  # 데이터 프레임
  k      # k 최근 이웃 분류 알고리즘에서 몇 개의 이웃을 볼 것인지를 지정
)

 

결측치의 존재를 확인하려면 complete.cases( )를 사용한다. complete.cases( )는 데이터 프레임의 각 행에 적용되며, 각 행에 저장된 모든 값이 NA가 아닐 때만 TRUE를 반환하므로 NA 값이 하나라도 존재하는 행을 찾는 데 편리하다. 다음은 아이리스에 일부러 NA 값을 입력하고 해당 행들을 다시 찾아내는 예다.

> iris_na<-iris
> iris_na[c(10,20,25,40,32),3]<-NA
> iris_na[c(33, 100, 123), 1] <- NA
> iris_na[!complete.cases(iris_na), ]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species Species2
10           4.9         3.1           NA         0.1     setosa        2
20           5.1         3.8           NA         0.3     setosa        2
25           4.8         3.4           NA         0.2     setosa        1
32           5.4         3.4           NA         0.4     setosa        2
33            NA         4.1          1.5         0.1     setosa        1
40           5.1         3.4           NA         0.2     setosa        2
100           NA         2.8          4.1         1.3 versicolor        2
123           NA         2.8          6.7         2.0  virginica        1

 

만약 한 컬럼에 대해서만 NA 여부를 조사하고자 한다면 is.na( )를 사용한다.

> iris_na[is.na(iris_na$Sepal.Length), ]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species Species2
33            NA         4.1          1.5         0.1     setosa        1
100           NA         2.8          4.1         1.3 versicolor        2
123           NA         2.8          6.7         2.0  virginica        1

 

NA 값의 처리를 분류 알고리즘에서 지원하지 않는다면 해당 데이터를 제외하고 모델링 및 예측을 수행하거나, NA 값을 다른 값으로 대체해야 한다. 값을 대체하는 가장 간단한 방법은 데이터의 평균이나 중앙값을 취하는 것이다.

 

다음 코드는 iris_na에서 중앙값을 구한 예다. median( ) 호출 시 na.rm=TRUE는 NA를 제외하고 중앙값을 계산하기 위해 사용했다. 만약 na.rm=TRUE를 지정하지 않으면, NA 값이 포함된 연산의 결과는 NA므로 제대로 된 중앙값을 얻을 수 없게 된다. na.rm=TRUE는 mapply( )의 인자로 전달하지만 mapply( )에서 직접 사용되는 것은 아니고, median( )을 호출할 때 인자로 전달된다. mapply( )는 앞에서 살펴봤다.

> mapply(median, iris_na[1:4], na.rm=TRUE)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
         5.8          3.0          4.4          1.3 

 

이제 이 값들을 iris_na에서 NA 값이 위치한 곳에 대체하면 된다. DMwR에는 이런 작업을 좀 더 편리하게 해주는 centralImputation( ) 함수가 있다.

>install.package("DMwR")
>library(DMwR)
> iris_na[!complete.cases(iris_na), ]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species Species2
10           4.9         3.1           NA         0.1     setosa        2
20           5.1         3.8           NA         0.3     setosa        2
25           4.8         3.4           NA         0.2     setosa        1
32           5.4         3.4           NA         0.4     setosa        2
33            NA         4.1          1.5         0.1     setosa        1
40           5.1         3.4           NA         0.2     setosa        2
100           NA         2.8          4.1         1.3 versicolor        2
123           NA         2.8          6.7         2.0  virginica        1
> centralImputation(iris_na[1:4])[
+   c(10, 20, 25, 32, 33, 40, 100, 123), ]
    Sepal.Length Sepal.Width Petal.Length Petal.Width
10           4.9         3.1          4.4         0.1
20           5.1         3.8          4.4         0.3
25           4.8         3.4          4.4         0.2
32           5.4         3.4          4.4         0.4
33           5.8         4.1          1.5         0.1
40           5.1         3.4          4.4         0.2
100          5.8         2.8          4.1         1.3
123          5.8         2.8          6.7         2.0

 

단순한 중앙값 대신 모델을 만들어 NA를 대체할 값을 구할 수도 있다. 다음은 NA 값을 k 최근 이웃 분류 알고리즘을 사용해 k개 근접 이웃 값의 가중 평균으로 대체한 예다. 가중치는 NA 값이 있는 데이터와의 거리로부터 계산되며, knnImputation( )이 자동으로 데이터 정규화를 수행하므로 scale( )을 별도로 수행하지는 않았다.

 

> knnImputation(iris_na[1:4])[c(10, 20, 25, 32, 33, 40, 100, 123), ]
    Sepal.Length Sepal.Width Petal.Length Petal.Width
10      4.900000         3.1     1.452250         0.1
20      5.100000         3.8     1.539881         0.3
25      4.800000         3.4     1.457144         0.2
32      5.400000         3.4     1.483821         0.4
33      5.462532         4.1     1.500000         0.1
40      5.100000         3.4     1.475718         0.2
100     5.891169         2.8     4.100000         1.3
123     7.077197         2.8     6.700000         2.0

 

위에 보인 방법들에서 NA를 대체할 값을 구할 때 Species는 사용하지 않았다는 점에 주목하기 바란다. 예를 들어, Sepal.Length를 중앙값으로 대체할 때 단순히 Sepal.Length 전체의 중앙값을 사용했으며, 붓꽃 종별로 중앙값을 계산해 사용하지 않았다. 만약 각 행의 Species 값을 참고해 Species.Length의 NA를 해당 붓꽃 종의 중앙값으로 대체한다면 NA가 있던 자리에 붓꽃 종별에 대한 직접적인 정보가 흘러들어갈 수 있다. 그러면 예측 모델을 만들었을 때 Sepal.Length의 예측력이 실제보다 크게 나타날 수 있다.

 

 

R을 이용한 데이터 처리&분석 실무 中

반응형

'R > R을 이용한 데이터 처리&분석 실무' 카테고리의 다른 글

모델 평가 방법  (0) 2020.02.11
전처리-2  (0) 2020.02.10
데이터 탐색  (0) 2020.02.09
변수 선택  (0) 2020.02.08
이상치  (0) 2020.02.08