'data.table 키에 대해 부분집합 가져와서 연산하고 연산 결과에 대해 키 재설정하기'에 해당되는 글 1건

  1. 2020.10.17 [R data.table] Key를 칼럼 j와 그룹 by와 함께 사용하기

지난 포스팅에서는 R data.table 이 키(Key)와 빠른 이진 탐색(fast binary search) 기반의 부분집합 선택을 하는 원리, 함수 사용방법에 대하여 소개하였습니다.


지난 포스팅이 R data.table에 Key 를 활용하여 행(row, i)에 대해서만 subset 하는 방법에 대해 한정해서 소개했습니다. 반면, 이번 포스팅은 지난 포스팅을 이어받아 더 확장하여, DT[i, j, by][order()] 기본 구문에서 행뿐만 아니라 칼럼(column, j), 그룹(group, by) 별 연산, 정렬, 키 재설정 하는 방법까지 소개하겠습니다.


1. 키 설정 후, 특정 키(Key) 값에 행을 한정해 칼럼 j 가져오기 (select in j)

2. 체인연산으로 정렬하기 (chaining)

3. 특정 키(Key) 값에 행을 한정해 칼럼 j 에 연산 수행하기 (compute or do in j)

4. 칼럼 j에 := 를 사용하여 키의 부분집합에 할당하기(sub-assign by reference)

5. by 를 사용하여 그룹별 집계하기 (aggregation using by)





  1. 키 설정 후, 특정 키(Key) 값에 행을 한정해 칼럼 j 가져오기 (select in j)


먼저 Cars93 data.frame에서 칼럼 몇 개만 가져와서 예제로 사용할 간단할 data.table DT 를 만들어보겠습니다. 그리고 DT의 Type, DriveTrain 칼럼에 대해 키를 설정(setkey) 하겠습니다.


이렇게 키를 설정하면 data.table DT는 키인 Type, DriveTrain을 참조으로 오름차순으로 재정렬됩니다.



library(data.table)
library(MASS)
DT <- data.table(Cars93[, c("Model", "Type", "Price", "MPG.highway", "DriveTrain")])

# Combining keys with j and by
# Set keys with Type and DriveTrain
setkey(DT, Type, DriveTrain)


> key(DT)
[1] "Type"       "DriveTrain"

>

> # reordered by Keys

> head(DT, 10)
       Model    Type Price MPG.highway DriveTrain
 1:   Legacy Compact  19.5          30        4WD
 2:       90 Compact  29.1          26      Front
 3: Cavalier Compact  13.4          36      Front
 4:  Corsica Compact  11.4          34      Front
 5:  LeBaron Compact  15.8          28      Front
 6:   Spirit Compact  13.3          27      Front
 7:    Tempo Compact  11.3          27      Front
 8:   Accord Compact  17.5          31      Front
 9:      626 Compact  16.5          34      Front
10:   Altima Compact  15.7          30      Front




이제 위에서 설정한 (a) Type, DriveTrain 키(Key) 값이 Type == "Compact" & DriveTrain == "Front" 인 행을 선별하고 (subset row i), (b) 이들 부분집합에 대해서 칼럼(column j) Model, MPG.highway 를 선택해서 가져오겠습니다.


이렇게 data.table의 특정 키 값의 행만 가져오기(subset) 할 때 Key를 설정하면서 재정렬 된 상태이고 빠른 이진 탐색(fast binary search)로 키 값 데이터를 선별하므로 매우 빠르게 수행됩니다. 그리고 이렇게 선별한 subset에 한정해서 칼럼 j를 선택해서 가져오므로 메모리 효율적이고 빠릅니다.



> # Select in j
> DT[.("Compact", "Front"), .(Model, MPG.highway)]


       Model MPG.highway
 1:       90          26
 2: Cavalier          36
 3:  Corsica          34
 4:  LeBaron          28
 5:   Spirit          27
 6:    Tempo          27
 7:   Accord          31
 8:      626          34
 9:   Altima          30
10:  Achieva          31
11:  Sunbird          31
12:      900          26
13:   Passat          30




아래에는 위에서 수행한 행과 열 선택 과정을 data.table과 dplyr로 각각 했을 때의 코드를 비교해 보았습니다. 코드의 간결성면에서나 수행 속도면에서 data.table이 dplyr보다 우수합니다. (단, 코드의 가독성은 dplyr이 더 나아보입니다.)

data.table

dplyr

library(data.table)


setkey(DT, Type, DriveTrain)

DT[.("Compact", "Front"), .(Model, MPG.highway)]



library(dplyr)


DT %>%
  filter(Type == "Compact"
         & DriveTrain == "Front") %>%
  select(Model, MPG.highway)



위의 코드에서 뒤의 칼럼 j 를 선택할 때 c("Model", "MPG.highway") 처럼 큰 따옴표(" ")를 대신 사용할 수도 있으며, 프로그래밍을 할 때 유용하게 쓸 수 있습니다.



# or alternatively
DT[.("Compact", "Front"), c("Model", "MPG.highway")] # useful for programming

 





  2.  체인연산으로 결과 정렬하기 (chaining)


위의 (1) 번 결과를 MPG.highway 를 기준으로 오름차순 정렬하려면 Chaining 으로 [order(MPG.highway)] 를 뒤에 이어서 써주면 됩니다.



> # Chaining
> # - use chaining to order the MPG.highway column.
> # in ascending order
> DT[.("Compact", "Front"), .(Model, MPG.highway)][order(MPG.highway)]
       Model MPG.highway
 1:       90          26
 2:      900          26
 3:   Spirit          27
 4:    Tempo          27
 5:  LeBaron          28
 6:   Altima          30
 7:   Passat          30
 8:   Accord          31
 9:  Achieva          31
10:  Sunbird          31
11:  Corsica          34
12:      626          34
13: Cavalier          36
>



위의 (1)번 결과를 MPG.highway를 기준으로 내림차순(in decreasing order)으로 정렬을 하고 싶으면 마이너스 부호('-') 를 같이 써주어서 Chaining으로 [order(-MPG.highway)] 을 이어서 써주면 됩니다.



> # in descending order
> DT[.("Compact", "Front"), .(Model, MPG.highway)][order(-MPG.highway)]
       Model MPG.highway
 1: Cavalier          36
 2:  Corsica          34
 3:      626          34
 4:   Accord          31
 5:  Achieva          31
 6:  Sunbird          31
 7:   Altima          30
 8:   Passat          30
 9:  LeBaron          28
10:   Spirit          27
11:    Tempo          27
12:       90          26
13:      900          26






  3. 특정 키(Key) 값에 행을 한정해 칼럼 j 에 연산 수행하기 (compute or do in j)


(1)에서 설정한 키의 특정값인 Type == "Compact" & DriveTrain == "Front" 인 행을 선별하여, 이들 subset에 대해서만 MPG.highway 칼럼 j의 최대값(max), 평균(mean), 중앙값(median), 최소값(min), 1/4분위수(Q1), 3/4분위수(Q3)를 구해보겠습니다.


이때 요약통계량 연산을 하는데 사용하는 함수는 base R 함수의 max(), mean(), median(), min(), quantile() 과 동일하되, 속도는 base R 보다 data.table이 훨씬 빠릅니다!



> # Compute or do in j
> # - Find the maximum, minimum MPG.highway corresponding to Type == "Compact" and DriveTrain == "Front".
> DT[.("Compact", "Front"), max(MPG.highway)] # max
[1] 36
>
> DT[.("Compact", "Front"), mean(MPG.highway)] # mean
[1] 30.07692
>
> DT[.("Compact", "Front"), median(MPG.highway)] # median
[1] 30
>
> DT[.("Compact", "Front"), min(MPG.highway)] # min
[1] 26
>
> DT[.("Compact", "Front"), quantile(MPG.highway, c(0.25, 0.75))] # Q1, Q3
25% 75%
 27  31

 




  4. 칼럼 j에 := 를 사용하여 키의 부분집합에 할당하기(sub-assign by reference)


이번에는 Type을 키로 설정하고, 키의 값이 Type == "Large" 이면 ':=' 특수부호를 사용하여 Type == "Big" 의 다른 값으로 재할당(sub-assign by reference)해보겠습니다.


이러한 부분집합 재할당 때도 data.frame 대비 data.table이 훨씬 더 빠르게 수행이 됩니다.



> # sub-assign by reference using := in j
> DT <- data.table(Cars93[, c("Model", "Type", "Price", "MPG.highway", "DriveTrain")])
> setkey(DT, Type)
> key(DT)
[1] "Type"
>
> DT[, .N, by=Type]
      Type  N
1: Compact 16
2:   Large 11
3: Midsize 22
4:   Small 21
5:  Sporty 14
6:     Van  9

>

> DT[.("Large"), Type := "Big"]

>

> DT[, .N, by=Type]
      Type  N
1: Compact 16
2:     Big 11
3: Midsize 22
4:   Small 21
5:  Sporty 14
6:     Van  9
>



다만, 키의 특정 값을 재할당하고 나면 애초 키를 설정할 때 재정렬(reorder) 되었던 것이 이제 더이상 유효하지 않으므로 키가 해제됩니다.(Key 가 NULL로 설정됨)



> key(DT) # the key is removed by setting to NULL
NULL

 




  5. by 를 사용하여 그룹별 집계하기 (aggregation using by)


다시 data.table DT에 차종(Type)을 키로 설정한 후에, 동력전달장치(DriveTrain) 그룹별로 고속도로연비(MPG.highway) 의 최대값(max)을 집계해보겠습니다. 집계된 칼럼은 mpg_max 라는 이름을 부여하겠습니다.


이때 그룹별 집계의 결과값은 keyby 의 기준인 동력전달장치(DriveTrain)을 기준으로 정렬됩니다.



> # Aggregation using by
> setkey(DT, Type)
> key(DT) # key is Type.
[1] "Type"
>
> agg <- DT["Compact",
+           .(mpg_max = max(MPG.highway)),
+           keyby = DriveTrain] # keyby sets DriveTrain as the key and order it.
>
> head(agg)
   DriveTrain mpg_max
1:        4WD      30
2:      Front      36
3:       Rear      29




그리고, 그룹별 집계의 결과는 keyby 칼럼인 동력전달장치(DriveTrain)을 키로 설정됩니다.



> key(agg) # key is changed to DriveTrain.
[1] "DriveTrain"

 



만약 그룹별 집계를 할 때 그룹 연산 부호에 'keyby' 대신에 'by'를 사용하면 나중에 setkey(agg_2, DriveTrain) 처럼 별도의 키를 설정하는 코드를 추가해주어야 키가 설정이 되고 키를 기준으로 재정렬이 됩니다. (조금 번거롭습니다.)



> agg_2 <- DT["Compact",
+    .(mpg_max = max(MPG.highway)),
+    by = DriveTrain]
>

> # set the column 'DriveTrain' as a Key using setkey() explicitely
> setkey(agg_2, DriveTrain)
> head(agg_2)
   DriveTrain mpg_max
1:        4WD      30
2:      Front      36
3:       Rear      29
> key(agg_2)
[1] "DriveTrain"

 


[Reference] 

* data.table vignette: https://cran.r-project.org/web/packages/data.table/vignettes/datatable-keys-fast-subset.html


다음번 포스팅에서는 R data.table에서 mult, nomatch 매개변수를 사용하여 Key값에 매칭되는 행 중에서 모든 행, 첫번째 행, 마지막 행, 값이 존재하는 행만 가져오는 방법을 소개하겠습니다. 


이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 학자 되세요!



728x90
반응형
Posted by Rfriend
,