R - dplyr 을 통한 데이터 변형과 장점


본 포스팅에서는 tidyverse의 핵심 구성원이라고 할 수 있는 dplyr 을 통해 데이터를 변형하는 기본적인 방법을 basic R과 비교하여 설명하겠습니다


dplyr 의 5가지 핵심 함수

  • -값을 기준으로 선택하라 (filter)
  • -행을 재정렬하라 (arrange)
  • -이름으로 변수를 선택하라 (select)
  • -기존 변수들의 함수로 새로운 변수를 생성하라 (mutate)
  • -많은 값을 하나의 요약값으로 합쳐라 (summarize)

사용할 데이터셋은 뉴욕시에서 2013년에 출발한 336,776 개의 모든 항공편이 포함된 데이터 (nycflights13 패키지의 flights 데이터셋)

library(nycflights13)
library(tidyverse)
nrow(flights)
## [1] 336776

Filter

  • -1월 1일만 고르기
jan <- flights[flights$month == 1 & flights$day == 1, ]
nrow(jan)
## [1] 842
jan <- filter(flights, month == 1, day == 1)
nrow(jan)
## [1] 842
  • -출발 지연 시간이 120 미만인 항공편 고르기
  • -여기서 basic R과 dplyr 의 차이가 드러나는데, basic R 의 경우 arr_delay 변수가 NA 인 경우, 모든 column이 NA인 row를 포함한 dataframe을 반환한다.
  • -basic R의 경우 filter 를 구현할 때, NA 인 경우도 고려해야하므로, dplyr이 더 간단하게 원하는 목적을 달성할 수 있다고 볼 수 있다. 
'under_two <- flights[(flights$arr_delay < 120), ]
nrow(under_two) # arr_delay가 NA인 row가 모든 column이 NA인 row로 추가된다. .
## [1] 326576
sum(is.na(under_two$arr_delay))
## [1] 9430
sum(is.na(under_two$dep_delay))
## [1] 9430
under_two <- filter(flights, arr_delay < 120) 
nrow(under_two) 
## [1] 317146
sum(is.na(under_two$arr_delay))
## [1] 0
sum(is.na(under_two$dep_delay)) 
## [1] 0
# R 기본문법으로 dplyr의 filter를 구현하려면 이렇게 함. 
under_two <- flights[(flights$arr_delay < 120) & !is.na(flights$arr_delay), ]
nrow(under_two) 
## [1] 317146
sum(is.na(under_two$arr_delay))
## [1] 0
sum(is.na(under_two$dep_delay))
## [1] 0
  • -출발 혹은 도착에서 2시간 이상 지연되지 않은 항공편을 모두 찾기
  • -이 때, 두 조건절이 모두 NA 인 경우, basic R 에서는 모든 컬럼이 NA 인 행을 dataframe에 추가하여 반환한다. 
under_two <- flights[!((flights$arr_delay > 120) | (flights$dep_delay > 120)), ]
nrow(under_two) # 둘다 NA인 행 9430개의 행이, 포함되어있음
## [1] 325354
sum(is.na(under_two$arr_delay))
## [1] 9304
sum(is.na(under_two$dep_delay))
## [1] 9304
under_two <- filter(flights, !(arr_delay > 120 | dep_delay > 120)) 
nrow(under_two) # 둘다 NA인 행 제외. 이게 정상적인 결과.
## [1] 316050
sum(is.na(under_two$arr_delay))
## [1] 0
sum(is.na(under_two$dep_delay)) 
## [1] 0
  • -출발 시간, 도착 시간 둘 중 하나가 2시간 이상 지연된 항공편을 모두 찾기
over_two <- flights[((flights$arr_delay > 120) | (flights$dep_delay > 120)), ]
nrow(over_two) # 둘중 하나가 NA 이면서, delay > 120 인 행 포함, 근데 둘 다 NA인 경우 모든 컬럼이 NA인 행을 포함함 (9403 개)
## [1] 20726
sum(is.na(over_two$arr_delay)) # 9430 -> 둘중 하나가 NA인 수
## [1] 9430
sum(is.na(over_two$dep_delay)) #9304 -> 둘다 NA인 수 
## [1] 9304
over_two <- filter(flights, ((arr_delay > 120) | (dep_delay > 120))) # 둘중 하나가 NA 이면서, delay > 120인 행 포함
nrow(over_two) # 정상적인 결과 
## [1] 11422
sum(is.na(over_two$arr_delay)) 
## [1] 126
sum(is.na(over_two$dep_delay)) 
## [1] 0
  • -일반 데이터 프레임의 문제점. 조건이 NA인 경우 모든 컬럼이 NA인 행이 출력된다. 
  • -dplyr 의 filter 는 이러한 문제 없이 원하는 결과를 출력해준다. 
flights[NA,]
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1    NA    NA    NA       NA             NA        NA       NA
##  2    NA    NA    NA       NA             NA        NA       NA
##  3    NA    NA    NA       NA             NA        NA       NA
##  4    NA    NA    NA       NA             NA        NA       NA
##  5    NA    NA    NA       NA             NA        NA       NA
##  6    NA    NA    NA       NA             NA        NA       NA
##  7    NA    NA    NA       NA             NA        NA       NA
##  8    NA    NA    NA       NA             NA        NA       NA
##  9    NA    NA    NA       NA             NA        NA       NA
## 10    NA    NA    NA       NA             NA        NA       NA
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Arrange

  • -날짜별로 오름차순 정렬하기
  • -정렬의 경우, 마찬가지로 dplyr 이 훨씬 직관적이다.
arranged <- flights[order(flights$year, flights$month, flights$day),]
head(arranged)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     1      517            515         2      830
## 2  2013     1     1      533            529         4      850
## 3  2013     1     1      542            540         2      923
## 4  2013     1     1      544            545        -1     1004
## 5  2013     1     1      554            600        -6      812
## 6  2013     1     1      554            558        -4      740
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
arranged <- arrange(flights, year, month, day)
head(arranged)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     1      517            515         2      830
## 2  2013     1     1      533            529         4      850
## 3  2013     1     1      542            540         2      923
## 4  2013     1     1      544            545        -1     1004
## 5  2013     1     1      554            600        -6      812
## 6  2013     1     1      554            558        -4      740
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
  • -내림차순 정렬하기
desc <- flights[order(-flights$arr_delay),]
head(desc)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     9      641            900      1301     1242
## 2  2013     6    15     1432           1935      1137     1607
## 3  2013     1    10     1121           1635      1126     1239
## 4  2013     9    20     1139           1845      1014     1457
## 5  2013     7    22      845           1600      1005     1044
## 6  2013     4    10     1100           1900       960     1342
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
desc <- arrange(flights, desc(arr_delay))
head(desc)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     9      641            900      1301     1242
## 2  2013     6    15     1432           1935      1137     1607
## 3  2013     1    10     1121           1635      1126     1239
## 4  2013     9    20     1139           1845      1014     1457
## 5  2013     7    22      845           1600      1005     1044
## 6  2013     4    10     1100           1900       960     1342
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>

Select

  • -select 함수는 열을 선택하는 함수이다.
selected <- flights[, c("year", "month", "day")]
head(selected)
## # A tibble: 6 x 3
##    year month   day
##   <int> <int> <int>
## 1  2013     1     1
## 2  2013     1     1
## 3  2013     1     1
## 4  2013     1     1
## 5  2013     1     1
## 6  2013     1     1
selected <- select(flights, year, month, day)
head(selected)
## # A tibble: 6 x 3
##    year month   day
##   <int> <int> <int>
## 1  2013     1     1
## 2  2013     1     1
## 3  2013     1     1
## 4  2013     1     1
## 5  2013     1     1
## 6  2013     1     1

Mutate

  • -mutate는 새로운 열을 추가하는 함수이다.
  • -dplyr은 select를 할 때 end_with function 을 통해 해당 문자로 끝나는 컬럼을 선택할 수도 있고, : 를 통해 두 문자의 사이에 있는 컬럼을 선택할 수도 있다. 
  • -따라서 열 선택에 있어 더욱 많은 기능을 짧은 코드로 구현할 수 있으며, 새로운 변수를 정의할 때도, 한 문장에 정의할 수 있어 코드의 길이가 짧아지고 가독성이 좋아진다. 
flights_sml <- select(flights, year:day, ends_with("delay"), distance, air_time)
flights_sml_new <- mutate(flights_sml, gain=arr_delay - dep_delay, speed = distance / air_time * 60)
nrow(flights_sml_new)
## [1] 336776
  • -새 변수만을 남기고 싶다면 transmute를 사용한다. 
flights_transmute <- transmute(flights, gain = arr_delay - dep_delay,
                               hours = air_time / 60, 
                               gain_per_hour = gain / hours)
nrow(flights_transmute)
## [1] 336776

Summarize

  • -데이터 프레임을 하나의 행으로 축약한다. 
# dplyr을 사용한 방법
summarize(flights, delay = mean(dep_delay, na.rm = TRUE), delay_sd = sd(dep_delay, na.rm=TRUE))
## # A tibble: 1 x 2
##   delay delay_sd
##   <dbl>    <dbl>
## 1  12.6     40.2
  • -summarize 함수는 group_by (dplyr 에서 제공하는 함수) 와 보통 함께 사용하는 경우가 많다. 이것은 dplyr 의 이전 버전이라고 할 수 있는 plyr에서도 잘 구현되어 있었는데, dplyr 이 되면서 조금 더 이해하기 쉽게 문법이 바뀌었다. 
by_day <- group_by(flights, year, month, day)
summarize(by_day, delay = mean(dep_delay, na.rm = TRUE))
## # A tibble: 365 x 4
## # Groups:   year, month [12]
##     year month   day delay
##    <int> <int> <int> <dbl>
##  1  2013     1     1 11.5 
##  2  2013     1     2 13.9 
##  3  2013     1     3 11.0 
##  4  2013     1     4  8.95
##  5  2013     1     5  5.73
##  6  2013     1     6  7.15
##  7  2013     1     7  5.42
##  8  2013     1     8  2.55
##  9  2013     1     9  2.28
## 10  2013     1    10  2.84
## # ... with 355 more rows

파이프로 여러 작업 결합하기

  • -파이프 명령어인 %>% 를 통해 dplyr 을 통한 데이터 변형 작업을 더욱 직관적이고, 가독성있게 수행할 수 있다. 
  • -각 위치에 대해 거리와 평균 지연 사이의 관계를 탐색하고 싶은 경우, 파이프를 사용하지 않는 방법은 아래와 같다. 
by_dest <- group_by(flights, dest)
delay <- summarize(by_dest, count = n(),
                   dist = mean(distance, na.rm = TRUE),
                   delay = mean(arr_delay, na.rm = TRUE))
delay <- filter(delay, count > 20, dest != "HNL")
ggplot(data = delay, mapping = aes(x = dist, y = delay)) + 
  geom_point(aes(size = count), alpha = 1/3) + 
  geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

  • -파이프를 사용하면? 
  • -아래와 같이 임시 변수를 따로 놓지 않으면서 단 한 번의 logic 으로 똑같은 목적을 달성할 수 있다! 
  • -이러한 파이프 방법은 일련의 데이터 변형 작업을 의미 단위로 묶어 코드가 모듈화가 되기 때문에 유저가 코드를 쉽게 구현하고 이해할 수 있도록 도와준다. 
  • -다만 파이프가 너무 길어지면 유용한 임시변수를 두는 방법이 효율적일 수 있다. 
delay <- flights %>% 
  group_by(dest) %>%
  summarize(count = n(),
            dist = mean(distance, na.rm=TRUE),
            delay = mean(arr_delay, na.rm=TRUE)
            ) %>% 
  filter(count > 20, dest != "HNL")
ggplot(data = delay, mapping = aes(x = dist, y = delay)) + 
  geom_point(aes(size = count), alpha = 1/3) + 
  geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'