본문 바로가기

회사생활/R

R apply 계열 함수 총 정리 1 ( apply / lapply / sapply / vapply )

apply 계열 함수 총 정리 1 ( apply / lapply / sapply / vapply )




apply 계열 함수는 주어진 함수 연산을 특정 단위로 쉽게 할 수 있도록 지원하는 함수 군이다. 어떤 함수이냐에 따라 1) 연산 대상 데이터의 종류, 2) 결과 출력 형태, 3) 연산 단위 등이 달라지게 된다.


apply 계열의 함수는 사용하기가 조금 까다로운 함수이지만 미리 익혀두어 편하게 사용할 수 있도록 연습해두는 것이 좋다. 그 이유는 for, while 등의 반복문 보다 (빠른 속도와 (경우에 따라) 짧은 코드로 반복 연산을 처리할 수 있는 함수이기 때문에 대용량 데이터에 대한 반복 연산은 apply 계열 함수를 적절히 사용하는 것을 추천한다.




▼ for / lapply / mclapply(멀티코어) 속도 비교 포스팅 ▼

2017/09/12 - [Analysis/R] - R apply 계열 함수 총 정리 2 ( lapply / mclapply )





for 보다 좋은 점?


어떤 식으로 for문보다 간단해지는지 예를 하나 확인해보자. iris 데이터에서 컬럼을 하나씩 체크하여 numeric인 컬럼만 필터링한 데이터프레임 iris_num을 만드는 코드를 for 함수와 apply 계열 함수인 sapply 함수를 사용하여 작성해보겠다.

# for 함수 사용하여 만들기
iris_num <- NULL
for(x in 1:ncol(iris)){
  if(is.numeric(iris[, x])) iris_num <- cbind(iris_num, iris[, x])
}
iris_num <- data.frame(iris_num)


# sapply 함수 사용하여 만들기
iris_num <- iris[, sapply(iris, is.numeric)]


이처럼 apply 계열 함수를 잘 사용하면 for 함수로는 복잡하게 작성했던 코드를 간단하게 만들 수 있다. (물론 경우에 따라서는 for를 사용하는 게 간편한 코드도 당연히 있으니 두 가지를 적절한 상황에서 잘 사용하는 것이 맞다.)




예시 데이터 만들기


먼저, 예시 코드 작성을 위해 iris 데이터를 1~10행, 1~4열만 필터링 하고 랜덤으로 NA를 2개 만들어 넣은 iris_num 데이터를 만들었다.

# iris 데이터 필터링
iris_num <- iris[1:10, 1:4]

# 랜덤으로 NA를 넣을 행/열 번호 뽑기
set.seed(123)
idx_r <- sample(1:10, 2)
idx_c <- sample(1:4, 2)

# NA 넣기
for(i in 1:2){
  iris_num[idx_r[i], idx_c[i]] <- NA
}

iris_num

iris_num 실행시 콘솔에 아래와 같이 출력된다.




apply


apply 계열 함수 중 가장 기본이 되는 함수으로, 행(Row) 또는 열(Column) 단위의 연산을 쉽게 할 수 있도록 지원하는 함수이다. MARGIN이라는 함수 인자에 어떤 단위로 연산을 할 것인지 1 또는 2로 표시하는데, 1이 행 단위 연산이고 2가 열 단위 연산이다.


apply의 Input Data는 배열(Array), 매트릭스(Matrix) 등 모두 같은 변수형을 가진 값으로 이루어진 데이터 타입만 가능하다. 모두 같은 변수형을 가진 데이터프레임(Data Frame)도 가능하다. apply 함수의 실행 결과는 매트릭스나 벡터(Vector)로 출력된다.

-
-

apply 함수를 사용하여 테스트 코드를 작성해 보았다. 행 단위 평균 연산과 열 단위 평균 연산을 하는 코드이다.

# 행(row) 단위로 mean 연산
apply(iris_num, 1, mean)

# 열(column) 단위로 mean 연산
apply(iris_num, 2, mean)




위 코드의 경우 NA가 있는 행 또는 열은 mean 계산을 할 수 없기 때문에 일부 값이 NA로 출력되게 된다. iris_num에서 NA를 제거하고 계산하고자 하는 경우 mean 함수의 na.rm = T 인자를 이용하여 NA를 제거한 상태로 연산을 할 수 있다.

# 열(column) 단위로 NA 제거하고 mean 연산
apply(iris_num, 2, mean, na.rm = T)




위 코드처럼 mean 함수의 인자를 apply 함수의 인자처럼 사용하면 된다. 이번에는 사용자 정의 함수를 이용하여 연산을 해보자.

# 열(column) 단위로 사용자 정의 함수(function)1 연산
apply(iris_num, 2, function(x) { x * 2 + 1 })




mean 함수와 달리 위에서 정의한 사용자 정의 함수 function(x) { x * 2 + 1 } 은 하나의 값으로 요약해주는 부분이 없기 때문에 모든 x가 연산된 값, 즉 iris_num의 모든 값이 연산된 개별 결과를 출력하게 된다. 다만 사용자 정의 함수를 조금 바꾸어 아래와 같이 요약 함수로 median 함수를 사용한다면 결과는 요약된 결과로 출력되게 된다.

# 열(column) 단위로 사용자 정의 함수(function)2 연산
apply(iris_num, 2, function(x) { median(x * 2 + 1) })




이때 NA를 제거한 상태로 연산하고 싶다면 na.rm = T 인자를 넣으면 되는데... 과연 어디에 넣는 것이 맞을까?

# apply 인자로 na.rm = T 를 사용한 경우 에러 발생
apply(iris_num, 2, function(x) { median(x * 2 + 1) }, na.rm = T)




처음 mean 함수 연산 코드의 경우 na.rm = T 인자가 mean 함수의 인자이기 때문에 바로 apply의 인자처럼 사용할 수 있었지만, 이번 코드에서는 na.rm = T 가 function 함수의 인자는 아니기 때문에 이번 경우에는 apply의 인자처럼 na.rm = T 를 사용할 수 없다. 따라서 아래와 같이 median 함수 안의 인자로 작성해주어야 한다.

# mean 함수 안의 인자로 na.rm = T를 써줘야 함
apply(iris_num, 2, function(x) { median(x * 2 + 1, na.rm = T) })






lapply


lapply 함수는 list + apply를 의미하는 이름의 함수로, 실행 결과가 list 형태로 출력되는데, 리스트(list)의 인자는 length( 데이터 ) 만큼 생성된다. 참고로 데이터프레임(data frame)인 경우 length( 데이터 )의 결과는 변수의 개수(열의 개수)이고, 리스트인 경우 length( 데이터 )는 리스트 인자의 개수이다.


-

리스트란?


리스트는 R에서 사용하는 데이터 타입 중 제약사항이 거의 없는 데이터 타입이다.

예를 들어 데이터프레임은 모든 변수(인자)가 벡터(vector)를 가져야 하는 반면, 리스트는 벡터 외에도 매트릭스(matrix), 데이터프레임(data frame) 등 어떠한 형태든 인자 안에 저장할 수 있다.

또한 데이터프레임은 NA를 넣더라도 모든 변수(인자)에 들어가는 값의 수(row)가 동일해야 하지만, 리스트는 인자에 따라 값의 길이가 달라도 상관이 없다.

-



apply 함수의 예제와 유사한 코드를 사용하여 apply 함수와 lapply 함수가 어떻게 다른지 확인해보자.

# iris_num의 열 단위 평균이 vector 형태로 출력됨
apply(iris_num, 2, mean, na.rm = T)

# iris_num의 열 단위 평균이 list 형태로 출력됨
lapply(iris_num, mean, na.rm = T)




위 코드에서 lapply 함수는 결과가 리스트 형태로 나오고 apply 함수는 결과가 벡터 형태로 나오게 된다.




sapply


sapply 함수는 lapply 함수에서 사용자 편의성을 고려한 함수이다. sapply( , simplify = F) 인 경우 lapply( ) 와 같은 결과를 출력한다. sapply 함수는 for문을 대체할 수 있는 가장 편리한 함수이기도 하다.


아래 코드와 같이 기본적으로 sapply 함수는 연산 결과를 벡터 형태로 출력한다. 하지만 Input Data가 아래 예제와 다르게 리스트 형태라 인자간 길이가 다른 경우에는 결과 값의 인자도 길이가 서로 다르기 때문에 결과가 벡터 형태가 아닌 리스트 형태로 출력된다.

# 기본적으로 sapply 함수는 연산 결과를 벡터 형태로 출력한다.
sapply(iris_num, mean, na.rm = T)

# simplify = F이면 lapply와 동일하게 리스트 형태로 결과를 출력한다.
sapply(iris_num, mean, na.rm = T, simplify = F)
lapply(iris_num, mean, na.rm = T)








vapply


vapply 함수는 sapply 함수와 유사한데 추가적으로 출력되는 결과의 양식(Template)을 지정할 수 있다. 아래 코드를 보면 더 이해가 쉽다.

# sapply 함수로 fivenum 출력
sapply(iris_num, fivenum)

# vapply 함수로 fivenum 출력양식 지정하여 출력 - 출력 내용은 sapply와 동일한 형태이다.
vapply(iris_num, fivenum, c("Min." = 0, "1st Qu." = 0, "Median" = 0, "3rd Qu." = 0, "Max." = 0))

# sapply에서는 출력양식 지정하기가 적용되지 않는다.
sapply(iris_num, fivenum, c(Min. = 0, "1st Qu." = 0, Median = 0, "3rd Qu." = 0, Max. = 0))






▼ 다음 포스팅 이어보기 ▼

2017/09/12 - [Analysis/R] - R apply 계열 함수 총 정리 2 ( lapply / mclapply )





#apply#apply함수#r#r apply 계열 함수#r for 대체 apply#r for문#r 반복문#rstudio#r샘플코드#r에서 for문 대신#r예제코드#R코드#데이터분석#빅데이터분석