데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

    - 이산형화

    - 이항변수화

(4) 개수 축소

(5) 차원 축소

   - (5-1) 주성분분석

   - (5-2) 요인분석

(6) 시그널 데이터 압축

의 6개 구분 중에서

 

등간척도(혹은 비율척도)로 측정한 두 개 이상의 다수의 변수들에 잠재되어 있는 공통인자를 찾아내는 (5-2) 요인분석(Factor Analysis)에 대해서 알아보겠습니다. 

 

요인분석은 통계학자 Spearman이 학생들의 여러개의 시험 성적(예: Classic, French, English, Math...) 간에 상관관계 행렬을 보다가 "어떻게 하면 연관성있는 변수들을 묶어주는 내재하는 속성을 찾을 수 있을까?"를 가지고 고민하다가 유래되었다고 합니다.

 

요인분석을 왜 하는지에 대해서는 이전에 포스팅한 (5-1) 주성분분석의 초반부를 참고하시기 바랍니다. (바로가기 ☞ 주성분분석)

 

 

 

 

대신에 요인분석과 주성분분석의 공통점과 차이점에 대해서 정리한 다른 분의 블로그(http://ai-times.tistory.com/112) 내용을 소개하겠습니다.

 

요인분석 과 주성분분석의 관계는?

많은 경우 (많은 사람들이) 요인분석과 주성분분석을 혼동한다.
두 용어를 같은 것으로 이해하는 사람들도 많다. ( 요인분석 = 주성분분석 ? ) 그러나 이것은 요인분석이나 주성분 분석을 잘 이해하지 못한 것이다. (참고는 요인분석은 Factor Analysis 이고, 주성분 분석은 Principle Component Analysis 이며 보통 PCA 라고 불린다.)

요인분석과 주성분분석은 물론 깊은 관계가 있다. 그러나 엄밀하게는 같은 것은 아니다.
요인분석을 수행하기 위해서 즉, 몇 개의 요인(잠재된 변수)들을 추출하기 위해서 여러 가지 방법이 사용될 수 있으나 그 중에 가장 많이 사용되는 방법이 <주성분 분석>이다. (그렇다고, 요인분석이 주성분분석의 상위 개념에 있는 것이라고 할 수는 없다. 집합으로 볼 때 포함 관계 아님)

 

* 공통점
[1] 모두 데이터를  축소한다. 
[2] 원래 데이터의 새로운 몇 개의 변수들로 만들어 낸다.


* 차이점
(아래에 정리해보았다. 요인분석은 FA 로, 주성분분석은 PCA 로 표현하였다.)

[1] 생성되는 변수의 수
FA  : 몇 개라고 지정할 수 없다. 데이터의 의미에 따라 다르다. 3개가 될 수도 있고, 또는 4개도 있고, ...
데이터에 서로 성관성을 갖는 변수들의 군집의 개수로 나뉘어질 것이다.
PCA : 주성분이라고 하며, 보통 2개를 찾는다. 제1주성분, 제2주성분 이라고 불린다.

[2] 생성되는 변수의 의미 (이름)
FA : 위에서 학생들의 성적데이터를 가지고 설명했듯이 분석가가 적절한 이름을 붙일 수 있다. 자동적으로 이름을 만들어주지는 않는다.
PCA : 보통 2개의 변수를 채택한다. 첫번째 것은 제1주성분, 제2주성분 이라고 부른다. (원래 데이터의 입력변수가 p라고 하면, ... 제p주성분까지 만들수 있다. 그러나 보통 2개 정도만 사용한다. 이걸로 보통 충분하다.)
요인분석에서는 서로 상관있는 변수들의 이름을 지을 수 있으나 제n주성분의 경우는 그게 좀 힘들다. (의미 중심으로 묶였다기 보다는 분류 결정력이 높은 임의의 변수를 만든 것이기 때문이다.)

[3] 생성된 변수들의 관계
FA : 새 (잠재)변수들은 기본적으로 대등한 관계를 갖는다. 어떤 것이 더 중요하다 라는 의미는 요인분석에서는 없다. 단, 분류/예측에 그 다음 단계로 사용된 다면 그 때 중요성의 의미가 부여될 것이다.  
PCA : 제1주성분이 가장 중요하고, 그 다음 제2주성분이 중요하게 취급된다. 그 다음은 제3주성분 ... 이런 식이다. 즉, 변수들 간의 중요성의 순위가 존재한다.

[4] 분석방법의 의미
FA : 목표 필드를 고려하지 않는다. 그냥 데이터가 주어지면 변수들을 비슷한 성격들로 묶어서 새로운 [잠재]변수들을 만들어 낸다.
PCA : 목표 변수를 고려한다. 목표 변수를 잘 예측/분류하기 위하여 원래 변수들의 선형 결합으로 이루어진 몇 개의 주성분(변수)들을 찾아낸다 

 

* 출처: http://ai-times.tistory.com/112

 

 

요인 추출 방법으로 주성분분석이 활용됩니다. 요인분석을 할 때 초기값 m을 어떻게 잡아주느냐에 따라서 계산 속도가 많이 영향을 받게 됩니다. 이때 보통은 반응변수들이 가지고 있는 변동량의 대부분들을 설명해줄 수 있는 고유값(eigenvalue)와 고객벡터(engenvector)의 수는 몇 개인가를 결정할 수 있는 주성분분석(Principal Component Analysis, PCA)를 활용해서 초기값 m을 잡게 됩니다. (지난 주성분분석 포스팅의 Scree Plot 참조)

 

 

[참고: 용어설명]

- 요인점수 (Factor Score) : 각 관측치의 요인 점수는 요인 점수 계수(Standardized Scoring Coefficients)와 실제 (표준화된) 관측치의 값의 곱으로 구하며, 요인별로 이를 summation하면 요인별 요인점수가 됨.

- 요인패턴 (Factor Loading) :  각 요인이 각 변수에 미치는 효과.  변수와 요인의 상관 행렬

- 공통 분산치 (Communality) : 요인에 의해 설명될 수 있는 변수의 분산량

- 요인회전 (Factor Rotation) : p개의 변수들을 m개의 요인(factor)로 묶어주기 편리하게 혹은 해석하기 쉽게하도록 축을 회전시키는 것. 직교회전에 varimax, transvarimax 등이 있고 비직교회전방법도 있으며, 보통 분산을 최대화하는 직교회전방법 varimax 를 많이 씀.

 

한국신용평가정보에서 나온 '국내 증권회사의 주요 재무제표' (2007.3.31 기준)를 가지고 요인분석을 R로 해보도록 하겠습니다. (지난번 포스팅에서는 똑같은 데이터에 대해 주성분분석을 해보았습니다)

 

이 데이터는 18개 증권사별로 V1.총자본순이익율, V2.자기자본순이익율, V3.자기자본비율, V4.부채비율, V5.자기자본회전율 재무지표 변수로 구성되어 있습니다.

 

예제 데이터('국내 증권회사의 주요 재무제표' (2007.3.31 기준)) 다운로드 ☞

secu_com_finance_2007.csv

 

R로 외부 csv 데이터 불러오기, 표준화 변환, 부채비율 방향 변환, 변수 선택, 상관계수분석, 산포도행렬은 아래와 같습니다. (지난 포스팅 주성분분석 설명과 동일)

 

주성분분석처럼 요인분석도 변수별 scale 영향을 없애기 위해서 표준화(standardization)한 관측값을 사용합니다.

 

> # csv 파일 불러오기 (file importing)
> secu_com_finance_2007 <- read.csv("C:/Users/user/Documents/R/secu_com_finance_2007.csv",
+                                   header = TRUE, 
+                                   stringsAsFactors = FALSE)
> # V1 : 총자본순이익율
> # V2 : 자기자본순이익율
> # V3 : 자기자본비율
> # V4 : 부채비율
> # V5 : 자기자본회전율
> 
> 
> # 표준화 변환 (standardization)
> secu_com_finance_2007 <- transform(secu_com_finance_2007, 
+                                    V1_s = scale(V1), 
+                                    V2_s = scale(V2), 
+                                    V3_s = scale(V3), 
+                                    V4_s = scale(V4),
+                                    V5_s = scale(V5))
> 
> # 부채비율(V4_s)을 방향(max(V4_s)-V4_s) 변환
> secu_com_finance_2007 <- transform(secu_com_finance_2007, 
+                                    V4_s2 = max(V4_s) - V4_s)
> 
> # variable selection
> secu_com_finance_2007_2 <- secu_com_finance_2007[,c("company", "V1_s", "V2_s", "V3_s", "V4_s2", "V5_s")]
> 
> 
> # Correlation analysis
> cor(secu_com_finance_2007_2[,-1])
            V1_s       V2_s       V3_s      V4_s2        V5_s
V1_s  1.00000000  0.6165153  0.3239780  0.3553930  0.01387883
V2_s  0.61651527  1.0000000 -0.5124351 -0.4659444  0.42263462
V3_s  0.32397800 -0.5124351  1.0000000  0.9366296 -0.56340782
V4_s2 0.35539305 -0.4659444  0.9366296  1.0000000 -0.53954570
V5_s  0.01387883  0.4226346 -0.5634078 -0.5395457  1.00000000
> 
> round(cor(secu_com_finance_2007_2[,-1]), digits=3) # 반올림
       V1_s   V2_s   V3_s  V4_s2   V5_s
V1_s  1.000  0.617  0.324  0.355  0.014
V2_s  0.617  1.000 -0.512 -0.466  0.423
V3_s  0.324 -0.512  1.000  0.937 -0.563
V4_s2 0.355 -0.466  0.937  1.000 -0.540
V5_s  0.014  0.423 -0.563 -0.540  1.000
> 
> 
> # Scatter plot matrix
> plot(secu_com_finance_2007_2[,-1])

 

 

 

factanal()함수를 활용해서 R로 요인분석을 해보도록 하겠습니다.

- secu_com_finance_2007_2 : 데이터를 지정해주고 (표준화된 숫자형 변수들)

- factors = 2 : 요인의 개수 지정

- ratation = "varimax" : 회전방법 지정

- scores = "regression" :  요인점수 계산 방법 지정

해주면 되겠습니다.

 

지난번 포스팅의 주성분분석에서는 동일한 데이터로 했을 때 주성분을 3개(Scree plot 보고서 결정)로 해서 분석 결과 해석을 했었는데요,

 

> # Scree Plot
> plot(prcomp(secu_com_finance_2007_2[,c(2:6)]), type="l",
+      sub = "Scree Plot")

 

 

 

 

 

 

요인분석에서 요인 개수를 3개로 집어넣었더닌 변수 5개밖에 안되는데 요인을 3개씩이나 한다고 경고메시지가 뜨네요. ^^;  그래서 요인 2개로 집어넣었습니다.

 

> # 요인분석(maximum likelihood factor analysis)
> # rotation = "varimax"
> secu_factanal <- factanal(secu_com_finance_2007_2[,2:6], 
+                           factors = 2, 
+                           rotation = "varimax", # "varimax", "promax", "none" 
+                           scores="regression") # "regression", "Bartlett"
> 
> print(secu_factanal)

Call:
factanal(x = secu_com_finance_2007_2[, 2:6], factors = 2, scores = "regression",     rotation = "varimax")

Uniquenesses:
 V1_s  V2_s  V3_s V4_s2  V5_s 
0.005 0.026 0.036 0.083 0.660 

Loadings:
      Factor1 Factor2
V1_s   0.252   0.965 
V2_s  -0.588   0.792 
V3_s   0.979         
V4_s2  0.950   0.120 
V5_s  -0.562   0.155 

               Factor1 Factor2
SS loadings      2.586   1.604
Proportion Var   0.517   0.321
Cumulative Var   0.517   0.838

Test of the hypothesis that 2 factors are sufficient.
The chi square statistic is 1.59 on 1 degree of freedom.
The p-value is 0.207 

 

 

 

위에 Loadings 에 보면 Factor2의 V3_s가 숫자가 비어있는데요, 아래처럼 cutoff 를 조정해주면 모두 볼 수 있습니다.

 

> print(secu_factanal$loadings, cutoff=0) # display every loadings

Loadings:
      Factor1 Factor2
V1_s   0.252   0.965 
V2_s  -0.588   0.792 
V3_s   0.979   0.080 
V4_s2  0.950   0.120 
V5_s  -0.562   0.155 

               Factor1 Factor2
SS loadings      2.586   1.604
Proportion Var   0.517   0.321
Cumulative Var   0.517   0.838 

 

요인1(Factor1)은 자기자본비율(V3_s)과 (방햔변환 후의) 부채비율(V4_s2) 이 같이 묶였으며, 요인2(Factor2)는 총자본순이익율(V1_s)과 자기자본순이익율(V2_s)이 함께 묶었습니다.  V5_s가 두 요인 중에서 어디에 속한다고 할지 좀 애매한데요, 요인1하고는 부호가 다르므로 요인2에 묶인다고 하겠습니다.

 

 

 

 

다음으로, 요인분석 Biplot을 그려보도록 하겠습니다.  주성분분석할 때는 prcomp() 함수로 분석하고 biplot()함수로 단 한번에 아주 쉽게 Biplot을 그렸었는데요, 요인분석에서는 biplot을 단번에 그릴 수 있는 함수를 못찾았습니다. (혹시 이 포스팅 보시는 분중에 요인분석 biplot 그릴 수 있는 패키지, 함수 알고 계신분은 댓글로 공유해주시면 감사하겠습니다. 미리 꾸벅~ ☞_☜)

 

> # factor scores plotting
> secu_factanal$scores
          Factor1     Factor2
 [1,] -1.01782141 -0.28535410
 [2,] -0.17230586  0.08808775
 [3,] -0.13294211 -0.71511403
 [4,] -1.03557284  2.77950626
 [5,] -0.34416962 -1.21841127
 [6,] -0.01993668  0.44223954
 [7,] -0.62177426  1.26909067
 [8,]  1.79002399  0.28314793
 [9,]  1.60353334  0.52158445
[10,] -0.55591603 -0.12331881
[11,]  0.55387868 -1.03939155
[12,] -0.93740279 -0.74332879
[13,]  0.45680247  0.06433085
[14,] -1.13490535 -0.63034122
[15,]  1.36209539 -0.98147959
[16,]  1.57141053  0.89812864
[17,] -0.56190944  0.38006982
[18,] -0.80308800 -0.98944656
> 
> plot(secu_factanal$scores, main="Biplot of the first 2 factors")
> 
 

 

 
 
> # 관측치별 이름 매핑(rownames mapping)
> text(secu_factanal$scores[,1], secu_factanal$scores[,2], 
+      labels = secu_com_finance_2007$company, 
+      cex = 0.7, pos = 3, col = "blue")
> 
 

 

 
 
> # factor loadings plotting
> points(secu_factanal$loadings, pch=19, col = "red")
>
 

 

> text(secu_factanal$loadings[,1], secu_factanal$loadings[,2], + labels = rownames(secu_factanal$loadings), + cex = 0.8, pos = 3, col = "red") >
> # plotting lines between (0,0) and (factor loadings by Var.)
> segments(0,0,secu_factanal$loadings[1,1], secu_factanal$loadings[1,2])
> segments(0,0,secu_factanal$loadings[2,1], secu_factanal$loadings[2,2])
> segments(0,0,secu_factanal$loadings[3,1], secu_factanal$loadings[3,2])
> segments(0,0,secu_factanal$loadings[4,1], secu_factanal$loadings[4,2])
> segments(0,0,secu_factanal$loadings[5,1], secu_factanal$loadings[5,2])

 

 

 

 

가로축 Factor1이 '안정성' (자기자본비율, 부채비율) 지표라고 했는데요, Factor1 축의 오른쪽에 위치한 한양증권, 브릿지증권, 부국증권, 유화증권사 등은 안정성이 높은 회사들이라고 해석할 수 있겠습니다.

 

(참고: Factor1 = 0.252*V1_s - 0.588*V2_s + 0.979*V3_s + 0.950*V4_s2 - 0.562*V5_s)

 

 

다음으로, 세로축 Factor2는 '수익성'(총자본순이익율, 자기자본순이익율, 자기자본회전율) 지표라고 했는데요, Factor2 축의 위쪽에 위치한 미래애셋증권, 한화증권, 메리츠증권, 교보증권, 삼성증권 등이 수익성이 양호한 증권사라고 해석할 수 있겠습니다.

 

(참고: Factor2 = 0.965*V1_s + 0.792*V2_s + 0.080*V3_s + 1.20*V4-s2 + 0.155*V5_s)

 

 

이처럼 요인분석을 활용하면 다수의 변수를 안정성과 수익성이라는 두 개의 축으로 차원을 축소해서 포지셔닝맵을 그려서 쉽게 전체 상황을 파악할 수 있겠습니다.

 

다음번 포스팅에서는 기계데이터, 신호데이터에서 나오는 신호를 압축 변환하는 방법에 대해서 알아보겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

728x90
반응형
Posted by Rfriend
,

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

   - 이산형화

   - 이항변수화

(4) 개수 축소

(5) 차원 축소

   - (5-1) 주성분분석

   - (5-2) 요인분석

(6) 시그널 데이터 압축

 

의 6개 구분 중에서 (5-1) 주성분분석(Principal Component Analysis, PCA)에 대해서 알아보겠습니다.

 

주성분분석이란 여러 변수들의 변량을 '주성분(Principal Component)'이라고 불리는, 서로 상관성이 높은 여러 변수들의 선형조합으로 만든 새로운 변수들로 요약, 축소하는 기법입니다.  첫번째 주성분으로 전체 변동을 가장 많이 설명할 수 있도록 하고, 두번째 주성분으로는 첫번째 주성분과는 상관성이 없어서(낮아서) 첫번째 주성분이 설명하지 못하는 나머지 변동을 정보의 손실없이 가장 많이 설명할 수 있도록 변수들의 선형조합을 만들게 됩니다.

 

 

 

 

주성분분석이나 요인분석은 다변량통계분석에서 배우게 되는데요, 선형대수(Linear Algebra)에 대한 기본적인 이해가 없으면 이론으로 깊이 들어가는 것이 매우 어렵습니다.  그래서 이번 포스팅에서는 수식이나 행렬 등 어려운 이론은 넘어가도록 하겠으며, 주성분분석이나 요인분석을 왜 하는지 알아보고, 분석을 위한 R script 와 결과 해석에 주안점을 두도록 하겠습니다.

 

(☞ 선형대수학은 본 블로그의 선형대수' 카테고리에 별도로 정리하였으니 참고하시기 바랍니다. (벡터, 행렬, 내적, 외적, 선형독립, 기저, 벡터공간, 부분공간, 생성공간, 차원, 핵, 상공간, 차원정리, 계수, 고유값, 고유벡터 등))

 

 

주성분분석, 요인분석은 왜 하는가?  어떤 분석에 연계활용되나? 

 

1. 여러 변수들 간에 내재하는 상관관계, 연관성을 이용해 소수의 주성분 또는 요인으로 차원을 축소함으로써 데이터를 이해하기 쉽고 관리하기 쉽게 해줍니다.  사람은 1차원, 2차원까지는 그래프로 그려서 직관적으로 이해를 할 수 있지만, 3차원 이상으로만 넘어가도 집단의 모습을 인식하는데 큰 어려움을 겪게 됩니다.(향후 홀로그램이 상용화되면 3차원까지는 그래도 사람이 인지하기 편할수도 있겠네요 ^^;)  만약 변수가 10개 있는데 이를 단 2개의 차원으로 요약을 했는데도 변수들이 가지는 변동을 80~90% 설명할 수 있다면 굳이 변수 10개를 모두 이용할 필요가 없겠지요.

 

 

 

 

2. 회귀분석이나 의사결정나무(decision tree) 등의 모형 개발 시 입력변수들간의 상관관계가 높은 다중공선성(multicollinearity)이 존재할 경우 모형이 잘못만들어지고 따라서 해석에도 문제가 생기게 됩니다.  다중공선성이 존재할 경우 해결 방법 중의 하나가 바로 상관도가 높은 변수들을 하나의 주성분 혹은 요인으로 축소하여 모형개발에 활용하는 것입니다.

 

3. 연관성이 높은 변수 간 주성분 또는 요인분석을 통해 차원을 축소한 후에 군집분석을 수행하면 군집화 결과, 연산속도 개선에 기여하게 됩니다.  가령, Benefit 고객세분화를 위해 survey를 하게 되면 소구점들에 대한 다수의 문항(변수)의 답변 결과에 대해서 먼저 요인분석을 한 후에 요인점수(factor score)를 가지고 군집분석(clustering)을 수행하고 세분군집을 명명(naming)하게 됩니다.

 

4. 기계에서 나오는 다수의 센서데이터를 주성분분석이나 요인분석을 하여 차원을 축소한 후에 시계열로 분포나 추세의 변화를 분석하면 기계의 고장(fatal failure) 징후를 사전에 파악하는데 활용하기도 합니다.

 

위의 몇 개 사용예처럼 다른 분석의 입력변수로 주성분분석(주성분점수)나 요인분석(요인점수)를 통해 데이터를 전처리/변환하기도 하며, 아니면 주성분분석이나 요인분석을 바로 그 자체로 바로 활용하기도 합니다. 

 

 

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

 

주성분분석에서는

 - 상관행렬과 공분산행렬 중 어느 것을 선택할 것인가?

 - 주성분의 개수를 몇 개로 할 것인가?

 - 주성분에 영향을 미치는 변수로 어떤 변수를 선택할 것인가?

에 대해서 결정을 해야 하는데요,

 

한국신용평가정보에서 나온 '국내 증권회사의 주요 재무제표' (2007.3.31 기준)를 가지고 주성분분석을 R로 해보도록 하겠습니다. (다음번 포스팅에서는 똑같은 데이터에 대해 요인분석을 해보겠습니다)

 

이 데이터는 18개 증권사별로 V1.총자본순이익율, V2.자기자본순이익율, V3.자기자본비율, V4.부채비율, V5.자기자본회전율 재무지표 변수로 구성되어 있습니다.

 

예제 데이터('국내 증권회사의 주요 재무제표' (2007.3.31 기준)) 다운로드 ☞

secu_com_finance_2007.csv

 

R로 외부 csv 데이터 불러오기

 

> ##----------------------------------------------------------------------
> ## 차원축소(dimension reduction) : (1) PCA(Principal Component Analysis)
> ##----------------------------------------------------------------------
> 
> # csv 파일 불러오기 (file importing)
> secu_com_finance_2007 <- read.csv("C:/Users/user/Documents/R/secu_com_finance_2007.csv",
+                                   header = TRUE, 
+                                   stringsAsFactors = FALSE)
> # V1 : 총자본순이익율
> # V2 : 자기자본순이익율
> # V3 : 자기자본비율
> # V4 : 부채비율
> # V5 : 자기자본회전율

 

 

주성분분석에서는 변수별로 단위가 다른 raw data를 사용하지 않고 평균과 표준편차를 가지고 표준화(standadization)한 데이터를 사용합니다. 그래야 scale이 다른 문제로 인한 데이터 왜곡을 피할 수 있기 때문입니다.

 

> # 표준화 변환 (standardization) > secu_com_finance_2007 <- transform(secu_com_finance_2007, + V1_s = scale(V1), + V2_s = scale(V2), + V3_s = scale(V3), + V4_s = scale(V4), + V5_s = scale(V5))

 

 

V1.총자본순이익율, V2.자기자본순이익율, V3.자기자본비율, V5.자기자본회전율의 네 개의 변수는 숫자가 클 수록 좋다는 뜻이지만 V4.부채비율는 높을 수록 안 좋다고 해석하게 됩니다.  즉 V1, V2, V3, V5와 V4는 반대방향으로 움직이게 되는데요, 서로 같은 방향으로 움직이게 해서 상관도가 높게 나와 같은 주성분에 반영되도록 하기 위해서 아래와 같이 V4.부채비율의 방향을 변환(표준화된 이후의 max 값에서 표준화된 이후의 관찰값을 뺌)하겠습니다. (부채비율 방향 변환 후에는 숫자가 높을 수록 좋은 회사라고 해석할 수 있습니다)  그리고 주성분분석에 필요한 변수(V4_s가 아니라 V4_s2 가져온거 유의)만 indexing해서 선별하였습니다.

 

> # 부채비율(V4_s)을 방향(max(V4_s)-V4_s) 변환
> secu_com_finance_2007 <- transform(secu_com_finance_2007, 
+                                    V4_s2 = max(V4_s) - V4_s)
> 
> # variable selection
> secu_com_finance_2007_2 <- secu_com_finance_2007[,c("company", "V1_s", "V2_s", "V3_s", "V4_s2", "V5_s")]

 

 

먼저, 변수들간의 상관계수를 분석해보겠습니다.  주성분분석이 변수들 간의 상관관계가 높다는 것을 가정하고 있기 때문에 한번 확인해보도록 하겠습니다.  

 

V1_s.총자본순이익률과 V2_s.자기자본순이익율이 상관관계가 높고(상관계수 0.615), V3_s.자기자본비율과 V4_s2.(방향변환 후의)부채비율이 상관관계가 매우 높게(상관계수 0.936) 나왔습니다.  V5_s.자기자본회전율은 V2_s.자기자본순이익율과 상관관계가 있고, V3_s.자기자본비율과 V4_s2.(방향전환후의 부채비율)과는 역의 상관관계가 나왔네요.

 

> # Correlation analysis
> cor(secu_com_finance_2007_2[,-1])
            V1_s       V2_s       V3_s      V4_s2        V5_s
V1_s  1.00000000  0.6165153  0.3239780  0.3553930  0.01387883
V2_s  0.61651527  1.0000000 -0.5124351 -0.4659444  0.42263462
V3_s  0.32397800 -0.5124351  1.0000000  0.9366296 -0.56340782
V4_s2 0.35539305 -0.4659444  0.9366296  1.0000000 -0.53954570
V5_s  0.01387883  0.4226346 -0.5634078 -0.5395457  1.00000000 
> 
> round(cor(secu_com_finance_2007_2[,-1]), digits=3) # 반올림

 

 

변수들간의 산점도 행렬도 살펴보도록 하죠.

 

> # Scatter plot matrix
> plot(secu_com_finance_2007_2[,-1])

 

 

 

 

이제 prcomp() 함수를 사용해서 주성분분석을 실시합니다.  아래 결과에 보면 누적기여율(Cummulative Proportion)에 제1주성분(PC1)이 55.23%, 제 2주성분(PC1 & PC2)까지의 누적기여율dl 87.34%로 매우 높게 나왔습니다.

 

> # 주성분분석 PCA(Principal Component Analysis) > secu_prcomp <- prcomp(secu_com_finance_2007_2[,c(2:6)]) # 첫번째 변수 회사명은 빼고 분석 > > summary(secu_prcomp) Importance of components: PC1 PC2 PC3 PC4 PC5 Standard deviation 1.6618 1.2671 0.7420 0.25311 0.13512 Proportion of Variance 0.5523 0.3211 0.1101 0.01281 0.00365 Cumulative Proportion 0.5523 0.8734 0.9835 0.99635 1.00000

 

 

아래에 주성분분석 결과를 출력해보았습니다. 제1요인이 표준편차가 1.66으로 가장 크고, 제2요인이 1.26으로 그 다음으로 큰 식으로 순서가 있습니다.

 

그리고 Rotation 후의 고유벡터(eigenvector)의 계수를 보면 제1요인(PC1)은 자기자본비율(V3_s)와 부채비율(V4_s2)와 관련이 있고, 제 2요인(PC2)은 총자본순이익율(V1_s)과 자기자본순이익율(V2_s)와 관련이 있으며, 제 3요인(PC3)은 자기자본회전율(V5_s)와 관련이 있음을 알 수 있습니다.

 

> print(secu_prcomp)
Standard deviations:
[1] 1.6617648 1.2671437 0.7419994 0.2531070 0.1351235

Rotation:
              PC1         PC2           PC3          PC4         PC5
V1_s   0.07608427 -0.77966993  0.0008915975 -0.140755404  0.60540325
V2_s  -0.39463007 -0.56541218 -0.2953216494  0.117644166 -0.65078503
V3_s   0.56970191 -0.16228156  0.2412221065 -0.637721889 -0.42921686
V4_s2  0.55982770 -0.19654293  0.2565972887  0.748094314 -0.14992183
V5_s  -0.44778451 -0.08636803  0.8881182665 -0.003668418 -0.05711464 
 

 

 

 

이처럼 변수와 주성분간 관계를 고려해서 주성분에 이름을 명명(naming)해보자면 PC1은 안정성, PC2는 수익성, PC3는 활동성이라고 할 수 있겠네요. (재무제표 배울 때 배우는 대표적인 재무평가 지표)

 

 

 

 

 

선형대수를 안배우면 용어, 이론 개념이 어려울 수 있는데요, 참고로 고유값(eigenvalue)와 고유벡터(eigenvector)에 대한 정의를 아래에 소개합니다.

 

[참고: 고유값(eigenvalue), 고유벡터(eigenvector) 용어설명]

 

 

 

PCA 로 차원축소 할 때 몇 개의 PCs 를 선택할 것인가에 대해 학문적으로 정의된 정답(universal rule)은 없으며, 많이 사용되는 '경험에서 나온 법칙(rule of thumb)'은 3가지 있습니다.


(1) 누적기여율(설명된 분산의 누적 비율)이 최소 (at least) 0.8 이상일 것.


(2) 단지 평균 분산보다 큰 PC만 선별할 것.

(만약 표준화한 데이터에 대한 상관관계행렬을 사용할 경우 고유값(eigenvalue)이 최소 1보다 큰 PC)


(3) Scree plot 을 그려봤을 때 꺽이는 부분 (elbow)이 있다면 elbow 지점 앞의 PC 개수 선택.


* source: Prof. Dr. Fabio Sigrist, Applied Multivariate Statistics



만약 PCA를 통해 계산한 PC score를 가지고 지도학습모형 (가령, 선형회귀모형)을 학습한다면 PCs 의 개수를 늘려가면서 Cross-validation을 해서 Loss function의 score를 최소로 하는 PC의 개수를 구하는 방법도 있습니다.


아래 예느 Scree plot 그래프를 그려서 고유값 곡선이 꺽이는 지점의 바로 앞의 주성분 개수를 선택한 것입니다.  아래 그래프로 보면 주성분 4개째에서 수평으로 드러누웠으므로 한개를 뺀 (4-1 = 3) 3개 주성분이 적합해 보입니다.

 

> # Scree Plot
> plot(prcomp(secu_com_finance_2007_2[,c(2:6)]), type="l",
+      sub = "Scree Plot")

 

 

 

 

주성분1점수(principal component 1 score)과 주성분2점수(principal component 2 score)를 가지고 Biplot을 그려보겠습니다.  

가로축 PC1(안정성)을 기준으로 보면 오른쪽에 V3_s, V4_s2 화살표가 향하는 쪽에 있는 부국증권, 한양증권, 유화증권사는 안정성이 높은 회사군이라고 해석을 할 수 있게 됩니다.

(참고: PC1 = 0.076*V1_s - 0.394*V2_s + 0.569*V3_s + 0.559*V4_s2 - 0.447*V5_s )

 

세로축 PC2(수익성)을 기준으로 보면 아래쪽에 있는 대우증권, 미래애셋증권사 등이 수익성이 좋은 축에 속합니다. (아래 식 부호가 (-) 라서 숫자 낮은 것이 수익성 좋다는 뜻)

(참고: PC2 = -0.779*V1_s - 0.565*V2_s - 0.162*V3_s - 0.196*V4_s2 - 0.086*V5_s )

 

> # Biplot
> biplot(prcomp(secu_com_finance_2007_2[,c(2:6)]), cex = c(0.7, 0.8))
> 
> # 관측치별 주성분1, 주성분2 점수 계산(PC1 score, PC2 score)
> secu_pc1 <- predict(secu_prcomp)[,1]
> secu_pc2 <- predict(secu_prcomp)[,2]
> 
> 
> # 관측치별 이름 매핑(rownames mapping)
> text(secu_pc1, secu_pc2, labels = secu_com_finance_2007_2$company, 
+      cex = 0.7, pos = 3, col = "blue")
 

 

 

 

 

이렇게 변환한 주성분점수를 가지고 다른 통계모형이나 데이터마이닝 모형 개발 시 input으로 활용해도 되겠습니다.  

 

 

 

참고로, 변수에 대한 설명력의 누적기여율(cummulative proportion)이 80%가 되는 주성분의 개수 k개를 찾아서, 주성분 1번부터 주성분 k번째까지의 주성분점수를 반환하는 사용자 정의함수는 아래와 같습니다. 

(Dr.Kevin 님의 댓글 덕분에 오류 잡아서 프로그램 수정하였습니다. Dr.Kevin님 감사합니다)

 

> ########################################################### > ## PCA (Principal Component Analysis) > ## User Defined Function > ## - finding PC k which Cumulative Proportion is over 0.8 > ########################################################### > > pca <- function(dataset){ + pc = prcomp(dataset, scale = TRUE) + + k = 0 + R = 0 + + while(R < 0.8) { + k = k + 1 + R = sum(pc[[1]][1:k]^2)/sum(pc[[1]]^2) + + cat("When number of Principal Component(k) is ", k, + ", Cumulative Proportion(R) is ", R, "\n", "\n", sep="") + } + + SelectedDataSet = pc[[5]][,1:k] + return(SelectedDataSet) + } > > pca(secu_com_finance_2007_2[,c(2:6)]) When number of Principal Component(k) is 1, Cumulative Proportion(R) is 0.5522924 When number of Principal Component(k) is 2, Cumulative Proportion(R) is 0.8734231 PC1 PC2 [1,] -1.4870243 0.6066594 [2,] -0.2063797 -0.0804627 [3,] 0.1968538 0.9704605 [4,] -2.3542884 -3.5056480 [5,] -0.8953707 1.4552899 [6,] -0.3682082 -0.5976313 [7,] -0.9354306 -1.4144519 [8,] 2.4129728 -0.6785064 [9,] 2.6991862 -0.7596591 [10,] -0.4050098 0.2800099 [11,] 1.3958199 1.1353513 [12,] -1.5381192 1.1576616 [13,] 0.3217681 -0.2378023 [14,] -2.0306806 0.9646122 [15,] 3.0389460 0.8841645 [16,] 2.0064063 -1.2831337 [17,] -0.4211779 -0.2987099 [18,] -1.4302634 1.4017959

 

 

 

다음 포스팅에서는 요인분석(factor analysis)에 대해서 알아보겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

 

 

 

728x90
반응형
Posted by Rfriend
,

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

   - 이산형화

   - 이항변수화

(4) 개수 축소 (Sampling)

(5) 차원 축소

   - 주성분분석

   - 요인분석

(6) 시그널 데이터 압축

의 6개 구분 중에서

 

(4) 개수 축소 (Sampling)에 대해서 알아보겠습니다.

 

통계를 크게 두 축으로 나누자면 모집단으로 부터 표본을 추출해서 표본의 분포와 데이터 특성을 파악하는 기술통계(descriptive statistics)와 표본집단의 통계량을 통해 모집단의 모수(parameter)를 추정하고 가설을 검정(test)하는 추론통계(inferential statistics)로 구분합니다.  기술통계의 시작은 표본 추출 (Sampling)이라고 할 수 있겠습니다.  즉, 표본 추출이 모집단을 대표하지 못하게 되는 경우, 샘플링 이후의 모든 기술통계와 추론통계가 말짱 꽝이 되는 것입니다.  반면, 샘플링이 모집단을 잘 대표할 경우 엄청난 시간과 돈을 절약할 수 있는 통계의 무기를 획득할 수 있게 됩니다.

 

 

 R 데이터 변환 : (4) 개수 축소 (Sampling)

 

 

 

[ 데이터 변환 구조 ]

 

미국 대공황 시절에 루즈벨트 대통령이 뉴딜정책을 펼 때 경제정책의 기조를 의사결정하기 위해 실업율을 조사해야 했다고 합니다.  통계학자들은 잘 설계된 표본 추출기법을 통해 일부만 조사하고도 실업율을 파악할 수 있다고 한 반면에, 경제학자들은 전수조사를 해야한다고 우겼다고 하는군요.  그래서 샘플 조사도 하고 전수조사도 했다고 하는데요, 샘플 조사야 며칠 이면 끝나지만 전수조사는 몇 달이 걸렸다고 합니다.  전수 조사결과와 샘플 조사 결과를 비교해보니 오차가 무시할 수 있을 정도로 작았다고 했답니다. 이처럼 잘 설계된(!) 샘플링은 시간과 돈을 많이 절약해줄 수 있습니다. 

 

제품을 생산하는 제조공장에서도 품질검사를 위해 샘플링을 많이 사용합니다.  품질검사한다고 전수조사 했다가는 시장에 내다 팔 제품이 남아나지 않아서 공장 망하겠지요?  이럴 때는 어쩔 수 없이 샘플링을 해야만 하는데요, 샘플링을 너무 적게 하면 품질검사 결과를 신뢰할 수 없고, 그렇다고 너무 많이 하게 되면 품질검사 한다고 많은 멀쩡한 제품이 손상되어 손실을 보게 되겠지요. 

 

따라서 샘플링 기법의 종류와 개념에 대해서 명확히 이해하고, Biz. 상황과 분석의 목적에 맞는 샘플링 기법을 적용해야 하겠습니다. 확률표본 추출 기법에는 (a) 단순 임의 추출, (b) 체계적 추출, (c) 층화 임의 추출, (d) 군집 추출, (e) 다단계 추출의 5가지 나눌 수 있으며, 아래에 개념 설명과 도식을 참고하시기 바랍니다. (비 확률표본 추출 기법은 생략)

 

 

[ 확률표본 추출 기법 ]

 

 

 

 

저는 실무에서는 단순 임의 추출과 층화 임의 추출을 가장 많이 사용하기에 이번 포스팅에서는 이 두개에 대해서 R 사용법을 소개해드리도록 하겠습니다. 

 

 

(1) 단순 임의 추출 (simple random sampling) : sample()

 

먼저 1~10까지 정수 벡터에 대해서 5개 표본을 비복원, 복원추출로 단순 임의 추출해보겠습니다.

 

> sample(1:10, 5, replace = FALSE) # 비복원추출
[1] 9 6 5 7 3
> 
> sample(1:10, 5, replace = TRUE) # 복원추출
[1] 9 3 3 2 3

 

단순 임의 추출은 sample(x, size, replace = FALSE/TRUE) 함수를 사용해서 쉽게 실행할 수 있습니다.  위 예시의 첫번째는 비복원 추출 (한번 뽑으면 다시는 안뽑힘) 옵션을 부여한 것이고, 두번째 예시는 복원추출(한번 뽑혔더라도 다시 뽑힐 수도 있음) 예시가 되겠습니다.  복원추출의 경우 1~10중에서 3이 3번 중복해서 추출되었습니다.

 

> sample(1:10, 5, replace = TRUE)
[1] 10  6  9  7 10
> sample(1:10, 5, replace = TRUE)
[1] 1 4 1 4 3

 

똑같은 sample() 명령어인데도 매번 실행할 때마다 표본 추출되는 결과가 다름을 알 수 있습니다.  R 내부적으로 난수표를 생성하면서 무작위로 샘플링을 하기 때문에 그렇습니다.

 

 

다음으로 MASS 패키지에 있는 Cars93 데이터 프레임의 93개 모집단에서 5개 표본을 단순 임의 추출을 해보도록 하겠습니다.

 

> library(MASS)
> dim(Cars93)
[1] 93 27

>

> sim_ran_sam <- sample(1:nrow(Cars93), 5)

> Cars93_srs <- Cars93[sim_ran_sam, ]
> dim(Cars93_srs)
[1]  5 27
 

 

 

 

 

(2) 층화 임의 추출 (stratified random sampling) : strata()

 

다음으로 성별, 연령대별로 고객을 계층(stratum)을 나누어서 임의 추출을 해보도록 하겠습니다.  이를 위해 데이터 핸들링하는 data.table 패키지와 샘플링 하는 sampling 패키지 설치 및 호출이 필요합니다. 

 

> ## data.table 패키지, sampling 패키지 설치 및 호출

> install.packages("data.table")
> install.packages("sampling")
> require(data.table)
> require(sampling)

 

 

 

다음으로 1000명으로 구성된 모집단을 성별(1, 0), 연령대별(1, 2, 3, 4, 5), 재구매여부별(1, 0)로 3개의 변수에 대해서 각각 확률을 설정해주고 d.t 라는 이름의 data.table 을 생성해보겠습니다. 

 

> set.seed(1) > n <- 1000 > d.t <- data.table(gender = rbinom(n, 1 , .5), + age = sample(1:5, n, replace=TRUE), + rebuy_yn = rbinom(n, 1, .2))

 

 

 

 

 

data.table 에서는 data.frame과는 달리 특정 칼럼을 key값으로 색인을 지정(setkey)해주게 되고, 이 key값으로 정렬을 해주게 됩니다. (참고로, 속도가 data.frame보다 빠름)  data.table 의 group 별 집계하는 방식이 data.frame하고는 좀 달라서 낯설것 같은데요, 아래의 두번째 예시 참고하시기 바랍니다.

 

> ## Key 색인 지정, 정렬 > setkey(d.t, gender, age) >

 

 

 

> ## 성, 연령대 계층(stratum) 별로 모집단 원소 수 (총 1,000명)

> d.t[ , .N, keyby = list(gender, age)] gender age N 1: 0 1 113 2: 0 2 108 3: 0 3 93 4: 0 4 106 5: 0 5 100 6: 1 1 115 7: 1 2 86 8: 1 3 96 9: 1 4 73 10: 1 5 110

 

 

다음으로 strata() 함수를 사용해서 층화 임의 추출을 하면 됩니다.

 

> ## 성별, 연령대 계층별 각 20명씩 층화 임화 추출 > set.seed(2) > samp <- data.table(strata(d.t, c("gender", "age"), rep(20, 10), "srswor")) >

 

 

> ## 성별, 연령대 계층별 각 표본 개수 (각 20명 씩 표본 추출) > samp[ , .N, keyby = list(gender, age)] gender age N 1: 0 1 20 2: 0 2 20 3: 0 3 20 4: 0 4 20 5: 0 5 20 6: 1 1 20 7: 1 2 20 8: 1 3 20 9: 1 4 20 10: 1 5 20

 

 

strata() 함수의 사용법은 아래와 같으며, 위에서 method로 "srswor"을 사용했는데요, 이는 simple random sampling without replacement (디폴트) 가 되겠습니다.

 

> help(strata)

 

strata(data, stratanames=NULL, size, method=c("srswor","srswr","poisson", "systematic"), pik,description=FALSE)

-- 중략 --

method

method to select units; the following methods are implemented: simple random sampling without replacement (srswor), simple random sampling with replacement (srswr), Poisson sampling (poisson), systematic sampling (systematic); if "method" is missing, the default method is "srswor".

-- 중략 --

 


여러개의 변수를 가진 DataFrame에 대해서 층화 무작위 추출을 사용해서 Train, Test set 분할을 하는 방법은 아래의 포스팅을 참고하세요. 

==> https://rfriend.tistory.com/515



이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

 

728x90
반응형
Posted by Rfriend
,

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

  - (3-1) 이산형화(discretization)    

  - (3-2) 이항변수화(binarization)

(4) 개수 축소

(5) 차원 축소

  - 주성분분석

  - 요인분석 

(6) 시그널 데이터 압축

 

중에서 이전 포스팅 (3-1) 이산형화(discretization)에 이어서 (3-2) 이항변수화(binarization)에 대해서 알아보겠습니다.  

 

이산형화가 다수의 구간으로 연속형 변수를 범주화 하는 것이라면, 이항변수화는 '1'과 '0'의 2개의 값으로 가변환(dummy variable)을 만드는 것을 말합니다.  연관분석이나 회귀분석 등에 이항변수화가 필요한 경우가 있습니다.  가령, 연관분석에서는 상품별 구매 여부(1, 0) 거래 데이터 (transaction data)를 가지고 패턴을 찾게 되는데요, 거주 지역이라든지 소득수준을 이항변수화 해서 행렬(matrix)로 만들어서 연관 규칙을 찾는데 활용할 수 있습니다 (더 정확히 말하자면, 메모리를 줄이기 위해서 sparse matrix 형식으로 저장).  또는 만약 시계열 자료가 고정계절변동(계절요인의 변동을 시간의 변화에도 일정하게 변동량을 유지하는 경우)을 가지는 경우에 가변수를 이용한 시계열 회귀모형을 시도해봄직 합니다. 

 

 

 R 데이터 변환 : (3-2) 이항변수화 (binarization)

 

[ 데이터 변환의 구조 ] 

 

 

(1) ifelse() 함수

 

ifelse() 함수를 활용해서 고객 프로파일 내 연령을 10대 간격으로 해서 20대 여부(1, 0), 30대 여부(1, 0), 40대 여부(1, 0), 50대 여부(1, 0) 의 가변수를 만들어보도록 하겠습니다.

 

> ## 고객 프로파일 데이터 프레임 생성

> cust_id <- c("c01", "c02", "c03", "c04", "c05", "c06", "c07")
> age <- c(25, 45, 31, 30, 49, 53, 27)
> 
> cust_profile <- data.frame(cust_id, age, stringsAsFactors = F)
> 
> cust_profile
  cust_id age
1     c01  25
2     c02  45
3     c03  31
4     c04  30
5     c05  49
6     c06  53
7     c07  27
> 
> sapply(cust_profile, class) 
    cust_id         age 
"character"   "numeric" 
> 
> ## 연령대 이항변수화
> cust_profile <- transform(cust_profile, 
+                           age_20 = ifelse(age >= 20 & age < 30, 1, 0), 
+                           age_30 = ifelse(age >= 30 & age < 40, 1, 0), 
+                           age_40 = ifelse(age >= 40 & age < 50, 1, 0), 
+                           age_50 = ifelse(age > 50 & age < 60, 1, 0))

 

> cust_profile
  cust_id age age_20 age_30 age_40 age_50
1     c01  25      1      0      0      0
2     c02  45      0      0      1      0
3     c03  31      0      1      0      0
4     c04  30      0      1      0      0
5     c05  49      0      0      1      0
6     c06  53      0      0      0      1
7     c07  27      1      0      0      0

 

 

 

위 화살표에서 보는 것처럼 해당 연령대에 '1'이, 아닌 곳에는 '0'이 들어가 있는 가변수들을 볼 수 있습니다.   이런 가변수를 활용해 연관분석을 하게 되면 if {연령대 = 20대, 케익구매} -> {반지 구매} 와 같이 구매 상품 정보뿐만 아니라 고객의 프로파일 정보도 같이 반영된, 그래서 타케팅 적중률을 더 높일 수 있는 연관규칙을 도출해낼 수 있습니다.

 

 

가변수를 이용한 시계열회귀모형을 예로 들자면, 아래의 식처럼 시점 t에서의 시계열 추세, 시점 t에서의 계절효과, 시점 t에서의 오차로 분해할 수 있습니다.  시계열회귀분석식을 자세히 보시면 [ D1,t = 1 시점 t에서 계절이 1인 경우, 0 그 이외의 경우 ] 라고 해서 가변수 처리 되어 있음을 알 수 있습니다.

 

 

 

> ## 시계열 데이터 생성 > Season <- c("S1", "S2", "S3", "S4", "S1", "S2", "S3", "S4") > SalesAmt <- c(300, 800, 400, 100, 280, 750, 390, 60) > TS <- data.frame(Season, SalesAmt, stringsAsFactors = F) > > TS Season SalesAmt 1 S1 300 2 S2 800 3 S3 400 4 S4 100 5 S1 280 6 S2 750 7 S3 390 8 S4 60 >

> ## 시계열회귀분석용 가변수 생성 > TS <- transform(TS, + Season1 = ifelse(Season=="S1", 1, 0), + Season2 = ifelse(Season=="S2", 1, 0), + Season3 = ifelse(Season=="S3", 1, 0)) > > TS Season SalesAmt Season1 Season2 Season3 1 S1 300 1 0 0 2 S2 800 0 1 0 3 S3 400 0 0 1 4 S4 100 0 0 0 5 S1 280 1 0 0 6 S2 750 0 1 0 7 S3 390 0 0 1 8 S4 60 0 0 0

 

 

 

 

예측모형적합을 위한 이항변수화를 할 때 한가지 주의해야 할 점이 있습니다.  바로 위의 캡쳐해놓은 표에 보면 S1(봄), S2(여름), S3(가을)은 가변수를 별도로 만들었지만, 노란색으로 동그라미 쳐놓은 S4 (겨울)은 별도의 가변수가 없고 그냥 S1 = 0, S2 = 0, S3 = 0 으로만 처리되어 있습니다.  이는 실수로 S4(겨울)을 빼먹은게 아니구요, 가변수 함정(dummy trap)을 피하기 위해 의도적으로 전체 계절의 갯수(여기서는 봄, 여름, 가을, 겨울의 4개)에서 1개를 빼게 됩니다.  운동회 때 서로의 간격을 맞추어서 줄을 서려면 제일 먼저 하는게 '기준'을 정하고 손을 번쩍 들어 크게 "기준~"하고 외칩니다.  그런 후에야 나머지 학생들이 "기준"이 되는 학생에 맞추어서 대열을 맞추게 되는데요, 계절효과를 반영하는 모형 적합에서도 "기준"이 되는 계절은 가변수를 안만들고, 나머지 계절만 가변수를 만들어서, "기준"이 되는 계절을 기준으로 상대적인 영향도를 계수로 모형에 반영하게 되는 원리입니다.  만약 계절의 전체 개수만큼 가변수를 만들게 되면 최소제곱추정법을 적용할 때 역행렬을 구할 수 없게 되는 가변수 함정에 빠지게 되어 해를 구할 수 없게 됩니다. 

 

시계열분석 통계이론이 잘 이해가 안되시더라도 일단 어떤 상황에서 이항변수화를 하게 되는지, R로는 ifelse() 함수를 써서 어떻게 변환하는지 정도만 이해하셨으면 일단 이번 포스팅에서는 얻어갈 것 다 얻어가신겁니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

 

 

728x90
반응형
Posted by Rfriend
,

 

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

  - (3-1) 이산형화(discretization)

  - (3-2) 이항변수화(binarization)

(4) 개수 축소

(5) 차원 축소

  - 주성분분석

  - 요인분석 

(6) 시그널 데이터 압축

 

중에서 우선 (3-1) 이산형화(discretization)에 대해서 알아보겠습니다. 

 

분할표(contingency table)를 구하거나 카이제곱 검정을 할 때 범주형 변수를 대상으로 집단 간 독립성 검정을 하게 됩니다.  이처럼 분석기법에 따라서 연속형 변수를 범주형 변수로 변환을 한 이후에야 분석이 가능한 경우가 있습니다. 

 

혹은, 회귀분석을 한다고 했을 때 명목형, 범주형 자료에 대해서 가변수(dummy variable) 화 해서 분석을 진행해야 할 때도 있습니다.  가령 요일효과를 모형에 적합시키고자 한다면 요일 변수를 월요일 여부(mon_yn), 화요일 여부(tue_yn), ... , 토요일 여부(sat_yn) 등과 같이 1, 0 으로 코드화된 가변수로 변환해야 하는 경우도 있습니다.

 

이번 포스팅에서는 첫번째 경우의 (1) 이산형화, 두번째 경우의 (2) 이항변수화에 대해서 R에서는 어떻게 처리하는지 알아보도록 하겠습니다.

 

 

 R 데이터 변환 (3) 범주화 : 이산형화(discretization)

 

 

[ 데이터 변환 구성 ] 

 

 

 

 

(3-1) 이산형화 (Discretization)

 

연속형 변수를 범주형 변수로 변환하는 작업을 이산형화라고 합니다.  이산형화 변화 시에는 '몇 개의 범주로 나눌지?''구분선(cutting line)을 무슨 기준으로, 어디로 할지?'가 중요한 질문이 되겠습니다.

 

두 질문에 대한 학술적인 단 하나의 답안은 없습니다.  두 질문에 대해 공통적으로 분석/활용의 목적이 무엇이냐와 Biz. Domain Konwledge가 충분히 반영이 되어서 의사결정을 해야만 하고, 운영 과정상의 시행착오와 경험을 통한 긍정/부정적 피드백을 반영하여 지속적으로 개선해나가야 할 것입니다. 

 

이번 포스팅에서는 (a) 간격을 동일하게 한 범주화와 (b) quantile을 활용한 범주화 (c) frequency를 동일하게 한 범주화를 R로 어떻게 하는지Cars93 데이터셋의 고속도로 연비(MPG.highway)을 가지고 예를 들어보겠습니다.

 

(참고로, Cars93 은 MASS 패키지에 내장된 데이터 셋으로서, 자동차의 속성에 대해서 27개의 변수, 93개 자동차 관측치를 가진 데이터 프레임)

 

(a) 간격을 동일하게 한 범주화는 R의 hist() 함수로 히스토그램을 그려보는 것이 좋은 출발점이 될 수 있습니다. R의 hist() 함수의 디폴트 구간 개수가 꽤 좋은 결과를 내주거든요.  bin size를 조정해가면서 분포를 탐색해보고서 특정 구간에서 변곡점이 있다면, 혹은 특정 segment 나 factor 별로 분포상의 큰 차이를 보인다면 그 구분선을 가지고 범주를 나눌 수도 있을 것입니다.

 

R의 within() 함수를 활용하여 아래의 Cars93의 고속도로연비는 20~50까지 5단위씩 등간격으로 6개 범주로 나누어 보겠습니다. 

 

> library(MASS)
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ..
 

> ## 고속도로연비(MPG.highway) 히스토그램

> hist(Cars93$MPG.highway)
 

 

 

> ## Model, MPG.highway 두 개 변수만 선택해서 disc_1 데이터 프레임 생성

> disc_1 <- Cars93[,c("Model", "MPG.highway")]

> ## 상위 6개 미리보기

> head(disc_1) Model MPG.highway 1 Integra 31 2 Legend 25

3 90 26 4 100 26

5 535i 30 6 Century 31

>

>

> ## 6개 범주로 등간격 범주화

 

 

> disc_1 <- within( disc_1, { + MPG.highway_cd = character(0) + MPG.highway_cd[ MPG.highway >=20 & MPG.highway <25 ] = "20~25" + MPG.highway_cd[ MPG.highway >=25 & MPG.highway <30 ] = "25~30" + MPG.highway_cd[ MPG.highway >=30 & MPG.highway <35 ] = "30~35" + MPG.highway_cd[ MPG.highway >=35 & MPG.highway <40 ] = "35~40" + MPG.highway_cd[ MPG.highway >=40 & MPG.highway <45 ] = "40~45" + MPG.highway_cd[ MPG.highway >=45 & MPG.highway <=50 ] = "45~50" + MPG.highway_cd = factor(MPG.highway_cd, + level = c("20~25", "25~30", "30~35", + "35~40", "40~45", "45~50")) + })

> 

> ## 상위 6개 보기

> head(disc_1)
    Model MPG.highway MPG.highway_cd
1 Integra          31          30~35
2  Legend          25          25~30
3      90          26          25~30
4     100          26          25~30
5    535i          30          30~35
6 Century          31          30~35
 

 

 

> attributes(disc_1$MPG.highway_cd)
$levels
[1] "20~25" "25~30" "30~35" "35~40" "40~45" "45~50"

$class
[1] "factor"

 

 

> table(disc_1$MPG.highway_cd) # 분할표 생성

 

20~25 25~30 30~35 35~40 40~45 45~50 14 41 27 7 2 2

 

"MPG.highway" 변수 옆에 "MPG.highway_cd" 라는 범주형 변수가 생겼음을 알 수 있습니다.  나중에 통계분석과 연계하기 위해 "MPG.highway_cd" 변수를 within()함수의 제일 마지막 줄에서 요인(factor)으로 지정을 해줬고, level = c("20~25", "25~30", "30~35", "35~40", "40~45", "45~50")) 으로 순서형 요인(ordered factor)의 수준을 지정해주었습니다.

 

 

다음으로 (b) quantile을 활용한 범주화 방법에 대해서 알아보겠습니다.  MPG.highway를 0~25%, 25~50%, 50~75%, 75~100%의 구성비로 해서 4개 범주로 나누어보겠습니다.

 

> summary(disc_1$MPG.highway)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  20.00   26.00   28.00   29.09   31.00   50.00
> 

> # 4분위수의 1Q, 2Q, 3Q, 4Q를 기준으로 4개의 범주 생성

> disc_1 <- within( disc_1, {
+   MPG.highway_cd2 = character(0)
+   MPG.highway_cd2[ MPG.highway <  quantile(MPG.highway, 0.25) ] = "1Q"
+   MPG.highway_cd2[ MPG.highway >= quantile(MPG.highway, 0.25) 
+                    & MPG.highway < quantile(MPG.highway, 0.50) ] = "2Q"
+   MPG.highway_cd2[ MPG.highway >=quantile(MPG.highway, 0.50) 
+                    & MPG.highway < quantile(MPG.highway, 0.75) ] = "3Q"
+   MPG.highway_cd2[ MPG.highway >= quantile(MPG.highway, 0.75) ] = "4Q"
+   MPG.highway_cd2 = factor(MPG.highway_cd2, 
+                           level = c("1Q", "2Q", "3Q", "4Q"))
+ })
> 

> # 상위 6개 보기

> head(disc_1)
    Model MPG.highway MPG.highway_cd MPG.highway_cd2
1 Integra          31          30~35              4Q
2  Legend          25          25~30              1Q
3      90          26          25~30              2Q
4     100          26          25~30              2Q
5    535i          30          30~35              3Q
6 Century          31          30~35              4Q
 

 

 

> table(disc_1$MPG.highway_cd2)  # 분할표 생성

1Q 2Q 3Q 4Q 
22 17 25 29 

 

 

다음으로, (c) frequency를 동일하게 해서 4개 범주를 구성해보도록 하겠습니다.  먼저 고속도로연비(MPG.highway) 기준으로 정렬을 해줘야합니다.

 

> ## 고속도로연비(MPG.highway) 기준으로 오름차순 정렬

> disc_1 <- disc_1[order(disc_1$MPG.highway), ]

 

 

> dim(disc_1) # 93개 관측치, 4개 변수 [1] 93 4

 

## 관측치 개수

> dim(disc_1)[1]
[1] 93
> 

> disc_1$N <- seq(1:length(disc_1$MPG.highway)) # 1~93까지 순서대로 1씩 증가하는 N이라는 변수 생성

>

> # 동일 frequency (23개)로 4개 범주 생성

> disc_1 <- within( disc_1, { + MPG.highway_cd3 = character(0) + MPG.highway_cd3[ N <= 23 ] = "1st_Freq" + MPG.highway_cd3[ N >= 24 & N <= 46 ] = "2nd_Freq" + MPG.highway_cd3[ N >= 47 & N <= 69 ] = "3rd_Freq" + MPG.highway_cd3[ N >= 70 ] = "4th_Freq" + MPG.highway_cd3 = factor(MPG.highway_cd3, + level = c("1st_Freq", "2nd_Freq", "3rd_Freq", "4th_Freq")) + }) > > head(disc_1) Model MPG.highway MPG.highway_cd MPG.highway_cd2 N MPG.highway_cd3 17 Astro 20 20~25 1Q 1 1st_Freq 36 Aerostar 20 20~25 1Q 2 1st_Freq 26 Caravan 21 20~25 1Q 3 1st_Freq 89 Eurovan 21 20~25 1Q 4 1st_Freq 48 Q45 22 20~25 1Q 5 1st_Freq 87 Previa 22 20~25 1Q 6 1st_Freq

 

 

 

 

>

 

 

> table(disc_1$MPG.highway_cd3) 1st_Freq 2nd_Freq 3rd_Freq 4th_Freq 23 23 23 24

 

위의 분할표를 보면 4개 범주별로 23개, 23개, 23개, 24개(총 93개여서 마지막에 1개 더 넣음) 로 동일 frequency로 범주화가 되었음을 알 수 있습니다.  그런데 (c) 같은 frequency 로 범주화 시에 동일한 고속도로연비임에도 범주가 다르게 구분이 되는 수가 생깁니다.  아래 예에서 보면 고속도로연비가 28인 경우 "2nd_Freq"와 "3rd_Freq" 범주에 양다리 걸쳐있는것을 확인할 수 있습니다.  ("1st_Freq"와 "2nd_Freq"에도 고속도로연비 26이 양다리를 걸치고 있습니다.  또한 31이 "3rd_Freq"와 "4th_Freq"에 양다리를 걸치고 있습니다.)  이처럼 동일 frequency로 범주화시에는 중첩됨이 없이 범주화하기가 어려운 문제점이 있습니다.  따라서 해석의 용이성과 중첩 방지를 위해서 (a) 등간격 범주화 또는 (b) quantile 활용 범주화가 (c) 동일 freqency보다는 좀더 유용하다고 볼 수 있겠습니다. 

 

 

이항변수화 (binarization)은 다음번 포스팅에서 소개해드리겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

 

728x90
반응형
Posted by Rfriend
,
 

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

   - 이산형화

   - 이항변수화

(4) 개수 축소

 

(5) 차원 축소

   - 주성분분석

   - 요인분석 

(6) 시그널 데이터 압축

 

중에서 로그 변환, 제곱급 변환을 활용한 정규분포화에 대해서 알아보겠습니다. 이들 변환들이 정규분포가 아니었던 분포를 정규분포로 변환시키는데 활용되므로 정규분포화 변환이라는 카테고리로 묶어보았습니다. 

 

많은 통계기법이 정규분포를 가정하고 있으므로 정규분포(Gausian distribution)가 아닌 경우 정규분포로 변환시키는 것은 사전작업으로 필수적이라고 하겠습니다. 

 

 

 R 데이터 변환 (2) 정규분포화 log(), sqrt()

 

 

[ 데이터 변환 구성 ]

 

 

 

 

자연현상 중에, 우리 주변의 일상 중에 정규분포가 많은데요(예: 키, 몸무게, 통계성적 등), 그에 못지않게 멱함수 분포(Power-law distribution)도 많이 있답니다.  특히 개체간 상호작용과 (긍정적/부정적) 피드백이 작용하는 관계에서는 멱함수 분포가 존재할 가능성이 높습니다. 

 

아래는 한겨레신문에서 카이스트 정하웅 교수님 인터뷰하면서 정규분포(고소도로 네트워크)와 멱함수분포(항공망 네트워크)의 예로 들은 것인데요(바라바시 링크 책 참조), 멱함수 분포를 띠는 항공망 네트워크의 경우 허브(Hub) 역할을 하는 공항이 있다는 것이지요.  이를 척도 없는 네트워크(scale-free network)라고도 하는데요, 이런 허브(Hub), 매개자(Connector) 가 있음으로 해서 세상이 좁아진다(small world)는 이론이 뒷받침을 받게 됩니다.

 

[ 네트워크의 두가지 유형 ]

 

 

한 국가의 부의 분포를 보면 20%의 국민이 부의80%를 차지하고 있고, 마태복은 13장 12절에 있는 ‘무릇 있는 자는 받아 풍족하게 되고 없는 자는 그 있는 것 까지도 빼앗기리라’ 말씀을 따서 마태효과(Matthew effect)라는 이론이 있기도 한데요, 멱함수 분포의 예라고 할 수 있겠습니다. 

 

그밖에도 멱함수 분포를 따르는 것으로 논문 인용, 인터넷 네트워크, 전기회로도, 전기/하수구 네트워크, 뇌의 뉴런 네트워크, 전염병이나 성병의 전파(아래 그림 예의 왼쪽에서 오른쪽, 상에서 하 순서 참고), 산불이나 지진의 강도별 발생 빈도, 프로야구선수 또는 프로축구선수 연봉 등... 그 예를 들자면 아주 많습니다.  혹시 복잡계과학, 네트워크과학에 대해서 관심이 있으시면 바라바시의 링크(Linked), 버스트(Birst), 던컨와츠의 스몰 월드(Small World), 마큐뷰캐넌 사회적원자(Social Atom), 우발과 패턴(Ubiquity) 등의 책을 추천합니다.  저자들이 물리학자 혹은 사회과학자들인데요, 수학적 공식없이도 일반인들이 쉽고 재미있게 읽을 수 있도록 책을 썼습니다.  지적유희 측면에서 재미있어요.  복잡한 세상 속에 이런 규칙이...하고 놀랄겁니다.

 

 

[ 다양한 멱함수 분포 예 ]

 

 

옆길로 많이 샜는데요 ^^;, 이처럼 많은 멱함수 분포를 정규분포로 변환할 때 로그 변환이나 제곱근 변환을 사용하게 됩니다.

 

UsingR 패키지에 들어있는 cfb 데이터 프레임을 가지고 예를 들어보겠습니다. cfb 데이터셋은 소비자 재정에 관한 설문조사 샘플 데이터로서, 14개의 변수와 1000명의 관측치가 들어있습니다.

 

> install.packages("UsingR")
> library(UsingR)

> > data(cfb) # cfb 데이터 불러오기 > head(cfb) # 상위 6개 미리보기 WGT AGE EDUC INCOME CHECKING SAVING NMMF STOCKS FIN VEHIC X17470 5749.975 54 14 66814.19 6000 2000 0 500 39600 6400 X315 5870.634 40 12 42144.34 400 0 0 0 5400 21000 X8795 8043.695 35 14 25697.77 1000 160 0 0 15460 2000 X10720 6092.872 55 12 35976.87 2600 19100 0 0 54700 18250 X19170 7161.757 40 12 39060.61 1000 8300 0 3500 12800 9100 X22075 11429.633 82 12 13362.84 1000 0 50000 0 70500 7500 HOMEEQ OTHNFIN DEBT NETWORTH X17470 84000 0 40200 170800 X315 8000 0 58640 17760 X8795 12000 0 19610 9850 X10720 90000 0 8000 284950 X19170 47000 0 21000 268900 X22075 175000 0 0 253000 >

> summary(cfb$INCOME) # INCOME 요약통계량 Min. 1st Qu. Median Mean 3rd Qu. Max. 0 20560 38030 63400 69900 1542000 >

> hist(cfb$INCOME, breaks=500, freq=TRUE) # INCOME 히스토그램

 

 

 

 

 

(1) 로그 변환 : log()

 

> ## 로그 변환
> cfb <- transform(cfb, INCOME_log = log(INCOME + 1))
> hist(cfb$INCOME_log, breaks=500, freq=TRUE)

 

 

 

 

위의 로그 변환 시에 INCOME_log = log(INCOME + 1) 처럼 (INCOME +1) 을 했습니다.  INCOME 이 '0'부터 시작하는데 '0'을 로그 취하면 마이너스 무한대가 나오기 때문에 1을 더해서 오른쪽으로 1씩 이동시킨 후에 로그변환을 취했습니다.

 

히스토그램을 보면 이전의 멱함수 분포의 소득이 정규분포로 변환되었음을 알 수 있습니다.

 

 

(2) 제곱근 변환 : sqrt()

 

> ## 제곱근 변환
> cfb <- transform(cfb, INCOME_sqrt = sqrt(INCOME + 1))
> hist(cfb$INCOME_sqrt, breaks=500, freq=TRUE)
 

 

 

 

제곱근 변환은 sqrt() 함수를 사용합니다.  위의 예시를 보면 로그변환 보다는 제곱근 변환이 오른쪽에 수입이 엄청나게 많은 부자들을 덜 정규분포화 시킨다는 것을  알 수 있는데요, 원래 데이터의 분포를 보고 로그변환과 제곱근 변환 중에서 더 적합한 것을 선택해서 사용하면 되겠습니다.

 

정규성 검정을 할 때 정규 분위수-분위수 그림(Q-Q Plot)을 사용하는데요, 아래에 원래 INCOME, 로그 변환 INCOME_log, 제곱근 변환 INCOME_sqrt 의 세개 변수에 대해서 Q-Q plot을 그려보았습니다.   아래 Q-Q plot으로 봐서는 로그 변환이 가장 잘 정규성을 띠고 있네요.

 

> ## Q-Q plot
> par( mfrow = c(1,3))
> qqnorm(cfb$INCOME, main="Q-Q plot of INCOME")
> qqline(cfb$INCOME)
> 
> qqnorm(cfb$INCOME_log, main="Q-Q plot of INCOME_log")
> qqline(cfb$INCOME_log)
> 
> qqnorm(cfb$INCOME_sqrt, main="Q-Q plot of INCOME_sqrt")
> qqline(cfb$INCOME_sqrt)
> par(mfrow = c(1,1))
 

 

 

 

 

모집단의 분포 형태에 따른 대략적인 정규분포 변환 방법은 아래 표와 같습니다

 

 [ 분포 형태별 정규분포 변환 방법 ]

 

 distribution

before transformation

transformation function 

distribution

after transformation 

 left

X^3 

 normal distribution

(bell shape)

 mild left

X^2 

 mild right

sqrt(X) 

 right

ln(X) 

 severe right

1/X 

 

 

단일모집단의 정규성 검정 (shapiro test, Q-Q plot) 방법은 아래의 링크를 참조하세요.

R 단일 모집단 분포의 정규성 검정 : shapiro.test(), qqnorm(), qqline()

 

많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

728x90
반응형
Posted by Rfriend
,

데이터 변환 방법으로서

 

(1) 표준화 (Standardizatio)

(2) 정규분포화

(3) 범주화

   - 이산형화

   - 이항변수화

(4) 개수 축소

(5) 차원 축소

   - 주성분분석

   - 요인분석 

(6) 시그널 데이터 압축

 

중에서 (1) 표준화(Standardization)에 대해서 알아보겠습니다. 

 

 

다양한 소스로 부터 데이터를 R로 불러와서, 결합하고, 결측값과 특이값을 확인 후 처리하고, 필요한 부분의 데이터만 선별적으로 선택 혹은 제거한 후에 분석의 목적과 필요에 따라서, 그리고 데이터의 형태에 따라서 다양한 데이터 변환 (data transformation) 작업을 수행합니다. 

 

고급 분석가와 그렇지 않는 분석가가 나뉘는 부분, 데이터 엔지니어와 데이터 분석가가 나뉘어 지는 부분이 여기서 부터 이지 않을까 싶습니다.  업에 대한 지시과 더불어 분석의 목적과 분석의 기법에 대해서 정확히 알아야 하고, 데이터의 형태가 그에 맞는지, 맞지 않다면 어떻게 변환을 해야 하는지 알아야 하기 때문입니다.  그리고 데이터 변환을 하는데 있어 통계적인 기본 지식이 필요하다보니 여기부터는 프로그래밍을 잘하지만 통계를 잘 모르는 데이터 엔지니어의 경우 어려움을 겪기 시작합니다.

 

이 변환 작업에는 많은 시간과 노력이 필요합니다.  그래서 데이터 분석을 업으로 삼으려고 생각했던 사람이라도 소위 데이터 전처리, 데이터 변환의 지난한 과정에 대해서 재미를 느끼지 못하면 오래 견디지 못하고 다른 커리어로 전향을 하기도 합니다.  그만큼 본격적인 통계/데이터마이닝 과정에 진입하기 위한 전초 단계로 중요하지만 쉽지 많은 않은 과정이라는 얘기입니다. 

 

모델링을 하는데 있어 분석 목적에 유의미하고 적합한 파생변수를 개발하고 input으로 넣는 것이 정말 중요합니다.  개념적 정의, 조작적 정의를 통해 파생변수를 개발하는 과정에 필수로 필요한 이론적 지식이 이번부터 해서 총 6번에 나누어서 진행할 데이터 변환이 되겠습니다. 

 

데이터 변환을 (1) 표준화, (2) 정규분포화, (3) 범주화, (4) 개수 축소(샘플링), (5) 차원 축소, (6) 시그널 데이터 압축 등의 6개 카테고리로 구분하였습니다.  대략적으로 봤을 때 (1) 표준화, (2) 정규분포화, (3) 범주화는 데이터 분포나 속성을 변화시키는 기법이고, (4) 개수 축소(샘플링), (5) 차원 축소, (6) 시그널 데이터 압축은 데이터 크기를 축소하는 기법이 되겠습니다.

 

이번 포스팅에서는 (1) 표준화의 (1-1) z 변환, (1-2) [0-1] 변환에 대해서 알아보겠습니다.  

 

 

데이터 변환 (1) 표준화 

 

 

[ 데이터 변환 구성 ]

 

 

 

 

(1-1) 표준정규분포 z 변환

 

우선 정규분포에 대해서 간략히 짚고 z 변환으로 넘어가겠습니다. 일상 생활 속에서 우리는 다양한 정규분포를 접하고 삽니다.  만약 100명의 수강생을 대상으로 통계와 R 분석 교육을 받고 시험을 치면 아마도 평균을 중심으로 종모양으로 좌우 분포가 비슷한 성적 분포를 띨 것입니다.  수강생 100명의 키와 몸무게를 조사를 해서 히스토그램을 그려보면 이 또한 평균을 중심으로 종모양으로 좌우 대칭인 정규분포를 띨 것입니다.  수강생 얼굴을 아직 본적도 없는데 이렇게 예언을 할 수 있다는거, 이게 참 신기한겁니다. ^^  만약 키의 평균과 표준편차를 저한테 알려주고, 수강생 100명 중에서 한 명의 수강생을 뽑아서 키를 재서 저에게 알려주면 그 수강생이 전체 100명 중에서 상위 몇 % 키에 속할지도 추측할 수 가 있습니다. 놀랍지요?

 

통계학에서는 '중심극한정리(central limit theorem)'이 정말 중요한 역할을 하는데요, 중심극한정리란 분포의 모양을 모르는 모집단으로부터 표본을 추출할 때, 표본평균 의 분포는 표본의 크기 n이 커짐(일반적으로 )에 따라 점점 정규분포로 근사해 간다는 성질을 말합니다.

 

 

참고 ) 중심극한정리 (Central Limit Theorem)

 

을 평균 , 분산 인 모집단으로부터의 크기 n 인 확률표본이라고 했을 때,

표본평균 의 분포는 n이 커짐에 따라 정규분포 으로 근사해 간다.

 

 

중심극한정리에서 표본평균 를 표준화하면

 

통계량 근사적으로 표준정규분포 을 따른다.

 

 

 

 

 이 중심극한정리에 근거해서 보통 샘플이 대략 30개 이상이면 표본평균이 정규분포로 근사한다고 가정하고 정규분포 가정에 근거한 다양한 통계분석 기법(추정과 검정 등...)을 적용할 수 있게 됩니다. 

 

이때 두 개 이상의 모집단으로 부터 표본의 크기가 큰 표본을 추출했을 때, 각 집단의 평균과 표준편차가 다르거나, 혹은 측정 scale 이 다른 경우에는 다수의 집단 간, 변수 간 직접적인 비교가 불가능하게 됩니다.   미국 달러, 유럽의 유로화, 중국의 위안화, 일본의 엔화, 그리고 한국의 원화를 각 각 1000 단위를 가지고 있다고 했을 때, 이게 서로간에 대비해서 얼마나 많은 건지, 값어치가 있는건지 직접 비교하는게 불가능한 것과 같은 이치입니다.  이때 특정 나라의 통화를 기준으로 삼고 다른 나라의 통화를 기준으로 변환을 하면 각 나라별 통화간의 돈의 가치를 비교할 수 있게 됩니다.  이게 표준화의 원리입니다.

 

위에서 정규분포의 중요성에 대해서 설명했는데요, 정규분포 중에서도 평균이 0, 표준편차가 1인 정규분포를 표준정규분포(standadized normal distribution) 이라고 합니다.  평균이 표준편차가 서로 다른 다수의 집합을 표준정규분포로 표준화를 하면 서로 비교를 할 수 있게 됩니다. 

 

그러면, 이제 R로 표준정규화 하는 방법에 대해서 알아보겠습니다.

 

  • 한국 성인 남성 1,000 명의 키가 평균 170cm, 표준편차 10cm의 정규분포
  • 남아프리카 부시맨 성인 남성 1,000명의 키가 평균 150cm, 표준편차 8cm의 정규분포

를 따른 다고 했을 때 두 집단의 키를 평균이 0, 표준편차가 1인 표준정규분포로 표준화를 해보도록 하겠습니다.

 

 

 먼저, 데이터 생성은 아래와 같이 랜덤하게 생성하였습니다.

 

> ## 한국인, 부시맨 각 성인 1000명 키 데이터 생성 > height_korean <- rnorm(n=1000, mean = 170, sd = 10) > height_bushman <- rnorm(n=1000, mean = 150, sd = 8) > > height <- data.frame(height_korean, height_bushman) # 데이터 프레임 생성

> rm(height_korean, height_bushman) # 벡터 삭제

> > head(height) # 상위 6개 데이터 확인 height_korean height_bushman 1 162.7654 132.5271 2 180.5701 135.5497 3 172.6752 142.5168 4 171.8035 156.7872 5 186.5258 154.3027 6 171.4634 156.1118 

 

> ## 한국인, 부시맨 키 히스토그램

> attach(height)
> par( mfrow = c(1,2))
> hist(height_korean, freq = TRUE, main = "한국인 키 빈도 히스토그램")
> hist(height_korean, freq = FALSE, main = "한국인 키 확률밀도함수 그래프")
> 

 

 

> hist(height_bushman, freq = TRUE, main = "부시맨 키 빈도 히스토그램")
> hist(height_bushman, freq = FALSE, main = "부시맨 키 확률밀도함수 그래프")

 

 

> detach(height)

 

 

그리고 표준정규화를 해보겠는데요, (a) scale()함수를 쓰는 방법과 (b) (x-mean(x))/sd(x) 처럼 공식을 직접 입력하는 방법이 있습니다.  결과는 동일합니다.

 

> ## a. scale() 함수
> 
> height <- transform(height, 
+                     z.height_korean = scale(height_korean), 
+                     z.height_bushman = scale(height_bushman)
+                     )
> 
> head(height)
  height_korean height_bushman z.height_korean z.height_bushman
1        179.19         140.60         0.89308         -1.18393
2        164.54         152.70        -0.60892          0.35689
3        184.18         136.76         1.40477         -1.67426
4        196.37         144.26         2.65531         -0.71833
5        162.61         155.72        -0.80706          0.74198
6        158.02         147.19        -1.27775         -0.34510

 

> ## b. z=(x-mean(x))/sd(x)
> height <- transform(height, 
+                     z2.height_korean = (height_korean - mean(height_korean))/sd(height_korean), 
+                     z2.height_bushman = (height_bushman - mean(height_bushman))/sd(height_bushman)
+                     )
> 
> head(height)
  height_korean height_bushman z.height_korean z.height_bushman z2.height_korean z2.height_bushman
1        179.19         140.60         0.89308         -1.18393          0.89308          -1.18393
2        164.54         152.70        -0.60892          0.35689         -0.60892           0.35689
3        184.18         136.76         1.40477         -1.67426          1.40477          -1.67426
4        196.37         144.26         2.65531         -0.71833          2.65531          -0.71833
5        162.61         155.72        -0.80706          0.74198         -0.80706           0.74198
6        158.02         147.19        -1.27775         -0.34510         -1.27775          -0.34510 

 

 

아래 히스토그램은 한국인과 부시맨의 성인 남자 키를 z 표준화 한 값에 대한 히스토그램이 되겠습니다.  둘다 평균이 0, 표준편차가 1인 표준정규분포로 표준화 되었음을 확인할 수 있습니다.

 

> hist(height$z.height_korean, freq=TRUE, main="standized freq. of Korean H")
> hist(height$z.height_bushman, freq=TRUE, main="standized  freq. of Bushman H ")

 

 

 

 

 

(1-2) [0-1] 변환

 

연속형 변수의 값을 '0~1' 사이의 값으로 변환하는 [0-1]변환도 z변환과 함께 많이 쓰이는 표준화 기법입니다.  만약 변수들 간의 scale 이 다른 상태에서 인공신경망 분석을 하려면 [0-1]변환으로 단위를 표준화해준 후에 분석을 시행해야 합니다.  Scale이 다른 두 변수를 [0-1] 변환하게 되면 상호간에 비교가 가능해집니다.

 

[0-1] 변환은  (x-min(x) /(max(x)-min(x)) 의 수식으로 계산하면 됩니다.

 

위의 한국 성인 남성과 부시맨 성인 남성 각 1,000명의 키 데이터를 가지고 이번에는 [0-1] 표준화 변환을 해보도록 하겠습니다.  일단 위 데이터셋 height에서 첫번째와 두번째 변수만 선택하고, 변수명이 너무 길므로 짧게 변수이름을 변경해보겠습니다.

 

> ## [0-1] transformation
> height <- height[,c(1:2)]
> library(reshape)
> height <- rename(height, c(height_korean = "h_kor", height_bushman = "h_bush"))
> head(height)
   h_kor h_bush
1 179.19 140.60
2 164.54 152.70
3 184.18 136.76
4 196.37 144.26
5 162.61 155.72
6 158.02 147.19

 

 

그 다음 [0-1] 변환을 하고 히스토그램을 그려보겠습니다.

 

> height <- transform(height, 
+                     h_kor_01 = (h_kor - min(h_kor))/(max(h_kor) - min(h_kor)), 
+                     h_bush_01 = (h_bush - min(h_bush))/(max(h_bush) - min(h_bush))
+                     )
> 
> head(height)
   h_kor h_bush h_kor_01 h_bush_01
1 179.19 140.60  0.64341   0.27053
2 164.54 152.70  0.41760   0.51072
3 184.18 136.76  0.72034   0.19410
4 196.37 144.26  0.90835   0.34311
5 162.61 155.72  0.38781   0.57074
6 158.02 147.19  0.31705   0.40129
> 
> hist(height$h_kor_01)
> hist(height$h_bush_01)
 
 

 

 

 

한국 성인 남성 키와 부시맨 성인 남성 키가 0~1 사이의 값으로 표준화되었음을 알 수 있습니다.

 

이해가 쉽도록 166cm의 한국 남성과 156cm의 부시맨 남성의 키를 가지고 [0-1] 변환 했을 때의 예시를 개념도로 아래에 작성하였습니다.  참고하시기 바랍니다.

 

 

[0-1] 변환 예시 (한국 남성 166cm, 부시맨 남성 156cm) 

 

 

 

많은 도움 되었기를 바랍니다. 

 

다음번 포스팅에서는 정상화 변환에 대해서 알아보도록 하겠습니다.



여러개의 변수를 가진 DataFrame에 대해서 층화 무작위 추출을 사용해서 Train, Test set 분할을 하는 방법은 아래의 포스팅을 참고하세요. 

==> https://rfriend.tistory.com/515


 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

 


728x90
반응형
Posted by Rfriend
,

분석을 진행하다 보면 하나의 데이터 셋에서 변수를 생성, 제거, 변환하는 작업 못지않게 새로운 데이터 셋을 기존의 데이터 셋과 결합하는 작업 또한 빈번합니다.  이번 포스팅에서는 rbind(), cbind(), merge()함수를 활용해서 데이터 프레임 결합하는 방법에 대해서 알아보도록 하겠습니다.

 

예전에 포스팅 했던 R 행렬 함수(☞ 바로가기) 에서 rbind(), cbind()를 다루었던 적이 있는데요, 데이터 프레임도 행렬에서의 데이터 결합과 동일하며, 복습하는 차원에서 한번 더 짚어 보고, key값 기준으로 결합하는 merge()에 대해서 추가로 알아보도록 하겠습니다.

 

 

 R 데이터 프레임 결합 : rbind(), cbind(), merge()

 

[ rbind(), cbind(), merge() 함수 비교 ]

 

 

 

(1) 행 결합 (위 + 아래) : rbind(A, B)

 

먼저 실습에 사용할 데이터 프레임 두개(cust_mart_1, cust_mart_2)를 생성해 보겠습니다.

 

## 데이터 프레임 생성 

cust_id <- c("c01","c02","c03","c04")
last_name <- c("Kim", "Lee", "Choi", "Park")
cust_mart_1 <- data.frame(cust_id, last_name)

cust_mart_1  
# cust_id last_name 
# 1 c01 Kim 
# 2 c02 Lee 
# 3 c03 Choi 
# 4 c04 Park 



cust_mart_2 <- data.frame(
    cust_id = c("c05", "c06", "c07"), 
    last_name = c("Bae", "Kim", "Lim"))
    
cust_mart_2  
# cust_id last_name 
# 1 c05 Bae 
# 2 c06 Kim 
# 3 c07 Lim

 

다음으로 두개의 데이터 프레임(cust_mart_1, cust_mart_2)을 세로 행 결합 (위 + 아래) 해보도록 하겠습니다.

 

## (1) 행 결합 (위 + 아래) rbind(A, B) 
cust_mart_12 <- rbind(cust_mart_1, cust_mart_2) 


cust_mart_12
# cust_id last_name 
# 1 c01 Kim 
# 2 c02 Lee 
# 3 c03 Choi 
# 4 c04 Park 
# 5 c05 Bae 
# 6 c06 Kim 
# 7 c07 Lim

 

rbind()는 row bind 의 약자입니다. rbind()를 무작정 외우려고 하지 마시고, row bind의 약자라는걸 이해하시면 됩니다. 

위의 행 결합 rbind()를 하기 위해서는 결합하려는 두개의 데이터 셋의 열의 갯수와 속성, 이름이 같아야만 합니다. 

 

아래의 예시 처럼 만약 칼럼의 갯수가 서로 다르다면 (cust_mart_12는 열이 2개, cust_mart_3은 열이 3개) 열의 갯수가 맞지 않는다고 에러 메시지가 뜹니다.

 

cust_mart_3 <- data.frame(
    cust_id = c("c08", "c09"), 
    last_name = c("Lee", "Park"), 
    gender = c("F", "M")) 
    
cust_mart_3  
# cust_id last_name gender 
# 1 c08 Lee F 
# 2 c09 Park M


## -- error
rbind(cust_mart_12, cust_mart_3) 
# Error in rbind(deparse.level, ...) 
# : numbers of columns of arguments do not match

 

아래의 예처럼 칼럼의 이름(cust_mart_12 는 cust_id, last_name 인 반면, cust_mart_4는 cust_id, first_name)이 서로 다르다면 역시 에러가 납니다.

 

cust_mart_4 <- data.frame(
    cust_id = c("c10", "c11"), 
    first_name = c("Kildong", "Yongpal"))
    
cust_mart_4
# cust_id first_name 
# 1 c10 Kildong 
# 2 c11 Yongpal 


## -- error
rbind(cust_mart_12, cust_mart_4) 
# Error in match.names(clabs, names(xi)) 
# : names do not match previous names 

 

 

(2) 열 결합 (왼쪽 + 오른쪽) : cbind(A, B)

 

## -- (2) 열 결합 cbind(A, B)
cust_mart_5 <- data.frame(
    age = c(20, 25, 19, 40, 32, 39, 28), 
    income = c(2500, 2700, 0, 7000, 3400, 3600, 2900))
    
cust_mart_12  
# cust_id last_name 
# 1 c01 Kim 
# 2 c02 Lee 
# 3 c03 Choi 
# 4 c04 Park 
# 5 c05 Bae 
# 6 c06 Kim 
# 7 c07 Lim 

cust_mart_5  
# age income 
# 1 20 2500 
# 2 25 2700 
# 3 19 0 
# 4 40 7000 
# 5 32 3400 
# 6 39 3600 
# 7 28 2900


cust_mart_125 <- cbind(cust_mart_12, cust_mart_5)

cust_mart_125  
# cust_id last_name age income 
# 1 c01 Kim 20 2500 
# 2 c02 Lee 25 2700 
# 3 c03 Choi 19 0 
# 4 c04 Park 40 7000 
# 5 c05 Bae 32 3400 
# 6 c06 Kim 39 3600 
# 7 c07 Lim 28 2900

 

cbind()는 column bind의 약자입니다.   cbind()도 열 결합을 하려고 하면 서로 결합하려는 두 데이터셋의 관측치가 행이 서로 동일 대상이어야만 하고, 행의 갯수가 서로 같아야만 합니다

 

만약, cbind()를 하는데 있어 행의 갯수가 서로 다르다면 아래의 예처럼 에러 메시지가 뜹니다.

 

cust_mart_6 <- data.frame(
    age = c(34, 50), 
    income = c(3600, 5100))
    
cust_mart_6 
# age income 
# 1 34 3600 
# 2 50 5100 

## -- error: different number of rows
cbind(cust_mart_125, cust_mart_6) 
# Error in data.frame(..., check.names = FALSE) 
# : arguments imply differing number of rows: 7, 2

 

 

 

(3) 동일 key 값 기준 결합 : merge(A, B, by='key)

 

두개의 데이터셋을 열 결합할 때 동일 key 값을 기준으로 결합을 해야 할 때가 있습니다.  cbind()의 경우 각 행의 관찰치가 서로 동일 대상일 때 그리고 갯수가 같을 때 가능하다고 했는데요, 만약 각 행의 관찰치가 서로 동일한 것도 있고 그렇지 않은 것도 섞여 있다면 그때는 cbind()를 사용하면 안됩니다.  이때는 동일 key 값을 기준으로 결합을 해주는 merge(A, B, by='key')를 사용해야만 합니다.

 

아래의 cbind()의 잘못된 예를 하나 보시겠습니다.

 

cust_mart_12  
# cust_id last_name 
# 1 c01 Kim 
# 2 c02 Lee 
# 3 c03 Choi 
# 4 c04 Park 
# 5 c05 Bae 
# 6 c06 Kim 
# 7 c07 Lim 


cust_mart_7 <- data.frame(
    cust_id = c("c03", "c04", "c05", "c06", "c07", "c08", "c09"), 
    buy_cnt = c(3, 1, 0, 7, 3, 4, 1))
    
cust_mart_7
# cust_id buy_cnt 
# 1 c03 3 
# 2 c04 1 
# 3 c05 0 
# 4 c06 7 
# 5 c07 3 
# 6 c08 4 
# 7 c09 1


cust_mart_127_cbind <- cbind(cust_mart_12, cust_mart_7)

cust_mart_127_cbind  
cust_id last_name cust_id buy_cnt 
# 1 c01 Kim c03 3 
# 2 c02 Lee c04 1 
# 3 c03 Choi c05 0 
# 4 c04 Park c06 7 
# 5 c05 Bae c07 3 
# 6 c06 Kim c08 4 
# 7 c07 Lim c09 1

 

 

cust_mart_12 와 cust_mart_7 의 두 개의 데이터 프레임의 관측치가 서로 같은 것(cust_id 가 c03 ~ c07)도 있는 반면, 서로 다른 것(cust_id 가 c01~c02, c08~c09)도 있습니다.  이런 데이터 셋을 cbind()로 결합시켜버리면 엉뚱한 데이터 셋이 생성되어 버립니다. Oh no~!!!!!

 

이런 경우에는 동일한 key 값을 기준으로 결합을 시켜주는 merge(A, B, by='key')가 답입니다.

SQL에 익숙한 분들은 잘 아시겠지만, merge에는 기준을 어느쪽에 두고 어디까지 포함하느냐에 따라 Inner Join, Outer Join, Left Outer Join, Right Outer Join 등의 4가지 종류가 있습니다.  이를 도식화하면 아래와 같습니다.

 

[ merge() 함수의 join 종류 ]

 

 

위에 제시한 4가지 join 유형별로 merge() 함수 사용예를 들어보겠습니다.

 

 

(3-1) merge() : Inner Join 

 

## -- (3-1) merge() : Inner Join

cust_mart_127_innerjoin <- merge(
    x = cust_mart_12, 
    y = cust_mart_7, 
    by = 'cust_id') 
    

cust_mart_127_innerjoin
# cust_id last_name buy_cnt 
# 1 c03 Choi 3 
# 2 c04 Park 1 
# 3 c05 Bae 0 
# 4 c06 Kim 7 
# 5 c07 Lim 3

 

 

(3-2) merge() - Outer Join

 

## -- (3-2) merge() : Outer Join

cust_mart_127_outerjoin <- merge(
    x = cust_mart_12, 
    y = cust_mart_7, 
    by = 'cust_id', 
    all = TRUE)
    
    
cust_mart_127_outerjoin
# cust_id last_name buy_cnt 
# 1 c01 Kim NA 
# 2 c02 Lee NA 
# 3 c03 Choi 3 
# 4 c04 Park 1 
# 5 c05 Bae 0 
# 6 c06 Kim 7 
# 7 c07 Lim 3 
# 8 c08 <NA> 4 
# 9 c09 <NA> 1

 

 

 

(3-3) merge() : Left Outer Join

 

## -- (3-3) merge() : Left Outer Join 

cust_mart_127_leftouter <- merge(
    x = cust_mart_12, 
    y = cust_mart_7, 
    by = 'cust_id', 
    all.x = TRUE)
    
    
cust_mart_127_leftouter  
# cust_id last_name buy_cnt 
# 1 c01 Kim NA 
# 2 c02 Lee NA 
# 3 c03 Choi 3 
# 4 c04 Park 1 
# 5 c05 Bae 0 
# 6 c06 Kim 7 
# 7 c07 Lim 3 

 

 

(3-4) merge() : Right Outer Join

 

## -- (3-4) merge : Right Outer Join 

cust_mart_127_rightouter <- merge(
    x = cust_mart_12, 
    y = cust_mart_7, 
    by = 'cust_id', 
    all.y = TRUE)
    

cust_mart_127_rightouter
# cust_id last_name buy_cnt 
# 1 c03 Choi 3 
# 2 c04 Park 1 
# 3 c05 Bae 0 
# 4 c06 Kim 7 
# 5 c07 Lim 3 
# 6 c08 <NA> 4 
# 7 c09 <NA> 1 

 

 

이상 merge() 함수의 4가지 유형의 join 에 대하여 알아보았습니다.  마지막으로, merge() 함수는 2개의 데이터 셋의 결합만 가능하며, 3개 이상의 데이터 셋에 대해서 key 값 기준 merge() 결합을 하려고 하면 에러가 나는 점 유의하시기 바랍니다.

 

## -- error

merge(cust_mart_12, cust_mart_5, cust_mart_7, by = 'cust_id') 
# Error in fix.by(by.x, x) 
# : 'by' must specify one or more columns as numbers, names or logical

 

따라서 데이터 프레임 2개씩을 key 값 기준으로 순차적으로 merge() 해나가야 합니다.

 

dplyr 패키지의 Mutating Joins (inner, left, right, full), Filtering Joins (semi, anti), Nesting Joins(nest) 방법은 rfriend.tistory.com/625 를 참고하세요. 

 

이상으로 데이터 프레임의 결합에 대해서 마치도록 하겠습니다. 

많은 도움 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

728x90
반응형
Posted by Rfriend
,

R 의 벡터나 데이터 프레임을 특정 기준에 따라서 정렬하는 방법에 대해여 알아 보도록 하겠습니다.  SAS를 사용해본 분석가라면 두개 이상의 데이터 셋을 특정 기준으로 merge() 하기 전에 정렬 sort 를 실행해봤을 겁니다.  데이터셋 사이즈가 커지면 merge 하기 전 sort 하느라 시간 많이 잡아먹곤 해서 퇴근하기 전이나 점심먹으러 가기 전에 sorting 돌려놓고 갔던 경험이 있지 않을까 추측해봅니다.  (참고로, R에서는 merge 할때 사전 sorting이 필요 없음)

 

R에서는 데이터 정렬을 위해 sort()와 order() 두개의 함수를 제공하는데요, sort()는 정렬된 값을 순서대로 보여주는 반면에, order()는 데이터 크기의 색인을 제공합니다.  order()가 색인을 제공한다는게 무슨 말인지 잘 이해가 안될 수도 있는데요, 아래 예시를 보면서 설명드리겠습니다.

 

 

 R 벡터, 데이터 프레임 정렬 : sort(), order()

 

 

예시를 위해 세개의 벡터(숫자형 2개, 문자형 1개)와 한개의 데이터 프레임을 만들어보겠습니다.

 

> v1 <- c(40, 30, 50, 50, 90, 40, 50)
> v2 <- c(5100, 6500, 2000, 2000, 9000, 4500, 3000)
> v3 <- c("A", "B", "A", "B", "A", "A", "B")
> v123 <- data.frame(v1, v2, v3)
> v123
  v1   v2 v3
1 40 5100  A
2 30 6500  B
3 50 2000  A
4 50 2000  B
5 90 9000  A
6 40 4500  A
7 50 3000  B

 

 

벡터의 정렬

 

 (1) 숫자 자체 정렬 sort()

 

> v1 [1] 40 30 50 50 90 40 50 >
>
sort(v1) # 오름차순 정렬 [1] 30 40 40 50 50 50 90 >

> sort(v1, decreasing = TRUE) # 내림차순 정렬 [1] 90 50 50 50 40 40 30

 

sort()의 디폴트 정렬순은 오름차순이 되겠습니다.  내림차순으로 하려면 decreasing = TRUE 라는 옵션을 붙여주면 됩니다.

 

 

(2) 정렬 색인 값 order()

 

> v1
[1] 40 30 50 50 90 40 50
> 
> order(v1)
[1] 2 1 6 3 4 7 5
> 

> v1[ order(v1) ] # sort(v1)과 결과 동일 [1] 30 40 40 50 50 50 90

 

 

order(v1) 했을 때 나오는 색인 숫자들 [1] 2 1 6 3 4 7 5 는 무슨 뜻이냐 하면요, v1 중 가장 작은 값(30)이 두번째에 있고, 두번째로 작은값(40)이 첫번째에 있고, 세번째로 작은 값(40)이 여섯번째에 있고.... 이런 뜻입니다.

 

따라서 v1[ order(v1) ] 처럼 v1의 요소를 order(v1)에서 제시한 정렬 색인으로 indexing을 해오면 (1)번의 sort(v1)과 동일한 결과를 얻을 수 있습니다.

 

그러면, 결과가 같은면 그냥 sort(v1)을 쓰면 되지 왜 굳이 order()를 구분해서 사용하고 또 배워야 하는지 의아할 수도 있겠습니다.  order()는 아래의 데이터 프레임에서의 정렬에서 사용하게 되며, sort()는 데이터 프레임에서는 사용할 수 없다는점 때문에 두개 다 배워두어야 합니다.

 

 

데이터 프레임의 정렬

 

> rm(v1, v2, v3) # 벡터 v1, v2, v3 제거
> attach(v123) # 데이터 프레임 활성화
> 

> # v123 데이터 프레임의 전체 행을 v1 오름차순, v2 내림차순, v3 오름차순의 순서대로 정렬

> v123_order <- v123[ order(v1, -v2, v3), ] >

> v123 # 원래 데이터셋 v1 v2 v3 1 40 5100 A 2 30 6500 B 3 50 2000 A 4 50 2000 B 5 90 9000 A 6 40 4500 A 7 50 3000 B >

> v123_order  # 정렬된 후의 데이터 셋
  v1   v2 v3
2 30 6500  B
1 40 5100  A
6 40 4500  A
7 50 3000  B
3 50 2000  A
4 50 2000  B
5 90 9000  A
> 
> detach(v123) 

 

위 예제에서 데이터 프레임 v123 의 행 전체를 v1 오름차순, v2 내림차순(변수 앞에 - 부호), v3 오름차순(문자형도 알파벳순 정렬 가능)의 순서대로 정렬하였습니다.

 

정렬된 후의 데이터 프레임 v123_order 의 제일 왼쪽의 row.names 가 order(v1, -v2, v3)의 색인 결과와 같게 정렬이 되어 있음을 알 수 있습니다. 

 

> order(v1, -v2, v3) [1] 2 1 6 7 3 4 5 >
>
row.names(v123_order) [1] "2" "1" "6" "7" "3" "4" "5" 

 

다시 한번 정리하자면, 데이터 프레임에서 정렬할 때는 order()로 정렬한 색인을 가져다가 index의 행의 위치 ( [, 열] 에 집어 넣고, 열 자리에는 비워둠으로써 [order(), ] 모든 열을 가져오게끔 해서 정렬을 키는 원리입니다.



plyr 패키지arrange() 함수를 사용해서 정렬하는 방법도 있습니다. 내림차순으로 정렬하고자 할 경우에는 desc() 옵션을 추가하면 됩니다.  arrange(data.frame, var1, desc(var2), ...) 이런 형식으로 사용하면 되겠습니다.  아래 예제는 위의 order와 indexing을 사용한 것과 동일한 경과를 얻었음을 알 수 있습니다. 


> library(plyr)

> arrange(v123, v1, desc(v2), v3)

  v1   v2 v3

1 30 6500  B

2 40 5100  A

3 40 4500  A

4 50 3000  B

5 50 2000  A

6 50 2000  B

7 90 9000  A 



많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

728x90
반응형
Posted by Rfriend
,

R 에서 데이터 분석을 하다보면 데이터셋 에서 필요한 부분만 선별적으로 취사선택해서 별도로 분석 마트를 만드는 경우가 다반사입니다.  따라서 이번 데이터 프레임에서의 변수 선택 방법을 잘 알아두시면 데이터셋을 떡주무르듯이 가지고 노는데 아주 유용할 것입니다.

 

R에서 데이터를 선별하는 방법으로 indexing 에 대해서 이전에 소개해드린적이 있는데요(☞ R indexing 바로가기), 선별 조건이 까다로워질수록 indexing 프로그램(index[]와 which() 함수 사용)이 복작해해지는 반면, subset() 함수는 상대적으로 깔끔한 면이 있습니다.  아래 두개의 기법별 예제를 보시고 사용하기에 편한 기법을 이용하시면 되겠습니다.  

 

실습을 위해서 mtcars 데이터 프레임을 활용하겠습니다.  mtcars는 자동차 관련된 11개 변수, 32개 관측치로 구성된 데이터 프레임이 되겠습니다. 이번 실습에는 아래 색칠해 놓은 연비(mpg), 실린더 개수(cyl), 변속기(am) 의 세개 변수를 사용하겠습니다.

 

> help(mtcars)
mtcars

Format

A data frame with 32 observations on 11 variables.

[, 1] mpg Miles/(US) gallon
[, 2] cyl Number of cylinders
[, 3] disp Displacement (cu.in.)
[, 4] hp Gross horsepower
[, 5] drat Rear axle ratio
[, 6] wt Weight (lb/1000)
[, 7] qsec 1/4 mile time
[, 8] vs V/S
[, 9] am Transmission (0 = automatic, 1 = manual)
[,10] gear Number of forward gears
[,11] carb Number of carburetors

Source

Henderson and Velleman (1981), Building multiple regression models interactively. Biometrics, 37, 391–411.

 

> str(mtcars)
'data.frame':	32 obs. of  11 variables:
 $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num  160 160 108 258 360 ...
 $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num  16.5 17 18.6 19.4 17 ...
 $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
 $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
> 
>
head(mtcars) mpg cyl disp hp drat wt qsec vs am gear carb Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

 

만약 아래의 요건으로 데이터 마트를 구성해야 한다고 해봅시다.

 

"변속기가 자동(am == 0)이고 & 실린더가 4개 또는 6개 (cyl == 4 or cyl == 6) 인
자동차들의
연비(mpg) 평균(mean())는?"

 

"변속기가 수동(am == 1)이고 & 실린더가 4개 또는 6개 (cyl == 4 or cyl == 6)) 인 자동차들의
연비(mpg) 평균(mean())는?"

 

 

(1) indexing & which() 함수를 활용한 특정 조건을 만족하는 변수, 관측치 선택


> attach(mtcars) >

> # (a) 변속기가 자동이고 & 실린더가 4개, 6개인 자동차의 연비, 실린더, 자동/수동 변수 선별
> mtcars_mart_0 <- mtcars[ which( am == 0 & cyl %in% c(4, 6)), c("mpg", "cyl", "am")] > mtcars_mart_0 mpg cyl am Hornet 4 Drive 21.4 6 0 Valiant 18.1 6 0 Merc 240D 24.4 4 0 Merc 230 22.8 4 0 Merc 280 19.2 6 0 Merc 280C 17.8 6 0 Toyota Corona 21.5 4 0

> > mean(mtcars_mart_0$mpg) [1] 20.74286

> # (b) 변속기가 수동이고 & 실린더가 4개, 6개인 자동차의 연비, 실린더, 자동/수동 변수 선별 > mtcars_mart_1 <- mtcars[ which( am == 1 & cyl %in% c(4, 6)), c("mpg", "cyl", "am")] > mtcars_mart_1 mpg cyl am Mazda RX4 21.0 6 1 Mazda RX4 Wag 21.0 6 1 Datsun 710 22.8 4 1 Fiat 128 32.4 4 1 Honda Civic 30.4 4 1 Toyota Corolla 33.9 4 1 Fiat X1-9 27.3 4 1 Porsche 914-2 26.0 4 1 Lotus Europa 30.4 4 1 Ferrari Dino 19.7 6 1 Volvo 142E 21.4 4 1

> > mean(mtcars_mart_1$mpg) [1] 26.02727 > > detach(mtcars)

 

attach()와 detach()로 데이터 프레임을 활성화해놓고 indexing을 했음에 유의하세요.

 

위의 indexing 에서 변수를 선택할 때 c("mpg", "cyl", "am")이라고 변수명을 직접 입력했는데요, 열의 위치를 숫자로 c(1, 2, 9) 라고 입력해도 동일한 결과가 나옵니다.

 

> mtcars_mart_9 <- mtcars[ which( am == 0 & cyl %in% c(4, 6)), c(1, 2, 9)] > mtcars_mart_9 mpg cyl am Hornet 4 Drive 21.4 6 0 Valiant 18.1 6 0 Merc 240D 24.4 4 0 Merc 230 22.8 4 0 Merc 280 19.2 6 0 Merc 280C 17.8 6 0 Toyota Corona 21.5 4 0

 

 

 (2) subset(Data 이름, select = c(변수명), subset = (선별 조건)) 변수, 관측치 선택

 

> # (a) 변속기가 자동이고 & 실린더가 4개 or 6개인 자동차의 연비, 실린더, 자동/수동 변수 선별

> mtcars_subset_0 <- subset(mtcars, + select = c(mpg, cyl, am), + subset = (am == 0 & cyl %in% c(4, 6))) > mtcars_subset_0 mpg cyl am Hornet 4 Drive 21.4 6 0 Valiant 18.1 6 0 Merc 240D 24.4 4 0 Merc 230 22.8 4 0 Merc 280 19.2 6 0 Merc 280C 17.8 6 0 Toyota Corona 21.5 4 0 > mean(mtcars_subset_0$mpg) [1] 20.74286 >

> # %in% 대신 수직바 '|' (or) 를 써서 할 수도 있음

> subset(mtcars, + select = c(mpg, cyl, am), + subset = ((am == 0 & cyl == 4) | (am == 0 & cyl == 6))) mpg cyl am Hornet 4 Drive 21.4 6 0 Valiant 18.1 6 0 Merc 240D 24.4 4 0 Merc 230 22.8 4 0 Merc 280 19.2 6 0 Merc 280C 17.8 6 0 Toyota Corona 21.5 4 0
>

>

> # (b) 변속기가 수동이고 & 실린더가 4개 or 6개인 자동차의 연비, 실린더, 자동/수동 변수 선별 > mtcars_subset_1 <- subset(mtcars, + select = c(mpg, cyl, am), + subset = (am == 1 & cyl %in% c(4, 6))) > mtcars_subset_1 mpg cyl am Mazda RX4 21.0 6 1 Mazda RX4 Wag 21.0 6 1 Datsun 710 22.8 4 1 Fiat 128 32.4 4 1 Honda Civic 30.4 4 1 Toyota Corolla 33.9 4 1 Fiat X1-9 27.3 4 1 Porsche 914-2 26.0 4 1 Lotus Europa 30.4 4 1 Ferrari Dino 19.7 6 1 Volvo 142E 21.4 4 1 > mean(mtcars_subset_1$mpg) [1] 26.02727

 

 

만약, 데이터프레임에서 1개의 변수만을 indexing & which() 함수로 해서 새로운 객체에 할당하면 vector로 생성이 됩니다.  반면에, 데이터프레임에서 1개의 변수만을 subset() 함수로 해서 새로운 객체에 할당하면 dataframe 으로 생성이 되는 차이가 있습니다.  따라서, 사용 목적/용도가 뭐냐에 따라서 그에 맞는 방법을 사용하시기 바랍니다. 

 

 

아래에는 연속 선택 c(1:5), 혹은 제외 -c(1:5)를 하는 팁을 소개하였습니다.  indexing 기법에서도 동일합니다.  여러개의 변수를 순서에 따라서 일괄 선택할 때는 일일이 변수를 나열하지 않고 몇번째에서 몇번째까지 숫자나 혹은 변수명을 : 을 사용해서 지정해주면 되니 편하겠지요.  제외하려면 - 를 사용하면 끝.  편하죠?!

 

> # 연속 선택 : c(1:5) > mtcars_subset_1_5 <- subset(mtcars, + select = c(1:5) + ) > > head(mtcars_subset_1_5) mpg cyl disp hp drat Mazda RX4 21.0 6 160 110 3.90 Mazda RX4 Wag 21.0 6 160 110 3.90 Datsun 710 22.8 4 108 93 3.85 Hornet 4 Drive 21.4 6 258 110 3.08 Hornet Sportabout 18.7 8 360 175 3.15 Valiant 18.1 6 225 105 2.76 > > # 제외 : -c() > mtcars_subset_6_11 <- subset(mtcars, + select = -c(1:5) + ) > > head(mtcars_subset_6_11) wt qsec vs am gear carb Mazda RX4 2.620 16.46 0 1 4 4 Mazda RX4 Wag 2.875 17.02 0 1 4 4 Datsun 710 2.320 18.61 1 1 4 1 Hornet 4 Drive 3.215 19.44 1 0 3 1 Hornet Sportabout 3.440 17.02 0 0 3 2 Valiant 3.460 20.22 1 0 3 1

 

 



(3) dplyr 패키지의 select() 로 변수 선택, filter() 로 조건에 맞는 관측치 선택, 

     summarize() 요약 통계량 계산



> install.packages("dplyr")

> library(dplyr)

> # (a) 변속기가 자동(am == 0)이고 & 실린더가 4개 or 6개인 자동차의 평균 연비

> mtcars %>% select(mpg, cyl, am) %>% filter(am == 0 & cyl %in% c(4, 6))

   mpg cyl am

1 21.4   6  0

2 18.1   6  0

3 24.4   4  0

4 22.8   4  0

5 19.2   6  0

6 17.8   6  0

7 21.5   4  0

> mtcars %>% select(mpg, cyl, am) %>% filter(am == 0 & cyl %in% c(4, 6)) %>% summarise(mean(mpg))

  mean(mpg)

1  20.74286

> # (b) 변속기가 수동(am == 1)이고 & 실린더가 4개 or 6개인 자동차의 평균 연비

> mtcars %>% select(mpg, cyl, am) %>% filter(am == 1 & cyl %in% c(4, 6))

    mpg cyl am

1  21.0   6  1

2  21.0   6  1

3  22.8   4  1

4  32.4   4  1

5  30.4   4  1

6  33.9   4  1

7  27.3   4  1

8  26.0   4  1

9  30.4   4  1

10 19.7   6  1

11 21.4   4  1

> mtcars %>% select(mpg, cyl, am) %>% filter(am == 1 & cyl %in% c(4, 6)) %>% summarise(mean(mpg))

  mean(mpg)

1  26.02727

 



이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 


728x90
반응형
Posted by Rfriend
,