[참고] 이 포스팅은 Hadley Wickham & Garrett Grolemund의 R for Data Science책의 챕터 5장을 개인적으로 중요한 내용을 뽑아내어 재구성한 포스팅입니다.

이 포스팅에 사용되는 라이브러리는 다음과 같다.

library(tidyverse)

데이터 만들기

함수가 어떻게 작동하는지 알아보기 위해서 작은 데이터 셋을 만들어 보자.

set.seed(123)
mydata <- data_frame(소속학급 = sample(3, 10, replace = TRUE),
                     학생번호 = sample(30, 10, replace = TRUE),
                     중간고사 = as.integer(rnorm(10, mean = 80, sd = 5)),
                     기말고사 = as.integer(rnorm(10, mean = 70, sd = 7)))
mydata
## # A tibble: 10 × 4
##    소속학급 학생번호 중간고사 기말고사
##       <int>    <int>    <int>    <int>
## 1         1       29       86       62
## 2         3       14       81       68
## 3         2       21       82       62
## 4         3       18       80       64
## 5         3        4       77       65
## 6         1       27       88       58
## 7         2        8       82       75
## 8         3        2       70       71
## 9         2       10       83       62
## 10        2       29       77       78

위의 데이터는 한 반이 30명으로 이루어진 3개의 학급에서 임으로 10명의 학생들의 성적을 기록한 데이터라고 생각하자. 데이터의 첫번째 열은 학급, 두번째 열은 학생의 학급 번호, 마지막 두 열에는 학생의 중간고사과 기말고사 성적이 기록되어 있다.

  1. filter() : 특정 조건을 만족하는 데이터를 필터링 - 가로행( 관측값) 기준으로 결과 값이 나옴
  2. arrange() : 데이터 정렬에 사용
  3. select() : 특정 열(variable)을 선택.
  4. mutate() : 기존의 변수(열)를 가지고 새로운 칼럼(column)을 생성.
  5. summarize() : 열 전체를 입력값으로 한 함수를 통하여 새로운 데이터를 생성.

1. filter() 함수를 이용한 데이터 필터링

1반에 속한 학생들만 필터링 하고싶을 때는 다음과 같이 할 수 있다.

filter(mydata, 소속학급 == 1)
## # A tibble: 2 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       29       86       62
## 2        1       27       88       58

조건이 여러 개일 경우는 다음과 같이 쉼표로 뒤에 이어서 써주면 된다. 다음은 1반 학생 중 번호가 10번 인 학생을 필터링하는 방법이다. 이렇게 쉼표로 이어서 쓰면 한국어로 ’그리고’를 의미하는 논리 필터임.

filter(mydata, 소속학급 == 1, 학생번호 == 27)
## # A tibble: 1 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       27       88       58

그렇다면 여러개의 조건 중 하나만 만족해도 참인 ’이거나’의 경우는 어떻게 구현할까? 이 경우 곧 배우게 될 논리 오퍼레이터인 |를 사용하여 구현할 수 도 있지만, 먼저 %in% 명령어를 사용하여 구현하는 방법을 배워보자. 다음은 3개의 반 중 1반에 속하거나 2반에 속하는 학생들의 데이터를 필터하는 방법이다.

filter( mydata, 소속학급 %in% c(1,2) )
## # A tibble: 6 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       29       86       62
## 2        2       21       82       62
## 3        1       27       88       58
## 4        2        8       82       75
## 5        2       10       83       62
## 6        2       29       77       78

앞에서 배운 위의 두 코드를 응용하면 ’그리고’과 ’이거나’가 함께 들어간 필터링도 생성가능하다.

filter(mydata, 소속학급 %in% c(1, 2), 중간고사 == 82)
## # A tibble: 2 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        2       21       82       62
## 2        2        8       82       75

다음은 R에서 사용되는 논리 연산자들이다.

  1. & : 그리고
  2. | : 이거나
  3. ! : 부정
  4. xor : 같지 않은
  5. >, <, <=, >= : 관계를 나타내는 표현들

바로 앞에서 사용한 코드를 논리 연산자를 사용해서 나타내어 보면 다음과 같다.

filter( mydata, ((소속학급 == 1) | (소속학급 == 2)) & 중간고사 == 82 )

응용 코드

이제까지 배운 코드를 이용하면 다음과 같은 복잡한 필터링이 가능하다.

filter(mydata,  소속학급  %in% c(1, 3),
      (중간고사 > 60 & 중간고사 < 80), 
      (기말고사 < 70 | 기말고사 > 90))
## # A tibble: 1 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        3        4       77       65

위 코드는 1반과 3반에 속한 학생들 중에서 중간고사 성적이 60점에서 80점 사이이면서 기말고사는 70점 아래이거나 90점 이상을 맞은 학생을 걸러내는 코드로 해석할 수 있다.

2. arrange()를 사용하여 정렬하기

앞에서 배운 필터링의 결과값은 아래와 같이 정렬이 되지 않는 상태로 나왔다.

filter( mydata, 소속학급 %in% c(1,3) )
## # A tibble: 6 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       29       86       62
## 2        3       14       81       68
## 3        3       18       80       64
## 4        3        4       77       65
## 5        1       27       88       58
## 6        3        2       70       71

이 결과값을 좀 더 보기 좋게 바꾸기 위해서는 arrange() 함수의 기능을 알아야 한다. 다음의 코드는 위의 결과값을 저장한 후 학급순으로 정렬하는 코드이다.

class1or3 <- filter( mydata, 소속학급 %in% c(1,3) )
arrange(class1or3, 소속학급)
## # A tibble: 6 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       29       86       62
## 2        1       27       88       58
## 3        3       14       81       68
## 4        3       18       80       64
## 5        3        4       77       65
## 6        3        2       70       71

만약 학급별 정렬후 같은반 학생들끼리 학생번호가 작은 순부터 다시 정렬하고 싶다면, 두번째 정렬기준을 추가해 주면 된다.

arrange(class1or3, 소속학급, 학생번호)
## # A tibble: 6 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       27       88       58
## 2        1       29       86       62
## 3        3        2       70       71
## 4        3        4       77       65
## 5        3       14       81       68
## 6        3       18       80       64

추가적으로 만약 소속학급은 오름차순 정렬이지만 중간고사 성적의 경우는 내림차순으로 정렬하고 싶을때는 desc()함수를 사용하도록 하자.

arrange(class1or3, 소속학급, desc(중간고사))
## # A tibble: 6 × 4
##   소속학급 학생번호 중간고사 기말고사
##      <int>    <int>    <int>    <int>
## 1        1       27       88       58
## 2        1       29       86       62
## 3        3       14       81       68
## 4        3       18       80       64
## 5        3        4       77       65
## 6        3        2       70       71

3. select()를 사용한 subdataframe 만들기

select() 함수는 주어진 데이터의 세로열을 선택하는데에 사용되는 함수이다. 데이터에 따라서 열의 개수가 천개가 넘어가는 경우, select() 함수를 사용하여 선택하는 방법이 유용할 것이다. 다음의 코드를 참고하여, 함수의 사용법을 알아보도록 하자.

select(mydata, 중간고사, 기말고사)
## # A tibble: 10 × 2
##    중간고사 기말고사
##       <int>    <int>
## 1        86       62
## 2        81       68
## 3        82       62
## 4        80       64
## 5        77       65
## 6        88       58
## 7        82       75
## 8        70       71
## 9        83       62
## 10       77       78

위의 코드는 반과 학생의 번호없이 주어진 mydata의 성적만을 선택하였다. 다음과 같이 ‘학생번호’ 열에서부터 ’기말고사’열까지 한꺼번에 선택할 수 도 있다.

select(mydata, 학생번호:기말고사)
## # A tibble: 10 × 3
##    학생번호 중간고사 기말고사
##       <int>    <int>    <int>
## 1        29       86       62
## 2        14       81       68
## 3        21       82       62
## 4        18       80       64
## 5         4       77       65
## 6        27       88       58
## 7         8       82       75
## 8         2       70       71
## 9        10       83       62
## 10       29       77       78

또, ‘학생번호’ 열만을 제외하고 선택하는 방법은 다음과 같다.

select(mydata, -학생번호)
## # A tibble: 10 × 3
##    소속학급 중간고사 기말고사
##       <int>    <int>    <int>
## 1         1       86       62
## 2         3       81       68
## 3         2       82       62
## 4         3       80       64
## 5         3       77       65
## 6         1       88       58
## 7         2       82       75
## 8         3       70       71
## 9         2       83       62
## 10        2       77       78

select() 함수에는 부가 함수들이 있는데, 이 함수들은 행이름을 기준으로 조건을 걸어서 선택할 수 있도록 해준다. 예를 들어, 주어진 데이터에서 행이름이 ’고사’로 끝나는 행들만을 선택하려면 다음과 같이 하면 된다. 좀 더 자세한 정보는 ?select를 사용하여 도움말을 읽어보도록 하자.

select(mydata, ends_with("고사"))
## # A tibble: 10 × 2
##    중간고사 기말고사
##       <int>    <int>
## 1        86       62
## 2        81       68
## 3        82       62
## 4        80       64
## 5        77       65
## 6        88       58
## 7        82       75
## 8        70       71
## 9        83       62
## 10       77       78

열 이름 바꾸기와 열 순서 변경 with rename(), everything()

기말고사 열의 이름을 중간고사2로 변경하기 위해서는 다음과 같이 rename() 함수를 사용하자. 주의할 점은 바꾸고 싶은 열의 이름을 오른쪽에 새롭게 부여할 이름을 왼쪽에 적는다. ’중간고사2’라는 새로운 열이름을 생성한 후 기존에 있는 열을 새로운 열에 넣는다는 의미로 받아드리면 될 것이다.

rename(mydata, 중간고사2 = 기말고사)
## # A tibble: 10 × 4
##    소속학급 학생번호 중간고사 중간고사2
##       <int>    <int>    <int>     <int>
## 1         1       29       86        62
## 2         3       14       81        68
## 3         2       21       82        62
## 4         3       18       80        64
## 5         3        4       77        65
## 6         1       27       88        58
## 7         2        8       82        75
## 8         3        2       70        71
## 9         2       10       83        62
## 10        2       29       77        78

다음은 열의 순서를 바꾸는 방법에 대하여 알아보자. 성적을 먼저 배치하고 소속학급과 번호를 나중에 배치하기 위해서 다음과 같이 하 수 있다.

select(mydata, 중간고사, 기말고사, everything())
## # A tibble: 10 × 4
##    중간고사 기말고사 소속학급 학생번호
##       <int>    <int>    <int>    <int>
## 1        86       62        1       29
## 2        81       68        3       14
## 3        82       62        2       21
## 4        80       64        3       18
## 5        77       65        3        4
## 6        88       58        1       27
## 7        82       75        2        8
## 8        70       71        3        2
## 9        83       62        2       10
## 10       77       78        2       29

4. mutate()를 이용하여 새로운 열 만들기

주어진 학생 성적 데이터에 마지막 열에 중간고사와 기말고사의 성적을 합하여 ‘합계’ 열을 만들어 보도록 하자.

mutate(mydata, 합계 = 중간고사 + 기말고사)
## # A tibble: 10 × 5
##    소속학급 학생번호 중간고사 기말고사  합계
##       <int>    <int>    <int>    <int> <int>
## 1         1       29       86       62   148
## 2         3       14       81       68   149
## 3         2       21       82       62   144
## 4         3       18       80       64   144
## 5         3        4       77       65   142
## 6         1       27       88       58   146
## 7         2        8       82       75   157
## 8         3        2       70       71   141
## 9         2       10       83       62   145
## 10        2       29       77       78   155

다음과 같이 mutate()함수는 명령어 안에서 새로만든 열을 다음 열을 만드는 데에 사용할 수 있다.

mutate(mydata, 합계 = 중간고사 + 기말고사,
               평균 = 합계 / 2)
## # A tibble: 10 × 6
##    소속학급 학생번호 중간고사 기말고사  합계  평균
##       <int>    <int>    <int>    <int> <int> <dbl>
## 1         1       29       86       62   148  74.0
## 2         3       14       81       68   149  74.5
## 3         2       21       82       62   144  72.0
## 4         3       18       80       64   144  72.0
## 5         3        4       77       65   142  71.0
## 6         1       27       88       58   146  73.0
## 7         2        8       82       75   157  78.5
## 8         3        2       70       71   141  70.5
## 9         2       10       83       62   145  72.5
## 10        2       29       77       78   155  77.5

만약 새롭게 계산한 합계과 평균 값만을 따로 데이터를 만들고 싶은 경우에는 transmute를 사용하자.

transmute(mydata, 합계 = 중간고사 + 기말고사,
                  평균 = 합계 / 2)
## # A tibble: 10 × 2
##     합계  평균
##    <int> <dbl>
## 1    148  74.0
## 2    149  74.5
## 3    144  72.0
## 4    144  72.0
## 5    142  71.0
## 6    146  73.0
## 7    157  78.5
## 8    141  70.5
## 9    145  72.5
## 10   155  77.5

5. summarize()를 이용한 스마트 서머리

우리가 주어진 학생들 성적 데이터를 가지고 있는 선생님의 입장이라고 생각해본다면, 우리는 학생 전체의 성적 평균을 구하고 싶을 것이다. 이 경우 summarized() 함수를 이용하면 쉽게 구할 수 있다.

summarise(mydata, 중간_전체평균 = mean(중간고사), 기말_전체평균 = mean(기말고사))
## # A tibble: 1 × 2
##   중간_전체평균 기말_전체평균
##           <dbl>         <dbl>
## 1          80.6          66.5

만약 반 별 전체 평균을 구하고 싶은 경우에는 어떻게 할까? 반별로 중간고사와 기말고사의 평균을 구해보도록 하자.

by_class <- group_by(mydata, 소속학급)
summarise(by_class, 중간_전체평균 = mean(중간고사), 기말_전체평균 = mean(기말고사))
## # A tibble: 3 × 3
##   소속학급 중간_전체평균 기말_전체평균
##      <int>         <dbl>         <dbl>
## 1        1            87         60.00
## 2        2            81         69.25
## 3        3            77         67.00

파이프(pipe)를 이용한 코딩

tidyverse에는 파이프라는 %>% 명령어가 존재하는데, 리눅스에서 유래한 명령어이다. 이 파이프를 이용하면 좀 더 읽기 쉬운 코드를 작성할 수 있다. 파이프는 파이프 왼쪽에서 계산된 결과 값을 파이프 오른쪽의 함수 제일 첫번째 입력값으로 넘겨주는 역할을 한다. 다음의 코드는 파이프를 이용한 1반 학생들의 데이터에 중간, 기말고사 성적의 평균 변수를 생성하는 코드이다.

filter(mydata, 소속학급 == 1) %>% mutate(평균 = (중간고사 + 기말고사)/2)
## # A tibble: 2 × 5
##   소속학급 학생번호 중간고사 기말고사  평균
##      <int>    <int>    <int>    <int> <dbl>
## 1        1       29       86       62    74
## 2        1       27       88       58    73

지금까지 배운 코드들을 이용하여 1반과 3반의 중간고사와 기말고사 성적의 평균을 기준으로 순차적으로 정리하여 뽑아보도록 하자.

filter(mydata, 소속학급  %in% c(1, 3)) %>%
  mutate(평균 = (중간고사 + 기말고사)/2) %>% 
  arrange(소속학급, desc(평균))
## # A tibble: 6 × 5
##   소속학급 학생번호 중간고사 기말고사  평균
##      <int>    <int>    <int>    <int> <dbl>
## 1        1       29       86       62  74.0
## 2        1       27       88       58  73.0
## 3        3       14       81       68  74.5
## 4        3       18       80       64  72.0
## 5        3        4       77       65  71.0
## 6        3        2       70       71  70.5

정리하며.

이제까지 우리는 tidyverse팩키지 중 하나인 dplyr의 주요 동사 5가지를 알아보았다. 이 다섯가지 동사와 파이프 그리고 R의 base팩키지에서 제공되는 여러 함수들의 조합만으로도 상당히 많은 일을 손쉽게 할 수 있을 것이라 생각한다. 추후 포스팅에서는 여러개의 서브 데이터 프레임이 존재 할 경우 이들의 관계를 이용하여 새로운 데이터 테이블을 만들어 내는 것에 대하여 포스팅 해보도록 하겠다.

Copyright © 2017 Issac Lee. All rights reserved.