##setup
#install.packages("collapse")
library(magrittr)
library(data.table)
library(dplyr)
library(collapse)
library(microbenchmark)
1. collapse 주요 특징 소개 및 설치
R의 고급 데이터 변환 및 통계 컴퓨팅을 위한 C/C++ 기반 패키지입니다.
유연하고 간결한 구문을 통해 매우 빠르고 클래스에 구애받지 않습니다,
기본 R, ‘dplyr’, ‘tibble’, ‘data.table’, ‘sf’, ‘plm’ 과 잘 통합됩니다.
Setup
load file
09-15년 공단 건강검진 데이터에서 실습용으로 32명을 뽑은 자료를 이용하겠습니다.
2. collapse 패키지
- 자주 사용하는 연산 위주로 collapse와 data.table을 비교하여 파악해보겠습니다.
collapse패키지는 dplyr패키지와 유사한 함수들이 존재합니다.주로 dplyr 함수명 앞에 “f”를 붙여 쓰입니다. 예를 들면, dplyr::select()와 collapse::fselect(), collapse::fgroup_by()와 dplyr::group_by()등 기능적으로 유사한 부분이 있지만, 속도 면에서 collapse가 월등한 수준을 보입니다. 속도에 대한 자료는 뒷부분에서 확인할 수 있기 때문에, 우선 자주 사용하고 익숙한 data.table을 코드를 통해 collapse패키지를 알아보도록 하겠습니다.
load
collapse는 데이터를 불러오는 함수가 존재하지 않기 때문에, data.table를 이용하여 읽습니다.
fselect()는 컬럼명을 명시하거나 인덱스를 전달하면 원하는 컬럼을 불러올 수 있습니다.
## data.table(Only specific column)
dt1 <- fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv",select = c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"))
dt2 <- fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv", select = 1:5)
dt3 <- fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv", drop = 6:10)
## collapse(Only specific column)
dt4 <- fselect(fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv"),EXMD_BZ_YYYY, RN_INDI, HME_YYYYMM)
dt5 <- fselect(fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv"),1:5)
dt6 <- fselect(fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv"),-(6:10))
- 예시로 dt6를 확인해보면, dt3와 마찬가지로 인덱스 6부터 10까지 제외되서 출력된 것을 볼 수 있습니다.
dt3
dt6
row
collapse::fselect와 dplyr::select는 유사하지만 fselect가 x100배 정도 빠릅니다.
ss()함수는 컬럼명이 아닌 인덱스로 행,열을 출력을 할 때 사용합니다. fsubset() 보다 빠르지만 기능이 제한적이기 떄문에 간단한 행,열 출력할 때 사용가능합니다.
## data.table(row)
dt[1:10]
dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25)]
dt[order(HME_YYYYMM)]
dt[order(HME_YYYYMM, -HGHT)]
dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25)][order(HGHT)]
dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25)] %>% .[order(HGHT)] #same
## collapse(row)
fsubset(dt, 1:10) #ss(dt,1:10)
fsubset(dt, EXMD_BZ_YYYY %in% 2009:2012 & BMI >= 25 )
roworder(dt, HME_YYYYMM)
roworder(dt, HME_YYYYMM, -HGHT)
roworder(dt, HGHT) %>% fsubset(EXMD_BZ_YYYY %in% 2009:2012 & BMI >= 25)
- 예시로 두번째 코드와 다섯번째 코드를 확인해보겠습니다.
두번째
dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25)]
다섯번째
column
열 이름을 정규식으로 선택하기 위해서는 fselect()가 아니라 get_vars() 함수를 이용해야합니다. regex = TRUE 정규식을 사용하겠다는 의미이고 return = “names” 컬럼명을 출력하겠다는 의미입니다. get_var()는 fselect()함수와 유사하지만, 수행속도가 좀 더 빠르며 벡터, 정수 형태로 값을 전달합니다.
유의사항은 컬럼명을 리스트로 전달하면 ERROR 발생합니다.
fsubset는 빠르고 부분적인 작업을 위해 base::subset 패키지 의 C 함수를 사용하는 향상된 버전입니다.
## data.table(column)
dt[, 1:10]
dt[, c("HGHT", "WGHT")]
dt[, .(HGHT, WGHT)]
dt[, .(Height = HGHT, Weight = WGHT)]
dt[, .(HGHT)]
dt[, "HGHT"]
colvars1 <- grep("Q_", names(dt), value = T)
dt[, ..colvars1]
dt[, colvars1, with = FALSE]
dt[, .SD, .SDcols = colvars1]
dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25), ..colvars]
dt[, !..colvars1]
dt[, -..colvars1]
dt[, .SD, .SDcols = -colvars1]
## collapse(column)
fselect(dt, 1:10)
fselect(dt, c("HGHT", "WGHT")) #get_vars(dt, 1:10)
fselect(dt, HGHT, WGHT) #get_vars(dt, c("HGHT", "WGHT"))
fselect(dt, Height = HGHT, Weight = WGHT)
fselect(dt, .(HGHT)) #ERROR
fselect(dt, "HGHT")
colvars2 <-get_vars(dt, "Q_", regex = TRUE, return = "names") #regex = TRUE 정규식 사용/ return = "names" 컬럼명 출력
fselect(dt, colvars2) #fselect(dt, c(colvars))
get_vars(dt, colvars2) #get_var(dt, c(colvars))
fsubset(dt,EXMD_BZ_YYYY %in% 2009:2012 & BMI >= 25, colvars2)
fselect(dt, -(4:12))
fselect(dt, -(Q_PHX_DX_STK:Q_DRK_FRQ_V09N))
- 예시로 같은 값을 출력하는지 확인해 보겠습니다.
colvars1
<char>
1: Q_PHX_DX_STK
2: Q_PHX_DX_HTDZ
3: Q_PHX_DX_HTN
4: Q_PHX_DX_DM
5: Q_PHX_DX_DLD
6: Q_PHX_DX_PTB
7: Q_HBV_AG
8: Q_SMK_YN
9: Q_DRK_FRQ_V09N
colvars2
<char>
1: Q_PHX_DX_STK
2: Q_PHX_DX_HTDZ
3: Q_PHX_DX_HTN
4: Q_PHX_DX_DM
5: Q_PHX_DX_DLD
6: Q_PHX_DX_PTB
7: Q_HBV_AG
8: Q_SMK_YN
9: Q_DRK_FRQ_V09N
dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25), ..colvars1]
Column summary
fmean는 (열별) 평균을 계산하고, (선택적으로) 그룹화 및 가중치를 계산하는 일반적인 함수입니다.
dapply는 데이터에 대한 정보(속성)를 잃거나 데이터의 클래스 또는 형식을 변경하지 않고 데이터의 행이나 열에 함수를 적용하는 효율적인 함수입니다.
- 두번째 코드를 비교해보겠습니다.
HGHT | WGHT | BMI |
---|---|---|
164.5487 | 65.09672 | 23.92257 |
fmean | |
---|---|
HGHT | 164.54866 |
WGHT | 65.09672 |
BMI | 23.92257 |
By
collap()는 ‘Fast Statistical Functions’ 사용하여 각 컬럼에 여러 함수를 적용할 수 있습니다.(#Fast Statistical Functions: fsum, fprod, fmean, fmedian, fmode, fvar, fsd, fmin, fmax, fnth, ffirst, flast, fnobs, fndistinct)
add_stub() 연산을 통해 열을 추가할 수 있는 명령입니다. ““를 통해 열 이름을 설정할 수 있습니다.
##data.table(By)
dt[, .(HGHT = mean(HGHT), WGHT = mean(WGHT), BMI = mean(BMI)), by = EXMD_BZ_YYYY]
dt[, .(HGHT = mean(HGHT), WGHT = mean(WGHT), BMI = mean(BMI)), by = "EXMD_BZ_YYYY"] #same
dt[, lapply(.SD, mean), .SDcols = c("HGHT", "WGHT", "BMI"), by = EXMD_BZ_YYYY] #same
dt[HGHT >= 175, .N, by= .(EXMD_BZ_YYYY, Q_SMK_YN)]
dt[HGHT >= 175, .N, by= c("EXMD_BZ_YYYY", "Q_SMK_YN")] #same
dt[HGHT >= 175, .N, keyby= c("EXMD_BZ_YYYY", "Q_SMK_YN")] #same(정렬)
#collapse(By)
collap(dt, ~ EXMD_BZ_YYYY, fmean, cols = c(13,14,16)) # ~ ≒ by
fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY) #same
add_stub(count(fsubset(dt, HGHT >= 175, EXMD_BZ_YYYY,Q_SMK_YN),EXMD_BZ_YYYY,Q_SMK_YN),"N") #dplyr::count()
- 마지막 코드를 비교해보겠습니다. 결측치 값의 정렬 순서의 차이가 있지만, 같은 기능을 수행할 수 있습니다.
dt[HGHT >= 175, .N, keyby= c("EXMD_BZ_YYYY", "Q_SMK_YN")]
New variable
ftransform 새 열을 계산하거나 기존 열을 수정 및 삭제하는 데 사용할 수 있으며 항상 전체 데이터 프레임을 반환합니다.
ftransform은 base::transform 데이터 프레임 및 목록 의 향상된 버전입니다.
## data.table(New variable)
dt[, BMI2 := round(WGHT/(HGHT/100)^2, 1)]
dt[, `:=`(BP_SYS140 = factor(as.integer(BP_SYS >= 140)), BMI25 = factor(as.integer(BMI >= 25)))]
dt[, BMI2 := NULL]
## collapse(New variable)
ftransform(dt, BMI2 = round(WGHT/(HGHT/100)^2, 1))
ftransform(dt,BP_SYS140 = factor(as.integer(BP_SYS >= 140)),BMI25 = factor(as.integer(BMI >= 25)))
ftransform(dt, BMI2 = NULL)
첫번째와 두번째 코드를 확인해보겠습니다.
data.table
- collapse
Specific symbol .N, .SD, .SDcols
- 두번째 코드를 비교해보겠습니다. 비슷하지만 출력하는 형태가 다릅니다. class()를 사용하여 확인해보면 형태가 다른 것을 알 수 있습니다.
dt[, lapply(.SD, class)]
dapply(dt,class)
[1] "data.table" "data.frame"
[1] "character"
order
#data.table(order)
dt[, .(HGHT=mean(HGHT), WGHT=mean(WGHT), BMI=mean(BMI)), by=EXMD_BZ_YYYY] [order(BMI)]
dt[, .(HGHT=mean(HGHT), WGHT=mean(WGHT), BMI=mean(BMI)), by=EXMD_BZ_YYYY] [order(-BMI)]
#collapse(order)
fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY) %>% roworder(BMI)
fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY) %>% roworder(-BMI)
- 첫번째 코드를 통해 data.table과 collapse 출력 결과를 확인해보겠습니다.
3. 벤치마킹
- 모든 벤치마크는 1.6GHZ Intel i5 프로세서, 16GB DDR4 RAM및 sk hynix sc308 ssd가 탑재된 Windows 10 노트북에서 실행됩니다.
기본 행 연산
## benchmarking
microbenchmark(data.table = dt[1:10] ,
collapse = fsubset(dt, 1:10))
Warning in microbenchmark(data.table = dt[1:10], collapse = fsubset(dt, : less
accurate nanosecond times to avoid potential integer overflows
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 38.335 40.426 45.42554 41.656 43.788 151.003 100 a
collapse 5.945 6.601 7.41772 7.052 7.585 18.614 100 b
microbenchmark(data.table = dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25)],
collapse = fsubset(dt, EXMD_BZ_YYYY %in% 2009:2012 & BMI >= 25 ))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 58.794 61.9715 66.39786 64.001 66.8300 163.303 100 a
collapse 25.338 27.9620 30.63520 29.889 30.9755 71.545 100 b
microbenchmark(data.table = dt[order(HME_YYYYMM, -HGHT)],
collapse = roworder(dt, HME_YYYYMM, -HGHT))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 135.177 140.9375 198.2108 146.2265 157.399 4024.929 100 a
collapse 53.997 62.9350 103.2974 65.7845 71.504 3298.860 100 a
microbenchmark(data.table = dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25)][order(HGHT)],
collapse = roworder(dt, HGHT) %>% fsubset(EXMD_BZ_YYYY %in% 2009:2012 & BMI >= 25))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 119.064 125.419 186.6652 130.4005 139.236 4590.770 100 a
collapse 65.477 78.556 102.0277 83.5580 91.471 1484.241 100 a
기본 열 연산
## benchmarking
microbenchmark(data.table = dt[, 1:10],
collapse = fselect(dt, 1:10))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 35.547 36.9820 40.57483 37.843 39.2165 129.437 100 a
collapse 3.936 4.3665 5.30868 4.633 5.0840 41.369 100 b
microbenchmark(data.table = dt[, .(Height = HGHT, Weight = WGHT)],
collapse = fselect(dt, Height = HGHT, Weight = WGHT))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 148.666 152.4380 168.39028 156.1075 168.3665 409.590 100 a
collapse 5.084 5.6785 7.38574 6.6010 7.1955 51.947 100 b
#base::grep() more faster
microbenchmark(data.table = colvars <- grep("Q_", names(dt), value = T),
collapse = colvars <-get_vars(dt, "Q_",regex = TRUE, return = "names"))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 5.002 5.125 5.74615 5.207 5.412 51.168 100 a
collapse 6.191 6.355 6.78632 6.519 6.724 25.215 100 b
microbenchmark(data.table = dt[, ..colvars],
collapse = get_vars(dt, colvars))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 33.210 36.8180 41.42394 37.761 40.7745 125.870 100 a
collapse 2.829 3.2595 4.56494 3.649 4.0590 78.351 100 b
microbenchmark(data.table = dt[, ..colvars],
collapse = fselect(dt, colvars) )
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 33.497 35.8955 39.87373 37.3100 40.0365 126.608 100 a
collapse 4.715 5.2890 6.26521 5.7605 6.1910 48.626 100 b
microbenchmark(data.table = dt[(EXMD_BZ_YYYY %in% 2009:2012) & (BMI >= 25), ..colvars],
collapse = fsubset(dt,EXMD_BZ_YYYY %in% 2009:2012 & BMI >= 25, colvars))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 66.092 67.4655 71.12967 68.716 70.7865 184.131 100 a
collapse 23.821 24.7230 26.42696 25.338 26.0350 99.302 100 b
평균 연산
## benchmarking
microbenchmark(data.table = dt[, .(mean(HGHT), mean(WGHT), mean(BMI))],
collapse = fmean(fselect(dt,HGHT,WGHT,BMI)))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 178.145 182.9010 227.1097 185.5045 194.1965 3139.698 100 a
collapse 9.881 10.6395 12.5009 11.4185 12.3205 36.285 100 b
microbenchmark(data.table = dt[, .(HGHT = mean(HGHT), WGHT = mean(WGHT), BMI = mean(BMI))],
collaspe = fmean(fselect(dt,HGHT,WGHT,BMI)))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 175.480 179.7850 214.44148 183.885 193.6430 1898.546 100 a
collaspe 9.799 10.5165 12.43899 11.439 12.3205 42.763 100 b
microbenchmark(data.table = dt[, lapply(.SD, mean), .SDcols = c(13, 14, 16)],
collapse = dapply(fselect(dt,HGHT,WGHT,BMI),fmean))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 184.705 189.6045 218.93303 193.7045 201.3305 1603.100 100 a
collapse 16.605 17.6505 20.41226 18.8395 21.0125 62.033 100 b
그룹별 통계 연산
## benchmarking
microbenchmark(data.table = dt[, .(HGHT = mean(HGHT), WGHT = mean(WGHT), BMI = mean(BMI)), by = EXMD_BZ_YYYY],
collapse.collap = collap(dt, ~ EXMD_BZ_YYYY, fmean, cols = c(13,14,16)),
collapse.fmean = fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 243.745 258.7920 283.10254 267.115 277.9595 550.179 100 a
collapse.collap 62.484 70.9505 90.54891 74.497 81.6925 938.244 100 b
collapse.fmean 36.080 38.7450 54.37092 40.303 42.0660 1197.528 100 c
microbenchmark(data.table = dt[, lapply(.SD, mean), .SDcols = c("HGHT", "WGHT", "BMI"), by = EXMD_BZ_YYYY],
collapse = fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 246.123 252.7445 295.35006 259.2635 266.9715 2695.586 100 a
collapse 35.752 37.6380 42.12914 38.9295 40.6310 97.744 100 b
#data.table more faster
microbenchmark(data.table = dt[HGHT >= 175, .N, by= .(EXMD_BZ_YYYY, Q_SMK_YN)],
collapse = add_stub(count(fsubset(dt, HGHT >= 175, EXMD_BZ_YYYY,Q_SMK_YN),EXMD_BZ_YYYY,Q_SMK_YN),"N") )
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 238.497 260.022 358.199 305.4295 377.3845 2476.646 100 a
collapse 1634.137 1770.421 2089.203 1925.2780 2118.9825 7643.589 100 b
특수 심볼
## benchmarking
microbenchmark(data.table = dt[, .SD],
collapse = fselect(dt,1:32))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 215.537 228.6775 253.58541 234.0485 243.909 762.108 100 a
collapse 4.182 4.8995 13.80265 5.5145 6.355 769.406 100 b
microbenchmark(data.table = dt[, lapply(.SD, class)],
collapse = dapply(dt,class))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 473.058 505.489 631.67511 530.048 590.7485 7756.503 100 a
collapse 7.954 8.856 12.01915 10.086 12.3615 51.988 100 b
#data.table more faster
microbenchmark(data.table = dt[, .N, keyby = "RN_INDI"],
collapse = add_stub(count(dt,RN_INDI),"N"))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 193.069 224.1675 285.7011 259.489 341.284 555.714 100 a
collapse 2592.430 2787.4670 3227.2088 3122.027 3329.856 6645.690 100 b
정렬 연산
## benchmarking
microbenchmark(data.table = dt[, .(HGHT=mean(HGHT), WGHT=mean(WGHT), BMI=mean(BMI)), by=EXMD_BZ_YYYY] [order(BMI)],
collapse = fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY) %>% roworder(BMI))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 313.732 336.9585 427.11053 353.379 406.269 2012.485 100 a
collapse 45.141 49.4665 64.50489 53.710 64.124 251.781 100 b
microbenchmark(data.table = dt[, .(HGHT=mean(HGHT), WGHT=mean(WGHT), BMI=mean(BMI)), by=EXMD_BZ_YYYY] [order(-BMI)],
collapse = fmean(fselect(dt,EXMD_BZ_YYYY,HGHT,WGHT,BMI), dt$EXMD_BZ_YYYY) %>% roworder(-BMI))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 304.097 311.764 326.76016 316.8275 324.3305 576.993 100 a
collapse 46.289 49.159 54.02201 51.7830 54.1405 155.267 100 b
4. 마치며
collapse는 dplyr ,data.table 과 유사한 기능을 가지고 더 빠른 수행 속도를 발현할 수 있습니다.
collapse는 데이터를 불러오는 함수가 존재하지 않기 때문에, data.table::fread()를 이용하여 읽습니다.
get_var()는 fselect()함수와 유사하지만, 수행속도가 좀 더 빠릅니다.
‘roworder’는 “-변수”를 내림차순 정렬로 출력합니다.
‘fmeans’은 열별 평균뿐만 아니라, 그룹별 평균과 가중치 평균을 계산할 수 있습니다.
위에 벤치마킹에서 확인 할 수 있는 것처럼 대부분은 collapse패키지의 수행속도가 빠르지만 문자열을 통한 열 선택은 base::grep()이 조금 더 빠른 것을 확인 할 수 있습니다.
Citation
@online{park2022,
author = {Park, Beomsu},
title = {Collapse {패키지} {소개}},
date = {2022-07-25},
url = {https://blog.zarathu.com/jp/posts/2022-07-25-collapse},
langid = {en}
}