apply 함수의 이해
앞선 포스트에서는 R에서 대규모 데이터에 대한 처리 과정에서 벡터 연산과 루프 연산간의 속도 차이를 살펴보았다. 사실 앞선 포스트에서 비교했던 세가지 방법(함수 자체의 벡터 연산을 통한 대규모 데이터 처리, for 루프에서 함수 호출을 사용한 대규모 데이터 처리, apply()
를 사용한 대규모 데이터 처리)들에서 사실 첫번째 방법의 속도가 우수하게 나올수 밖에 없었던 하나의 이유는 함수 호출에 대한 오버헤드가 없었다는 점이다. 루프를 사용하는 방법이나 apply()
를 사용하는 방법은 데이터를 계산할 때마다 함수를 호출해야하기 때문에 이에 대한 속도의 문제가 발생할 수 밖에 없다. 그렇기 때문에 함수 자체에서 벡터 연산을 지원하도록 설계하는 것이 R에서의 대규모 데이터 처리에 핵심일 것이다. 그렇다면 함수 호출이 빈번하게 발생하는 나머지 두 개의 방법에 대한 속도에서도 R에서 대규모의 데이터를 루프없이 실행시키는 apply()
계열 함수는 데이터의 갯수가 적으면 루프보다 비효율적이지만 대규모 데이터가 될수록 루프보다는 효율적임을 알 수 있었다. 그러면 apply()
의 구체적인 사용방법에 대해 알아보도록 하겠다.
apply()
는 R에서 기본적으로 제공하는 함수로써 대규모의 데이터를 처리할 때 루프를 대신하여 반복되는 작업을 함수화하여 처리할 수 있는 함수이다. apply()
는 주로 벡터, 리스트, 데이터프레임에서 사용하는 sum()
, mean()
, median()
과 같은 요약 함수들을 반복하여 적용할 때 사용된다. apply()
는 apply()
외에도 적용하는 데이터의 종류에 따라 lapply()
, sapply()
, tapply()
들을 사용할 수 있다.
데이터 Import
이번 포스트에서 사용하는 데이터는 한국교육개발원 교육통계서비스 홈페이지의 고등교육기관 대학 시도별 학교수를 활용하겠다.
library(readxl)
library(tidyverse)
df <- read_excel('./대학 시도별 학생수.xlsx', col_names = T, col_types = c('text', rep('numeric', 18)))
df <- df |> filter(합계 != is.na(합계))
df |> head()
## # A tibble: 6 x 19
## 연도 합계 서울 부산 대구 인천 광주 대전 울산 세종 경기 강원
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1980 402979 172155 34771 0 0 0 0 0 0 23829 15040
## 2 1981 535876 211205 46899 0 0 0 0 0 0 36322 21152
## 3 1982 661125 250643 61415 51043 23366 0 0 0 0 29523 23797
## 4 1983 772907 282166 70577 59602 26556 0 0 0 0 39374 28332
## 5 1984 870170 309021 80564 67098 28977 0 0 0 0 48439 32486
## 6 1985 931884 308763 90123 72255 30531 0 0 0 0 65895 35760
## # ... with 7 more variables: 충북 <dbl>, 충남 <dbl>, 전북 <dbl>, 전남 <dbl>,
## # 경북 <dbl>, 경남 <dbl>, 제주 <dbl>
apply()
apply()
는 apply()
계열의 함수중에 가장 기본적으로 사용되는 함수이다. 사실 lapply()
, sapply()
, tapply()
도 기본적으로 apply()
에서 파생되어 나온 함수들이기 때문에 apply()
의 사용법을 잘 알아두면 나머지 apply()
계열의 함수를 사용하는데 큰 문제가 없다.
apply()
의 기본적인 작동 원리는 다음의 그림과 같다.
apply()
의 기본적인 문법은 다음과 같다.
apply(X, MARGIN, FUN, ..., simplify = TRUE)
- X : apply를 적용할 array나 matrix
- MARGIN : 함수를 행 단위(MARGIN = 1)로 적용할지 열 단위(MARGIN = 2)로 적용할지를 설정
- FUN : 적용해야 할 함수 이름
앞서 설명한 바와 같이 apply()
는 array나 matrix에 대한 행방향, 혹은 열방향으로 특정 요약 함수를 적용하는 것이기 때문에 MARGIN을 통해 FUN으로 지정된 함수를 행방향으로 실행할지 열방향으로 실행할지를 결정한다. array나 matrix는 데이터프레임과 같이 2차원 테이블 형태로 표현되는데 데이터프레임은 다양한 데이터타입을 포함할 수 있는 반면 array나 matrix는 동일한 하나의 데이터타입을 가져야하기 때문에 데이터프레임을 사용하기 위해서는 동일한 데이터타입을 가지는 데이터프레임으로 처리한 후 사용하여야 한다.
앞서 불러들인 데이터에 대해 apply()
를 적용해보겠다.
앞서 불러들인 데이터에는 행방향으로 연도별 입학자 수가 들어있고 열방향으로 지역별 입학자수가 들어있다. 먼저 각 지역별 평균을 루프를 사용하여 평균을 산출하면 다음과 같다.
means_loop <- NULL
for(i in 2:(ncol(df))) {
means_loop[i-1] <- mean(pull(df[, i]))
}
means_loop <- set_names(means_loop, colnames(df)[2:ncol(df)])
means_loop
## 합계 서울 부산 대구 인천 광주
## 1511692.500 389251.905 146542.690 57595.810 35481.167 59574.619
## 대전 울산 세종 경기 강원 충북
## 67545.762 11784.738 3814.738 170260.976 71427.143 69262.833
## 충남 전북 전남 경북 경남 제주
## 113260.405 80075.548 41172.381 116164.238 65369.833 13107.714
위의 코드를 apply()
를 사용하여 작성하면 다음과 같다.
means_apply <- apply(df[, 2:ncol(df)], 2, mean)
means_apply
## 합계 서울 부산 대구 인천 광주
## 1511692.500 389251.905 146542.690 57595.810 35481.167 59574.619
## 대전 울산 세종 경기 강원 충북
## 67545.762 11784.738 3814.738 170260.976 71427.143 69262.833
## 충남 전북 전남 경북 경남 제주
## 113260.405 80075.548 41172.381 116164.238 65369.833 13107.714
위의 코드에서 보면 확실히 apply()
를 사용하는 코드가 짧고 명확하게 느껴진다. 이번에는 연도별 합계를 구하는 코드를 loop와 apply()
로 구분하여 살펴보면 다음과 같다.
means_loop <- NULL
for(i in 1:(nrow(df))) {
means_loop[i] <- sum(df[i, 3:19])
}
means_loop
## [1] 402979 535876 661125 772907 870170 931884 971127 989503 1003648
## [10] 1020771 1040166 1052140 1070169 1092464 1132437 1187735 1266876 1368461
## [19] 1477715 1587667 1665398 1729638 1771738 1808539 1836649 1859639 1888436
## [28] 1919504 1943437 1984043 2028841 2065451 2103958 2120296 2130046 2113293
## [37] 2084807 2050619 2030033 2001643 1981003 1938254
sum_apply <- apply(df[, 3:ncol(df)], 1, sum)
sum_apply
## [1] 402979 535876 661125 772907 870170 931884 971127 989503 1003648
## [10] 1020771 1040166 1052140 1070169 1092464 1132437 1187735 1266876 1368461
## [19] 1477715 1587667 1665398 1729638 1771738 1808539 1836649 1859639 1888436
## [28] 1919504 1943437 1984043 2028841 2065451 2103958 2120296 2130046 2113293
## [37] 2084807 2050619 2030033 2001643 1981003 1938254
위에서 실행한 열방향의 apply()
와 행방향의 apply()
코드 실행결과를 잘 살펴보면 하나 차이점이 눈에 보일 것이다. 열방향으로 apply()
를 실행하면 결과값이 각각의 열 이름이 붙은 named vector가 리턴되지만 행방향의 apply()
의 실행 결과는 단순 벡터가 리턴된다. 행 방향 apply()
의 결과를 named vector로 바꾸기 위해서는 다음과 같이 추가적인 코드를 실행시킨다.
sum_apply <- set_names(sum_apply, pull(df[, 1]))
sum_apply
## 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
## 402979 535876 661125 772907 870170 931884 971127 989503 1003648 1020771
## 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999
## 1040166 1052140 1070169 1092464 1132437 1187735 1266876 1368461 1477715 1587667
## 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009
## 1665398 1729638 1771738 1808539 1836649 1859639 1888436 1919504 1943437 1984043
## 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019
## 2028841 2065451 2103958 2120296 2130046 2113293 2084807 2050619 2030033 2001643
## 2020 2021
## 1981003 1938254
lapply()
lapply()
는 기본적으로 apply()
와 동일하게 작동하는 함수이다. 하지만 apply()
는 array와 matrix을 대상으로 적용되는 함수이지만 lapply()
의 ’l’은 list를 의미하는것으로 apply()
와 달리 list를 대상으로 적용되며 그 결과를 list로 반환한다. lapply()
의 코드를 살펴보기 위해서 먼저 앞의 데이터프레임을 리스트로 전환한다. 리스트는 행과 열이 없기때문에 apply()
에 있던 MARGIN 매개변수는 사용하지 않는다.
df_list_row <- as.list(as.data.frame(t(df[, 3:19])))
names(df_list_row) <- pull(df[, 1])
df_list_row |> head()
## $`1980`
## [1] 172155 34771 0 0 0 0 0 0 23829 15040
## [11] 15202 22977 24565 26540 47175 17316 3409
##
## $`1981`
## [1] 211205 46899 0 0 0 0 0 0 36322 21152
## [11] 20867 34113 34707 34751 66799 24361 4700
##
## $`1982`
## [1] 250643 61415 51043 23366 0 0 0 0 29523 23797
## [11] 26044 44837 42279 43401 27871 30860 6046
##
## $`1983`
## [1] 282166 70577 59602 26556 0 0 0 0 39374 28332
## [11] 31373 55063 49351 51531 35066 36742 7174
##
## $`1984`
## [1] 309021 80564 67098 28977 0 0 0 0 48439 32486
## [11] 35657 63669 54504 58713 40797 42148 8097
##
## $`1985`
## [1] 308763 90123 72255 30531 0 0 0 0 65895 35760
## [11] 39134 68407 58380 63419 44420 46131 8666
df_list_col <- as.list(as.data.frame(df[, 3:19]))
df_list_col |> head()
## $서울
## [1] 172155 211205 250643 282166 309021 308763 312779 311852 292796 287859
## [11] 288642 284521 285530 289965 299102 310723 327977 345481 381331 408911
## [21] 422594 432787 438986 445169 453265 446599 451481 454639 440846 447982
## [31] 468509 471648 494016 495395 504569 500178 498944 499021 502807 505743
## [41] 507319 504661
##
## $부산
## [1] 34771 46899 61415 70577 80564 90123 94777 97420 96426 100737
## [11] 102449 103097 99020 99453 102253 106851 113339 135924 143897 152125
## [21] 158799 164039 169004 173664 176804 179114 184810 190344 196441 204627
## [31] 209389 211926 213882 213594 213240 208811 203419 198348 194927 190526
## [41] 186383 180585
##
## $대구
## [1] 0 0 51043 59602 67098 72255 74465 66758 66452 66211 65584 48566
## [13] 48060 47339 47548 48395 49786 51307 53165 56741 57442 59432 57990 58387
## [25] 59478 60591 62473 63117 64033 63507 63726 63203 63279 69015 68000 67079
## [37] 65812 64336 63316 62200 61667 60566
##
## $인천
## [1] 0 0 23366 26556 28977 30531 31514 31858 32007 31987 31728 30868
## [13] 30398 29676 29662 29854 31003 31346 32608 34562 34834 35736 36204 36617
## [25] 37415 38829 40364 41394 42910 45278 46728 50392 48520 49247 47608 46379
## [37] 45283 43768 44183 43779 43435 42805
##
## $광주
## [1] 0 0 0 0 0 0 0 55420 54810 54268 54430 54416
## [13] 53907 53794 54861 55530 57592 59741 62216 65874 68074 70078 70595 71177
## [25] 73330 75414 77219 79861 81218 82334 83602 84268 85459 86712 88274 87415
## [37] 86280 84814 84187 82653 82338 79973
##
## $대전
## [1] 0 0 0 0 0 0 0 0 0 49975
## [11] 51347 52687 53902 54949 56925 59753 63253 67027 71578 76006
## [21] 79833 82860 83567 86188 86764 86448 87968 88389 88924 91426
## [31] 95252 104426 109416 111612 113583 115724 114956 112638 113709 111699
## [41] 108791 105347
전환된 리스트를 사용하여 연도별 lapply()
를 적용하여 합계를 내는 코드는 다음과 같다.
means_loop <- NULL
for(i in 1:length(df_list_row)) {
means_loop[i] <- mean(df_list_row[[i]])
}
means_loop
## [1] 23704.65 31522.12 38889.71 45465.12 51186.47 54816.71 57125.12
## [8] 58206.06 59038.12 60045.35 61186.24 61890.59 62951.12 64262.59
## [15] 66613.94 69866.76 74522.12 80497.71 86924.41 93392.18 97964.59
## [22] 101743.41 104219.88 106384.65 108038.18 109390.53 111084.47 112912.00
## [29] 114319.82 116708.41 119343.59 121497.12 123762.24 124723.29 125296.82
## [36] 124311.35 122635.71 120624.65 119413.71 117743.71 116529.59 114014.94
위의 코드를 lapply()
를 사용하면 다음과 같다.
means_lapply <- lapply(df_list_row, mean)
typeof(means_lapply)
## [1] "list"
means_lapply |> head()
## $`1980`
## [1] 23704.65
##
## $`1981`
## [1] 31522.12
##
## $`1982`
## [1] 38889.71
##
## $`1983`
## [1] 45465.12
##
## $`1984`
## [1] 51186.47
##
## $`1985`
## [1] 54816.71
이번에는 데이터프레임의 열을 각각의 리스트로 뽑아낸 리스트를 lapply()
를 사용하여 평균을 내는 코드는 다음과 같다.
sum_lapply <- lapply(df_list_col, sum)
typeof(sum_lapply)
## [1] "list"
sum_lapply |> head()
## $서울
## [1] 16348580
##
## $부산
## [1] 6154793
##
## $대구
## [1] 2419024
##
## $인천
## [1] 1490209
##
## $광주
## [1] 2502134
##
## $대전
## [1] 2836922
sapply()
앞선 lapply()
의 결과는 리스트로 반환된다. 하지만 각각의 리스트 엘리먼트는 평균값 하나만을 가지고 있는 리스트이다. 이는 리스트의 장점을 사용하기 어려운 형태의 결과이다. 이는 사실 벡터로 반환되는 것이 더 사용하기가 편리하다. 이렇게 리스트를 대상으로 apply()
를 적용하여 결과를 벡터 형태로 돌려주는 함수가 sapply()
이다. sapply()
는 lapply()
와 사용방법은 동일하지만 결과값의 형태가 벡터라는 점이 다르다.
means_sapply <- sapply(df_list_row, mean)
typeof(means_sapply)
## [1] "double"
means_sapply
## 1980 1981 1982 1983 1984 1985 1986 1987
## 23704.65 31522.12 38889.71 45465.12 51186.47 54816.71 57125.12 58206.06
## 1988 1989 1990 1991 1992 1993 1994 1995
## 59038.12 60045.35 61186.24 61890.59 62951.12 64262.59 66613.94 69866.76
## 1996 1997 1998 1999 2000 2001 2002 2003
## 74522.12 80497.71 86924.41 93392.18 97964.59 101743.41 104219.88 106384.65
## 2004 2005 2006 2007 2008 2009 2010 2011
## 108038.18 109390.53 111084.47 112912.00 114319.82 116708.41 119343.59 121497.12
## 2012 2013 2014 2015 2016 2017 2018 2019
## 123762.24 124723.29 125296.82 124311.35 122635.71 120624.65 119413.71 117743.71
## 2020 2021
## 116529.59 114014.94
sum_sapply <- sapply(df_list_col, sum)
typeof(sum_sapply)
## [1] "double"
sum_sapply
## 서울 부산 대구 인천 광주 대전 울산 세종
## 16348580 6154793 2419024 1490209 2502134 2836922 494959 160219
## 경기 강원 충북 충남 전북 전남 경북 경남
## 7150961 2999940 2909039 4756937 3363173 1729240 4878898 2745533
## 제주
## 550524
tapply
tapply()
는 앞선 apply()
와 같은 방식으로 작동하지만 그룹화된 각각의 그룹에 대해 적용된다는 점이 다르다. 이는 dplyr
패키지의 group_by()
와 summarize()
를 사용하여 동작하는 것과 동일한 결과를 낸다. tapply()
는 내부적으로 split()
를 사용하여 벡터나 데이터프레임을 분할하기 때문에 split()
가 작동되는 R 객체에 한해 작동한다. 따라서 내부적으로 그룹화할 컬럼을 INDEX 매개변수로 전달해야 한다. 앞선 df 데이터프레임을 tapply()
에 적용하기 위해서 먼저 긴 형태의 데이터프레임으로 변환한다.
df_long <- pivot_longer(df, -1, names_to = '지역')
df_long <- df_long |> filter(지역 != '합계')
df_long$지역 <- as.factor(df_long$지역)
df_long |> head()
## # A tibble: 6 x 3
## 연도 지역 value
## <chr> <fct> <dbl>
## 1 1980 서울 172155
## 2 1980 부산 34771
## 3 1980 대구 0
## 4 1980 인천 0
## 5 1980 광주 0
## 6 1980 대전 0
tapply(df_long$value, INDEX = df_long$지역, sum)
## 강원 경기 경남 경북 광주 대구 대전 부산
## 2999940 7150961 2745533 4878898 2502134 2419024 2836922 6154793
## 서울 세종 울산 인천 전남 전북 제주 충남
## 16348580 160219 494959 1490209 1729240 3363173 550524 4756937
## 충북
## 2909039
tapply(df_long$value, INDEX = df_long$연도, mean)
## 1980 1981 1982 1983 1984 1985 1986 1987
## 23704.65 31522.12 38889.71 45465.12 51186.47 54816.71 57125.12 58206.06
## 1988 1989 1990 1991 1992 1993 1994 1995
## 59038.12 60045.35 61186.24 61890.59 62951.12 64262.59 66613.94 69866.76
## 1996 1997 1998 1999 2000 2001 2002 2003
## 74522.12 80497.71 86924.41 93392.18 97964.59 101743.41 104219.88 106384.65
## 2004 2005 2006 2007 2008 2009 2010 2011
## 108038.18 109390.53 111084.47 112912.00 114319.82 116708.41 119343.59 121497.12
## 2012 2013 2014 2015 2016 2017 2018 2019
## 123762.24 124723.29 125296.82 124311.35 122635.71 120624.65 119413.71 117743.71
## 2020 2021
## 116529.59 114014.94
'데이터 전처리' 카테고리의 다른 글
빈도표(분할표, Contingency table)로 데이터 개수, 비율 구하기 in R (0) | 2022.06.04 |
---|---|
데이터 프레임 피봇(pivot) in R - pivot_longer, pivot_wider (0) | 2022.04.30 |
for loop, apply, 벡터 연산의 속도 차이 in R (0) | 2022.02.26 |
색 in R (0) | 2022.02.24 |
summarise와 mutate (0) | 2022.01.06 |
댓글