##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
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
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 43.091 43.8905 46.85316 44.4645 45.756 173.512 100 b
collapse 5.084 5.6990 6.12294 5.9655 6.232 15.703 100 a
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 67.732 70.6225 74.87338 72.2830 74.8455 196.636 100 b
collapse 25.994 29.4995 31.62248 30.4835 32.1235 48.339 100 a
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 136.653 142.5365 190.75250 148.543 154.8775 3990.366 100 b
collapse 56.744 62.3405 66.85255 65.764 68.7570 109.429 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 131.200 135.2795 144.9817 139.8920 142.9875 347.475 100 a
collapse 62.402 68.2445 110.8259 75.7065 78.4330 3636.495 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 32.472 33.3945 37.27023 34.8705 37.720 112.258 100 b
collapse 3.649 4.0590 4.56576 4.3460 4.674 18.286 100 a
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 145.837 149.6705 156.56383 152.2945 156.333 334.888 100 b
collapse 4.510 5.1455 6.05652 5.8630 6.560 18.040 100 a
#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 4.633 4.715 4.82078 4.715 4.8380 8.405 100 a
collapse 5.658 5.781 6.05119 5.863 5.9655 22.058 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 31.570 32.3695 34.59252 33.1895 34.0505 121.401 100 b
collapse 2.501 2.9520 4.11599 3.2390 3.4645 85.813 100 a
microbenchmark(data.table = dt[, ..colvars],
collapse = fselect(dt, colvars) )
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 31.980 32.636 34.10544 33.087 33.7020 101.844 100 b
collapse 4.141 4.879 5.43537 5.248 5.5555 21.566 100 a
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 72.775 74.333 78.30959 75.809 78.7200 204.959 100 b
collapse 23.575 24.764 25.98047 25.133 26.2605 46.125 100 a
평균 연산
## 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 174.414 179.0265 184.97683 181.1175 184.336 386.015 100 b
collapse 7.216 7.9335 8.94415 8.6920 9.266 35.014 100 a
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 170.027 172.1180 178.54762 174.1475 177.3045 343.211 100 b
collaspe 7.380 8.0975 9.17539 8.9585 9.4915 31.816 100 a
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 176.710 179.8260 185.93008 181.5275 185.0740 321.686 100 b
collapse 13.858 14.6575 16.01911 15.4775 16.6255 35.178 100 a
그룹별 통계 연산
## 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
data.table 203.442 210.6580 217.08270 214.5325 218.3660 366.704 100
collapse.collap 57.728 62.7505 76.49821 65.6000 68.6340 1098.431 100
collapse.fmean 36.367 37.3510 38.55107 38.2120 39.0935 55.350 100
cld
c
b
a
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 205.984 209.0180 214.88469 210.9245 215.783 404.096 100 b
collapse 35.834 36.8795 38.00495 37.4740 38.294 62.853 100 a
#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 243.868 269.1855 316.4114 303.0925 348.951 543.619 100 a
collapse 2144.587 2261.2935 2491.8049 2338.5375 2538.556 5655.335 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 211.888 216.0290 230.11783 223.3270 231.158 388.516 100 b
collapse 4.100 4.5715 5.47678 5.1455 5.576 23.124 100 a
microbenchmark(data.table = dt[, lapply(.SD, class)],
collapse = dapply(dt,class))
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 450.303 461.5165 489.46415 483.144 499.6875 656.574 100 b
collapse 7.421 8.1385 9.17088 9.020 9.5325 16.441 100 a
#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 195.365 217.2385 253.8142 251.863 277.6725 382.120 100 a
collapse 3653.633 3710.2540 3957.8870 3780.897 3938.3780 6161.275 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 273.470 279.825 288.99301 284.1505 289.3165 523.980 100 b
collapse 42.025 44.403 47.12991 46.7810 47.9085 92.865 100 a
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 276.135 282.695 289.30994 285.7085 289.378 488.474 100 b
collapse 44.854 47.150 50.61163 49.3640 51.045 115.169 100 a
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/posts/2022-07-25-collapse},
langid = {en}
}