Post List

2019년 11월 15일 금요일

dtplyr: dplyr의 편리함과 data.table의 속도를 그대로!



R 내에서 데이터 처리시 가장 많이 사용되는 패키지는 dplyr 입니다. 개인적으로 dplyr을 쓰지 않는 다는 것은 R의 1%도 활용하지 못한다는 생각이 들 정도입니다. 그러나 데이터 양이 늘어날수록 속도가 느려지는 것은 당연합니다.

물론 data.table 패키지라는 해결책도 있습니다. R 뿐만 아니라 다른 언어들과 비교해도 속도에서 만큼은 깡패급으로 빠릅니다. 그러나 사용하기에 문법이 너무 복잡해 접근이 쉽지 않습니다.

(아무리 외워도 외워지지 않는 그놈의 DT[i, j, by].....)


이런 문제를 해결하기 위해 Rstudio에서 새로운 해법을 내놓았습니다. dplyr 문법을 입력하면 data.table 명령어로 래핑하여 데이터를 처리하는, 즉 dplyr의 편리함과 data.table의 속도를 둘 다 잡은 dtplyr 패키지를 선보였습니다.


두말할 필요없이 속도 비교를 해보도록 합시다.

library(nycflights13)
library(data.table)
library(dtplyr)
library(dplyr, warn.conflicts = FALSE)
library(microbenchmark)
library(ggplot2)

먼저 필요한 패키지들을 불러옵니다. dplyr의 경우 warn.conflicts() 인자를 FALSE로 두어 충돌 메세지를 끄도록 합니다.
CRAN 버젼 dtplyr 설치시 에러가 날 수 있습니다. 이는 연결 패키지인 data.table이 최근에 업데이트 되어서이며, data.table을 제거 후 인스톨 한후 dtplyr을 설치하면 잘 설치 됩니다.
# load data
df = flights
dim(df)
## [1] 336776     19
head(df)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##                            
## 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 , arr_delay ,
## #   carrier , flight , tailnum , origin , dest ,
## #   air_time , distance , hour , minute ,
## #   time_hour 
필요한 flight 데이터를 불러오도록 합니다. 총 336776개의 행으로 구성되어 있습니다.

df_dt = data.table(df)
df_tb = as_tibble(df)
df_lz = lazy_dt(df)
데이터 테이블을 각 형식에 맞게 바꿔주도록 합니다. data.table() 함수를 이용해 데이터테이블 형태로, as_tibble() 함수를 이용해 티블 형태로, 마지막으로 dtplyr 패키지의 lazy_dt() 함수를 이용해 dtplyr_step_first 형태로 바꾸어 줍니다. 해당 형식을 통해 dplyr 명령어를 data.table 명령어로 래핑하여 데이터를 처리하게 됩니다.

summary(df_lz)
##               Length Class      Mode       
## parent        19     data.table list       
## vars          19     -none-     character  
## groups         0     -none-     character  
## implicit_copy  1     -none-     logical    
## needs_copy     1     -none-     logical    
## env            5     -none-     environment
## name           1     -none-     name
summary()를 통해 확인해보면 data.table 클래스에 굉장히 독특한 형태로 생겼습니다.
이제 본격적으로 속도를 비교해보도록 하겠습니다. 먼저 필터에 대한 속도 비교입니다.

dtply을 이용할 경우 lazy_dt()를 통해 변경된 데이터에 기존 dplyr 명령어를 그대로 사용해서 데이터처리를 하며, as_tibble() 을 통해 티블 형태로 변경해주길 권장합니다. (그렇지 않으면 용량이 매우 큰 dtplyr_step_first 형태로 남아있게 됩니다.)
results = microbenchmark(
  `data.table` = df_dt[origin == 'JFK' & carrier == 'AA'] ,
  `dplyr` = df_tb %>% filter(origin == 'JFK' & carrier == 'AA'),
  `dtplyr` = df_lz %>% filter(origin == 'JFK' & carrier == 'AA') %>% as_tibble(),
  times = 100
)

results
## Unit: milliseconds
##        expr    min      lq      mean   median       uq     max neval cld
##  data.table 5.2220 6.92075  7.842533  7.49525  8.31910 19.4979   100 a  
##       dplyr 8.1038 9.33090 11.130208 10.14395 11.50605 27.7445   100   c
##      dtplyr 6.5998 8.35225  8.902763  8.83630  9.42860 11.7533   100  b
autoplot(results) +
  aes(fill = expr) +
  theme_bw() +
  labs(title = "Filter")

data.table이 압도적으로 빠르며, 그뒤가 dtplyr, dplyr 순입니다. 물론 data.table과 dtplyr은 간혹 속도가 매우 오래 걸리는 경우가 있어 mean 값이 증가하는 경우도 있습니다.
그러나 data.table와 dtplyr 진정한 위력은 매우 복잡한 데이터를 처리하는데 있습니다. 이번에는 그룹화, 통계값 계산, 필터링 이라는 복잡한 데이터처리를 해보도록 하겠습니다.

results2 = microbenchmark(

  `data.table` = df_dt[, .(mean_delay = mean(dep_delay, na.rm = TRUE)),
        by = c('year', 'month', 'day', 'carrier', 'origin')][mean_delay >= 10],

  `dplyr` = df_tb %>%
    group_by(year, month, day, carrier, origin) %>%
    summarize(mean_delay = mean(dep_delay, na.rm = TRUE)) %>%
    ungroup() %>%
    filter(mean_delay >= 10),

  `dtplyr` = df_lz %>%
    group_by(year, month, day, carrier, origin) %>%
    summarize(mean_delay = mean(dep_delay, na.rm = TRUE)) %>%
    ungroup() %>%
    filter(mean_delay >= 10) %>%
    as_tibble(),

  times = 100
)

results2
## Unit: milliseconds
##        expr     min       lq     mean   median       uq      max neval cld
##  data.table 14.1369 16.05185 17.85918 16.82690 18.81835  25.7218   100 a  
##       dplyr 71.1181 81.33950 91.00665 90.43740 99.06195 130.1543   100   c
##      dtplyr 14.9752 19.12145 22.18545 20.24325 21.60995 148.0419   100  b
autoplot(results2) +
  aes(fill = expr) +
  theme_bw() +
  labs(title = "group_by, mean, filter")


data.table과 dtply이 dplyr에 비해 압도적인 속도 차이를 보입니다. 이러한 차이는 데이터 크기가 늘어날수록, 그리고 데이터 처리 구조가 복잡해 질수록 늘어나게 됩니다.
이처럼 dtplyr은 기존 dplyr 문법을 활용하여 data.table과 거의 비슷한 성능을 내는 보물같은 패키지입니다.

댓글 1개:

  1. 우왕 dtplyr.. 엄청난 패키지를 알게 된 거 같네요 당장 사용해봐야겠어요!!! 감샤합니다ㅎㅎ

    답글삭제