본문 바로가기
  • plotly로 바로쓰는 동적시각화 in R & 파이썬
데이터 전처리

summarise와 mutate

by 아참형인간 2022. 1. 6.
groupby.knit

group_by()summarise()mutate()

데이터를 다루다 보면 데이터들을 특정한 기준에 따라 구분하여 그루핑해서 연산해야할 경우가 많다. 예를 들어 성적 데이터를 다룰때 학년별로 혹은 반별로 남여별로 그루핑하여 연산을 해야하는 경우이다. 위에서 불러들인 예에서도 연도별로 그루핑을 할 수 있고 지역별로 그루핑을 할수도 있을 것이다. 보통 group_by()후에는 그룹별로 요약값을 내는 경우가 일반적이다. 하지만 일부 응영에서는 그룹별로 연산을 수행해야 하는 겅우도 있을 것이다. 이러한 경우를 어떻게 처리해야 하는지 알아보자.

이번 포스트에서 사용할 데이터는 한국교육개발원 교육통계서비스 홈페이지고등교육기관 연도별 입학자수를 활용하였다.

df_입학자 <- read_excel('2021_연도별 입학자수.xlsx', 
                 ## 'data' 시트의 데이터를 불러오는데,
                 sheet = 'Sheet0',
                 ## 앞의 10행을 제외하고
                 skip = 3, 
                 ## 첫번째 행은 열 이름을 설정
                 col_names = FALSE, 
                 ## 열의 타입을 설정, 처음 8개는 문자형으로 다음 56개는 수치형으로 설정
                 col_types = c(rep('text', 2), rep('numeric', 30)))
## New names:
## * `` -> ...1
## * `` -> ...2
## * `` -> ...3
## * `` -> ...4
## * `` -> ...5
## * ...
## df_입학자 데이터에서 1, 2열과 3번부터 32열까지 2열마다 하나씩 선택하여  다시 df_입학자로 저장
df_입학자 <- df_입학자 |> select(1, 2, 5, 7, 9, 11, 13, 19, 29, 31)

## df_입학자의 열이름을 적절한 이름으로 설정
colnames(df_입학자) <- c('연도', '지역', '전문대학', '교육대학', '일반대학', '방송통신대학', '산업대학', '원격및사이버대학', '석사', '박사')

## 연도에 결측치가 입력된 데이터는 제외하여 df_입학자로 저장(엑셀 데이터 하단의 설명 라인 제거)
df_입학자 <- df_입학자 |> filter(!is.na(지역))

이 작업을 filter()를 사용해서 수행한다면 filter()로 서브셋을 각각 만들고 연산을 적용하는 과정을 반복적으로 수행해야 할 것이고 이 과정에서 코드도 길어지고 오류를 낼 가능성도 많아진다. 이를 위해 사용되는 함수가 group_by()이다. group_by()는 구분자로 사용될 열에 포함된 값에 따라 데이터를 분리하고 연산을 각각의 그룹에 적용시켜주는 함수이다.

group_by(.data, 그루핑 열, ...)
  - .data : 데이터프레임, `tibble`과 같은 확장된 데이터프레임
  - 그루핑 열 : 그루핑할 구분자가 포함된 열 이름

df_입학자 |> group_by(연도)
## # A tibble: 400 x 10
## # Groups:   연도 [23]
##    연도  지역  전문대학 교육대학 일반대학 방송통신대학 산업대학 원격및사이버대학
##    <chr> <chr>    <dbl>    <dbl>    <dbl>        <dbl>    <dbl>            <dbl>
##  1 1999  전체    306802     4840   319278        49648    30882                0
##  2 2000  전체    318135     5075   321399        47387    33240                0
##  3 2001  전체    322687     4959   327031        50949    33870                0
##  4 2002  전체    311304     4971   320534        47175    31896                0
##  5 2003  전체    275318     5166   321116        40195    29720                0
##  6 2004  전체    259182     5783   329509        35203    28444            12044
##  7 2005  전체    251283     6188   326284        32389    28197            16086
##  8 2006  전체    254433     6235   335581        31545    22061            14529
##  9 2007  전체    255395     5741   342250        32493    22304            14209
## 10 2008  전체    249291     5459   342916        33725    22374            18401
## # ... with 390 more rows, and 2 more variables: 석사 <dbl>, 박사 <dbl>
df_입학자 |> group_by(연도, 지역)
## # A tibble: 400 x 10
## # Groups:   연도, 지역 [400]
##    연도  지역  전문대학 교육대학 일반대학 방송통신대학 산업대학 원격및사이버대학
##    <chr> <chr>    <dbl>    <dbl>    <dbl>        <dbl>    <dbl>            <dbl>
##  1 1999  전체    306802     4840   319278        49648    30882                0
##  2 2000  전체    318135     5075   321399        47387    33240                0
##  3 2001  전체    322687     4959   327031        50949    33870                0
##  4 2002  전체    311304     4971   320534        47175    31896                0
##  5 2003  전체    275318     5166   321116        40195    29720                0
##  6 2004  전체    259182     5783   329509        35203    28444            12044
##  7 2005  전체    251283     6188   326284        32389    28197            16086
##  8 2006  전체    254433     6235   335581        31545    22061            14529
##  9 2007  전체    255395     5741   342250        32493    22304            14209
## 10 2008  전체    249291     5459   342916        33725    22374            18401
## # ... with 390 more rows, and 2 more variables: 석사 <dbl>, 박사 <dbl>

위의 코드를 실행시켜보면 실행 결과는 별 차이가 없는 것같다. 하지만 자세히 보면 하나 차이를 발견할 수 있는데 데이터가 표시되는 바로 윗 줄에 ‘# Groups:’ 로 시작하는 한 줄이 표시된다. group_by()는 데이터프레임을 내부적으로 tibble로 변환하고, 변환된 tibble을 그루핑 구분자에 따라 해당 그룹으로 분할하여 저장한다. 그렇기 때문에 group_by()를 적용한 객체의 타입이 grouped_df로 변경되고 내부적으로 그루핑된 데이터 구조를 가지는 객체로 변환된다. 다음의 코드를 실행해보자.

## df_입학자의 데이터 구조 출력
df_입학자 |>  str()
## tibble [400 x 10] (S3: tbl_df/tbl/data.frame)
##  $ 연도            : chr [1:400] "1999" "2000" "2001" "2002" ...
##  $ 지역            : chr [1:400] "전체" "전체" "전체" "전체" ...
##  $ 전문대학        : num [1:400] 306802 318135 322687 311304 275318 ...
##  $ 교육대학        : num [1:400] 4840 5075 4959 4971 5166 ...
##  $ 일반대학        : num [1:400] 319278 321399 327031 320534 321116 ...
##  $ 방송통신대학    : num [1:400] 49648 47387 50949 47175 40195 ...
##  $ 산업대학        : num [1:400] 30882 33240 33870 31896 29720 ...
##  $ 원격및사이버대학: num [1:400] 0 0 0 0 0 ...
##  $ 석사            : num [1:400] 73826 82374 86992 89557 91178 ...
##  $ 박사            : num [1:400] 10447 11705 12570 13227 13310 ...
## df_입학자를 연도별로 그루핑한 결과의 구조 출력
df_입학자 |> group_by(연도) |> str()
## grouped_df [400 x 10] (S3: grouped_df/tbl_df/tbl/data.frame)
##  $ 연도            : chr [1:400] "1999" "2000" "2001" "2002" ...
##  $ 지역            : chr [1:400] "전체" "전체" "전체" "전체" ...
##  $ 전문대학        : num [1:400] 306802 318135 322687 311304 275318 ...
##  $ 교육대학        : num [1:400] 4840 5075 4959 4971 5166 ...
##  $ 일반대학        : num [1:400] 319278 321399 327031 320534 321116 ...
##  $ 방송통신대학    : num [1:400] 49648 47387 50949 47175 40195 ...
##  $ 산업대학        : num [1:400] 30882 33240 33870 31896 29720 ...
##  $ 원격및사이버대학: num [1:400] 0 0 0 0 0 ...
##  $ 석사            : num [1:400] 73826 82374 86992 89557 91178 ...
##  $ 박사            : num [1:400] 10447 11705 12570 13227 13310 ...
##  - attr(*, "groups")= tibble [23 x 2] (S3: tbl_df/tbl/data.frame)
##   ..$ 연도 : chr [1:23] "1999" "2000" "2001" "2002" ...
##   ..$ .rows: list<int> [1:23] 
##   .. ..$ : int [1:17] 1 24 47 70 93 116 139 162 194 217 ...
##   .. ..$ : int [1:17] 2 25 48 71 94 117 140 163 195 218 ...
##   .. ..$ : int [1:17] 3 26 49 72 95 118 141 164 196 219 ...
##   .. ..$ : int [1:17] 4 27 50 73 96 119 142 165 197 220 ...
##   .. ..$ : int [1:17] 5 28 51 74 97 120 143 166 198 221 ...
##   .. ..$ : int [1:17] 6 29 52 75 98 121 144 167 199 222 ...
##   .. ..$ : int [1:17] 7 30 53 76 99 122 145 168 200 223 ...
##   .. ..$ : int [1:17] 8 31 54 77 100 123 146 169 201 224 ...
##   .. ..$ : int [1:17] 9 32 55 78 101 124 147 170 202 225 ...
##   .. ..$ : int [1:17] 10 33 56 79 102 125 148 171 203 226 ...
##   .. ..$ : int [1:17] 11 34 57 80 103 126 149 172 204 227 ...
##   .. ..$ : int [1:17] 12 35 58 81 104 127 150 173 205 228 ...
##   .. ..$ : int [1:17] 13 36 59 82 105 128 151 174 206 229 ...
##   .. ..$ : int [1:17] 14 37 60 83 106 129 152 175 207 230 ...
##   .. ..$ : int [1:18] 15 38 61 84 107 130 153 176 185 208 ...
##   .. ..$ : int [1:18] 16 39 62 85 108 131 154 177 186 209 ...
##   .. ..$ : int [1:18] 17 40 63 86 109 132 155 178 187 210 ...
##   .. ..$ : int [1:18] 18 41 64 87 110 133 156 179 188 211 ...
##   .. ..$ : int [1:18] 19 42 65 88 111 134 157 180 189 212 ...
##   .. ..$ : int [1:18] 20 43 66 89 112 135 158 181 190 213 ...
##   .. ..$ : int [1:18] 21 44 67 90 113 136 159 182 191 214 ...
##   .. ..$ : int [1:18] 22 45 68 91 114 137 160 183 192 215 ...
##   .. ..$ : int [1:18] 23 46 69 92 115 138 161 184 193 216 ...
##   .. ..@ ptype: int(0) 
##   ..- attr(*, ".drop")= logi TRUE

위의 코드 중 앞선 코드는 df_입학자의 데이터 구조를 출력하는 코드이다. 데이터의 열과 열의 타입, 몇 개의 데이터 내용이 표시된다. 두번째 코드는 연도별로 그루핑된 데이터 구조를 출력하는 코드이다. 데이터 타입이 grouped_df로 변경되었다. 열은 앞선 결과와 별 차이없이 나타나지만 그 아래 추가적인 정보가 나타나는데 각각의 그룹에 속한 행번호가 표기된다. 이렇게 그루핑된 데이터를 다시 하나의 데이터로 변환하기 위해서는 ungroup()을 사용한다. 이 함수를 사용하면 grouped_df로 바뀌었던 데이터 구조가 다시 데이터프레임이나 tibble로 변환된다.

## df_입학자를 연도별로 그루핑한 결과를 다시 ungroup()한 결과의 구조 출력
df_입학자 |> group_by(연도) |> ungroup() |> str()
## tibble [400 x 10] (S3: tbl_df/tbl/data.frame)
##  $ 연도            : chr [1:400] "1999" "2000" "2001" "2002" ...
##  $ 지역            : chr [1:400] "전체" "전체" "전체" "전체" ...
##  $ 전문대학        : num [1:400] 306802 318135 322687 311304 275318 ...
##  $ 교육대학        : num [1:400] 4840 5075 4959 4971 5166 ...
##  $ 일반대학        : num [1:400] 319278 321399 327031 320534 321116 ...
##  $ 방송통신대학    : num [1:400] 49648 47387 50949 47175 40195 ...
##  $ 산업대학        : num [1:400] 30882 33240 33870 31896 29720 ...
##  $ 원격및사이버대학: num [1:400] 0 0 0 0 0 ...
##  $ 석사            : num [1:400] 73826 82374 86992 89557 91178 ...
##  $ 박사            : num [1:400] 10447 11705 12570 13227 13310 ...

위에서는 group_by()의 구조를 설명하였다. group_by()의 사용은 summarise()를 사용하여 그룹별로 요약값을 산출할 때 주로 사용된다.

## df_입학자를 연도별로 그루핑하고 `summarise()`를 사용하여 연도별 전문대학 합계, 평균, 최고, 최저를 산출
df_입학자 |> group_by(연도) |> summarise(전문대학합계 = sum(전문대학), 전문대학평균 = mean(전문대학), 전문대학최고 = max(전문대학), 전문대학최저 = min(전문대학))
## # A tibble: 23 x 5
##    연도  전문대학합계 전문대학평균 전문대학최고 전문대학최저
##    <chr>        <dbl>        <dbl>        <dbl>        <dbl>
##  1 1999        613604       36094.       306802         2576
##  2 2000        636270       37428.       318135         3641
##  3 2001        645374       37963.       322687         4739
##  4 2002        622608       36624        311304         4503
##  5 2003        550636       32390.       275318         4129
##  6 2004        518364       30492        259182         3543
##  7 2005        502566       29563.       251283         3602
##  8 2006        508866       29933.       254433         3441
##  9 2007        510790       30046.       255395         3545
## 10 2008        498582       29328.       249291         3512
## # ... with 13 more rows
## df_입학자를 지역별로 그루핑하고 `summarise()`를 사용하여 연도별 일반대학 합계, 평균, 최고, 최저를 산출
df_입학자 |> group_by(지역) |> summarise(일반대학합계 = sum(일반대학), 일반대학평균 = mean(일반대학), 일반대학최고 = max(일반대학), 일반대학최저 = min(일반대학))
## # A tibble: 18 x 5
##    지역  일반대학합계 일반대학평균 일반대학최고 일반대학최저
##    <chr>        <dbl>        <dbl>        <dbl>        <dbl>
##  1 강원        394317       17144.        19314        14657
##  2 경기        935864       40690.        45211        35937
##  3 경남        321578       13982.        16080        11698
##  4 경북        640879       27864.        31531        23648
##  5 광주        332712       14466.        16193        12761
##  6 대구        237326       10319.        10901         9854
##  7 대전        411064       17872.        21053        14853
##  8 부산        769485       33456.        36533        29505
##  9 서울       1827126       79440.        84771        73674
## 10 세종         26911        2990.         3157         2847
## 11 울산         78185        3399.         4011         3035
## 12 인천        157015        6827.         8235         5849
## 13 전남        210609        9157.        10974         7959
## 14 전북        381670       16594.        19852        13522
## 15 전체       7861291      341795.       372941       319278
## 16 제주         62679        2725.         3205         2398
## 17 충남        692863       30124.        35584        25938
## 18 충북        381008       16566.        19058        14428

앞서 설명한 바와 같이 group_by()는 주로 summarise()와 요약 함수를 사용하여 그룹별로 요약 값을 산출하는데 많이 사용된다. 하지만 group_by()mutate()를 사용할 수도 있다. group_by()된 객체에 summarise()를 사용하면 그룹마다 요약 행이 하나씩 생성되어 그룹수만큼의 행이 출력된다. 반면 mutate()를 사용하면 각 그룹의 데이터를 대상으로 연산된 결과 열이 포함된 결과가 산출된다.

예를 들어 위의 예에서 각 연도마다 일반대학 입학생의 지역별 분포 비율을 구해야 한다고 생각해보자. 먼저 각 연도마다 일반대학 전체 입학생의 합계값이 필요할 것이다. 이 합계값을 지역별 입학생으로 나누어야 지역별 분포가 나올 것이다. 이를 위해서는 먼저 각 연도 그룹별로 일반대학 입학생의 합계를 모든 행에 넣어주어야 mutate()를 사용하여 비율을 구할 것이다. 이를 group_by()없이 구현해야 한다면 다음과 같이 구할 수 있을 것이다.

## df_입학자를 연도 1999년으로 필터링하고 전체행을 삭제한 후에 전체 일반대학 합계(sum(일반대학))를 각각의 지역값으로 나누어 비율을 산출
df_입학자 |> filter(연도 == 1999, 지역 != '전체') |> transmute(연도, 지역, 일반대학비율 = 일반대학/sum(일반대학))
## # A tibble: 16 x 3
##    연도  지역  일반대학비율
##    <chr> <chr>        <dbl>
##  1 1999  서울       0.243  
##  2 1999  부산       0.0924 
##  3 1999  대구       0.0341 
##  4 1999  인천       0.0187 
##  5 1999  광주       0.0408 
##  6 1999  대전       0.0482 
##  7 1999  울산       0.00954
##  8 1999  경기       0.114  
##  9 1999  강원       0.0473 
## 10 1999  충북       0.0452 
## 11 1999  충남       0.0820 
## 12 1999  전북       0.0596 
## 13 1999  전남       0.0344 
## 14 1999  경북       0.0846 
## 15 1999  경남       0.0367 
## 16 1999  제주       0.00898

위의 코드를 각각의 연도에 적용해야 하기 때문에 이 코드를 1999부터 2021까지 22번 실행해야 한다. 다른 프로그래밍 경험이 있는 사용자라면 for 루프를 사용할 수도 있겠지만 group_by()mutate()를 다음과 같이 사용하면 간단히 해결된다.

df_입학자 |>    ## df_입학자에서 
  filter(지역 != '전체') |>  ## 지역이 전체가 아닌 행들만 걸러내고
  group_by(연도) |>   ## 연도별로 그루핑하고
  transmute(연도, 지역, 일반대학합계 = sum(일반대학), 일반대학비율 = 일반대학/일반대학합계) |>  ## 연도, 지역, 일반대학합계, 일반대학비율 열을 생성
  filter(연도 == 1999)  ## 데이터 확인을 위해 연도가 1999년만 필터링
## # A tibble: 16 x 4
## # Groups:   연도 [1]
##    연도  지역  일반대학합계 일반대학비율
##    <chr> <chr>        <dbl>        <dbl>
##  1 1999  서울        319278      0.243  
##  2 1999  부산        319278      0.0924 
##  3 1999  대구        319278      0.0341 
##  4 1999  인천        319278      0.0187 
##  5 1999  광주        319278      0.0408 
##  6 1999  대전        319278      0.0482 
##  7 1999  울산        319278      0.00954
##  8 1999  경기        319278      0.114  
##  9 1999  강원        319278      0.0473 
## 10 1999  충북        319278      0.0452 
## 11 1999  충남        319278      0.0820 
## 12 1999  전북        319278      0.0596 
## 13 1999  전남        319278      0.0344 
## 14 1999  경북        319278      0.0846 
## 15 1999  경남        319278      0.0367 
## 16 1999  제주        319278      0.00898

댓글