data.table 소개

분석해야될 데이터가 많아지면 프로세싱에 많은 시간이 걸리게 되고 분석 소요 시간에 대해서 예측할 수 없는 상황에 처하게 된다. 따라서 자신의 데이터 특징에 맞는 데이터 처리 라이브러리를 사용하는게 중요해진다. 많은 패키지가 있기 때문에 이런 선택의 고민에 빠지게 되는데, 이럴때 data.table은 하나의 황금룰로 가져가는것도 나쁘지 않을듯 하다.

예전 외부 세미나에서 data.table은 컬럼에 인덱스를 걸 수 있는거 빼놓고는 그다지 효용성이 없다는 말을 했었다. 물론 그때까지만 해도 나 역시 이 패키지를 그리 많이 사용하지 않았고, 그렇기 때문에 그 가치에 대해서 잘 몰랐던것 같다. 아마도 data.frame이나 ddply로 작업했을때 엄청난 시간이 걸릴것을 data.table로 단 몇분만에 해결한 경험이 그 당시 있었다면 그렇게 말하진 않았을 듯 하다.

내부 구현 알고리즘만으로 자명한 속도 개선이 실체 피부로 와닿기 까지는 많은 데이터를 이 패키지와 처리해 봐야 되는것 같으며 그런 측면에서 아무래도 아래에서 제안하는 예제코드가 피부에 닿지는 않을 수 있을거란 생각을 해본다(16만건의 데이터도 적은것이다).

결론적으로 말하자면 data.table은 배워둘 가치가 있다는 것이다.

# 데이터 준비
data(diamonds, package = "ggplot2")

# 데이터를 크게 만들자.
dbl.diamonds <- rbind(diamonds, diamonds, diamonds)


library(data.table)
# data.table로 변환
diamonds.dt <- data.table(dbl.diamonds)

# 커팅에 인덱스
setkey(diamonds.dt, cut)

# 바이너리 서치로 빠르게 검색한다.  사실 내부적으로는 키 필드와 조인을
# 걸어버리는 방식임
system.time({
    diamonds.dt[J("Ideal")]
})
##    user  system elapsed 
##   0.032   0.000   0.031

# 이런 표현방식은 같은 결과를 가져오나 느리다 ... J()를 사용하길 추천.
system.time({
    diamonds.dt[cut == "Ideal"]
})
##    user  system elapsed 
##   0.100   0.000   0.102


# 기본 data.frame은 순차검색을 한다.
system.time({
    dbl.diamonds[dbl.diamonds$cut == "Ideal", ]
})
##    user  system elapsed 
##   0.328   0.000   0.331

# group by에 대한 속도 검증
system.time({
    diamonds.dt[, list(m.carat = mean(carat), m.depth = mean(depth), m.table = mean(table), 
        m.price = mean(price)), by = color]
})
##    user  system elapsed 
##   0.068   0.000   0.070

## ddply를와 속도 비교
library(plyr)

system.time({
    ddply(dbl.diamonds, .(color), summarise, m.carat = mean(carat), m.depth = mean(depth), 
        m.table = mean(table), m.price = mean(price))
})
##    user  system elapsed 
##   0.556   0.008   0.565

여러가지 data.table을 만들다 보면 이들에 대한 요약을 볼 필요가 있는데, 패키지에서는 아래와 같은 편리한 함수도 제공해준다. 테이블 정보에 컬럼명과 키컬럼 그리고 이들이 차지하고 있는 데이터 크기에 대한 정보를 보여준다.

tables()
##      NAME           NROW MB
## [1,] diamonds.dt 161,820 10
##      COLS                                            KEY
## [1,] carat,cut,color,clarity,depth,table,price,x,y,z cut
## Total: 10MB

# 문자열의 정규식 검색 만일 이 구문을 사용하지 않는다면 grepl() 함수로
# 정규식 매칭을 해야 된다.
diamonds.dt[cut %like% "^Ideal"]
##        carat   cut color clarity depth table price    x    y    z
##     1:  0.23 Ideal     E     SI2  61.5    55   326 3.95 3.98 2.43
##     2:  0.23 Ideal     J     VS1  62.8    56   340 3.93 3.90 2.46
##     3:  0.31 Ideal     J     SI2  62.2    54   344 4.35 4.37 2.71
##     4:  0.30 Ideal     I     SI2  62.0    54   348 4.31 4.34 2.68
##     5:  0.33 Ideal     I     SI2  61.8    55   403 4.49 4.51 2.78
##    ---                                                           
## 64649:  0.79 Ideal     I     SI1  61.6    56  2756 5.95 5.97 3.67
## 64650:  0.71 Ideal     E     SI1  61.9    56  2756 5.71 5.73 3.54
## 64651:  0.71 Ideal     G     VS1  61.4    56  2756 5.76 5.73 3.53
## 64652:  0.72 Ideal     D     SI1  60.8    57  2757 5.75 5.76 3.50
## 64653:  0.75 Ideal     D     SI2  62.2    55  2757 5.83 5.87 3.64

# 길어질 표현식을 짧게
diamonds.dt[depth %between% c(60, 60.4)]
##       carat   cut color clarity depth table price    x    y    z
##    1:  0.62  Fair     F      IF  60.1    61  2861 5.53 5.56 3.33
##    2:  1.01  Fair     F     SI2  60.1    66  3597 6.44 6.41 3.86
##    3:  1.01  Fair     E     SI2  60.0    60  3801 6.48 6.38 3.86
##    4:  0.90  Fair     D     SI1  60.4    61  3812 6.24 6.22 3.76
##    5:  1.16  Fair     G     SI2  60.1    60  4307 6.90 6.72 4.09
##   ---                                                           
## 8666:  0.72 Ideal     E     SI2  60.1    56  2674 5.85 5.83 3.51
## 8667:  0.75 Ideal     I     VS2  60.4    59  2680 5.86 5.90 3.55
## 8668:  0.70 Ideal     E     VS2  60.3    57  2719 5.70 5.80 3.47
## 8669:  0.72 Ideal     F     VS2  60.3    56  2724 5.84 5.81 3.51
## 8670:  0.75 Ideal     D     SI2  60.3    57  2726 5.85 5.89 3.54

# transform 기능
diamonds.dt[, `:=`(cut, as.character(cut))]
##         carat   cut color clarity depth table price    x    y    z
##      1:  0.22  Fair     E     VS2  65.1    61   337 3.87 3.78 2.49
##      2:  0.86  Fair     E     SI2  55.1    69  2757 6.45 6.33 3.52
##      3:  0.96  Fair     F     SI2  66.3    62  2759 6.27 5.95 4.07
##      4:  0.70  Fair     F     VS2  64.5    57  2762 5.57 5.53 3.58
##      5:  0.70  Fair     F     VS2  65.3    55  2762 5.63 5.58 3.66
##     ---                                                           
## 161816:  0.79 Ideal     I     SI1  61.6    56  2756 5.95 5.97 3.67
## 161817:  0.71 Ideal     E     SI1  61.9    56  2756 5.71 5.73 3.54
## 161818:  0.71 Ideal     G     VS1  61.4    56  2756 5.76 5.73 3.53
## 161819:  0.72 Ideal     D     SI1  60.8    57  2757 5.75 5.76 3.50
## 161820:  0.75 Ideal     D     SI2  62.2    55  2757 5.83 5.87 3.64

# 역시 순간적으로 hash 자료구조를 만들어 검색을 빠르게 한다.
diamonds.dt[cut %chin% c("Fair")]
##       carat  cut color clarity depth table price    x    y    z
##    1:  0.22 Fair     E     VS2  65.1    61   337 3.87 3.78 2.49
##    2:  0.86 Fair     E     SI2  55.1    69  2757 6.45 6.33 3.52
##    3:  0.96 Fair     F     SI2  66.3    62  2759 6.27 5.95 4.07
##    4:  0.70 Fair     F     VS2  64.5    57  2762 5.57 5.53 3.58
##    5:  0.70 Fair     F     VS2  65.3    55  2762 5.63 5.58 3.66
##   ---                                                          
## 4826:  0.72 Fair     F     VS2  55.4    64  2724 6.06 5.97 3.34
## 4827:  0.90 Fair     I     VS1  68.7    62  2732 5.83 5.79 3.99
## 4828:  1.00 Fair     I     SI2  66.8    56  2743 6.22 6.12 4.13
## 4829:  1.04 Fair     G     SI2  65.2    57  2745 6.25 6.23 4.07
## 4830:  0.71 Fair     D     VS1  65.4    59  2747 5.62 5.58 3.66

setkey(diamonds.dt, cut)

tables()
##      NAME           NROW MB
## [1,] diamonds.dt 161,820 11
##      COLS                                            KEY
## [1,] carat,cut,color,clarity,depth,table,price,x,y,z cut
## Total: 11MB

# system.time의 대용품
started.at = proc.time()
diamonds.dt[cut %chin% c("Fair")]
##       carat  cut color clarity depth table price    x    y    z
##    1:  0.22 Fair     E     VS2  65.1    61   337 3.87 3.78 2.49
##    2:  0.86 Fair     E     SI2  55.1    69  2757 6.45 6.33 3.52
##    3:  0.96 Fair     F     SI2  66.3    62  2759 6.27 5.95 4.07
##    4:  0.70 Fair     F     VS2  64.5    57  2762 5.57 5.53 3.58
##    5:  0.70 Fair     F     VS2  65.3    55  2762 5.63 5.58 3.66
##   ---                                                          
## 4826:  0.72 Fair     F     VS2  55.4    64  2724 6.06 5.97 3.34
## 4827:  0.90 Fair     I     VS1  68.7    62  2732 5.83 5.79 3.99
## 4828:  1.00 Fair     I     SI2  66.8    56  2743 6.22 6.12 4.13
## 4829:  1.04 Fair     G     SI2  65.2    57  2745 6.25 6.23 4.07
## 4830:  0.71 Fair     D     VS1  65.4    59  2747 5.62 5.58 3.66
cat("Finished in", timetaken(started.at), "\n")
## Finished in 0.066sec

# transform 기능
diamonds.dt[, `:=`(minb = max(cut), meanb = mean(depth), sumbdp = sum(depth, 
    price)), by = carat%/%10]
##         carat       cut color clarity depth table price    x    y    z
##      1:  0.22      Fair     E     VS2  65.1    61   337 3.87 3.78 2.49
##      2:  0.86      Fair     E     SI2  55.1    69  2757 6.45 6.33 3.52
##      3:  0.96      Fair     F     SI2  66.3    62  2759 6.27 5.95 4.07
##      4:  0.70      Fair     F     VS2  64.5    57  2762 5.57 5.53 3.58
##      5:  0.70      Fair     F     VS2  65.3    55  2762 5.63 5.58 3.66
##     ---                                                               
## 161816:  0.70 Very Good     E     VS2  62.8    60  2755 5.59 5.65 3.53
## 161817:  0.70 Very Good     D     VS1  63.1    59  2755 5.67 5.58 3.55
## 161818:  0.70 Very Good     E     VS2  60.5    59  2757 5.71 5.76 3.47
## 161819:  0.70 Very Good     E     VS2  61.2    59  2757 5.69 5.72 3.49
## 161820:  0.70 Very Good     D     SI1  62.8    60  2757 5.66 5.68 3.56
##              minb meanb    sumbdp
##      1: Very Good 61.75 646397940
##      2: Very Good 61.75 646397940
##      3: Very Good 61.75 646397940
##      4: Very Good 61.75 646397940
##      5: Very Good 61.75 646397940
##     ---                          
## 161816: Very Good 61.75 646397940
## 161817: Very Good 61.75 646397940
## 161818: Very Good 61.75 646397940
## 161819: Very Good 61.75 646397940
## 161820: Very Good 61.75 646397940

이런 data.table의 장점은 아래와 같다.

  • data.tabledata.frame의 단점을 보완한 훌륭한 대체제이다.
  • 때로 수백, 수천만건을 대상으로 조인을 걸거나 분석을 할때 data.table은 거의 필수품이다.
    • 10시간 걸릴 작업을 6분만에 가능하게 한다면 당연히 사용해야 된다.
  • data.frame을 상속받아 data.frame을 입력받는 모든 관련 패키지나 라이브러리에 별도의 변환없이 사용 가능하다.
  • 메모리 사용에 최적화되었다.

단점도 있다.

  • data.frame 문법과 다른 부분이 있으며, 새로 배워야 되는 부분이 어느정도 있다.

항상 그렇지만 효용이 크다면 충분히 익숙해지는데 시간을 투자할 가치가 있다. 물론 분석하는 사람이 다루는 데이터와 상황에 따라 그 효용은 달라지겠지만 말이다.

CC BY-NC 4.0 data.table 소개 by from __future__ import dream is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.