[R] Apply 계열 함수

DongGyu Jung·2021년 10월 24일
1

Apply 계열 함수는 뜻 그대로 적용시키는 기능을 가진 함수이다.
이 함수는 array(배열)matrix(행렬)/data frame에 적용시킬 수 있는데
보통 특정 행(row)이나 열(column) 내부에 있는 데이터들에 어떠한 명령 적용시켜 반환하기 위해서는
보통 for문 이 많이 사용된다.

하지만 for문의 단점 중 하나는 처리할 데이터가 많을 때,
시간이 많이 소요되고 코드를 비교적 길게 써야한다는 것이다.

이 부분에 대해
매트릭스나 데이터프레임에 있는 행들이나 열들을 알아서 하나하나 차례대로 꺼내 연산 작업을 수행하도록 만들어진 함수라고 보면 된다.
우선 가장 기초가 되는 apply함수를 알아보자.


📕 apply

[구조] apply ( X, MARGIN, FUN, ..., simplify = T )

  • X : 행렬 ( Array / Matrix )
  • MARGIN : 1 ( 단위) / 2 ( 단위)
  • FUN : 정한 단위의 각 값들에 적용시킬 함수/연산
  • ... : 해당 apply 함수에 적용시킬 선택적 인수들 / 위 FUN의 properties 값을 입력

입력 : 행렬 ( Array )
출력 : 행렬 ( Array )

간단하게 말하자면 행렬이나 배열에 대해서 Margin으로 지정한 기준에 따라
기준에 해당하는 위치에 Function을 적용시켜주는 기능이다.

weight <- c(65.4, 55, 380, 72.2, 51, NA)
height <- c(170, 155, NA, 173, 161, 166)
gender <- c("M", "F","M","M","F","F")

df <- data.frame(w=weight, h=height)
> df
      w   h
1  65.4 170
2  55.0 155
3 380.0  NA
4  72.2 173
5  51.0 161
6    NA 166

이렇게 하나의 Data Frame을 생성해보자.
이곳에 "NA 값은 무시하고 각 행의 합 & 각 열의 합" 을 구하는 apply 함수를 사용해보자.

apply(df, 1, sum, na.rm=TRUE) # 각 행들의 합 
[1] 235.4 210.0 380.0 245.2 212.0 166.0

apply(df, 2, sum, na.rm=TRUE) # 각 열들의 합
    w     h 
623.6 825.0 

앞서 설명했던 apply()함수는 행렬만을 입력 받을 수 있다는 단점이 있다.
함수를 각 값에 적용시키고 싶은 대상이 벡터(Vector)리스트(List) 일 경우에도 사용할 수 있는 함수들은 아래와 같다.


📗 lapply

[구조] lapply ( X, FUN, ...)

  • X : 벡터 / 리스트 / 행렬 / 배열 ( Vector / List / Matrix / Array )
  • FUN : 정한 단위의 각 값들에 적용시킬 함수/연산
  • ... : 해당 apply 함수에 적용시킬 선택적 인수들 / 위 FUN의 properties 값을 입력

입력 : 벡터 ( Vector ) / 리스트 ( List ) / 행렬 ( Array / Matrix / DataFrame)
출력 : 리스트 ( List )

우선 간단하게 위에서 사용했던 DataFrame을 써보자

lapply(df, sum, na.rm=TRUE)
$w
[1] 623.6

$h
[1] 825

리스트형으로 출력되는 모습이고 각 열에 대해 연산되었다는 것을 알 수 있다.

이번엔 벡터와 리스트를 입력했을 때의 결과를 살펴보자.

v<-c("abc", "DEF", "TwT") # 벡터
l<-list("abc", "DEF", "TwT") # 리스트

[[1]]
[1] "-abc-"

[[2]]
[1] "-DEF-"

[[3]]
[1] "-TwT-"

이렇게 lapply() 함수는 이름 그대로 list + apply
벡터든 리스트든 출력은 무조건 list형식으로 한다.

따라서, 적용 후에 벡터로 변환하고 싶으면 unlist()함수를 꼭 사용해주어야 한다.

📘 sapply

[구조] sapply ( X , FUN , ... , simplify = T , USE.NAMES = TRUE )

  • X : 리스트 / 벡터 ( list / vector )
  • FUN : 정한 단위의 각 값들에 적용시킬 함수/연산
  • ... : 해당 apply 함수에 적용시킬 선택적 인수들 / 위 FUN의 properties 값을 입력

입력 : 리스트( list ) / 벡터( vector )
출력 : 벡터( vector ) / 행렬 ( Array )

lapply()unlist()함수를 써서 풀어줘야 한다는 점에서 번거롭고 불편할 수 있는 함수이다.
그 점을 보완하여 벡터(Vector) / 행렬(Array)을 반환하는 '유저(user) 친화.ver' 으로
lapply 함수의 wrapper라고 볼 수 있다.

이번에도 apply함수 예시로 썼던 data frame을 사용해보자

sapply(df, sum, na.rm=TRUE)

    w     h 
623.6 825.0 

결과물을 보면 apply()함수에서 을 기준 (MARGIN = 2) 으로 처리했던 모습과 동일하게 리스트가 아닌 벡터(vector) 형태로 출력이 된다.


📘 vapply

[구조] vapply ( X , FUN , FUN.VALUE , ... , USE.NAMES = TRUE )

  • X : 리스트 / 벡터 ( list / vector )
  • FUN : 정한 단위의 각 값들에 적용시킬 함수/연산
  • FUN.VALUE : 함수 연산 결과를 어떤 데이터 타입으로 반환시킬지 설정
  • ... : 해당 apply 함수에 적용시킬 선택적 인수들 / 위 FUN의 properties 값을 입력

입력 : 리스트( list ) / 벡터( vector )
출력 : 벡터( vector ) / 행렬 ( Array )

vapply()는 위의 sapply()와 유사한 함수이지만
FUN.VALUE 인수에 출력(output) format 을 명확히 정의해서 더 안전한 함수이다.
(가끔씩은 더 빠르다고..한다..!)

이번 예시는 R에서 빼놓을 수 없는 iris 데이터 셋을 사용하겠다

# 총 다섯개의 열 중 다섯 번째 열은 문자 데이터이기 때문에 함수 적용에 어려움이 있음. -> iris[,1:4]
# numeric
a <- vapply(iris[,1:4], function(x) {x > 5}, numeric(length(iris[, 1])))
head(a)

# 각 값에 함수를 적용한 결과로 5보다 큰 값들만 존재하기 때문에 length()를 적용했을 때, 존재 : 1 / 해당하지 않는 값 : 0 으로 출력된다.
     Sepal.Length Sepal.Width Petal.Length Petal.Width
[1,]            1           0            0           0
[2,]            0           0            0           0
[3,]            0           0            0           0
[4,]            0           0            0           0
[5,]            0           0            0           0
[6,]            1           0            0           0

# 1 : TRUE / 0 : FALSE 인 점을 활용
b <- vapply(iris[,1:4], function(x) {x > 3}, logical(length(iris[, 1])))
head(b)

Sepal.Length Sepal.Width Petal.Length Petal.Width
[1,]         TRUE       FALSE        FALSE       FALSE
[2,]        FALSE       FALSE        FALSE       FALSE
[3,]        FALSE       FALSE        FALSE       FALSE
[4,]        FALSE       FALSE        FALSE       FALSE
[5,]        FALSE       FALSE        FALSE       FALSE
[6,]         TRUE       FALSE        FALSE       FALSE

위와 같이 출력되는 유형을 결정해줌으로서 안전한 함수라고 볼 수 있다.

단, 함수 적용을 모든 값들 대상으로 처리되기 때문에
하나의 열이 다른 타입의 데이터이거나
각 각의 값에 적용되지 않는 큰 범위의 함수나 출력값을 설정할 경우, 작동되지 않는다.


📙 mapply

[구조] mapply ( FUN , ... )

  • FUN : 정한 단위의 각 값들에 적용시킬 함수/연산
  • ... : 해당 함수에 적용시킬 선택적 인수들

입력 : 리스트( list ) / 벡터( vector )
출력 : 벡터( vector ) / 행렬 ( Array )

구조를 살펴보면 알 수 있듯이 함수가장 첫 번쨰에 위치한다.
그 이후, 여러가지 처리 대상(Arguments)를 추가하여 여러 값을 기입할 수 있다.

# sprintf : 포맷팅 방식 _ 첫번째 인수 : 적용 문자열 / 이후 : 순서대로 투입될 값들 
mapply(function(d, s) { sprintf("%d%s", d, s) }, 1:3, c("a", "b", "c"))

 [1] "1a" "2b" "3c"

# 연산기호로 함수 적용시킬 수 있음.
mapply(`+`, 1:10, 101:110)
 [1] 102 104 106 108 110 112 114 116 118 120

📒 tapply

[구조] tapply ( X , INDEX , FUN = NULL , ... )

  • X : 리스트 / 벡터 ( list / vector )
  • INDEX : 앞선 X와 같은 갯수의 인덱스가 필요하며 Grouping되는 기준이 된다.

입력 : 리스트( list ) / 벡터( vector ) & 팩터( factor/level )
출력 : 벡터( vector ) / 행렬 ( Array )


tapply()는 독특하게
값들을 "특정 기준으로 Grouping 한 후" ▶ Factor 형 ,
함수를 적용하는 방식이다.
"원소별 처리"가 아니라 그룹별 처리를 한다

# factor로 쓰일 INDEX
# M과 F (factor) 
gender <- c("M", "F","M","M","F","F")

# 1:M 2:F 3:M 4:M 5:F 6:F
# M -> 1,3,4 / F -> 2,5,6 _factor끼리 함수적용
tapply(1:6, gender, sum)

 F  M 
13  8

# factor = 1, 2
# 1:1  1:2  1:3  1:4  1:5  2:6  2:7  2:8  2:9  2:10 
# 1: 1,2,3,4,5 / 2: 6,7,8,9,10
tapply(1:10, rep(c(1,2), each=5), sum)

 1  2 
15 40 

0개의 댓글

관련 채용 정보