지난 포스팅에서는 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 특수 부호에 대해서 알아보겠습니다. 


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,