분석을 진행하다 보면 하나의 데이터 셋에서 변수를 생성, 제거, 변환하는 작업 못지않게 새로운 데이터 셋을 기존의 데이터 셋과 결합하는 작업 또한 빈번합니다.  이번 포스팅에서는 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
> 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
> 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 > 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() 결합을 하려고 하면 에러가 나는 점 유의하시기 바랍니다.

 

> 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() 해나가야 합니다.

 

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

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

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. AshtrayK 2016.09.02 14:14 신고  댓글주소  수정/삭제  댓글쓰기

    이번은 질문도 없네요 ㅎㅎㅎ
    감사합니다!

  2. 2016.09.22 16:08  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. 제발 2016.09.28 20:16  댓글주소  수정/삭제  댓글쓰기

    혹시 개인적으로 궁금한거 여쭤봐도될까요 ㅠㅠ

  4. 산낙지 2016.10.21 10:09  댓글주소  수정/삭제  댓글쓰기

    merge에 대해 이해가 너무 잘 되네요!
    좋은 설명 너무 감사합니다 ^^
    정말 구글에서는 영어로만 나와서 아무래도 찾기 막막했는데
    r friend님 블로그에 정리가 너무 잘 되어 있어서 항상 도움 받네요 ^^

  5. 바람 2017.05.31 15:23  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 merge와 join에 대하여 이해를 잘 했는데 혹시 데이터id 가 중복이 됐는데 다른 변수들 값이 다른 경우에 병합은 merge로 못하나요??

    • R Friend R_Friend 2017.05.31 15:27 신고  댓글주소  수정/삭제

      그런 경우라면 merge 시 뻥튀기가 될겁니다. id 외 추가로 key 값으로 쓸 수 있는게 있으면 paste로 id와 다른 변수를 합쳐서 새로운 key 변수 만든후에 merge하면 되구요.

  6. 2017.06.03 15:35  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  7. 너무 좋아요! 2018.08.27 18:03  댓글주소  수정/삭제  댓글쓰기

    잘 보고 있어요 너무 감사합니다. ㅎ

  8. 2018.12.02 16:10  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • R Friend R_Friend 2018.12.02 17:00 신고  댓글주소  수정/삭제

      분석 목적이 무엇인가, 분석기법이 무엇인가에 따라 자료 구조가 달라집니다. 분석 목적과 기법에 맞는 예제 자료 형태와 코드를 미리 살펴보시는게 좋겠습니다.

      melt, cast 함수를 사용하면 자료 구조를 원하는 형태로 바꾸실 수 있습니다.

  9. redsky 2019.10.31 16:53  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 항상 님 블로그에서 많은 도움을 받고 있습니다. 이번에 데이터를 정리하면서 엑셀 vlookup의 유사일치 기능 처럼 값이 완전히 일치하지 않고 유사한 경우에도 매칭을 시키고 싶은데 혹시 R의 merge 함수 에서도 이런게 가능한지 궁금합니다. 아니면 혹시 다른 함수로 구현이 가능하다면 좀 알려주시면 감사하겠습니다.

    • R Friend R_Friend 2019.10.31 17:47 신고  댓글주소  수정/삭제

      안녕하세요 redsky님.
      질믄하신 부분은 저도 해본적이 없어서 잘 모르겠네요. merge() 에서 바로 말고 다른 함수(regular expression??)에서 처리 후 merge 함수 사용해보는 방법도 있을거 같습니다.

    • R Friend R_Friend 2019.10.31 20:14 신고  댓글주소  수정/삭제

      간단한 예제 샘플 데이터와 로직, 그리고 아웃풋 이미지 남겨주시면 한번 살펴볼께요.

    • redsky 2019.11.04 11:29  댓글주소  수정/삭제

      아 감사합니다. 샘플 예제는 아래와 같습니다.
      1. 기준유량 : 10, 20, 30, 40, 50
      2. 기준유량 별 초과확률 : 90%, 80%, 70%, 60%, 50%
      3. 실측유량 : 19.8, 10.5, 50.5, 30.6, 39.1

      정해진 기준유량(1)과 각 기준유량별 초과확률(2) 데이터가 있고

      이때 실측유량(3) 값을 가장 근접한 기준유량(1) 값과 매칭하여

      해당 기준유량과 연결된 초과확률(2)을 구하려고 합니다.

      위 예시로 제가 원하는 결과 데이터는 80%, 90%, 50%, 70%, 60% 입니다.

      이게 엑셀에서는 vlookup으로 마지막 인수를 TRUE로 작성하면 비슷하게 일치하는

      데이터를 찾아줘서 쉽게 해결이 가능하긴 한데 워낙 데이터가 많아서

      R로 해볼려고 했더니 막상 이런 기능을 하는 함수를 모르겠네요;;

    • R Friend R_Friend 2019.11.04 11:54 신고  댓글주소  수정/삭제

      안녕하세요 redsky님,

      아래에 실제유량과 기준유량간의 거리(distance)를 구해서 최소거리 위치(index)의 초과확률을 가져오는(indexing) 코드를 짜보았습니다. 도움이 되었기를 바랍니다.

      # input data
      ref_flow <- c(10, 20, 30 40, 50)
      ref_excess_prob <- c(90, 80, 70, 60, 50)
      real_flow <- c(19.8, 10.5, 50.5, 30.6, 39.1)

      # blank vector to store the result
      real_excess_prob <- c()

      # for loop to get the most similar ref_flow and ref_excess_prob using distance

      for (i in 1:length(real_flow)){
      gap_abs <- abs(ref_flow - real_flow[i])
      min_idx <- which.min(gap_abs)
      real_excess_prob[i] <- ref_excess_prob[min_idx]
      }

      # print the result
      real_excess_prob
      [1] 80 90 50 70 60

    • redsky 2019.11.04 13:41  댓글주소  수정/삭제

      알려주신 방법으로 해봐야 겠네요.
      답변 정말 감사합니다~

    • R Friend R_Friend 2019.11.04 15:02 신고  댓글주소  수정/삭제

      도움이 되었기를 바래요. :-)

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 



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

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

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

 



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

 


Posted by R Friend R_Friend

댓글을 달아 주세요

  1. AshtrayK 2016.08.31 11:29 신고  댓글주소  수정/삭제  댓글쓰기

    which에서는 c("mpg","cyl","am")처럼 따옴표로 싸주고
    subset의 select에서는 따옴표를 안쓰셨는데
    이런부분은 왜 차이가 발생한건가요? 그냥 외워야하는건지요?

  2. AshtrayK 2016.09.01 16:02 신고  댓글주소  수정/삭제  댓글쓰기

    cyl==c(4, 6)은 cyl이 동시에 두 값을 가지는 경우는 없을테니 or조건으로 해석하면 되는거죠?
    이런식으로 쓰는군요.. 좋은거 배워갑니다!

  3. 정현복 2017.08.02 09:33  댓글주소  수정/삭제  댓글쓰기

    거의 이 블로그가 빅데이터 공부하는 사람들의 성지인거같아요
    구글에서도 항상 1페이지에 뜨고
    정말 유용한게 많습니다
    다만 무리한 부탁 1가지라면
    데이터 테이블이나 요런 좀더 세련된 기술들도 소개해 주세요^^
    병렬 처리 라던지 ㅎㅎ
    그냥 제 의견입니다
    항상 잘 보고 있습니다.

    • R Friend R_Friend 2017.08.03 08:03 신고  댓글주소  수정/삭제

      안녕하세요 정현복님, 블로그 좋게 봐주셔서 감사합니다.

      요즘에 회사일도 바쁘고 딥러닝, 텐서플로우 공부하느라 시간이 없어서 블로그 포스팅을 거의 못하고 있습니다. ^^;

      다시 힘내서 주신 의견처럼 R 최신 기술도 포스팅 해보겠습니다. 조만간 SparkR을 쓸거 같이니 써보고 포스팅할께요.

    • 김진양 2019.01.19 18:02  댓글주소  수정/삭제

      성지라는 말에 동감합니다

  4. 데분데분 2019.10.10 18:40  댓글주소  수정/삭제  댓글쓰기

    (a) 변속기가 자동이고 & 실린더가 4개, 6개인 자동차 (am==0 & cyl==c(4,6)) 조건 써서 똑같이 했는데 "Merc 240D"라던가 "Merc 280C"도 조건을 만족하는 것 같은데 왜 추출되지 않을까요?
    실습 해봐도 선생님이 쓰신 결과랑 똑같이 나오긴 하는데 원래 "Merc 240D"라던가 "Merc 280C"같은것들도 추출되어야하는것 아닐까요?? ㅠㅠ 아무리봐도 조건을 만족하는것 같은데,, 제 눈이 이상한건지,, 궁금합니다..

    • R Friend R_Friend 2019.10.10 22:58 신고  댓글주소  수정/삭제

      안녕하세요 데분데분님,
      질문해주신대로 cyl 인 4 또는 6인 경우면 둘 다 뽑히게 하려면 'or(|)' 조건이 적용되도록 본문 포스팅의 코드 수정하였습니다.

      그리고 dplyr 패키지로 %>% chain operator 사용해서 변수 선택, 조건 필터링하고 요약통계량 내는 방법도 마지막에 새로 추가해놓았습니다.

      댓글 남겨주신 덕분에 잘못된 부분 바로 잡을 수 있어서 다행입니다. 꼼꼼히 봐주시고 댓글 남겨주셔서 고맙습니다.

    • 데분데분 2019.10.11 02:38  댓글주소  수정/삭제

      오오..빠른댓글 너무나 감사드립니다!
      %in%을 써서 or조건을 적용할수있군요ㅎㅎ 추가해주신 내용도 공부하겠습니다 ! 많이 배워갑니다~

R 의 데이터 구조에는 스칼라, 벡터, 행렬, 요인, 데이터 프레임, 리스트가 있습니다.  이중에서 벡터와 데이터 프레임이 통계 분석 시에 가장 많이 사용됩니다. 

 

이번 포스티에서는 데이터 프레임에서 신규 변수를 생성하는 두 가지 방법에 대해서 알아보겠습니다.  (1) 첫번째 방법은 'dataframe$variable' 처럼 '$'를 사용하는 것이며, (2) 두번째 방법으로는 transform() 함수를 사용하는 것입니다.

 

transform() 함수와 함께 within()함수를 사용해서 연속형 변수를 범주형 변수로 변환하는 방법에 대해서는 이전 포스팅 (☞ 바로가기) 을 참고하시기 바랍니다.

 

 

 R 데이터 프레임 신규 변수 생성 : dataframe$variable, transform()

 

(1) dataframe$variable

 

성인의 키와 몸무게를 가지고 비만도를 나타내는 지수인 체질량 지수(體質量指數, body mass index, BMI)를 신규로 생성하여 보도록 하겠습니다.

 

먼저 가상으로 성인 10명의 키와 몸무게로 구성된 데이터 프레임을 만들어보겠습니다.

 

> height <- c(175, 159, 166, 189, 171, 173, 179, 167, 182, 170)
> weight <- c(62, 55, 59, 75, 61, 64, 63, 65, 70, 60)
> h_w_d.f <- data.frame(height, weight)
> h_w_d.f
   height weight
1     175     62
2     159     55
3     166     59
4     189     75
5     171     61
6     173     64
7     179     63
8     167     65
9     182     70
10    170     60

 

체질량 지수(BMI)를 구하는 공식은 키가 t 미터, 몸무게가 w 킬로그램일 때 BMI = w/t^2 입니다. (키 단위는 미터 임에 주의)

 

이번에는 위의 체질량 지수(BMI) 공식에 따라 dataframe$variable를 이용하여 데이터 프레임에 BMI 변수를 신규로 생성해 보도록 하겠습니다.

 

> options(digits=4) # 숫자 개수 지정해주는 옵션. 이거 지정 안해주면 소숫점 5~6자리까지 나옴 > h_w_d.f$bmi_1 <- h_w_d.f$weight/(h_w_d.f$height/100)^2 > h_w_d.f height weight bmi_1 1 175 62 20.24 2 159 55 21.76 3 166 59 21.41 4 189 75 21.00 5 171 61 20.86 6 173 64 21.38 7 179 63 19.66 8 167 65 23.31 9 182 70 21.13 10 170 60 20.76

 

 

위에서 보시는 것처럼 매번 dataframe$variable 을 입력해줘야만 하는게 꽤 불편합니다.  신규 변수 생성 하나 하고 말거면 뭐 그럭저럭 쓸 수도 있겠읍니다만, 다수 변수를 이용해서 다수 변수를 신규 생성해야 하는 경우라면 아무래도 손이 많이 가는 방법이라고 하겠습니다.

 

손, 발이 편하고자 하는 분이라면 아래의 transfrom() 함수를 이용해보시기 바랍니다.

 

 

(2) transform(dataframe, new_variable = 수식)

 

> ## transform()
> h_w_d.f <- transform(h_w_d.f, 
+                      bmi_2 = weight/(height/100)^2)
> 
> h_w_d.f
   height weight bmi_1 bmi_2
1     175     62 20.24 20.24
2     159     55 21.76 21.76
3     166     59 21.41 21.41
4     189     75 21.00 21.00
5     171     61 20.86 20.86
6     173     64 21.38 21.38
7     179     63 19.66 19.66
8     167     65 23.31 23.31
9     182     70 21.13 21.13
10    170     60 20.76 20.76

 

(1)번의 dataset$variable 에서 매번 '$'를 입력해줘야하는 번거로움 대비 transform()은 정말 깔끔 그 자체임을 알 수 있습니다. 

거기다가 한꺼번에 여러개의 변수를 생성하는 잇점도 있답니다.  아래 예제를 보시지요.

 

> options(digits=3)
> h_w_d.f <- transform(h_w_d.f, 
+                      bmi_sqrt = sqrt(bmi_2), 
+                      bmi_log10 = log10(bmi_2)
+                      )

 

> View(h_w_d.f)
 

 

 

transform() 함수와 함께 within()함수를 사용해서 연속형 변수를 범주형 변수로 변환하는 방법에 대해서는 이전 포스팅 (☞ 바로가기) 을 참고하시기 바랍니다.

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

R에서 데이터 분석을 한다고 했을 때 대부분의 데이터 형식은 데이터 프레임일 것입니다.  R을 처음 교육 받을 때는 벡터를 가지고 주로 실습을 하다가, 실전으로 넘어오면 데이터 프레임을 가지고 데이터 탐색, 전처리를 하게 되다 보니 데이터 구조 (스칼라, 벡터, 행렬, 요인, 데이터 프레임, 리스트)에 대해서 명확한 이해를 하지 않는 분들의 경우 헷갈리고 어려워하기도 합니다.

 

그래서 앞으로 서너번에 나누어서 데이터 프레임에서 사용할 수 있는 데이터 전처리/변환에 대한 함수들을 알아보도록 하겠습니다.

 

이번 포스팅에서는 먼저 데이터 프레임에서  names(), rename() 함수를 사용해서 변수명 변경하기를 해보겠습니다.

 

 

 데이터 프레임 변수명 변경 names(), rename()

 

 

(1) 데이터 프레임 변수명 변경 names()

 

먼저 MASS 패키지에 있는 Cars93 데이터 프레임 내 1~5번째 변수만 선택해서, base 패키지에 있는 names() 함수로 변수명을 변경해보겠습니다.

 

> ## 데이터 프레임 변수명 변경 rename()
> library(MASS)
> # Cars93 데이터 프레임 내 변수명 확인
> names(Cars93)
 [1] "Manufacturer"       "Model"              "Type"               "Min.Price"          "Price"             
 [6] "Max.Price"          "MPG.city"           "MPG.highway"        "AirBags"            "DriveTrain"        
[11] "Cylinders"          "EngineSize"         "Horsepower"         "RPM"                "Rev.per.mile"      
[16] "Man.trans.avail"    "Fuel.tank.capacity" "Passengers"         "Length"             "Wheelbase"         
[21] "Width"              "Turn.circle"        "Rear.seat.room"     "Luggage.room"       "Weight"            
[26] "Origin"             "Make"              
> 

>
> Cars93 데이터 프레임의 1~5번째 변수만 선택한 후 names()로 변수명 변경 > Cars93_subset <- Cars93[,c(1:5)] > names(Cars93_subset) [1] "Manufacturer" "Model" "Type" "Min.Price" "Price" >

 

 


> names(Cars93_subset) <- c("V1", "V2", "V3", "V4", "V5")
> names(Cars93_subset)
[1] "V1" "V2" "V3" "V4" "V5"
 

 

 

 

(2-1) 데이터 프레임 변수명 변경 : reshape 패키지의 rename() 함수

 

다음으로 reshape 패키지에 들어있는 rename() 함수에 대해서 알아보겠습니다.  reshape 패키지는 install.packages("reshape") 으로 새로 설치 후에 library(reshape)로 호출해서 사용해야 합니다.

 

> # rename() 
> install.packages("reshape")
> library(reshape)
> 
> Cars93_subset <- rename(Cars93_subset, 
+                         c(V1 = "V1_Manufacturer", 
+                           V2 = "V2_Model", 
+                           V3 = "V3_Type", 
+                           V4 = "V4_Min.Price", 
+                           V5 = "V5_Price"))
 

 

 

 

(2-2) 데이터 프레임 변수명 변경 : plyr 패키지의 rename() 함수

 

데이터 전처리에 plyr 패키지도 많이 사용되는데요, 변수명 변경에 rename() 함수명은 똑같구요, 다만 변경하고자 하는 old 변수명에도 큰따옴표 ""를 사용한다는 것이 위의 reshape패키지의 rename()함수와 다른 점이 되겠습니다.

 

> install.packages("plyr")
> library(plyr)
> Cars93_subset <- rename(Cars93_subset, 
+                         c("V1_Manufacturer" = "Manufacturer", 
+                           "V2_Model" = "Model", 
+                           "V3_Type" = "Type", 
+                           "V4_Min.Price" = "Min.Price", 
+                           "V5_Price" = "Price"))
> View(Cars93_subset)
 

 

 

 

 

 

(2-3) 데이터 프레임의 변수명 변경 : dplyr 패키지의 rename() 함수

 

데이터 프레임의 데이터 전처리에 막강한 기능을 제공하는 dplyr 패키지에도 변수명 변경을 위한 rename() 함수를 제공합니다.  dplyr 패키지는 plyr 패키지와 친척 관계이지만 rename() 함수의 문법은 차이가 많습니다. 헷갈리지 않도록 조심하시기 바랍니다.

 

위이 plyr 패키지의 rename() 함수와 비교해서 dplyr 패키지의 rename() 함수의 차이점을 정리해보자면,

 

  - 새로운 변수명(new_var)이 앞에 나오고, 이전 변수명(old_var)이 뒤에 나옵니다

  - 큰 따옴표("") 안씁니다.

  - 바꾸고자 하는 변수가 여러개 있을 때 c() 로 안묶어주며, ","(comma)로 나열해줍니다.

 

# dplyr package, rename(dataframe, new_var1 = old_var1, new_var2 = old_var2, ...)
install.packages("dplyr")
library(dplyr) 

 

> Cars93_2 <- Cars93[ ,c(1:3)]
> names(Cars93_2)
[1] "Manufacturer" "Model"        "Type"
> Cars93_3 <- rename(Cars93_2, 
+                    New_Manufacturer = Manufacturer,
+                    New_Model = Model, 
+                    New_Type = Type)
> 
> names(Cars93_3)
[1] "New_Manufacturer" "New_Model"        "New_Type"

 

 

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 김진양 2019.01.18 23:13  댓글주소  수정/삭제  댓글쓰기

    안녕하세요, 선생님. 공부하다가 R 전체적으로 궁금한 점이 이번 rename()에서도 보여 질문드립니다.

    질문은 Cars93_3<-rename(Cars93_2, A=Manufacturer, B=Model, C=Type) 코드에서 A=Manufacturer, B=Model, C=Type 을 일일이 입력하지 않는 방법이 있을까? 하는 것 입니다.

    예를 들어서, Cars93_2의 변수 이름을 B<-names(Cars93_2)로 하고 NAMES<-c("a,"b","c")라는 변수로 지정하고 Cars93_3<-rename(Cars93_2, NAMES=B)라는 코드를 짜봤는데 에러가 나더라고요.
    R 함수 중에서 rename()과 같이 old_var 를 일일이 입력해줘야하는 경우가 많아 단순화 시키고 싶은데 혹시 방법이 있을까요?

선형대수, 통계분석, 데이터마이닝, 최적화 등을 수행할 때 행렬을 많이 사용합니다. 분석에 필요한 변수가 많아질 수록 변수들의 계수를 행렬로 해서 수식을 표현하고 컴퓨터에게 연산을 시키는 것이 편리하기 때문입니다.

 

선형대수(Linear Algebra)를 배우지 않은 분들께서는 행렬연산이 좀 낯설텐데요, 행렬연산에 대해 좀더 깊이 들어가는 부분은 일단 이번 포스팅에서는 생략하겠으며, 앞으로 특정 분석 주제에 대한 포스팅에서 기회가 되면 다루도록 하겠습니다.

 

대신, 이번 포스팅에서는 행렬 연산을 위한 R의 함수 중에서 특히 행렬, 데이터 프레임에서 데이터 전처리 하는데 있어 활용도가 높은 함수들 위주로 몇 가지를 살펴보겠습니다.  데이터 분석 쪽으로 진로를 잡으려고 생각하는 분이라면 선형대수는 꼭 배워두시면 기초를 다잡을 수 있을 거라서 추천드립니다.

 

 

[ m*n 행렬 (m by n matrix) ]

 

 

 

 

이번에 살표볼 R 행렬 연산 함수로 +, -, *, /, ^, %*%, cbind(), rbind(), colMeans(), rowMeans(), colSums(), rowSums(), t() 등을 순서대로 예를 들어 설명하겠습니다.

 

(참고로, R에서 배열, 행렬도 모양이 조금 다른 벡터입니다.  따라서 벡터의 명령어가 배열, 행렬에도 적용된다고 보면 되겠습니다.)

 

 

 R 행렬 연산 : +, -, *, /, ^, %*%, cbind(), rbind(),

                   colMeans(), rowMeans(), colSums(), rowSums(), t()

 

(1) 행렬 내 각 숫자끼리의 연산 : +, -, *, /, ^

 

> ## 행렬 X와 행렬 Y 생성

> X <- matrix(1:4, nrow=2, ncol=2, byrow=FALSE, dimnames = NULL)
> X
     [,1] [,2]
[1,]    1    3
[2,]    2    4
>
> Y <- matrix(5:8, nrow=2, ncol=2, byrow=TRUE, dimnames = NULL)
> Y
     [,1] [,2]
[1,]    5    6
[2,]    7    8

 

예전 데이터 구조에 대한 포스팅에서 행렬 생성에 대해 다루었었는데요, matrix()함수와 각 옵션에 대해서 한번 더 복습해 보겠습니다.  ncol 은 칼럼 갯수, nrow 는 행의 갯수, byrow=FALSE 는 X 행렬 예에서 처럼 위에서 아래로 byrow=TRUE는 Y 행렬 예시 처럼 왼쪽에서 오른쪽으로 행렬이 생성됩니다.

 

> ## 행렬 X와 행렬 Y의 각 숫자끼리의 연산: +, -, *, /, ^ > X + Y [,1] [,2] [1,] 6 9 [2,] 9 12 > > X - Y [,1] [,2] [1,] -4 -3 [2,] -5 -4 > > X * Y [,1] [,2] [1,] 5 18 [2,] 14 32 > > X / Y [,1] [,2] [1,] 0.2000000 0.5 [2,] 0.2857143 0.5 > > X ^ Y [,1] [,2] [1,] 1 729 [2,] 128 65536

 

숫자형으로 구성된 두 행렬에 대해 +, -, *, /, ^ 연산을 하게 되면 같은 위치에 있는 숫자끼리 연산을 하게 됩니다.  (1)번 X * Y 곱셉의 경우 아래 (2)번 예시의 X %*% Y 와 어떻게 다른지 유심히 살펴보시기 바랍니다.  선형대수를 공부하신 분이라면 (1)번 X * Y 곱셈 결과를 보고 '이거 뭐지?' 하고 의아해 하실 것 같은데요, (1) 번 형식의 X * Y 는 각 구성 원소를 순서대로 그냥 곱한 겁니다.  선형대수에서 배웠던 행렬과 행렬의 곱셉은 아래 (2) 번 X %&% Y 형식의 명령문을 사용하게 됩니다.

 

 

(2) 행렬 X와 행렬 Y의 곱 : X %*% Y

 

> X %*% Y
     [,1] [,2]
[1,]   26   30
[2,]   38   44

 

통계, 머신러닝, 최적화 등에서 사용하는 곱셉은 아래 (2)번 X %*% Y 곱셉인 경우가 많을 텐데요, 분석 목적에 맞게 선택해서 사용하시기 바랍니다.

 

 

(3) 행렬 세로 결합 cbind(), 행렬 가로 결합 rbind()

 

> ## 행렬 세로 결합 cbind() : column bind
>
cbind(X, Y) [,1] [,2] [,3] [,4] [1,] 1 3 5 6 [2,] 2 4 7 8 >
> ## 행렬 가로 겹합 rbind() : row bind
>
rbind(X, Y) [,1] [,2] [1,] 1 3 [2,] 2 4 [3,] 5 6 [4,] 7 8

 

두 행렬을 cbind(), rbind()가 행끼리 결합하는 건지, 열끼리 결합하는 건지 헷갈릴 수 도 있는데요, cbind()는 column bind, rbind()는 row bind 로 해서 기억하시면 이해하기 쉽겠지요?

 

 

(4) 행렬 X의 각 열의 평균값으로 구성된 벡터 colMeans(X), 행렬 Y의 각 행의 평균값으로 구성된 벡터 rowMeans(Y)

 

> ## colMeans(), rowMeans()
> colMeans(X)
[1] 1.5 3.5
> rowMeans(X)
[1] 2 3
> 
> colMeans(Y)
[1] 6 7
> rowMeans(Y)
[1] 5.5 7.5

 

colMeans()의 경우 데이터 프레임에서 특정 변수를 '$'로 지정해놓고 mean() 함수를 실행하면 동일한 값을 구할 수 있습니다.  데이터 프레임에서는 보통 열(변수)를 기준으로 통계 분석을 실시하므로, 만약 열을 기준으로 요약 통계를 보려면 colMeans(), 혹은 아래 colSums() 함수는 알아두면 편하겠지요. 

 

참고로, 보통은 행(row) 데이터에 대해서 분석을 하려면 (6)번의 전치 t() 함수나 melt(), cast()함수로 데이터를 열(column)으로 재구성해서 colMeans(), colSums() 나 그 밖의 통계함수를 써서 분석을 합니다.

 

 

(5)  행렬 X의 각 열의 합계로 구성된 벡터 colSums(X), 행렬 Y의 각 행의 합계로 구성된 벡터 rowSums(Y)

 

> ## colSums(), rowSums()
> colSums(X)
[1] 3 7
> rowSums(X)
[1] 4 6
> 
> colSums(Y, na.rm = TRUE)
[1] 12 14
> rowSums(Y, na.rm = TRUE)
[1] 11 15

 

na.rm = TRUE 는 행렬 연산 시에 결측값이 있으면 포함하지 말고 계산하라는 뜻입니다.  예전 포스팅에서 결측값 확인/처리 (☞ 바로 가기) 에 대해서 다룬 적이 있는데요, 아래에 Cars93 데이터 프레임을 가지고 na.omit() 함수와 동일하게 행 내에 결측값이 있으면 그 행 전체를 삭제하는 방법을  rowSums() 함수와 is.na() 함수를 사용해서 수행하는 방법을 알아보겠습니다.

 

> 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 ...
> sum(is.na(Cars93))
[1] 13
> 
> Cars93_na.omit <- na.omit(Cars93)
> sum(is.na(Cars93_na.omit))
[1] 0
> 
> Cars93_rowSums <- Cars93[rowSums(is.na(Cars93)) == 0, ]
> sum(is.na(Cars93_rowSums))
[1] 0

 

na.omit()함수가 훨씬 수월하므로 굳이 dataset[rowSums(is.na(dataset)) == 0, ] 처럼 프로그래밍을 할 필요가 있을까 싶기는 합니다만, rowSums() 함수를 이렇게도 이용할 수 있구나 정도로 알아두시면 좋겠습니다.

 

 

(6) 행렬 X의 전치 t(X)

 

> ## 행렬의 전치 t()
> X
     [,1] [,2]
[1,]    1    3
[2,]    2    4
> 
>
t(X) [,1] [,2] [1,] 1 2 [2,] 3 4 >
> > Y [,1] [,2] [1,] 5 6 [2,] 7 8 >
>
t(Y) [,1] [,2] [1,] 5 7 [2,] 6 8 > > Z <- matrix(1:6, nrow=2, ncol=3) > Z [,1] [,2] [,3] [1,] 1 3 5 [2,] 2 4 6 >
>
t(Z) [,1] [,2] [1,] 1 2 [2,] 3 4 [3,] 5 6

 

t(X)로 행렬을 전치하면 위의 예에서 보는 것처럼 행과 열이 서로 바뀌게 됩니다.  통계분석의 행과 열 기준을 바꾸고 싶거나, 그래프 그릴 때 가로와 세로를 바꾸고 싶을 때 t() 함수로 전치를 해서 쓰면 되겠지요.

 

한번더 부언하자면, 선형대수에 나오는 행렬 연산 전부를 다루지는 않았습니다만, 데이터 분석 쪽으로 계속 공부하려는 분이라면 선형대수 공부는 몸에 좋은 밑거름이 될것이니 따로 공부해보시길 권합니다.

 

 

행렬에 대해 소개한 포스팅을 아래에 링크 걸어놓습니다. 참고하세요.

 

행렬 기본 이해

특수한 형태의 행렬 (zero matrixtranspose matrixsymmetric matrixupper triangular matrixlower triangular matrixdiagonal matrixidentity matrix, I, or unit matrix, U)

가우스 소거법을 활용한 역행렬 계산 (Invertible matrix, Gauss-Jordan elimination method)

여인수를 활용한 역행렬 계산 (Invertible matrix, by using cofactor)

벡터의 기본 이해와 연산 (vector: addition, subtraction, multiplication by scalar)

벡터의 곱 (1) 내적 (inner product, dot product, scalar product, projection product)

벡터의 곱 (2) 외적 (outer product, cross product, vector product, tensor product)

 

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

 

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 김진양 2019.01.09 21:26  댓글주소  수정/삭제  댓글쓰기

    선생님, 안녕하세요!
    공부를 하다 궁금한게 있어 여쭤봅니다!
    Cars93에서 결측치를 제거한 Cars93_na.omit에 대해 궁금한게 있는데요!
    결측치를 제거한 후 str()을 통해 관측치가 93개 → 82개로 줄어든 것을 확인했습니다.
    하지만, 이후 Cars93_na.omit 데이터를 통째로 까보니 행이 93개로 여전히 있어서요.
    관측치가 82개가 제거되었다면, 해당 행도 제거가 되어야할 것 같은데 93개가 있어서 혼동이 생기네요!
    혹시 어떻게 된 건지 알 수 있을까요?

  2. 꾸리꾸리 2019.05.08 20:43  댓글주소  수정/삭제  댓글쓰기

    안녕하세요.

    혹시 data.frame에서
    원하는 열에만 전체적으로 값을 더하거나 뺄 수 있는 방법이 있는지 질문 드립니다.

    ex) A B C D -> A B C D
    1 2 3 4 1 3 3 5
    2 3 4 5 2 4 4 6

    데이터 프레임은 유지하대 b와 d에만 +1 및 -1을 하는 방법이 있을지 궁금합니다.

    • R Friend R_Friend 2019.05.09 18:26 신고  댓글주소  수정/삭제

      안녕하세요. 답글이 늦어서 죄송합니다.

      아래의 transform() 함수를 참고하세요.

      > A <- c(1, 2)
      > B <- c(2, 3)
      > C <- c(3, 4)
      > D <- c(4, 5)
      >
      > df <- data.frame(A, B, C, D)
      > df
      A B C D
      1 1 2 3 4
      2 2 3 4 5
      >
      > df <- transform(df,
      + B = B + 1,
      + D = D - 1)
      >
      > df
      A B C D
      1 1 3 3 3
      2 2 4 4 4

    • R Friend R_Friend 2019.05.09 19:03 신고  댓글주소  수정/삭제

      + C, + D 에서 앞에 + 부호는 제거해주세요. 콘솔창에 결과 나타날때 계속 이어진다는 의미의 + 예요.

    • 꾸리꾸리 2019.05.09 19:15  댓글주소  수정/삭제

      네 정말 감사합니다 잘 적용 되었습니다.

      실례지만 추가질문을 드리자면

      기존의 값에 +,-를 하여 나온 두 컬럼으로
      나누기, 곱하기 및 LOG값 (log10, log2)에 대해 계산을 하여 나타내려면 transform을 응용하여야 하나요?
      ( ex
      1.
      A B C D E F G,H, I 중 B and C의 값에 변화 ( +, -)를 주어 나타내고
      2. B 및 C의 값으로 위에서 질문드린 계산을 하여 E(곱), F(log10),G(log2) 에 나타내고 합니다.
      3. A , H and I는 기존 값을 유지한 상태

    • R Friend R_Friend 2019.05.09 19:38 신고  댓글주소  수정/삭제

      (1) transform() 을 사용하시려면 2-step 으로 나누어서 B, C 에 (+, -) 변화를 준 DataFrame을 먼저 만들고 -> 이후 이어서 곱, 로그10, 로그2 를 취해서 DataFrame을 만들어주어야 합니다.

      (2) 대안으로, dplyr 을 이용하시면 한번에 처리하실 수 있습니다. https://rfriend.tistory.com/235 포스팅을 참고하세요.

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

댓글을 달아 주세요

  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 R_Friend 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 R_Friend 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 R_Friend 2017.12.20 14:11 신고  댓글주소  수정/삭제

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

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

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

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

그동안 R 에서 숫자형 벡터의 처리에 대한 여러가지 함수를 알아 보았습니다.  벡터는 R의 똘망똘망한 일꾼이자 무기라고 말씀드렸는데요, 이번 포스팅에서는 숫자형 벡터 외에 문자형 벡터를 가지고 떡 주무르듯이 가지고 놀 수 있는 문자형 함수들을 알아보도록 하겠습니다.

 

R 문자형 벡터를 다루는 함수로는 nchar(), substr(), paste(), strsplit(), sub(), gsub(), grep(), regexpr(), gregexpr() 등이 있습니다.  아래에 예제를 들어가면서 하나씩 설명 드리도록 하겠습니다.

 

R을 활용한 텍스트 마이닝은 별도로 나중에 분석 주제로 들어가면 그때 설명드리도록 하겠습니다. 

 

 

 R 문자함수 nchar(), substr(), paste(), strsplit(), sub(), gsub()

 

(1) nchar(x) : 문자형 벡터 x의 구성요소 개수 구하기

 

> # nchar()
> x <- c("Seoul", "New York", "London", "1234")
> nchar(x)
[1] 5 8 6 4

 

"New York"의 경우 중간에 스페이스바 공간 하나가 있는데요, 이것도 '1'개로 count해서 '8'로 계산했다는 점은 유의하기 바랍니다.

 

 

(2) substr(x, start, stop) : 문자형 벡터 x의 start에서 부터 stop 까지만 잘라오기 (부분 선택)

 

> # substr()로 문자형 벡터 부분 선택하기 > time_stamp <- c("201507251040", "201507251041", "201507251042", "201507251043", "201507251044") > t_yyyymm <- substr(time_stamp, 1, 6) > t_yyyymm [1] "201507" "201507" "201507" "201507" "201507" >

> # 데이터 프레임에서 transfrom()함수와 substr()함수로 부분 선택한 내용으로 새로운 변수 만들기
>
gas_temp <- c(145.0, 145.1, 145.5, 150.1, 150.6) > ts_gas_temp <- data.frame(time_stamp, gas_temp) > ts_gas_temp time_stamp gas_temp 1 201507251040 145.0 2 201507251041 145.1 3 201507251042 145.5 4 201507251043 150.1 5 201507251044 150.6 > > ts_gas_temp <- transform(ts_gas_temp, mmdd = substr(time_stamp, 5, 8)) > ts_gas_temp <- transform(ts_gas_temp, hhmm = substr(time_stamp, 9, 12)) > > ts_gas_temp time_stamp gas_temp mmdd hhmm 1 201507251040 145.0 0725 1040 2 201507251041 145.1 0725 1041 3 201507251042 145.5 0725 1042 4 201507251043 150.1 0725 1043 5 201507251044 150.6 0725 1044

 

첫번째 예제는 time_stamp 라는 벡터를 가지고 부분 선택하는 것이고, 두번째 예제는 ts_gas_temp라는 데이터 프레임에서 특정 변수를 선택해서 transfrom()이라는 함수와 substr()함수를 사용해서 부분 선택한 내용으로 새로운 변수를 만들어 보는 예제가 되겠습니다.

 

 

(3) paste(x, y, sep = " ") : 문자형 벡터 x와 y를 붙이기

 

> # 문자형 벡터의 객체들을 paste()로 하나로 붙이기 > paste("I", "Love", "New York", sep = "") [1] "ILoveNew York" > paste("I", "Love", "New York", sep = " ") [1] "I Love New York" > paste("I", "Love", "New York", sep = "_") [1] "I_Love_New York" >

> # 데이터 프레임에서 transform()함수와 paste()함수로 두개의 변수를 하나로 합쳐서 새로운 변수 만들기 > ts_gas_temp <- transform(ts_gas_temp, mmddhhmm = paste(mmdd, "일_", hhmm, "분", sep="")) > ts_gas_temp time_stamp gas_temp mmdd hhmm mmddhhmm 1 201507251040 145.0 0725 1040 0725일_1040분 2 201507251041 145.1 0725 1041 0725일_1041분 3 201507251042 145.5 0725 1042 0725일_1042분 4 201507251043 150.1 0725 1043 0725일_1043분 5 201507251044 150.6 0725 1044 0725일_1044분

 

첫번째 예제는 문자형 벡터의 객체들을 paste() 함수를 사용해 하나로 붙인 것인데요, sep="", sep=" ", sep="_" 등 sep에 무엇을 넣느냐에 따라 결과가 달라지는 것을 알 수 있습니다.

 

두번째 예제는 데이터 프레임에서 transfrom()과 paste()함수를 사용해 두개 이상의 문자형 벡터 변수를 합쳐서 새로운 문자형 변수를 만드는 예제입니다.  실전에서는 데이터 프레임 구조의 데이터 셋을 가지고 작업을 많이 하므로 알아두면 유용하겠지요.

 

 

(4-1) strsplit(x, split= ",") : 문자형 벡터 x를 split 기준으로 해서 나누기

 

> # strsplit() 으로 분리하기
> name <- c("Chulsu, Kim", "Younghei, Lee", "Dongho, Choi")
> name_split <- strsplit(name, split=",")
> name_split
[[1]]
[1] "Chulsu" " Kim"  

[[2]]
[1] "Younghei" " Lee"    

[[3]]
[1] "Dongho" " Choi" 

> 

> # indexing 해오기 > last_name <- c(name_split[[1]][2], name_split[[2]][2], name_split[[3]][2]) > last_name [1] " Kim" " Lee" " Choi" > > first_name <- c(name_split[[1]][1], name_split[[2]][1], name_split[[3]][1]) > first_name [1] "Chulsu" "Younghei" "Dongho" > > # last_name과 first_name, name을 데이터 프레임으로 묶기

> name_d.f <- data.frame(last_name, first_name, name)
> name_d.f
  last_name first_name          name
1       Kim     Chulsu   Chulsu, Kim
2       Lee   Younghei Younghei, Lee
3      Choi     Dongho  Dongho, Choi

 

strsplit()함수는 split="any" 의 큰따옴표 안에 들어가는 구분자 기준에 따라서 문자열을 분리해주는 함수입니다.

첫번째 예제 strsplit()함수로 name 문자형 벡터를 나누어보니 결과가 리스트(list) 구조로 나왔습니다.

 

두번째 예제는 리스트(list) 결과에서 Indexing해오는 방법을 소개하여보았습니다.  Indexing은 데이터 처리, 프로그래밍할 때 정말 많이 쓰고 반드시 알아두어야 하는 핵심 개념 중의 하나입니다. Indexing에 관한 자세한 내용은 이전 포스팅을 참고하세요 (☞ 바로가기)

 

세번째 예제는 strsplit()함수로 분리한 개별 벡터들을 하나의 데이터 프레임으로 묶는 방법이 되겠습니다. 세트로 알아두면 좋겠지요?

 

 

 

(4-2) 데이터프레임에서 문자열을 구분자 기준으로 나누는 방법은 아래 예제를 참조하세요.

        (spliting character in dataframe by delimeter)

         :  data.frame(do.call('rbind', strsplit(as.character(df$var), split='delimeter', fixed=T))) 

 

> ##############################################
> ## split character in dataframe by delimeter
> ##############################################
> 
> # example data frame
> name_df <- data.frame(ID = c(1:3), name = c("Chulsu/Kim", "Younghei/Lee", "Dongho/Choi"))
> name_df
  ID         name
1  1   Chulsu/Kim
2  2 Younghei/Lee
3  3  Dongho/Choi
> 
> 
> # strsplit character in dataframe by delimeter
> name_strsplit <- data.frame(do.call('rbind', 
+                                     strsplit(as.character(name_df$name), 
+                                              split = '/', 
+                                              fixed = TRUE)))
> name_strsplit
        X1   X2
1   Chulsu  Kim
2 Younghei  Lee
3   Dongho Choi
> 
> 
> # changing name
> # install.packages("reshape")
> library(reshape)
> name_strsplit <- rename(name_strsplit, 
+                         c(X1 = "First_Name", 
+                         X2 = "Last_Name"))
> 
> name_strsplit
  First_Name Last_Name
1     Chulsu       Kim
2   Younghei       Lee
3     Dongho      Choi

 

 

 

 

(5) sub(old, new, x): 문자형 벡터 x에서 처음 나오는 old문자를 new문자로 한번만 바꾸기

(6) gsub(old, new, x): 문자형 벡터 x 내에 모든 old 문자를 new 문자로 모두 바꾸기

 

> # sub()는 처음 나오는 old 문자만 new 문자로 한번만 바꿈 > z <- c("My name is Chulsu. What's your name?") > sub("name", "first name", z) [1] "My first name is Chulsu. What's your name?" >

> # gsub()는 모든 old 문자를 new 문자로 바꿈
> gsub("name", "first name", z)
[1] "My first name is Chulsu. What's your first name?"
> 

> # new 자리에 ""를 넣으면 없애는 효과

> sub("My name is Chulsu. ", "", z)
[1] "What's your name?"

 

첫번째 예제 sub()함수에서는 "name"을 "first name"으로 바꾸라는 명령문입니다.  필자가 이해를 돕기 위해 name에다가 밑줄을 그어놓았는데요, z 벡터에는 name이 두번 나오는데 sub()함수로 바꾸기를 했더니 처음 나오는 "name"은 "first name"으로 바뀌었지만 두번째 나오는 "name"은 그대로인 것을 알 수 있습니다.

 

반면에 두번째 예시에서 gsub()는 첫번째 나오는 "name"을 "first name"으로 바꾸었을 뿐만 아니라, 두번째 나오는 "name" 또한 "first name"으로 바꾸었습니다.  따라서 분석 목적에 따라서 sub()와 gsub()를 선택적으로 사용하시면 되겠습니다.

 

 아래 예제는 데이터 프레임에서 transform()함수로 sub()함수를 사용해서 특정 변수 내 특정 문자열을 old -> new로 바꾸는 내용이 되겠습니다. 

 

> cust_id <- c("c1", "c2", "c3", "c4", "c5", "c6")
> size <- c("XS", "L", "M", "XS", "XL", "S")
> cust_db <- data.frame(cust_id, size)
> cust_db
  cust_id size
1      c1   XS
2      c2    L
3      c3    M
4      c4   XS
5      c5   XL
6      c6    S
>

> # size 변수 ㄴ "XS" 사이즈를 "S" 사이즈로 바꿔서 size_1 이라는 새로운 변수에 저장(생성)해라 > cust_db <- transform(cust_db, size_1 = sub("XS", "S", size)) > cust_db cust_id size size_1 1 c1 XS S 2 c2 L L 3 c3 M M 4 c4 XS S 5 c5 XL XL 6 c6 S S 

 

예제의 경우 만약 'old'에서 'new'로 바꿔야 하는 조건이 2개 이상이 되면 ifelse() 라든지 within() 함수 등을 사용해야 하는데요, 이것은 나중에 새로운 범주형 변수 만들기에서 별도로 소개해드리도록 하겠습니다.

 


문자열에 포함되어 있는 모든 점(".", point)을 없애려면 gsub(".", "", col, fixed=TRUE) 라고 하거나, 혹은 정규 표현식을 이용해서 gsub("[.]", "", col) 이라고 해주면 됩니다. 


> # how to remove a point "." in a string

> id <- c("a", "b", "c", "c")

> col <- c("11.23", "64.12", "931.01", "3.3.0.4.1.2")

> df <- data.frame(id, col)

> df

  id         col

1  a       11.23

2  b       64.12

3  c      931.01

4  c 3.3.0.4.1.2

> df <- transform(df, 

+                 col_2 = gsub(".", "", col, fixed=TRUE))

> df

  id         col  col_2

1  a       11.23   1123

2  b       64.12   6412

3  c      931.01  93101

4  c 3.3.0.4.1.2 330412

> df <- transform(df, 

+                 col_3 = gsub("[.]", "", col))

> df

  id         col  col_2  col_3

1  a       11.23   1123   1123

2  b       64.12   6412   6412

3  c      931.01  93101  93101

4  c 3.3.0.4.1.2 330412 330412


 

(7) grep(pattern, x) : 문자열 벡터에서 특정 부분 문자열 패턴 찾기

 

> grep("1010", c("1001", "1010", "1110", "101000"))
[1] 2 4
> 
> grep("1010", c("1001", "1009", "1110", "100000"))
integer(0) 

 

위의 첫번째 예는 문자열 "1010"이라는 패턴이 2번째와 4번째 원소에 들어있다는 뜻입니다.

두번째 예에서는 문자열 "1010"이라는 패턴이 하나도 안들어 있다는 뜻이 되겠습니다.

 

 

(8) regexpr() : text 내에서 패턴이 가장 먼저 나오는 위치 찾기

 

> regexpr("NY", "I love NY and I'm from NY")
[1] 8
attr(,"match.length")
[1] 2
attr(,"useBytes")
[1] TRUE


"NY"이라는 패턴이 8번째 (스페이스 포함) 자리에서 처음으로 나왔다는 뜻입니다. 

 

 

(9) gregexpr() : text 내에서 패턴이 나오는 모든 위치를 찾기

 

> gregexpr("NY", "I love NY and I'm from NY")
[[1]]
[1]  8 24
attr(,"match.length")
[1] 2 2
attr(,"useBytes")
[1] TRUE 

 

"NY"이라는 패턴이 8번째, 그리고 24번째 자리에서 나왔다는 뜻입니다. 

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. AshtrayK 2016.08.30 16:38 신고  댓글주소  수정/삭제  댓글쓰기

    sub는 한개의 문자형 스칼라?원소? 내에서 처음나오는 old를 바꿔주는 거죠?
    그래서 XS를 S로 바꾸는 예제에서 sub를 쓴 결과가 저렇게 나온거구 gsub를 여기에 써도 마찬가지 결과가 나오는 거 맞나요?
    (현재 실습이 불가하여 ㅠㅠ)

  2. AshtrayK 2016.08.30 16:47 신고  댓글주소  수정/삭제  댓글쓰기

    (4-2)부분이 생소하네요 ㅠㅠ
    do.call과 fixed=TRUE 부분 설명좀 부탁드립니다~

  3. AshtrayK 2016.09.26 10:57 신고  댓글주소  수정/삭제  댓글쓰기

    strsplit으로 분리해볼게있는데요. split="^"으로 하면 작동이 안되는것 같은데 어떻게해야 할까요ㅠㅠ
    예제 만들어서 해봐도 이상하게 나오네요

  4. 궁금해요 2016.11.01 14:10  댓글주소  수정/삭제  댓글쓰기

    안녕하세요ㅜ 오랜만에 다시 글을 남깁니다..

    데이터 프레임내에서 데이터 내의 문자로 filter 하고 싶은데...

    예를 들어 코코아분말, 코코아음료 등의 분류가 있는데, 코코아라는 공통된 문자로 데이터를 filter하고 싶은데요. 어떻게 하면 될까요?

    • R Friend R_Friend 2016.11.01 14:19 신고  댓글주소  수정/삭제

      substr(x, 1, 3) 으로 앞 3자리 문자만 가져다가 새로운 변수를 만든 다음에 => "코코아" 조건으로 filter() 또는 subset() 하면 될거 같습니다.

    • R Friend R_Friend 2016.11.02 10:41 신고  댓글주소  수정/삭제

      좀더 쉬운 방법, 함수가 있을거 같긴 한데요,,,, 일단 제가 아는 선에서 (복잡한... -_-;) 프로그램 짜봤습니다. 급하시면 일단 이거라도 참고하시지요.

      ##------------------------------------------
      # making dataframe
      prd <- data.frame(ID = c(1:5),
      prd_name = c("코코아분말", "브라질산 코코아",
      "맛있는 코코아 우유", "커피 사탕",
      "바닐라라떼"))

      str(prd)

      # prd_name : changing from factor -> character
      prd <- transform(prd, prd_name = as.character(prd_name))
      class(prd$prd_name)

      # number of character
      prd <- transform(prd, num_char = nchar(prd$prd_name))
      prd

      # splitting character in dataframe
      prd_strsplit <- data.frame(do.call('rbind',
      strsplit(as.character(prd$prd_name),
      split = "코코아",
      fixed = TRUE)))

      prd_strsplit

      str(prd_strsplit)

      # prd_name : changing from factor -> character
      prd_strsplit <- transform(prd_strsplit,
      X2 = as.character(X2))
      class(prd_strsplit$X2)

      # number of character of prd_strsplit dataframe
      prd_strsplit <- transform(prd_strsplit,
      num_char_strsplit_X2 = nchar(X2))

      prd_strsplit

      # cbind
      prd$num_char_strsplit_X2 <- prd_strsplit$num_char_strsplit_X2
      prd

      # if num_char and num_char_strsplit_X2 is not equal, then filter it
      prd <- transform(prd, nchar_gap = num_char - num_char_strsplit_X2)

      prd_subset <- subset(prd,
      subset = nchar_gap != 0)

      prd_subset

    • Ara 2018.07.31 10:45  댓글주소  수정/삭제

      덕분에 많이 배우고 갑니다. 코코아가 들어간 문자에만 nchar에 변화를 주는 방법으로 필터링 하셨네요. 엑셀로는 몇번의 클릭으로 되는 일이 R에서는 이정도의 작업을 거쳐야 한다는게 번거롭긴 하지만.. 덕분에 그 기저에 깔려있는 논리나 프로세스를 많이 배웁니다. 항상 좋은 콘텐츠 감사합니다.

    • R Friend R_Friend 2018.07.31 10:52 신고  댓글주소  수정/삭제

      안녕하세요 Ara님,
      아마도 stringr 같은 문자열 처리 전문 패키지에 찾아보면 훨씬 간단하게 할 수 있는 험수가 있을거 같습니다. ^^;

      블로그 좋게 봐주셔서 감사합니다.

  5. AshtrayK 2016.11.09 17:10  댓글주소  수정/삭제  댓글쓰기

    regexpr 질문있습니다.
    결과물이
    [1] -1 4 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
    attr(,"match.length")
    [1] -1 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
    attr(,"useBytes")
    [1] TRUE

    이렇게 3가지가 나오는데요.
    첫번째 줄의 뜻은 알겠는데요(처음으로 패턴이 등장하는 index시작위치)
    나머지는 잘 모르겠습니다.

    match.length는, regexpr("en", txt)로 실행할 때, 찾고싶은 패턴(여기서는 en)의 길이와 항상 똑같은 값이 나오게 되는 것 같은데..
    en을 찾으라고 했으면 당연히 찾으라고 한 값의 길이인 2가 나온다는 것을 굳이 출력해줄 이유가 있을까요?
    예외가 없다면 이건 어디에 써먹을 수 있는 정보인지 모르겠어서요..

    그리고 마지막 useBytes는 무슨뜻인지 전혀 감이 안잡힙니다ㅠㅠ

    • R Friend R_Friend 2016.11.16 23:52 신고  댓글주소  수정/삭제

      저도 AshtrayK님과 비슷한 생각입니다. 말씀해주신 것처럼 match.length 가 추가로 제공해주는 정보라는게 미미하다고 생각합니다.

      "useBytes"는 TRUE면 index 숫자로 표기되고 FALSE면 문자형(character)로 표기된다고 매뉴얼에는 나오는데요, FALSE인 경우를 한번도 못봤습니다. 이것도 추가 제공 정보라는게 거의 없다고 생각합니다.

      근데, 이 함수 만든 사람이 뭔가 쓸모가 있으니깐 이렇게 3개 값을 반환하라고 해놨을 텐데요, 이걸 어디에 써먹을 수 있을까 좀 억지로 생각을 해보자면요,

      우리가 예로 든것 처럼 몇 개의 소소한 데이터셋을 가지고 공부하려고 하는 분석이 아니라, 대용량의 수백, 수천개의 변수를 가진 데이터셋에 대해서 '자동화'하는 애플리케이션을 운영하는 상황이라고 했을 때요,

      (1) "usrBytes" 가 TRUE 인 것을 확인하고 (=> 음, 찾으려는 문자열의 시작위치와 길이를 나타내는 index값을 사용해도 되겠군. OK, go!)

      => (2) 시작 위치와 => (3) 길이(match.length)의 index 값을

      regexpr 분석결과의 반환 객체에서 찾아서 자동으로 찾아가서 뭔가 추가 조치를 취할 수 있도록 하는데 쓰일 수 있지 않을까...하고 긍정적인 마인드로 생각해보았습니다. ^^;

  6. 산낙지 2017.05.31 22:19  댓글주소  수정/삭제  댓글쓰기

    각각 변수의 값들을 paste하려고 하는데, 변수에 na가 있는 경우 na까지 그대로 paste가 되더라고요... "산낙지 na" 이런 식으로요. na.rm=TRUE와 같은 옵션을 줘도 안되고요. paste에서 na를 무력화할 수 있는 방법이 있을까요?

    • R Friend R_Friend 2017.05.31 22:39 신고  댓글주소  수정/삭제

      집에 인터넷이 고장나서 핸드폰으로 간단히 답변 달아요.

      # input data
      c1 <- c("A", "B", "C")
      c2 <- c(1, 2, NA)

      # combining c1, c2 as a dataframe
      mydata <- data.frame(c1, c2)

      # c1: factor => character
      mydata$c1 <- as.character(c1)

      # c3 = paste0(c1, c2)
      mydata <- transform(mydata, c3 = ifelse(is.na(c2) == TRUE, c1, paste0(c1, c2)))

  7. 꾸리꾸리 2019.11.17 05:40  댓글주소  수정/삭제  댓글쓰기

    4-2번을 보면서 진행중입니다. 제 데이터는 split = ";" 을 분리를 합니다.

    하나의 행을 예로 들자면 (사과;배;오렌지;수박;토마토) -> ';'가 2~10개 정도 있는 행도 있습니다.

    4-2 예제로 분리까지 하는건 가능하였으나, 제가 하고싶은 구성은 ';'을 분리한 후, 하나의 column에 각각 나열을 하고 싶은데 이런 방법은 어떻게 해야 하나요?

    ## 4-2 예제 진행 후 데이터 결과

    raw-data :(사과;배;오렌지;수박;토마토)
    results-data :
    col1에는 사과
    col2에는 사과(1행), 배(2행)
    col3에는 사과(1행), 배(2행), 오렌지(3행) ....

    원하는 데이터 결과 :
    col1
    사과

    오렌지
    수박
    토마토 ...

R의 연산자 중에 %any% 식으로 해서 %가 들어간 특이한 경우를 본 적이 있으신지요?  혹시 사용자 정의 함수나 루프 연산 예제로 '홀수' 나 '짝수' 개수 구하기 등의 예제를 본 적이 있다면 '%%' 연산자를 보았을 수도 있겠습니다.  알고나면 사실 별거 아닌데요, 모르면 당최 이게 무슨 뜻일까 가늠이 안되는 연산자이기도 합니다. 그러니 한번은 봐두는게 좋겠습니다.

 

%any% 연산자의 예로 (1) 나머지 연산자 %%, (2) 정수 나누기 연산자 %/%, (3) 행렬 곱하기 연산자 %*%, (4) 벡터 내 특정 값 포함 여부 확인 연산자 %in% 의 4가지 연산자에 대해서 하나씩 예를 들어가면서 알아보도록 하겠습니다.

 

저는 앞의 3개는 사용해본적이 아직까지는 없는데요, 그래도 %in%는 나름 유용하게 잘 써먹고 있습니다.

 

 

 R %any% 특별연산자

(1) 나머지 연산자 %%

 

> # (1) 나머지 연산자 %% > x <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) > y <- c(5) > x %% y [1] 1 2 3 4 0 1 2 3 4 0 >
> # 5의 배수만 indexing 해오기 > x[x %% y == c(0)] [1] 5 10

 

x %% y 를 하게 되면 x가 y로 나누어지는 경우는 나머지가 '0'이고, 그 외에는 나머지 값이 숫자로 나오게 됩니다.  x가 1~10까지 정수이고 y가 5라고 했을 때 5의 배수만 indexing해오는 방법으로 x[x %% y == c(0)] 를 사용했는데요, x %% y == c(0) 하게 되면 5의 배수인 5와 10일때만 TRUE 가 되고, 이를 indexing해오면 5의 배수가 되겠지요.

 

 

(2) 정수 나누기 연산자 %/%

 

> x %/% y
 [1] 0 0 0 0 1 1 1 1 1 2

 

위 (1)번의 x와 y 벡터를 가지고 (2)번 예를 들어보았습니다.  %/% 연산자는 나누었을 때 '정수 몫' 만을 가져다가 결과로 나타내 줍니다. 5로 1~4까지 나누면 소수점 자리수 이므로 0, 5~9까지는 소수점 이하는 버리고 정수 몫만 취하므로 '1', 10을 5로 나누면 '2'가 되는 것이지요.

 

 

(3) 행렬 곱하기 연산자 %*%

 

> x %*% y
Error in x %*% y : non-conformable arguments
> c(1, 2, 3) %*% c(4, 5, 6)
     [,1]
[1,]   32

 

위 (1)번 x, y 벡터를 가지고 행렬 곱하기 %*%하면 'non-conformable arguments'라는 에러 메시지가 뜹니다. 행렬 곱하기를 하려고 하는데 갯수가 서로 안맞아서 그렇습니다.  구성요소가 각 각 3개씩인 두개의 벡터를 곱하려면 c(1, 2, 3) %*% c(4, 5, 6) 이렇게 입력하면 됩니다.  그러면 순서대로 곱하고 더해서, 즉, (1*4 + 2*5 + 3*6) = 4 + 10 + 18= 32 가 됩니다.

 

 

(4) 벡터 내 특정 값 포함 여부 확인 연산자 %in%

 

> # (4) 벡터 내 특정 값 포함 여부 확인 %in%
> x %in% y
 [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
> 

> # x 내에 y 값이 포함된 개수의 합

> sum( x %in% y)
[1] 1

 

마지막으로 벡터 내 특정 값이 포함되었는지 여부를 확인하는 %in%는 SQL이나 SAS에서 %like% 와 유사한 연산자라고 보면 되겠습니다.  R은 %in% 연산자의 결과로 TRUE, FALSE 논리형 벡터를 출력합니다.  위 (4)번 예에서는 x의 5번째 자리에 y 값 5가 하나 들어있어서 TRUE로 나왔음을 알 수 있습니다.  위의 예는 x가 단지 10개 뿐이어서 눈으로도 확인할 수 있지만 구성요소 갯수가 수천, 수만, 수백만개면 눈으로 일일이 확인하는 것은 불가능합니다.  이럴 때 sum(x %in% y) 함수를 사용하면 x 에 y가 총 몇개나 들어있는지 금방 확인할 수가 있답니다.

 

%any% 연산자에 대해서 알아보았습니다.  도움이 되었기를 바랍니다.

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 깜끼 2020.01.13 16:10  댓글주소  수정/삭제  댓글쓰기

    그러면, 벡터 내 특정 값 포함 여부 확인 할 때, 문자열도 상관 없나요?

외부 텍스트 파일로 대용량의 데이터 셋을 R로 불러들이고 나면 가장 먼저 하는 것이 str()로 데이터 구조 파악하기, head(), tail()로 데이터 몇 개 미리보기, 그 다음에 하는 것이 바로 결측값 확인 및 처리, 특이값/영향치 확인 및 처리 등의 탐색적 데이터 분석입니다.

 

R에서 결측값이 들어있는 상태에서 통계 분석을 진행하면 NA 라는 결과가 나올 뿐, 원하는 결과를 얻지 못합니다.  그리고 대부분의 R 통계 함수에는 옵션으로 "na.rm = TRUE" 라는 옵션을 제공해서 결측값을 통계량 계산할 때 포함하지 말지를 선택할 수 있게 해줍니다.

 

 

이번 포스팅에서는 데이터 셋에 (1) 결측값이 포함되어 있는지 확인하는 방법, (2) 결측값이 들어있다면 결측값을 제거하는 방법, (3) 결측값을 다른 값으로 대체하는 방법에 대해서 알아보도록 하겠습니다.

 

 

 

 

 R 결측값 확인 및 처리: is.na(), na.omit(), complete.cases()

 

 

(1) 결측값이 포함되어 있는지 확인하는 방법: is.na()

 

> x <- c(1, 2, 3, 4, NA, 6, 7, 8, 9, NA)
> is.na(x)
 [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE

 

위의 벡터처럼 구성요소 갯수가 몇 개 안될 경우 is.na() 한 후에 TRUE, FALSE 논리형 값을 눈으로 보고 확인할 수 있습니다. 하지만 아래의 Cars93 데이터 프레임처럼 변수 갯수도 많고, 관측치 갯수도 많은 경우 (대부분의 실무에서 쓰는 데이터 셋은 이처럼 변수도 많고 관측치도 많지요) is.na() 함수만 가지고서는 아무래도 결측치 현황을 파악하는데 무리가 있습니다.

 

 

> library(MASS) > is.na(Cars93) Manufacturer Model Type Min.Price Price Max.Price MPG.city MPG.highway AirBags DriveTrain Cylinders EngineSize 1 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 2 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 3 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 4 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 5 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 6 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 7 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 8 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 9 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 10 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 11 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 12 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 13 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 14 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 15 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 16 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 17 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 18 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 19 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

..... (뒤에 계속 있으며, 너무 많아서 이쯤에서 중략) .....

 

 

(2) 결측값이 총 몇 개인지 계산하는 방법: sum(is.na())

 

> sum(is.na(x))
[1] 2

 

 

 

> 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 ...
 

> # Cars93 데이터 프레임에 결측값 갯수 총 합계 구하기

> sum(is.na(Cars93))
[1] 13
> 
> # Cars93 의 각 변수별로 결측값 개수 구하기 (27개 중에서 4개만 예시로 함)
> sum(is.na(Cars93$Manufacturer)) 
[1] 0
> sum(is.na(Cars93$Price))
[1] 0
> sum(is.na(Cars93$Rear.seat.room))
[1] 2
> sum(is.na(Cars93$Luggage.room))
[1] 11

 

sum(is.na()) 함수를 이용하니 변수가 많거나 관측값이 많은 경우도 결측값 현황을 금방 파악할 수 있습니다.  R은 TRUE 를 '1'로, FALSE 를 '0'으로 인식하기 때문에 sum(is.na())를 하게 되면 TRUE 값을 '1'로 해서 합계를 내기 때문에 가능한 함수가 되겠습니다.



colSums() 함수를 사용하면 데이터 프레임 내 다수 변수들에 대해서 한번에 각 개별 변수별 결측값의 개수 합계를 구할 수 있습니다. 바로 위에서 개별 함수별로 일일이 sum(is.na(Cars93$Manufacturer))...이런 식으로 변수의 개수만큼 쓰는 것을 colSums() 함수로는 한줄이면 해결할 수 있으니 훨씬 편합니다. 


> colSums(is.na(Cars93))

      Manufacturer              Model               Type          Min.Price              Price 

                 0                  0                  0                  0                  0 

         Max.Price           MPG.city        MPG.highway            AirBags         DriveTrain 

                 0                  0                  0                  0                  0 

         Cylinders         EngineSize         Horsepower                RPM       Rev.per.mile 

                 0                  0                  0                  0                  0 

   Man.trans.avail Fuel.tank.capacity         Passengers             Length          Wheelbase 

                 0                  0                  0                  0                  0 

             Width        Turn.circle     Rear.seat.room       Luggage.room             Weight 

                 0                  0                  2                 11                  0 

            Origin               Make 

                 0                  0  


 

 

(3) 결측값을 통계 분석 시 제외(미포함): na.rm = TRUE

 

> sum(x)
[1] NA
> mean(x)
[1] NA
> sum(x, na.rm = TRUE)
[1] 40
> mean(x, na.rm = TRUE)
[1] 5

 

결측값이 들어있는 벡터에 대해서 sum(), mean(), sd(), min(), max(), range() 등의 통계 함수를 적용하면 'NA'만 나오게 됩니다. 결측값을 포함하지 말고 통계 함수 계산을 하라는 옵션이 na.rm = TRUE 가 되겠습니다.

 

> sum(Cars93$Luggage.room)
[1] NA
> mean(Cars93$Luggage.room)
[1] NA
> 
> sum(Cars93$Luggage.room, na.rm = TRUE)
[1] 1139
> mean(Cars93$Luggage.room, na.rm = TRUE)
[1] 13.89024

 

위 예제는 결측값이 포함된 데이터 프레임의 특정 변수에 대해 indexing을 해서 통계 함수를 적용해본 경우 입니다. 역시 na.rm = TRUE 옵션을 설정해 주어야 통계 계산이 제대로 됨을 알 수 있습니다.  na.rm =  FALSE 가 디폴트이다보니 na.rm=TRUE를 설정하지 않는 경우 결측값이 포함되어 있으면 NA가 결과로 나타나게 됩니다.

 

 

 

(4) 결측값이 들어있는 행 전체를 데이터 셋에서 제거: na.omit()

 

 na.rm = TRUE 옵션은 원래의 데이터 셋은 그대로 둔채 통계량 계산할 때만 포함하지 않게 됩니다. 따라서 다수의 통계 함수 혹은 다수의 변수에 통계 함수를 사용해야 하는 경우 매번 na.rm = TRUE 옵션을 설정해주는게 번거로울 수 있겠지요?  차라리 원래 데이터 셋에서 결측값을 제거해버리면 되겠다는 생각이 드셨을 겁니다.

 

결측값이 들어있는 행을 통째로 무식하게 제거하는 함수가 na.omit()이며, 좀더 예리하게 특정 행과 열을 지정해서 그곳에 결측값이 있는 경우만 메스로 정밀 수술하는 함수가 complete.cases()가 되겠습니다.

 

> Cars93_1 <- na.omit(Cars93)
> str(Cars93_1)
'data.frame':	82 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 ...
 - attr(*, "na.action")=Class 'omit'  Named int [1:11] 16 17 19 26 36 56 57 66 70 87 ...
  .. ..- attr(*, "names")= chr [1:11] "16" "17" "19" "26" ..

 

 

처음에 Cars93이  "'data.frame': 93 obs. of  27 variables:", 즉 93개의 관측치가 있었는데요,

na.omit(Cars93) 함수를 적용한 후에 Cars93_1 이라는 이름으로 새로 저장해서 str()로 데이터 구조를 보니 "'data.frame': 82 obs. of  27 variables:", 즉 82개 관측치로 총 11개 관측치가 줄어들었음을 알 수 있습니다. 결측값이 하나라도 들어있는 행 11개가 통째로 삭제되어 버렸기 때문입니다.

 

위의 예처럼 결측값이 들어있는 행을 통째로 삭제할 때는 만약의 사태를 대비해서 원본은 그대로 유지하고, 행을 삭제한 데이터 셋을 별도의 이름으로 저장해서 분석을 진행하는 것을 추천합니다.

 

 

 

(5) 특정 행과 열에 결측값이 들어있는 행을 데이터 셋에서 제거 : complete.cases()

 

> sum(is.na(Cars93))
[1] 13
> 

> # Cars93 데이터 프레임의 "Rear.seat.room" 칼럼 내 결측값이 있는 행 전체 삭제

> Cars93_2 <- Cars93[ complete.cases(Cars93[ , c("Rear.seat.room")]), ] > sum(is.na(Cars93_2)) [1] 9 >

> # Cars93 데이터 프레임의 23~24번째 칼럼 내 결측값이 있는 행 전체 삭제

> Cars93_3 <- Cars93[ complete.cases(Cars93[ , c(23:24)]), ] > sum(is.na(Cars93_3)) [1] 0 

> 
> dim(Cars93_3) # 관측값이 82개로서 11개 줄어듬
[1] 82 27

> str(Cars93_3) # 관측값이 82개로서 11개 줄어듬 'data.frame': 82 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 ...

 

 

(6) 결측값을 다른 값으로 대체: dataset$var[is.na(dataset$var)] <- new_value

 

> Cars93$Luggage.room [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 NA NA 20 NA 15 14 17 11 13 14 NA 16 11 11 15 12 12 13 12 18 NA 18 [38] 21 10 11 8 12 14 11 12 9 14 15 14 9 19 22 16 13 14 NA NA 12 15 6 15 11 14 12 14 NA 14 14 16 NA 17 8 17 13 [75] 13 16 18 14 12 10 15 14 10 11 13 15 NA 10 NA 14 15 14 15 > sum(is.na(Cars93$Luggage.room)) [1] 11

> 

> # Luggage.room 변수 내 결측값을 '0'으로 대체

> Cars93_4 <- Cars93
> Cars93_4$Luggage.room[is.na(Cars93_4$Luggage.room)] <- 0
> Cars93_4$Luggage.room
 [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16  0  0 20  0 15 14 17 11 13 14  0 16 11 11 15 12 12 13 12 18  0 18
[38] 21 10 11  8 12 14 11 12  9 14 15 14  9 19 22 16 13 14  0  0 12 15  6 15 11 14 12 14  0 14 14 16  0 17  8 17 13
[75] 13 16 18 14 12 10 15 14 10 11 13 15  0 10  0 14 15 14 15

 

 

 

sum(is.na(Cars93$Luggage.room)) 함수를 사용해 Cars93의 Luggage.room 변수에 보면 11개의 결측값이 있음을 알 수 있습니다.  Luggage.room 변수 내 결측값을 indexing 기법을 활용해서 '0'로 대체하는 방법이 위의 예제가 되겠습니다.

 

물론 '0'이 아니라 다른 값으로도 대체가 가능합니다.  아래의 예제에서는 결측값을 미포함(na.rm = TRUE)했을 때의 Luggage.room의 평균값으로 결측값을 대체하여 보도록 하겠습니다.

 

> Cars93_5 <- Cars93
> Cars93_5$Luggage.room[is.na(Cars93_5$Luggage.room)] <- mean(Cars93_5$Luggage.room, na.rm = TRUE)
> sum(is.na(Cars93_5$Luggage.room))
[1] 0
> Cars93_5$Luggage.room
 [1] 11.00000 15.00000 14.00000 17.00000 13.00000 16.00000 17.00000 21.00000 14.00000 18.00000 14.00000 13.00000
[13] 14.00000 13.00000 16.00000 13.89024 13.89024 20.00000 13.89024 15.00000 14.00000 17.00000 11.00000 13.00000
[25] 14.00000 13.89024 16.00000 11.00000 11.00000 15.00000 12.00000 12.00000 13.00000 12.00000 18.00000 13.89024
[37] 18.00000 21.00000 10.00000 11.00000  8.00000 12.00000 14.00000 11.00000 12.00000  9.00000 14.00000 15.00000
[49] 14.00000  9.00000 19.00000 22.00000 16.00000 13.00000 14.00000 13.89024 13.89024 12.00000 15.00000  6.00000
[61] 15.00000 11.00000 14.00000 12.00000 14.00000 13.89024 14.00000 14.00000 16.00000 13.89024 17.00000  8.00000
[73] 17.00000 13.00000 13.00000 16.00000 18.00000 14.00000 12.00000 10.00000 15.00000 14.00000 10.00000 11.00000
[85] 13.00000 15.00000 13.89024 10.00000 13.89024 14.00000 15.00000 14.00000 15.00000

 

 

 

 

 

(7) 데이터프레임의 모든 행의 결측값을 특정 값(가령, '0')으로 일괄 대체 

: dataset[is.na(dataset)] <- 0

 

> Cars93_6 <- Cars93
> # counting the number of missing values in Cars93 dataset
> sum(is.na(Cars93_6)) # 13
[1] 13
> 
> # converting the missing value in Cars93 dataframe to '0'
> Cars93_6[is.na(Cars93_6)] <- 0
> 
> # counting the number of missing values in Cars93 dataset
> sum(is.na(Cars93_6)) # 0
[1] 0

 

 

 

(8) 데이터프레임의 각 변수의 결측값을 각 변수 별 평균값으로 일괄 대체

: sapply(dataset, function(x) ifelse(is.na(x), mean(x, na.rm=TRUE), x)) 

 

위의 (6)번에서 결측값을 그 열의 평균으로 대체하는 방법을 소개했는데요, 만약 결측값을 포함한 열이 매우 많다면 일일이 변수이름을 지정해가면서 나열해서 입력하기가 번거롭습니다. 이럴 때 sapply() 함수를 사용하면 일괄로 결측값을 포함한 변수에 대해서는 해당 변수의 평균으로 대체하라고 프로그래밍할 수 있습니다. sapply()를 적용하면 matrix를 반환하므로 dataframe으로 만들기 위해서 앞에 data.frame() 을 추가로 붙여주었습니다.

 

 

> # converting the missing value in Cars93 dataframe to each column's mean value
> Cars93_7 <- Cars93[1:20,c("Rear.seat.room", "Luggage.room")]
> 
> colSums(is.na(Cars93_7))
Rear.seat.room   Luggage.room 
             1              3 
> 
> Cars93_7
   Rear.seat.room Luggage.room
1            26.5           11
2            30.0           15
3            28.0           14
4            31.0           17
5            27.0           13
6            28.0           16
7            30.5           17
8            30.5           21
9            26.5           14
10           35.0           18
11           31.0           14
12           25.0           13
13           26.0           14
14           25.0           13
15           28.5           16
16           30.5           NA
17           33.5           NA
18           29.5           20
19             NA           NA
20           31.0           15
> 
> sapply(Cars93_7, function(x) mean(x, na.rm=T))
Rear.seat.room   Luggage.room 
      29.10526       15.35294 
> 
> 
> Cars93_7 <- data.frame(sapply(Cars93_7, 
+                               function(x) ifelse(is.na(x), 
+                                                  mean(x, na.rm = TRUE), x)))
> 
> Cars93_7
   Rear.seat.room Luggage.room
1        26.50000     11.00000
2        30.00000     15.00000
3        28.00000     14.00000
4        31.00000     17.00000
5        27.00000     13.00000
6        28.00000     16.00000
7        30.50000     17.00000
8        30.50000     21.00000
9        26.50000     14.00000
10       35.00000     18.00000
11       31.00000     14.00000
12       25.00000     13.00000
13       26.00000     14.00000
14       25.00000     13.00000
15       28.50000     16.00000
16       30.50000     15.35294
17       33.50000     15.35294
18       29.50000     20.00000
19       29.10526     15.35294
20       31.00000     15.00000

 

 

 


(9) 그룹 별 평균값으로 결측값 대체하기 (filling missing values by group mean)


base 패키지를 사용할 수도 있긴 한데요, dplyr 패키지의 group_by 와 ifelse 조건문을 사용한  mutate 함수로 그룹 별 평균값으로 결측값 채우는 방법을 소개하겠습니다. 


> # make a sample DataFrame

> grp <- c(rep('a', 5), rep('b', 5))

> val <- c(1, 2, 3, NaN, 6, 2, 4, NaN, 10, 8)

> df <- data.frame(grp, val)

> df

   grp val

1    a   1

2    a   2

3    a   3

4    a NaN

5    a   6

6    b   2

7    b   4

8    b NaN

9    b  10

10   b   8



결측값을 제외하고 그룹 'a'와 그룹 'b' 별 평균을 계산해보겠습니다. 


> # mean value by group 'a' and 'b'

> library(dplyr)

> df %>% group_by(grp) %>% summarise(grp_mean = mean(val, na.rm = TRUE))

# A tibble: 2 x 2

  grp   grp_mean

  <fct>    <dbl>

1 a            3

2 b            6



그룹 'a'의 평균은 3, 그룹 'b'의 평균은 6이군요. 그러면 4번째 행에 있는 그룹 'a'의 'val' 칼럼 결측값을 그룹 'a'의 평균 3으로 대체(replace) 또는 채워넣기(fill in)를 해보겠습니다. 그리고 8번째 행에 있는 그룹 'b'의 'val' 칼럼 결측값을 그룹 'b'의 평균 6으로 대체해보겠습니다. 


> df %>% 

+   group_by(grp) %>% 

+   mutate(val = ifelse(is.na(val), mean(val, na.rm=TRUE), val))

# A tibble: 10 x 2

# Groups:   grp [2]

   grp     val

   <fct> <dbl>

 1 a         1

 2 a         2

 3 a         3

 4 a         3

 5 a         6

 6 b         2

 7 b         4

 8 b         6

 9 b        10

10 b         8 


 

--------------------

 

우에서 NA(Not Available)에 대해서 소개를 했는데요, 참고로 벡터에서 NaN (Not a Number), 무한값 Inf (Infinity), 유한값 finite 확인하는 방법도 마저 소개하겠습니다.

 

유의할 것은, is.na()에서 NA 뿐만 아니라 NaN 도 TRUE 를 반환합니다.

 

 

> my_data <- c(-1, 0, 10, NA, NaN, Inf)
>
> my_data
[1]  -1   0  10  NA NaN Inf
>
> is.finite(my_data)
[1]  TRUE  TRUE  TRUE FALSE FALSE FALSE
>
> is.na(my_data)
[1] FALSE FALSE FALSE  TRUE  TRUE FALSE
>
> is.nan(my_data)
[1] FALSE FALSE FALSE FALSE  TRUE FALSE
>
> is.infinite(my_data)
[1] FALSE FALSE FALSE FALSE FALSE  TRUE

 

 


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


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

 


Posted by R Friend R_Friend

댓글을 달아 주세요

  1. Anthony 2015.12.15 22:07  댓글주소  수정/삭제  댓글쓰기

    감사합니다~ 많은 도움이 됐습니다.^_^

  2. AshtrayK 2016.08.29 16:43 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 kjh입니다 ㅎㅎ
    오늘도 어김없이..
    is.na와 complete.cases 는
    TRUE FALSE를 리턴하는 함수라고 보면 되고
    na.omit은 결측값이 제거된 데이터 객체를 반환하는 것인가요?

  3. 셀리던트 2016.12.04 18:05  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 강좌 열심히 보며 따라 해보고 있습니다. 덕분에 도움이 많이 되네요 한가지 궁금한 점이 생겨서 전의 포스팅의 sapply / lapply를 활용하여 Cars93 변수별 결측값 구하기를 하려고 sapply(Cars93,sum(is.na))으로 실행해보니 오류가 나서 질문드립니다. Apply함수를 사용할때 이중 함수(sum(is.na)처럼) 사용은 불가능한가요?

    • R Friend R_Friend 2016.12.04 20:14 신고  댓글주소  수정/삭제

      is.na() 가 칼럼 단위가 아니라 원소(element) 단위로 TRUE 또는 FALSE 블리언값을 반환합니다.

      따라서 sapply(Cars93, sum(is.na))는 에러가 납니다.

      원하시는 분석을 하려면 colSums(is.na(Cars93)) 을 사용하시면 됩니다.

      참고로, sum(is.na(Cars93)) 와
      sum(sapply(Cars93, is.na)) 모두 전체 93개의 관측치 원소 중에서 결측값인 원소의 총 개수의 합인 13을 반환합니다.

  4. kusskt 2017.03.30 02:34  댓글주소  수정/삭제  댓글쓰기

    올려주시는 블로그 내용만 다 봐도 R은 웬만큼 다 할 수 있을 것 같습니다. R 연습하는 데 도움 굉장히 많이 되어서 감사하다는 말씀 전하구요, 열심히 공부하고 있습니다. ㅎㅎ

    위 내용 중에 데이터 프레임 결측값 대체에서 cars93[is.na(cars93)] <- 0 이라고 해도 다 대체가 되더군요. 너무 잘 설명해 주셨는데, 이 방식이 변수별 NA값 대체에 한정되어 있는 것처럼 보여서, 데이터 프레임 파트에 추가하셔도 좋을 것 같아서 글 남깁니다.
    항상 감사합니다!

  5. 꾸리꾸리 2019.05.09 21:04  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 추가로 연습하다 궁금한 부분이 생겨 질문 드립니다.

    이전의 transform으로 계산 ( 곱, 나누기, log값)을 진행 하였습니다

    계산 값이 무한대로 표시가 되면서 Inf and -Inf로 나타났을시 혹시 이부분을 문자 형태가 아닌 숫자 ( Inf 가 아닌 0.xxxxxx) 소수점으로 표시하는 방법이 있는지 궁금합니다.

    • R Friend R_Friend 2019.05.09 21:13 신고  댓글주소  수정/삭제

      지수표기를 숫자표기로 변경하는 방법은

      https://rfriend.tistory.com/224

      포스팅을 참고하시기 바랍니다.

    • 꾸리꾸리 2019.05.09 21:16  댓글주소  수정/삭제

      감사합니다 빠른 답변 정말 감사드립니다.

    • 꾸리꾸리 2019.05.09 21:35  댓글주소  수정/삭제

      안녕하세요 질문드립니다.

      포스팅 참고 후, 다시 수정을 진행중입니다.

      하지만, Inf & -Inf 를 처리하는 방법에 대해서는 아직 이해가 부족한것 같습니다.

      log값을 취하여 Inf & -Inf가 나온것을 숫자로 변환하는 쉬운 방법은 어떤 포스팅을 참고해야 할까요?

      계산후, 무한대 결측값을 이용하여 플롯을 작성해야 하는데 통계분석 진행에 큰 어려움이 생겨 질문드렸습니다.

    • R Friend R_Friend 2019.05.09 21:48 신고  댓글주소  수정/삭제

      원하시는 아웃풋을 제가 정확하게 이해한건지 좀 불확실한데요, 일단 제가 이해한데로 코드를 짜봤습니다. 아래 참고하시기 바랍니다.

      > # fixed notation
      > options(scipen=100)
      >
      > # infinite value
      > x <- Inf
      > is.infinite(x)
      [1] TRUE
      > x
      [1] Inf
      >
      > # converting infinite number into big integer
      > if (is.infinite(x) == TRUE) {
      + x = 100000
      + }
      > x
      [1] 100000


      ################
      그리고 참고로, 로그, 지수 관계에 대해서는 https://rfriend.tistory.com/295 포스팅을 참고하시기 바랍니다.

    • 2019.05.09 22:01  댓글주소  수정/삭제

      비밀댓글입니다

    • R Friend R_Friend 2019.05.09 22:12 신고  댓글주소  수정/삭제

      0에 대해서 log를 취하면 -Inf 가 나옵니다. 따라서 보통 log 연산할 때 매우 매우 작은 수를 더해서 -Inf 가 나오는 것을 방지하곤 합니다.

      > x <- 0
      > log2(x)
      [1] -Inf
      > log2(x+0.0000000001)
      [1] -33.21928

      ps. 저도 회사업무랑 공부랑 너무 바빠서 퇴근 후에도 시간을 쪼개서 쓰고 있습니다. 그래서 이렇게 계속 추가 답변을 드리기 힘이 듭니다. 이해해주세요. ㅠ_ㅠ

    • 꾸리꾸리 2019.05.09 22:15  댓글주소  수정/삭제

      답변 감사드립니다!

      서두없는 질문에도 친절히 답변해주셔서 감사합니다.

      그래도 어느정도 솔루션을 찾게되어 진행할 수 있게 되었습니다.

      감사합니다 !

    • R Friend R_Friend 2019.05.09 22:29 신고  댓글주소  수정/삭제

      어느정도 해결이 되었다니 정말 다행입니다.

  6. Tacong 2019.11.07 16:48  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 선생님 게시글 보면서 열심히 공부하고 있으며 많은 도움이 되고 있습니다 ㅜ 감사합니다

    궁금한게 있는데요

    8) 데이터프레임의 각 변수의 결측값을 각 변수 별 평균값으로 일괄 대체

    여기서 sapply(Cars93_7, function(x) mean(x, na.rm = T))

    function(x) mean(x) 이건 갑자기 왜 나오는 것인지 모르겠습니다 ㅜㅜ


    그리고 ifelse(is.na(x) 이 함수는 뭘 뜻하는건지 모르겠습니다 ㅜ

    • R Friend R_Friend 2019.11.07 17:21 신고  댓글주소  수정/삭제

      안녕하세요 Tacong님,

      supply(DayaFrame, Function) 은 데이터프레임 내 모든 변수들에게 함수 명령어를 동시에 적용하여 벡터 또는 행렬을 반환해주는 굉장히 유용한 함수입니다. (==> rfriend.tistory.com/33 참조)

      즉, 위의 예에서는 sapply(df, function)의 function 자리에 Cars93 데이터프레임의 모든 칼럼들의 결측값 여부를 확인(is.na(x))해서 TRUE이면 평균으로 결측값 대체하고, 결축측이 아니면 원래 값을 그대로 두라는 ifelse 조건절의 함수가 필요합니다. (==> ifelse 조건절은 rfriend.tistory.com/91 참조)

      두개 이상의 논리값 벡터에 대한 판단에 사용하는 ifelse(조건식, 표현식1, 표현식2) 형태는 "만약 조건식이 TRUE 이면 => Then 표현식1을 수행하고, FALSE이면 표현식2를 실행하라" 라는 의미입니다.

      사용자정의함수 (==> rfriend.tistory.com/96)를 따로 정의해놓고 sapply(df, functiin)의 function 자리에 넣을수도 있지민, 1줄짜리의 간단한 함수라면 이번 예제처럼 함수의 (사용자정의)이름없이 바로 function(x) 하고 바로 함수 표현식을 써줄 수도 있습니다. (마치 python의 lambda 와 유사함)

      이번 예제를 잘 익혀두시면 매우 유용한게요, 만약 apply 함수를 안쓴다고 하면 for loop 반복문을 써야 하니 코드도 길어지고 (큰 데이터셋의 경우) 속도도 느려집니다.

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