데이터 변환 방법으로서

(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)은 다음번 포스팅에서 소개해드리겠습니다.

 

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

 

 

Posted by R Friend R_Friend

데이터는 크게 (1) 명목형 또는 순서형의 범주형 데이터 (categorical data)와 (2) 연속형 데이터 (continuous data) 로 구분할 수 있습니다.  R에서는 범주형 데이터를 요인(factor)형 데이터 구조라고 부르고 있으며, 순서(order)가 있는 경우는 순서형 요인(ordered factor)라고 해서 구분하기도 합니다.

 

분석하고자 하는 데이터 셋을 받으면 제일 먼저 데이터 구조와 데이터 형태를 탐색하게 됩니다.  그리고 분석 목적과 시나리오에 따라서 변수를 변환하게 되지요.  이번 포스팅에서는 연속형 변수를 범주형 변수로 변환하는 3가지 방법에 대해서 알아보도록 하겠습니다.  통계기법 중 도수분포표, 교차분할표, 카이제곱 검정이라든지, 로지스틱회귀분석, 그래프 중 막대그림, 원그림, 점그림 등의 경우 범주형 변수로 변환을 해야만 하며, 데이터 탐색 시에도 범주형 변수로 변환하여 분포 형태나 집단 간 비교를 하게 되므로 이번 포스팅은 활용도가 매우 높다고 하겠습니다.

 

cut() 함수, ifelse() 함수, within() 함수를 이용해서 아래 예를 들어 설명하도록 하겠습니다.

 

 

 연속형 변수를 범주형 변수로 변환하기: cut(), ifesle(), within()

 

(1) cut()

 

> ## 통계시험 점수 (stat_score) > student_id <- c("s01", "s02", "s03", "s04", "s05", "s06", "s07", "s08", "s09", "s10") > stat_score <- c(56, 94, 82, 70, 64, 82, 78, 80, 76, 78) > mean(stat_score) [1] 76 > hist(stat_score)

 

 

 

> # 데이터 프레임 생성
> score_d.f <- data.frame(student_id, stat_score)
> score_d.f
   student_id stat_score
1         s01         56
2         s02         94
3         s03         82
4         s04         70
5         s05         64
6         s06         82
7         s07         78
8         s08         80
9         s09         76
10        s10         78
 
> rm(student_id, stat_score)

 

 

위의 통계시험 성적을 가지고 cut() 함수를 이용하여 "수", "우", "미", "양", "가" 등급을 매겨보도록 하겠습니다.

right = TRUE 옵션을 주면 a < x <= b  와 같이 오른쪽 숫자까지 포함하여 해당 등급을 부여하게 됩니다.

right = FALSE 옵션을 주면 a<= x <b 의 조건으로 등급을 부여하며, include.lowest = TRUE 옵션을 주면 구성요소 값이 최소값과 같아도 변환을 시키게 됩니다.

 

> ## (1) cut()
> score_d.f <- transform(score_d.f, 
+                  stat_score_1 = cut(stat_score, breaks = c(0, 60, 70, 80, 90, 100), 
+                                     include.lowest = TRUE, 
+                                     right = FALSE, 
+                                     labels = c("가", "양", "미", "우", "수")
+                                     ), 
+                  stat_score_2 = cut(stat_score, breaks = c(0, 60, 70, 80, 90, 100), 
+                                     include.lowest = FALSE, 
+                                     right = FALSE, 
+                                     labels = c("가", "양", "미", "우", "수")
+                                     ),
+                  stat_score_3 = cut(stat_score, breaks = c(0, 60, 70, 80, 90, 100), 
+                                     include.lowest = FALSE, 
+                                     right = TRUE, 
+                                     labels = c("가", "양", "미", "우", "수")
+                                     ), 
+                  stat_score_4 = cut(stat_score, breaks = c(0, 60, 70, 80, 90, 100), 
+                                     include.lowest = TRUE, 
+                                     right = TRUE, 
+                                     labels = c("가", "양", "미", "우", "수")
+                                     )
+                        )
> 
> score_d.f
   student_id stat_score stat_score_1 stat_score_2 stat_score_3 stat_score_4
1         s01         56           가           가           가           가
2         s02         94           수           수           수           수
3         s03         82           우           우           우           우
4         s04         70           미           미           양           양
5         s05         64           양           양           양           양
6         s06         82           우           우           우           우
7         s07         78           미           미           미           미
8         s08         80           우           우           미           미
9         s09         76           미           미           미           미
10        s10         78           미           미           미           미

 

그런데 사용하다 보면 right 옵션, include.right 옵션, 그리고 labels 부여하는 순서도 그렇고, 머리속이 복잡해집니다. 아래의 ifelse()나 within() 함수는 위의 cut()보다는 수식의 부호를 직접 입력한다는 측면에서 사용하기에 더 편하고 직관적인 면이 있습니다.

 

 

(2) ifelse()

 

> attach(score_d.f)

> score_d.f <- transform(score_d.f, + stat_score_5 = ifelse(stat_score < 60, "가", + ifelse(stat_score >= 60 & stat_score < 70, "양", + ifelse(stat_score >= 70 & stat_score < 80, "미", + ifelse(stat_score >= 80 & stat_score < 90, "우", "수" + )))) + ) > detach(score_d.f) > score_d.f

 

 

 

 

 

> class(score_d.f$stat_score_5)
[1] "character"
 

 

위 표의 제일 오른쪽에 'stat_score_5' 변수가 ifelse() 함수를 이용해서 만든 범주형 변수가 되겠습니다.  cut() 대비 수식 등호, 부등호를 직접 입력하니 직관적으로 분석가가 원하는 범주로 수식을 적을 수 있는 장점이 있습니다만, 범주의 수준(level)이 많아질 수록 괄호 열고 닫는데 유의해야 합니다.  위의 예제의 경우 5개 범주로 나누는데 괄호 열고 "(((("  닫는 것이 "))))" 총 4개가 사용이 되었네요.  갯수 조심하지 않으면 콘솔 창에 에러날거예요.  RStudio 사용하면 ifelse() 괄호 하나씩 더해갈 때 마다 괄호 닫는것도 저절로 생기니 차근 차근 하시면 될겁니다.

 

그리고 stat_score_5 의 속성(class)이 요인(factor)이 아닌 문자(character)로 되어 있습니다.  만약 요인별로 통계 분석을 하고자 한다면 as.factor() 함수로 문자형을 요인형으로 먼저 변환을 시킨 후에 분석을 진행해야 합니다.

 

 

(3) within()

 

> ## within()
> score_d.f <- within( score_d.f, {
+   stat_score_6 = character(0) 
+   stat_score_6[ stat_score < 60 ] = "가" 
+   stat_score_6[ stat_score >=60 & stat_score < 70 ] = "양" 
+   stat_score_6[ stat_score >=70 & stat_score < 80 ] = "미" 
+   stat_score_6[ stat_score >=80 & stat_score < 90 ] = "우" 
+   stat_score_6[ stat_score >=90 ] = "수" 
+   
+   stat_score_6 = factor(stat_score_6, level = c("수", "우", "미", "양", "가"))
+ })
> 
> score_d.f$stat_score_6
 [1] 가 수 우 미 양 우 미 우 미 미
Levels: 수 우 미 양 가

 

 

 

within() 함수는 먼저 새로 만들 변수 stat_score_6 = character(0)  이라고 해서 문자형 변수라고 신규생성/지정을 해주고 시작합니다.

수식 등호, 부등호로 구간 설정하구요, 제일 마지막 줄에 factor() 함수로 해서 level = c("수", "우", "미", "양", "가") 라고 해서 수준을 지정해 줄 수 있습니다.  성적은 순서(order)가 있으므로 level 에 지정한 순서가 stat_score_6 요인 변수의 level 순서가 되겠습니다.

 

score_d.f$stat_score_6  라고 해서 indexing을 해서 보면 제일 아랫줄에 "Levels: 수 우 미 양 가" 라고 해서 순서가 제대로 인식되어 있음을 알 수 있습니다.  개인적으로 within() 함수를 순서형 요인변수 만들 때 위 셋 중에서 가장 많이 사용하는 편입니다.

 

아래는 제일 오른쪽에 within()함수로 만든 stat_score_6 변수까지 모두 한꺼번에 열어본 score_d.f 데이터 프레임이 되겠습니다. 

 

 

 

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

 

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

 

Posted by R Friend R_Friend