##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 36.695 37.433 38.85693 37.7405 38.417 102.418 100 a
collapse 5.412 5.781 6.32097 6.1090 6.396 23.493 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 57.113 61.1515 64.52006 62.5865 65.2310 162.975 100 a
collapse 24.928 27.8800 29.87875 28.6795 30.4835 76.014 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 120.253 131.8355 174.81129 135.2385 138.2315 3891.105 100 a
collapse 52.849 61.2540 63.31917 62.8120 65.3950 89.790 100 b
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 117.588 123.9635 129.6945 125.7880 128.6785 294.380 100 a
collapse 61.992 76.5675 124.4481 79.1915 82.4305 4509.344 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 34.276 35.916 39.65479 38.3965 40.1185 108.650 100 a
collapse 3.649 4.059 4.57888 4.3870 4.7150 20.295 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 147.559 150.3060 154.07759 151.4540 153.2785 317.299 100 a
collapse 4.756 5.5555 6.20412 6.1295 6.6010 16.195 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 4.674 4.756 4.87121 4.797 4.879 8.241 100 a
collapse 5.822 5.945 6.21683 6.068 6.150 19.680 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 32.226 32.882 34.28584 33.1485 33.579 106.682 100 a
collapse 2.788 3.034 4.16806 3.3620 3.567 83.927 100 b
microbenchmark(data.table = dt[, ..colvars],
collapse = fselect(dt, colvars) )
Unit: microseconds
expr min lq mean median uq max neval cld
data.table 32.021 32.7795 34.12676 33.128 33.620 108.609 100 a
collapse 4.551 4.9610 5.46407 5.289 5.617 19.844 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 65.928 67.035 70.10344 68.0805 71.0325 154.693 100 a
collapse 23.165 24.354 26.88411 25.1535 26.9985 86.100 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 175.685 179.129 184.59266 181.0355 185.3405 337.061 100 a
collapse 9.758 10.496 11.42629 11.2340 11.7465 32.636 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 170.642 174.127 178.11917 176.382 178.5345 325.950 100 a
collaspe 9.635 10.291 11.35331 10.988 11.6850 36.367 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 181.220 185.6275 189.84107 187.862 189.7890 341.571 100 a
collapse 16.072 17.1790 18.66771 18.204 19.4135 42.066 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 241.613 251.084 257.63170 254.815 260.0835 439.356 100 a
collapse.collap 59.368 65.559 76.82416 68.757 72.4880 854.850 100 b
collapse.fmean 36.162 37.638 38.99387 38.417 39.6060 65.723 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 245.426 250.5305 256.20285 253.503 256.5780 432.058 100 a
collapse 35.793 37.1050 38.26489 37.884 39.0115 62.279 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 234.028 257.7055 283.492 281.875 300.0175 409.549 100 a
collapse 1579.730 1602.6900 1699.509 1628.274 1666.7935 3867.161 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 210.822 220.2315 230.43927 230.3995 233.6385 375.601 100 a
collapse 4.141 4.6125 5.15329 5.1865 5.4530 13.325 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 464.653 469.4705 496.58462 474.083 532.2825 638.411 100 a
collapse 7.749 8.4870 9.53168 9.102 9.7170 24.764 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 195.529 217.6075 243.6118 241.3875 263.9375 331.034 100 a
collapse 2538.310 2574.7795 2712.1881 2601.4910 2634.5165 4780.026 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 298.972 304.5275 312.43968 308.3405 312.9325 619.879 100 a
collapse 42.968 44.7515 47.97123 47.3140 50.1430 96.801 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 296.020 304.097 310.80911 309.0170 313.2195 477.732 100 a
collapse 45.305 48.380 50.97243 50.4095 52.5415 110.741 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/en/posts/2022-07-25-collapse},
langid = {en}
}