데이터는 크게 (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 Rfriend

댓글을 달아 주세요

  1. kjh 2016.08.27 16:45  댓글주소  수정/삭제  댓글쓰기

    버전에 따라서 달라진건지는 모르겠지만요,
    within으로 만든 stat_score_5의 클래스가 character라고 나온다고 하셨는데
    코드 복사해서 제가 돌려보면 factor로 나오네요

    그리고 cut함수의 right와 include.lowest 관련해서
    위에 내용중에 코드보면 좀 헷갈리는 부분이 있어서요
    1. 만약 right=FALSE이면 include.lowest옵션은 줄 필요가 없는거 맞나요?
    2. right=FALSE일 경우, 구간 전체의 최대값(include.lowest와 반대되는 값)을 범주로 처리하는 옵션은 없는건가요?(?cut쳐봐도 안보이는 것 같아서요..)

    • kjh 2016.08.27 16:49  댓글주소  수정/삭제

      아 위 질문의 2번항목은 해결됐네요
      include.highest라는 옵션은 없고,
      include.lowest라는 옵션이 right=F일 경우 최대값을 처리해주기도 하는거였네요

  2. kjh 2016.08.27 16:57  댓글주소  수정/삭제  댓글쓰기

    위 2번 질문에 대해서 다른분들 참고하시라고 실습해본 코드 올립니다.

    id <- c("juice", "cola", "cloud", "haha", "light")
    score <- c(0, 10, 11, 20, 30)
    id_score <- data.frame(id, score)

    # right=TRUE이므로 구간을 0<x<=10,10<x<=20,20<x<=30 으로 분할.
    # include.lowest=FALSE 이므로, 0이 포함되지 않아서 juice의 categorized_score값이 NA처리됨.
    attach(id_score)
    id_score <- transform(id_score,
    categorized_score=cut(score, breaks=c(0,10,20,30),
    include.lowest=FALSE, right=TRUE,
    labels=c("C","B","A")
    )
    )
    id_score

    # right=FALSE이므로 구간을 0<=x<10,10<=x<20,20<=x<30 으로 분할.
    # 애초에 구간이 최소값 0도 포함하므로 include.lowest=T를 줄 필요가 없어보임.
    # 그러나 30점인 light의 경우 결과가 NA로 나옴.
    id_score <- transform(id_score,
    categorized_score=cut(score, breaks=c(0,10,20,30),
    right=FALSE,
    labels=c("C","B","A")
    )
    )
    id_score

    # right=FALSE이므로 구간을 0<=x<10,10<=x<20,20<=x<30 으로 분할.
    # 옵션명이 include.lowest이나, right=FALSE인 경우 최대값(여기서는 30)을 처리하는 옵션이 됨.
    # 즉, 아래처럼 include.lowest=T만 추가해주면 30점을 처리해줌.
    id_score <- transform(id_score,
    categorized_score=cut(score, breaks=c(0,10,20,30),
    right=FALSE, include.lowest=TRUE,
    labels=c("C","B","A")
    )
    )
    detach(id_score)
    id_score

  3. kjh 2016.08.27 18:05  댓글주소  수정/삭제  댓글쓰기

    ㅠㅠ 그리고 within이 이해가 안되는 부분이
    stat_score_6 = character(0)
    ~~

    이부분에 대해서, 이게 무슨뜻인지 처음 보는 문법인것 같아서요
    짤막하게나마 설명 부탁드립니다.
    (혹시 블로그 내에 설명되어 있는 부분이 있나요?)

    • R Friend Rfriend 2016.08.27 20:57 신고  댓글주소  수정/삭제

      새로 생성될 변수에 대해서 character 형이라고 미리 설정/할당해주는 것입니다.

      within()함수만의 독특한 프로그래밍 문법이구요, 그냥 이런거구나라고 하고 사용하시면 됩니다.

      사용자정의함수 짤때 가끔 빈 벡터를 미리 설정/할당하고 루프 돌리면서 채워나가는 경우가 있기은 한데요 , 패키지의 함수에서 within() 함수처럼 미리 character 형 미리 설정하는 경우는 매우 드물고 낯선게 사실입니다.

  4. hjh 2016.09.01 19:44  댓글주소  수정/삭제  댓글쓰기

    정리를 잘 해주셔서 정말 감사합니다.

    한 가지 궁금한 점이 있습니다. ifelse나 within을 쓸 때 위의 경우와 달리, stat_score와 새로운 열 stat_score2를 기준으로 하여 a,b,c로 나누고 싶을 땐 어떻게 해야할까요?

    예를 들어, 아래와 같이 데이터를 구성합니다.
    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)
    stat_score2 <- c(1,2,3,4,5,6,7,8,9,10)
    score_d.f2 <- data.frame(student_id, stat_score, stat_score2)

    그리고 ifelse를 쓴다면 기준을 stat_score에 대해선 동일하게 두고,
    stat_score2 에 대한 기준도 만들어서 'a', 'b', 'c'로 범주화할 수 있게 하는 것입니다.
    stat_score2에 대한 기준을 예를 들어 stat_score2> 5와 stat_score2<=5와 같이 나누어서 모든 경우의 수를 다 '&'로 묶어주면 가능할까요?

    실제로 응용해보았는데 계속 에러가 나서 두 개의 열에 있는 데이터 각각의 조건을 합쳐서 새로운 범주형 데이터를 만드는 것은 다른 방법을 써야하는 것인지 여쭤보고 싶습니다!

    • R Friend Rfriend 2016.09.01 19:58 신고  댓글주소  수정/삭제

      hjh님, 가능합니다.

      transform() 함수에 ifelse 를 가자고 두 변수 & 조건을 걸면 됩니다.

      질문하신거에 딱 맞는 예제는 아닙니다만, 아래의 링크 참고하셔서 transform() 함수랑 ifelse & 조건 사용해서 테스트해보시기 바랍니다.

      http://rfriend.tistory.com/57

  5. 산낙지 2016.10.13 17:27  댓글주소  수정/삭제  댓글쓰기

    전에 assign 함수에서 질문했던 것을 transform으로 바꾸어 활용해보았는데요!

    for (i in 1:3) {
    pdt <- transform(pdt,
    paste("birth", i, sep=""), c(1,0,0,0,0)[match(paste("r_birth", i, sep=""), c(1, 2, 3, 4, 5))])
    }

    transform 함수를 loop에 넣을 경우엔 <-나 =를 인식하지 못하더라고요! 그래서 ,로 구분을 했습니다. 그런데 이건 명령어가 인식은 되는데 작동이 안 되네요 ㅠㅠ...
    사실 두 번째에 cut 예제도 loop를 이용하면 간단하게 줄일 수 있는 것 아닌가요? 구글에 아무리 'transform loop in r' 관련해서 검색해도 나오질 않네요 ㅠㅠ transform에는 어떻게 loop를 활용할 수 있는 것인가요?

  6. 배고파 2017.12.20 11:37  댓글주소  수정/삭제  댓글쓰기

    혹시 순서형 요인변수로 왜 만들어야 하는건가요? 시각화 할 때는 순서형으로 만들어 놓으면 그 가나다 순이 아닌 순서형 설정해놓은대로 나오던데 그 이유인지요? 회귀분석 할 때 순서형 변수를 명목형 변수로 바꾸는 경우도 있던데 이건 또 왜이런지 궁굼합니다!

    • R Friend Rfriend 2017.12.20 14:11 신고  댓글주소  수정/삭제

      시각화할때 요인형 변수 level 설정하는 이유는 댓글에 언급해주신 그 이유때문입니다.

      화귀분석할때 범주형변수의 코드별로 1, 0 의 가변수로 만들어 주어면 해당 변수의 코드별 y값에 대한 효과를 반영할 수 있습니다.

    • 배고파 2017.12.24 09:44  댓글주소  수정/삭제

      답변 감사합니다. 그럼 더미변수로 둘 때 순서형요인변수와 명목형변수가 차이가 있나요?

  7. 질문 2020.11.04 18:27  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 블로그 보고 참고 많이했습니다. 감사합니다.

    저는 설문조사가 코딩되어있는것을 범주화하려고 합니다.
    예를들면, 1번으로 답한 것을 "A", 2번,4번으로 답한 것을 "B", 3범으로 답한것을 "C"로 재범주화 하고 싶은데, 이 경우 어떤 함수를 써야할까요..?

    지금까지는 within으로 했는데.. 이렇게 2,4번을 하나로 묶어야하는 경우 어떻게 해야하는지 궁금합니다. 감사합니다!

    +그리고, 혹시 범주형을 더미변수로 만들었을 때, 여기서 ref 변수를 설정은 어떻게 하는걸까요?

    result<-glm(y~ sex+ age+ edu:(ele+mid+high,data=data)
    이렇게 했을때, edu에서 ele를 ref변수로 설정하고 싶습니다.

    • R Friend Rfriend 2020.11.05 10:23 신고  댓글주소  수정/삭제

      안녕하세요.

      아래 코드 참고하시기 바랍니다.

      ## making sample data
      df <- data.frame(x = c(1, 2, 3, 4))
      df
      # x
      # 1 1
      # 2 2
      # 3 3
      # 4 4

      ## categorization
      df <- transform(df,
      x_cat = ifelse(x == 1, "A",
      ifelse(x %in% c(2, 4), "B", "C")))

      df
      # x x_cat
      # 1 1 A
      # 2 2 B
      # 3 3 C
      # 4 4 B

      str(df)
      # 'data.frame': 4 obs. of 2 variables:
      # $ x : num 1 2 3 4
      # $ x_cat: chr "A" "B" "C" "B"


      # converting chr into factor, setting a specific factor level as reference
      df <- within(df, x_cat <- relevel(as.factor(x_cat), ref = "A"))
      str(df)
      # 'data.frame': 4 obs. of 2 variables:
      # $ x : num 1 2 3 4
      # $ x_cat: Factor w/ 3 levels "A","B","C": 1 2 3 2

    • 질문 2020.11.05 11:37  댓글주소  수정/삭제

      답변 감사합니다!!
      카테고리는 알려주신대로 하면 될것같습니다!
      다만, 제가 지금 교육을 원래 이렇게 같이 코딩해두었는데, Factor w/ 4 levels "ele","mid","high",..: 1 4 4 1 4 4 3 4 4 4 ...
      로지스틱회귀를 돌릴 때 이렇게 하면 안되고.. 일일히 다 더미변수를 만들어야 한다고 하더라고요..?
      그래서
      str(data)하면 이렇게 나오도록 하였습니다.
      $ eled : num 1 0 0 1 0 0 0 0 0 0 ...
      $ midd : num 0 0 0 0 0 0 0 0 0 0 ...
      $ highd : num 0 0 0 0 0 0 1 0 0 0 ...
      $ unid : num 0 1 1 0 1 1 0 1 1 1 .
      다만, 이 때 교육의 레퍼런스 그룹을 eled라고 하고 싶을 때.. 그냥 회귀식에서 eled를 빼고 result<-glm(y~ sex+ age+ edu:(midd+highd +unid), data=data)이렇게 하면 되는걸까요..?

      사실 edu:(midd+highd +unid) 이렇게 하는것도 처음 봐서잘 한건지 잘 모르겠습니다. edu는
      num [1:12217] 3 51 44 16 42 52 33 44 51 42 ...
      이런 데이터여서 코드북을 참고하여 아래와 같이 범주화하였습니다.

      data<- transform(data,
      eled = ifelse(edu<=16,1,0),
      midd = ifelse(edu>=21 & edu<=23,1,0),
      highd = ifelse(edu>=31 & edu<=33,1,0),
      unid= ifelse(edu>=41,1,0))

      항상 감사드립니다 ㅠㅠ

    • R Friend Rfriend 2020.11.05 15:13 신고  댓글주소  수정/삭제

      안녕하세요.

      R로 회귀모형 분석하실거면 요인(factor)형 변수를 그냥 회귀모형에 넣어서 훈련하면 R이 알아서 내부적으로 dummy variable 만들어서 모형적합 해줍니다.

      별도로 가변수로 변환할 필요 없이 그냥 요인형 변수 그대로 넣어서 적합해보세요.

    • 질문 2020.11.05 17:19  댓글주소  수정/삭제

      아... 그렇군요. 답변 감사합니다 ㅠㅠ 많은 도움이 되었습니다!

    • 질문 2020.11.05 19:54  댓글주소  수정/삭제

      안녕하세요.. 다름이 아니라 제가 더미로 넣고 돌렸을 때랑, 그냥 factor형 을 넣고 돌렸을 때, 유의한 내용은 같으나, OR 값이 조금 달라서요.. 혹시 어떤 것이 더 정확하다고 할 수 있나요? 그리고 만약 더미변수를 직접 만들어 돌린다고 하면 edu:(midd+highd +unid) 이렇게 하는것이 맞는걸까요.. 항상 감사합니다ㅠㅠ

    • R Friend Rfriend 2020.11.05 22:37 신고  댓글주소  수정/삭제

      안녕하세요.

      로지스틱회귀모형의 회귀계수 값이 서로 같거나 비슷할걸로 예상했는데요, 조금 다른가 보네요. (iteration 하면서 회귀계수 적합을 하는데 거기서 차이가 날 수도 있지 않을까 싶습니다.)

      test set으로 실제 예측해서 모델 성과를 한번 비교해보시지요(confusion matrix, accuracy, precision, recall, F1 score, etc). 결과가 거의 비슷하지 않을까 예상이 되는데요...

      만약 더미변수로 하려면 ref 요인 level의 더미변수는 빼고 나머지를 써주시면 됩니다.

      질문에 로지스틱 회귀모형을 적합한다고 하셨으므로 glm 함수 사용하실 대 link function 으로 family = binomial(link = "logit") 추가해주셔야 합니다.

      그리고 혹시 train, test set 분할하는 index 가 있으면 subset = train_idx 추가해주시면 됩니다. (전체 데이터셋으로 모형적합할거면 무시)

      glm_fit <- glm(y ~ sex + age + midd + highd + unid, data = data, family = binomial(link = "logit"), subset = train_idx)