[R data.table] data.table의 by 구문으로 그룹별 집계하기 (Aggregations by Group)
R 분석과 프로그래밍/R 데이터 전처리 2020. 10. 4. 22:32지난 포스팅에서는 R data.table 의 기본 구문 DT[i, j, by] 에서 행과 열 선택하고 계산하는 방법에 대하여 소개하였습니다.
이번 포스팅에서는 R data.table 의 DT[i, j, by] 의 by 표현를 이용하여 그룹별로 집계하는 방법을 소개하겠습니다.
(1) Base R dplyr, data.table 패키지별 그룹별 집계하기
(2) data.table에서 두 개 이상 그룹 기준별 집계하기
(3) data.table에서 keyby 로 그룹별 집계결과를 그룹 key 기준으로 정렬하기
(4) data.table에서 Chaining과 order()로 그룹별 집계결과 정렬하기
(5) data.table에서 by 에 조건절의 블리언 그룹별로 집계하기
예제로 사용할 데이터는 MASS 패키지에 내장되어 있는 Cars93 데이터프레임입니다. Base R과 dplyr에 사용할 'df' 이름의 data.frame과 data.table에서 사용할 'DT' 이름의 data.table을 만들어보겠습니다.
library(data.table) library(dplyr) library(MASS) df <- data.frame(Cars93) DT <- data.table(df) str(DT) > str(DT) Classes 'data.table' and 'data.frame': 93 obs. of 27 variables: $ Manufacturer : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ... $ Model : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ... $ Type : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ... $ Min.Price : num 12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ... $ Price : num 15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ... $ Max.Price : num 18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ... $ MPG.city : int 25 18 20 19 22 22 19 16 19 16 ... $ MPG.highway : int 31 25 26 26 30 31 28 25 27 25 ... $ AirBags : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ... $ DriveTrain : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ... $ Cylinders : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ... $ EngineSize : num 1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ... $ Horsepower : int 140 200 172 172 208 110 170 180 170 200 ... $ RPM : int 6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ... $ Rev.per.mile : int 2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ... $ Man.trans.avail : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ... $ Fuel.tank.capacity: num 13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ... $ Passengers : int 5 5 5 6 4 6 6 6 5 6 ... $ Length : int 177 195 180 193 186 189 200 216 198 206 ... $ Wheelbase : int 102 115 102 106 109 105 111 116 108 114 ... $ Width : int 68 71 67 70 69 69 74 78 73 73 ... $ Turn.circle : int 37 38 37 37 39 41 42 45 41 43 ... $ Rear.seat.room : num 26.5 30 28 31 27 28 30.5 30.5 26.5 35 ... $ Luggage.room : int 11 15 14 17 13 16 17 21 14 18 ... $ Weight : int 2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ... $ Origin : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ... $ Make : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ... - attr(*, ".internal.selfref")=<externalptr>
|
(1) Base R dplyr, data.table 패키지별 그룹별 집계하기 |
생산기지(Origin)이 "USA" 인 차에 대해서 차종(Type) 별로 차량 대수를 집계(aggregation by Type for Origin from "USA") 를 base R, dplyr, data.table 패키지별로 해보겠습니다.
data.table 패키지는 기본 구분 DT[i, j, by] 를 사용하여,
(a) DT data.table 로 부터
(b) i 에서 Origin == "USA" 조건으로 행 subset 을 해주고,
(c) j 에서 .(cnt = .N) 으로 행의 개수를 세어서 'cnt' 라는 칼럼 이름으로 집계하는데 이때,
(d) by 에서 by = Type 으로 설정하여 차종(Type) 그룹별로 집계를 합니다. 이때 'by ='는 생략 가능합니다.
다른 패키지 대비 data.table 의 그룹별 집계 코드가 매우 간결하고 또 속도도 빠릅니다!
이때 결과가 Type 의 알파벳 순서로 정렬되어 있지 않으며, keyby 를 사용하면 그룹 기준 변수의 항목 순서대로 정렬할 수 있습니다. ( (3)번 예시 참조 )
base R 에서는 table() 함수로 행 개수 집계를 한 후에 data.frame() 으로 변환을 해주었는데요, 그룹 기준의 알파벳 순서대로 정렬이 되어 있습니다. 집계 결과 표의 변수 이름이 "Var1", "Freq" 로 되어 있어서 칼럼 이름을 바꿔주고 싶으면 colnames() 함수로 사용해야 하는 불편함이 있습니다.
dplyr 는 연산 체인(Chaining of operations) 방식으로 구문을 표현하므로 사람이 생각하는 방식과 유사한 순서로서 코드 짜기도 쉽고 또 가독성도 매우 좋습니다. 집계 결과는 그룹 기준이 Type의 알파벳 순서대로 정렬이 되었습니다.
"생산기지(Origin)이 "USA"인 차 중에서 차종(Type) 별로 차 대수를 집계하시오."
패키지 |
R codes (aggregation by group) |
결과 (results) |
Base R | data.frame(table(df[df$Origin == "USA", ]$Type)) | Var1 Freq 1 Compact 7 2 Large 11 3 Midsize 10 4 Small 7 5 Sporty 8 6 Van 5 |
dplyr | df %>% filter(Origin == "USA") %>% group_by(Type) %>% summarise(cnt = n()) | # A tibble: 6 x 2 Type cnt <fct> <int> 1 Compact 7 2 Large 11 3 Midsize 10 4 Small 7 5 Sporty 8 6 Van 5 |
data.table |
DT[Origin == "USA", .(cnt = .N), by = Type] # equivalent : without 'by' DT[Origin == "USA", .N, Type] |
Type cnt 1: Midsize 10 2: Large 11 3: Compact 7 4: Sporty 8 5: Van 5 6: Small 7 |
(2) data.table에서 두 개 이상 그룹 기준별 집계하기 |
이번에는 by = .(그룹기준1, 그룹기준2, ...) 의 'by' 구분으로 두 개 이상의 그룹 기준 별로 집계하는 방법입니다. 그룹기준을 묶어놓은 괄호의 앞에 있는 작은 점('.') 은 오타가 아니라 list() 를 의미하는 것이므로 꼭 포함시키기 바랍니다.
생산기지(Origin)가 "USA"인 차 중에서 차종(Type) 과 실린더(Cylinders) 그룹을 기준으로 고속도로연비(MPG.highway)의 평균(mean)을 구하시오.
# Counting the number of rows for each Type and Cylinders for Origin "USA". DT[Origin == "USA", .(mean_mpg_highway = mean(MPG.highway)), by = .(Type, Cylinders)] # or equivalently DT[Origin == "USA", .(mean_mpg_highway = mean(MPG.highway)), by = list(Type, Cylinders)] [Out] Type Cylinders mean_mpg_highway 1: Midsize 4 29.50000 2: Large 6 27.28571 3: Midsize 6 27.20000 4: Large 8 25.75000 5: Midsize 8 25.00000 6: Compact 4 30.57143 7: Sporty 6 26.66667 8: Van 6 21.40000 9: Sporty 8 25.00000 10: Small 4 33.85714 11: Sporty 4 28.75000
|
(3) data.table에서 keyby 로 그룹별 집계결과를 그룹 key 기준으로 정렬하기 |
위의 (2)번에서 차종과 실린더 그룹별 평균 계산 결과가 정렬이 안되어 있는데요(original order 로 정렬되어 있다보니 정렬이 안된것 처럼 보임), keyby = .(Type, Cylinders) 를 사용하여 차종과 실린더 기준으로 정렬을 해서 결과를 제시해보겠습니다.
이때 keyby 를 사용하면 단순히 정렬만 해서 제시하는 것 뿐만 아니라 'sorted'라는 attribute를 설정하여 'Key'를 설정해주게 됩니다.
생산기지(Origin)가 "USA"인 차 중에서 차종(Type) 과 실린더(Cylinders) 그룹을 기준으로 고속도로연비(MPG.highway)의 평균(mean)을 구하시오. 이때 그룹별 평균 계산 결과를 차종(Type)과 실린더(Cylinders) 그룹 기준으로 정렬하여 제시하시오.
# Sorted by: keyby # Directly order by all the grouping variables using 'keyby'. DT[Origin == "USA", .(mean_mpg_highway = mean(MPG.highway)), keyby = .(Type, Cylinders)] [Out] Type Cylinders mean_mpg_highway 1: Compact 4 30.57143 2: Large 6 27.28571 3: Large 8 25.75000 4: Midsize 4 29.50000 5: Midsize 6 27.20000 6: Midsize 8 25.00000 7: Small 4 33.85714 8: Sporty 4 28.75000 9: Sporty 6 26.66667 10: Sporty 8 25.00000 11: Van 6 21.40000
|
(4) data.table에서 Chaining과 order()로 그룹별 집계결과 정렬하기 |
만약 그룹별 집계 결과를 제시할 때 오름차순(in ascending order) 또는 내림차순(in descending order) 으로 정렬하는 방법을 설정하고 싶을 때는 Chaining 으로 [order()] 구문을 추가해주면 됩니다.
order() 구문에서 오름차순을 변수 이름만 써주면 되며, 내림차순일 때는 변수 앞에 '마이너스 부호('-')를 붙여줍니다.
생산기지(Origin)이 "USA"인 차 중에서 차종(Type)과 실린더(Cylinders) 그룹 별로 고속도로 연비(MPG.highway)의 평균(mean)을 구하여, 차종 내림차순(Type in descending order)과 실린더 오름차순(Cylinders in ascending order) 으로 정렬하여 제시하시오.
# Chaining of operations # Ordering by Type in descending order and Cylinders in ascending order using 'order' DT[Origin == "USA", .(mean_mpg_highway = mean(MPG.highway)), by = .(Type, Cylinders)][ order(-Type, # in descending order Cylinders # in ascending order )] [Out] Type Cylinders mean_mpg_highway 1: Van 6 21.40000 2: Sporty 4 28.75000 3: Sporty 6 26.66667 4: Sporty 8 25.00000 5: Small 4 33.85714 6: Midsize 4 29.50000 7: Midsize 6 27.20000 8: Midsize 8 25.00000 9: Large 6 27.28571 10: Large 8 25.75000 11: Compact 4 30.57143
|
dplyr 는 Chaining 으로 표기할 때 '%>%' (shift + ctrl + M) 을 사용하는데요, data.table 은 그냥 기본 구문인 DT[i, j, by] 의 뒤에 바로 [ ... ] 를 이어서 붙여서 써주면 되므로 dplyr 대비 data.table 이 Chaining 표기도 더 깔끔합니다.
data.table 의 Chaining 시 행이 너무 길 경우에는 가독성을 높이기 위해 줄 넘김을 할 수 있습니다. (아래의 2가지 형태 참조)
# Chaining of operations in data.table DT[ ... ][ ... ][ ... ][ ... ] # or equivalently DT[ ... ][ ... ][ ... ][ ... ] |
(5) data.table에서 by 에 조건절의 블리언 그룹별로 집계하기 |
그룹별 집계의 마지막으로, by 구문에 조건절을 넣어서 TRUE, FALSE 블리언 그룹별로 집계를 하는 방법에 대한 소개입니다.
가격(Price)이 19.0 보다 큰 지 여부(TRUE, FALSE)와 고속도로 연비(MPG.highway)가 29.0 보다 큰지 여부(TRUE, FALSE)를 기준으로 그룹별로 차의 대수를 세어서 집계하시오.
keyby = .(Price > 19.0, MPG.highway > 29.0) 으로 해서 정렬(ordering)하여 제시하라고 했더니 R은 FALSE 를 '0'으로 인식하고, TRUE는 '1'로 인식하므로 FALSE 그룹을 먼저 제시하였군요.
# Expressions in 'by' # to find out how many cars price DT[, .N, keyby = .(Price > 19.0, MPG.highway > 29.0)] [Out] Price MPG.highway N 1: FALSE FALSE 20 2: FALSE TRUE 33 3: TRUE FALSE 35 4: TRUE TRUE 5
|
[Reference]
* https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html
다음 포스팅에서는 R data.table에서 여러개의 칼럼을 처리할 때 사용하는 .SD, .SDcols 특수 부호에 대해서 알아보겠습니다.
많은 도움이 되었기를 바랍니다.
이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)