본문 바로가기
R

(R) apply 계열 함수/apply, lapply, sapply, mapply, tapply / 함수 동시 적용/ 예시

by jangpiano 2021. 6. 3.
반응형

apply() 계열 함수로는, apply(), lapply(), sapply(), mapply(), tapply(), vapply() 가 있습니다. 이는 어떠한 자료의 그룹, 열, 행별로 함수를 적용시키는 R 내장 함수입니다. 

위 함수 모두, 함수를 데이터에 동시에 적용시키기에 매우 편리하게 하는 함수입니다, 하지만 각기 다른 특징, arguments을 가지기 때문에 정확한 이해가 필요하죠. 

위 함수들을 적절히 적용하기 위해서는 데이터 구조에 대한 이해도가 필요할 수 있습니다. 

*데이터 구조에 대한 자세한 설명은, 아래의 포스팅을 참고해주세요:) 

https://jangpiano-science.tistory.com/127?category=875433 

 

R 데이터 구조와 색인(Indexing)/ scalar, vector, factor, matrix, array, data frame, list

R의 데이터 구조는 7개의 형태로 구분이 됩니다. 7개의 구조를 사용하기에 적절한 상황이 모두 다르기 때문에, 각각의 구조에 대한 특징과 필요성을 정확하게 이해하는것이 중요하겠죠? 데이터

jangpiano-science.tistory.com

<apply 함수> 

apply 함수에 대하여, R에서 정의하는 바는 다음과 같습니다. 

array 또는 matrix 를 입력값으로 받아, 행 또는 열, 혹은 행과 열에 특정 함수를 동시적용시켜주는 함수입니다. 

 

Input : 행렬, 배열 

output : 벡터, 배열, 리스트 

 

apply()의 arguments: X, MARGIN, FUN 

X: 전체 데이터 ; 행렬 또는 배열 

MARGIN: 함수를 적용하고자 데이터 내의 벡터  (MARGIN = 1 --> 행     | MARGIN = 2 --> 열   | MARGIN = c(1, 2) --> 행과 열) 

FUN: 지정된 MARGIN에 적용하고자 하는 함수 (R의 내장함수와 직접 지정한 함수 모두 사용 가능) 

 

rowSums, rowMeans, columnSums, columnMeans 와 같은 R 내장 함수는, 행과 열에 sum 혹은 mean 함수를 일괄 적용시킵니다. 

위와 같은 역할을 똑같이 수행하는 함수를 apply()를 이용하여 만들어봅시다. rowSums 는 apply()에서 MARGIN=1로 설정하고, FUN = sum 으로 설정함으로써 똑같이 구현할 수 있습니다. MARGIN = 1이 행을 의미하고, MARGIN = 2가 열을 의미하는것만 헷갈리지 않으신다면, 쉽게 비교가능하실겁니다. 

x <- matrix(runif(21, min = 0, max = 15), nrow = 3)
x
'''
          [,1]     [,2]      [,3]     [,4]      [,5]     [,6]      [,7]
[1,] 3.5944412 13.14404  6.829117 9.073999  4.053902 3.198122 13.861117
[2,] 0.8840157 11.68372  6.151261 9.820859 14.890261 1.940585  8.981415
[3,] 9.6343239 11.95963 12.163054 5.297959  9.502399 7.171771 14.642560
'''

#<rowSums, rowMeans, colSums, colMeans>

rowSums(x)
#[1] 53.75474 54.35212 70.37170
rowMeans(x)
#[1]  7.679248  7.764588 10.053100
colSums(x)
#[1] 14.11278 36.78739 25.14343 24.19282 28.44656 12.31048 37.48509
colMeans(x)
#[1]  4.704260 12.262464  8.381144  8.064272  9.482187  4.103493 12.495031

#<apply() 이용하여 똑같이 구현>
apply(x, MARGIN = 1, sum)
#[1] 53.75474 54.35212 70.37170
apply(x, MARGIN = 1, mean)
#[1]  7.679248  7.764588 10.053100
apply(x, MARGIN = 2, sum)
#[1] 14.11278 36.78739 25.14343 24.19282 28.44656 12.31048 37.48509
apply(x, MARGIN = 2, mean)
#[1]  4.704260 12.262464  8.381144  8.064272  9.482187  4.103493 12.495031

 

*결측치(NA)가 발견되는 경우 

결측치가 발견되는 특정 행과 열에 대해서는, 어떤 함수를 적용해도 NA를 출력하게 됩니다. apply 계열 함수에서는, 'na.rm = TRUE ' 를 추가해주므로써, 결측치가 제거된 행과 열에 함수가 적용된 결과를 출력받을 수 있습니다. 

#missing value 
x[3,4]=NA
x
'''
          [,1]     [,2]      [,3]     [,4]      [,5]     [,6]      [,7]
[1,] 3.5944412 13.14404  6.829117 9.073999  4.053902 3.198122 13.861117
[2,] 0.8840157 11.68372  6.151261 9.820859 14.890261 1.940585  8.981415
[3,] 9.6343239 11.95963 12.163054       NA  9.502399 7.171771 14.642560
'''

apply(x, MARGIN = 1, sum)
#[1] 53.75474 54.35212       NA

apply(x, MARGIN = 1, sum, na.rm = TRUE)
#[1] 53.75474 54.35212 65.07374

 

<lapply 함수> 

lapply 함수에 대하여, R 에서 정의하는 정리는 다음과 같습니다. 

list를 입력값으로 받아, 각각의 변수에 특정 함수를 동시적용시켜주는 함수입니다. 주의하여야 할 점은, list를 입력값으로 받으며, 출력값 역시 list 로 반환한다는 것입니다. 

 

Input : 리스트 (리스트는 다른 데이터 구조를 모두 어우르는 구조이므로, 당연히 dataframe 혹은 matrix 도 input으로 받을 수 있게 된다) 

output : 리스트 

 

lapply()의  arguments : X, FUN

X : 함수를 적용하고자 하는 리스트 

FUN : 적용하고자 하는 함수

 

*내장함수(mean) 이용 예시 

*내장 함수로는, mean 이외에도 sd, is.character, is.factor, as.factor 등 다양한 함수들이 존재힙니다. 

두개의 변수로 이루어진 데이터 프레임에 lapply를 이용하여 내장함수인 mean함수를 적용해봅니다. 

lapply 함수가 가지는 두개의 변수(x,y)에 대하여, lapply함수를 이용해 동시에 mean이라는 함수가 적용하면, 각각의 변수에 대한 평균값이 list형태로 반환되는것을 확인하실 수 있습니다. 

list_1 <- data.frame(x = rnorm(5, 0, 1), y = runif(5, 3, 10))
list_1 

'''
            x        y
1  1.68255759 5.154824
2 -0.03035871 3.802796
3  0.77978934 5.242952
4 -0.25534083 4.353862
5  0.50317363 9.491140
'''

output <- lapply(list_1, mean) 
str(output)
'''
List of 2
 $ x: num 0.536
 $ y: num 5.61
'''

*직접 만든 함수 이용 예시 

이번에는, R 내장 연산함수가 아닌, 직접 fun_1이라는 함수를 지정한 후, lapply를 이용해서 각각의 변수들(x,y)에 함수 명령어를 동시에 적용해보도록 합시다. 

list_2 <- data.frame(x = seq(1, 10, by =2), y = c(rep(c(1, 2),2), 3))
list_2 
'''
  x y
1 1 1
2 3 2
3 5 1
4 7 2
5 9 3
'''
fun_1<- function(x)(3*x+2) 
lapply(list_2, fun_1)
'''
$x
[1]  5 11 17 23 29

$y
[1]  5  8  5  8 11
'''

 

<sapply 함수> 

sapply함수 역시, lapply함수와 기능적으로 다를바가 없는 함수입니다. 두 함수 모두, 데이터의 모든 변수에 함수 명령어를 동시에 적용하죠. 

하지만 다른점이 있다면, lapply함수는 list를 반환하는 반면, sapply함수는 vector 혹은 matrix를 반환한다는 차이가 있습니다. 따라서, 여러분께서, 어떤 형테로 데이터가 반환되기가 원하는지에 따라 lapply함수 혹은 sapply함수를 채택하면 되겠죠?

lapply 함수  sapply 함수 
데이터의 모든 변수에 함수 명령어를 동시 적용 
Input : list 
Output : list
Input : list
Output : vector & matrix 

*내장함수(mean) 이용 예시 

list_1 <- data.frame(x = rnorm(5, 0, 1), y = runif(5, 3, 10))
list_1 
'''
           x        y
1 -0.5729215 9.524570
2  0.3783453 5.056278
3  0.7958272 8.083943
4 -0.8097802 7.283971
5 -0.6551110 8.936221
'''

output <- sapply(list_1, mean) 
output
'''
        x         y 
-0.172728  7.776997 
'''

is.vector(output)
#[1] TRUE
is.list(output)
#[1] FALSE

 

*직접 만든 함수 이용 예시 

list_2 <- data.frame(x = seq(1, 10, by =2), y = c(rep(c(1, 2),2), 3))
list_2 
'''
  x y
1 1 1
2 3 2
3 5 1
4 7 2
5 9 3
'''

fun_1<- function(x)(3*x+2) 
output = sapply(list_2, fun_1) 
output 
'''
      x  y
[1,]  5  5
[2,] 11  8
[3,] 17  5
[4,] 23  8
[5,] 29 11
'''

is.matrix(output) 
#[1] TRUE

is.list(output)
#[1] FALSE

* 'simplify' argument in sapply

list_2 <- data.frame(x = seq(1, 10, by =2), y = c(rep(c(1, 2),2), 3))
list_2
'''
  x y
1 1 1
2 3 2
3 5 1
4 7 2
5 9 3
'''

fun_1<- function(x)(3*x+2) 
sapply(list_2, fun_1)                      #default : simplify = TRUE 
'''      x  y
[1,]  5  5
[2,] 11  8
[3,] 17  5
[4,] 23  8
[5,] 29 11
'''


sapply(list_2, fun_1, simplify= FALSE)
'''
$x
[1]  5 11 17 23 29

$y
[1]  5  8  5  8 11
'''

 

<mapply 함수> 

mapply함수란, sapply함수의 다변량 버전입니다. mapply()는 함수를 지정할때, 두개 이상의 인수 포함되곤 하죠. 

예를들면, sapply에서는, 함수를 지정할때, fun_1<- function(x)(3*x+2) 로 하나의 인수에 대한 함수로 지정을 했다면,

mapply함수는, 두개 이상의 인수에 대한 함수로, func_2 <- function(x, y)(3*x+y), func_3 <- function(x, y)(x^2+10*y) 다음과 같은 함수를 argumet로 채택할 수 있게 됩니다. 

또한, sapply함수의 다변량 버전이기 때문에, mapply 함수의 output 역시 vector 혹은 matrix 형태로 출력됩니다. 

 

주의할 점이 있다면, mapply함수는 FUN arguments를 먼저 받은 후, 그 이후에 함수를 적용할 인수들을 받습니다. 

#Example 1 
list_2 <- data.frame(x = seq(1, 10, by =2), y = c(rep(c(1, 2),2), 3))
list_2 
'''
  x y
1 1 1
2 3 2
3 5 1
4 7 2
5 9 3
'''

func_2 <- function(x, y)(3*x+y) 
output = mapply(func_2, list_2$x, list_2$y) 
output 
#[1]  4 11 16 23 30

is.vector(output) 
#TRUE



#Example 2 
x1 <- seq(30, 34, by = 2) 
x1
#[1] 30 32 34

x2 <- seq(25, 35, by = 5) 
x2
#[1] 25 30 35

x1.x2 <- expand.grid(x1, x2) 
x1.x2
'''
  Var1 Var2
1   30   25
2   32   25
3   34   25
4   30   30
5   32   30
6   34   30
7   30   35
8   32   35
9   34   35
'''

func_3 <- function(x, y)(x^2+10*y) 

output = mapply(func_3, x1.x2[,1], x1.x2[,2]) 
output 
#[1] 1150 1274 1406 1200 1324 1456 1250 1374 1506

is.vector(output) 
#TRUE

<tapply 함수> 

tapply()는, 특정 벡터의 요인(factor)의 수준(level)별로 함수 명령어를 동시에 적용하는 함수입니다. 

 

tapply함수는 세개의 arguments를 갖습니다: X, INDEX, FUN 

X: 함수를 적용하고자 하는 특정 벡터 

INDEX: 하나 이상의 요인을 포함하고 있는 리스트 (X와 길이가 같아야함 )

 

INDEX의 요인별로, X를 분리하고, 각각의 요인을 기준으로 나누어진 X에 함수를 적용시키는 것이죠. 

바로, airquality 데이터를 사용해 예시를 보여드리겠습니다. airquality 데이터는 여섯개의 열을 포함한 데이터 입니다. 

> str(airquality) 'data.frame': 153 obs. of  6 variables:  

$ Ozone  : int  41 36 12 18 NA 28 23 19 8 NA ...  

$ Solar.R: int  190 118 149 313 NA NA 299 99 19 194 ...  

$ Wind   : num  7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...  

$ Temp   : int  67 72 74 62 56 66 65 59 61 69 ...  

$ Month  : int  5 5 5 5 5 5 5 5 5 5 ...  

$ Day    : int  1 2 3 4 5 6 7 8 9 10 ...

 

tapply()를 이용해 월별 기온의 평균을 구하여 봅시다. X에는 함수에 적용시킬 'Temp'을, INDEX에는 기준이 되어 요인을 나눌 'Month'를, 그리고 함수에는 mean을 적용해보도록 합시다. 그러면, 우리는 각 월에 대한 기온의 평균을 구할 수 있겠죠?

Temp_month_mean <- tapply(airquality$Temp, airquality$Month, mean)
Temp_month_mean
'''
       5        6        7        8        9 
65.54839 79.10000 83.90323 83.96774 76.90000
'''

#with 함수 이용해서 접근 --> $앞의 데이터 생략 가능

with(airquality, tapply(Temp, Month, mean))
'''
       5        6        7        8        9 
65.54839 79.10000 83.90323 83.96774 76.90000 
'''

여기에서 하나 의문이 생길 수 있습니다, 아까 str()로 확인을 했을때 Month는 fac 아닌, int 였는데, 어떻게 tapply함수에서 Month를 요인 취급해, 각각이 레벨로 그룹을 나누었지? 하는 의문이요. 

tapply함수는 INDEX로 받는 리스트를 as.factor에 의해 요인으로 강제 변환시킵니다. 따라서, tapply함수의 INDEX에 'Month'를 적용시키며, 바로 요인으로 변환된것이고, 각 요인에 대해 그룹으로 나눈 온도의 평균을 구할 수 있게된 것이죠. 

 

새로운 함수를 직접 만들어 적용하면, 평균과 표준편차를 동시에 출력할 수 있겠죠?

mean.sd <- function(x)c(Mean = mean(x), SD = sd(x))
Temp_month_mean_sd <- tapply(airquality$Temp, airquality$Month, mean.sd)
Temp_month_mean_sd
'''
$`5`
    Mean       SD 
65.54839  6.85487 

$`6`
     Mean        SD 
79.100000  6.598589 

$`7`
     Mean        SD 
83.903226  4.315513 

$`8`
     Mean        SD 
83.967742  6.585256 

$`9`
     Mean        SD 
76.900000  8.355671 
'''

위의 예시에서는, 하나의 큰 데이터를 이루는 각각의 열을 기준으로 그룹을 나누고 각각의 그룹에 함수를 적용했지만, tapply 함수를 적용하는 모든 경우에, 전체데이터가 존재해야하는것은 아닙니다. 단지, 하나의 데이터 안에서 어떠한 요인에 의해 나누어진 그룹의 특성을 알고자 할때, tapply함수가 유용하게 사용되는 것이죠. 

 

이번에는, 하나의 데이터로 묶이지 않았지만, 길이(length)가 같아 tapply함수 적용이 가능한 경우를 예시로 들어 설명하겠습니다. 

x2 = sample(1:3, 10, replace = TRUE)
length(x2)
#[1] 10

x4 = sample(c("Male", "Female", "Male", "Male", "Female"), 10, replace = TRUE)
length(x4)
#[1] 10

tapply(x2, x4, mean)
#  Female     Male 
#2.333333 2.285714 

 

반응형