분석해야될 데이터가 많아지면 프로세싱에 많은 시간이 걸리게 되고 분석 소요 시간에 대해서 예측할 수 없는 상황에 처하게 된다. 따라서 자신의 데이터 특징에 맞는 데이터 처리 라이브러리를 사용하는게 중요해진다. 많은 패키지가 있기 때문에 이런 선택의 고민에 빠지게 되는데, 이럴때 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.table
은data.frame
의 단점을 보완한 훌륭한 대체제이다.- 때로 수백, 수천만건을 대상으로 조인을 걸거나 분석을 할때
data.table
은 거의 필수품이다.- 10시간 걸릴 작업을 6분만에 가능하게 한다면 당연히 사용해야 된다.
data.frame
을 상속받아data.frame
을 입력받는 모든 관련 패키지나 라이브러리에 별도의 변환없이 사용 가능하다.- 메모리 사용에 최적화되었다.
단점도 있다.
data.frame
문법과 다른 부분이 있으며, 새로 배워야 되는 부분이 어느정도 있다.
항상 그렇지만 효용이 크다면 충분히 익숙해지는데 시간을 투자할 가치가 있다. 물론 분석하는 사람이 다루는 데이터와 상황에 따라 그 효용은 달라지겠지만 말이다.
data.table 소개 by from __future__ import dream is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.