{plyr}은 가라! {dplyr}이 왔다!

dplyr package release: "Fast and easy data munging, with dplyr"
Join and set operations come to dplyr

{dplyr} 패키지 후속편이 등장했습니다. 편리함 때문에 {plyr} 패키지에 밀렸던 기억이 나는데 속도를 바짝 올리고 %.% 오퍼레이터를 새로 도입해서 편의성을 높였습니다.

아마 R의 aggregate()를 분석 과정에서 채택하는 경우는 드물텐데요...
data.frame 객체에서 group별로 집합함수를 적용할 때가 많습니다.
코딩도 복잡하고 속도도 느려서 R의 {stat} 패키지의 기본 함수는 잘 쓰이지 않습니다.

예를 들어,

> install.packages("Lahman")
# Sean Lahman's Baseball Database
> require(Lahman)

야구 데이터를 불러서...

> head(Batting[,c("playerID","R")])
# 선수별 도루
결과는
   playerID R
1 aardsda01 0
2 aardsda01 0
3 aardsda01 0
4 aardsda01 0
5 aardsda01 0
6 aardsda01 0

이제 선수별 도루 합계를 구하고 크기 순서대로 정렬하려면 다음과 같이 합니다.

test.stats <- function(data) {
  totals <- aggregate(. ~ playerID, data=data[,c("playerID","R")], sum)
  ranks <- sort.list(-totals$R)
  totals[ranks[1:5],]
}

함수를 만들었으니 시간을 재 봅시다. i7 프로세서에서 싱글코어로 계산할 때...

> system.time(test.stats(Batting))
 사용자  시스템 elapsed
   0.33    0.00    0.36

이렇게 나옵니다.

{dplyr}을 시험해 봅시다.

> install.packages("dplyr")
> library(dplyr)

test.dplyr <- function(data) {
  total <- data %.% group_by(playerID) %.% summarise(total=sum(R)) %.% arrange(desc(total))
  head(total,n=5)
}

함수를 실행한 다음, 실행 시간을 측정하면 다음과 같습니다.

> system.time(test.dplyr(Batting))
 사용자  시스템 elapsed
   0.03    0.00    0.03

data.table로 동일한 작업을 한다고 하면

> library(data.table)
test.data.table <- function(data) {
  dt.test <- data.table(data[,c("playerID","R")])
  dt.result <- dt.test[,sum(R),by="playerID"]
  head(as.data.frame(dt.result[order(-dt.result[[2]]),]),n=5)
}

결과를 측정하면
> system.time(test.data.table(Batting))
 사용자  시스템 elapsed
   0.01    0.00    0.01

{plyr}패키지의 경우
> library(plyr)
test.plyr <- function(data) {
  rv <- ddply(data,.(playerID),summarize,total=sum(R))
  head(plyr::arrange(rv,plyr::desc(total)),n=5)
}
결과를 측정해보면
> system.time(test.plyr(Batting))
 사용자  시스템 elapsed
   7.61    0.00    7.66

결과적으로
data.table이 가장 빠른 0.01초
dplyr이 0.03초
stats가 0.36초
그리고 plyr이 7.66초가 걸립니다.

명백히 dplyr을 쓰는 것이 여러모로 합리적으로 보입니다. %.% 오퍼레이터 때문에 코딩의 양도 대폭 줄었습니다. %>%를 %.% 대신 써도 괜찮습니다.

{dplyr}에 대해서 좀 더 알아봅시다.

batting.df <- Batting[,c("playerID","R")]
batting.tbl <- tbl_df(batting.df)

이 코드는 dplyr의 객체인 tbl_df를 생성합니다. 물론 data.frame의 wrapping object라서 data.frame의 기는을 모두 가지고 있습니다. 굳이 편리한 것은 head(...) 이렇게 안해도 긴 데이터는 줄여서 표현한다는 것.

> batting.tbl
Source: local data frame [97,889 x 2]

    playerID   R
1  aardsda01   0
2  aardsda01   0
3  aardsda01   0
4  aardsda01   0
5  aardsda01   0
6  aardsda01   0
7  aardsda01  NA
8  aaronha01  58
9  aaronha01 105
10 aaronha01 106
..       ... ...

점점점... 으로 줄였습니다.

{plyr}은 출력결과를 다양하게 선택할 수 있지만, dplyr은 tbl_df형 데이터가 기본입니다. 따라서 {plyr}의 ddply() 정도밖에 대응할 수단이 없습니다. 아쉽네요. 그렇지만 cube라는 데이터 처리 방식을 새로 도입했습니다. 나중에 설명할 기회가 있기를.

{dplyr}은 객체를 data.frame뿐만 아니라 data.table에도 대응하게 만들었습니다. 다음과 같이 tbl_dt 객체를 생성하여data.table의 기능을 쓸 수 있습니다.

> batting.tbl2 <- tbl_dt(batting.df)
> class(batting.tbl2)
[1] "tbl_dt"     "tbl"        "data.table" "data.frame"

결과를 보면 data.frame과 data.table 개체를 모두 싸안고 있습니다.

변수의 이름을 얻기 위해

> tbl_vars(batting.tbl)
[1] "playerID" "R"

간단히 tbl_vars() 함수를 호출합니다. colnames()를 써도 결과는 같습니다.




앞으로 plyr 대신에 dplyr을 써보아용~

댓글

이 블로그의 인기 게시물

Bradley-Terry Model: paired comparison models

xlwings tutorial - 데이터 계산하여 붙여 넣기

R에서 csv 파일 읽는 법