R을 이용한 데이터처리 & 분석실무 책을 참고로 합니다. 정확한 지식은 책을 통해 얻으시길 바랍니다
apply series는 데이터 정리에 있어서 굉장히 중요한 요소입니다. apply 함수에는 4가지가 5가지 종류가 있습니다.
이것들에 대해서 알아봅니다.
1. apply()
apply 함수는 행렬의 행 또는 열 방향으로 특정 함수를 적용하는데 사용됩니다.
d <- matrix(1:9, ncol = 3)
d
## [,1] [,2] [,3]
## [1,] 1 4 7
## [2,] 2 5 8
## [3,] 3 6 9
d 행렬의 합을 행별로, 열별로 지정할 수 있습니다.
# 행별로 합
apply(d, MARGIN = 1, sum)
## [1] 12 15 18
# 열별로 합
apply(d, MARGIN = 2, sum)
## [1] 6 15 24
MARGIN에 1 또는 2를 넣음으로써 행 또는 열에 대한 연산을 수행합니다. 이 같은 연산은 rowSums, colSums, rowMeans, colMeans 함수로 똑같이 수행 가능합니다.
rowSums(d)
## [1] 12 15 18
colSums(d)
## [1] 6 15 24
2. lapply()
lapply는 list를 반환하는 특징이 있습니다.
d <- c(1, 2, 3)
result <- lapply(d, function(x) {x * 2})
result
## [[1]]
## [1] 2
##
## [[2]]
## [1] 4
##
## [[3]]
## [1] 6
lapply는 적용된 함수의 결과를 list 형태로 반환해 줍니다. 이 결과를 다시 vector로 바꾸고 싶다면
unlist(result)
## [1] 2 4 6
unlist() 함수를 이용하면 됩니다. 한 가지 예를 더 살펴보면,
x <- list(a = 1:3, b = 4:6)
lapply(x, mean)
## $a
## [1] 2
##
## $b
## [1] 5
a와 b의 vector에 대해 평균 값을 list 형태로 반환해 줍니다. iris 데이터에 한번 적용해 봅시다.
head(iris)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
lapply(iris[ , 1:4], mean)
## $Sepal.Length
## [1] 5.843333
##
## $Sepal.Width
## [1] 3.057333
##
## $Petal.Length
## [1] 3.758
##
## $Petal.Width
## [1] 1.199333
irish data도 항목별로 평균을 보기좋게 내줍니다.
3. sapply()
sapply()는 결과를 matrix, vector 등의 데이터 타입으로 반환해줍니다.
lapply(iris[ , 1:4], mean)
## $Sepal.Length
## [1] 5.843333
##
## $Sepal.Width
## [1] 3.057333
##
## $Petal.Length
## [1] 3.758
##
## $Petal.Width
## [1] 1.199333
sapply(iris[ , 1:4], mean)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 5.843333 3.057333 3.758000 1.199333
class(sapply(iris[ , 1:4], mean))
## [1] "numeric"
lapply()와 다르게 sapply()는 numeric으로 결과를 반환해 줬습니다. 이를 dataframe으로 바꾸는 것 역시 가능합니다.
x <- sapply(iris[ , 1:4], mean)
as.data.frame(x)
## x
## Sepal.Length 5.843333
## Sepal.Width 3.057333
## Petal.Length 3.758000
## Petal.Width 1.199333
위에서 봤던 모양과 비슷하게 만들려면 전치행렬을 사용해야됩니다.
as.data.frame(t(x))
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1 5.843333 3.057333 3.758 1.199333
조금 응용해 봅시다. 많은 컬럼을 포함하는 데이터 프레임을 볼 때 각 컬럼의 데이터 타입을 보는 방법입니다.
sapply(iris, class)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## "numeric" "numeric" "numeric" "numeric" "factor"
str(iris)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
sapply()를 사용하면 한눈에 볼 수 있습니다. 물론 str() 사용해도 됩니다.
만약 sapply()의 연산 결과 값이 1개가 아니라면 어떻게 될까요? 행렬로 결과를 반환합니다.
x <- sapply(iris[ , 1:4], function(x) {x >= 3})
class(x)
## [1] "matrix" "array"
head(x) ; tail(x)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## [1,] TRUE TRUE FALSE FALSE
## [2,] TRUE TRUE FALSE FALSE
## [3,] TRUE TRUE FALSE FALSE
## [4,] TRUE TRUE FALSE FALSE
## [5,] TRUE TRUE FALSE FALSE
## [6,] TRUE TRUE FALSE FALSE
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## [145,] TRUE TRUE TRUE FALSE
## [146,] TRUE TRUE TRUE FALSE
## [147,] TRUE FALSE TRUE FALSE
## [148,] TRUE TRUE TRUE FALSE
## [149,] TRUE TRUE TRUE FALSE
## [150,] TRUE TRUE TRUE FALSE
sapply는 한 가지 타입만 저장 가능합니다. 그러므로 sapply(group, function) 구조에서 function 항목에 여러가지 데이터 타입을 섞어주면 안됩니다.
4. tapply
tapply()는 그룹별로 function을 적용하기 위해 사용합니다. iris 데이터 내에는 다양한 종류가 섞여 있죠.
이들을 종 별로 나눠서 평균을 내봅시다.
tapply(X = iris$Sepal.Length, INDEX = iris$Species, mean)
## setosa versicolor virginica
## 5.006 5.936 6.588
3가지 종이 존재하고 각각의 sepal length에 대한 평균을 확인할 수 있습니다.
이번에는 조금 더 복잡한 그룹화를 해봅시다.
m <- matrix(11:18, ncol = 2, dimnames = list(c("spring", "summer", "fall", "winter"),
c("male", "female")))
m
## male female
## spring 11 15
## summer 12 16
## fall 13 17
## winter 14 18
4x2의 행렬이 만들어졌습니다. 여기서 반기별 성별 셀의 합을 구할 겁니다.
다시 말해, 봄, 여름의 남성과 여성 셀 각각의 합 그리고 가을, 겨울의 남성과 여성 셀 각각의 합을 구합니다.
상기 행렬에서 INDEX를 지정 시, (n, m)에서 n을 먼저 나열한 뒤 m 값을 나열하면 됩니다.
일단 행부터 묶는것이고 spring, summer는 첫 번째 그룹이니 1을 할당하고, fall, winter는 2를 할당합니다.
열을 묶을 때는 male을 첫 번째 그룹으로 1에 할당하고 female은 2에 할당합니다.
이것을 코드로 표현하면 아래와 같습니다.
tapply(X = m, INDEX = list(c(1, 1, 2, 2, 1, 1, 2, 2), c(1, 1, 1, 1, 2, 2, 2, 2)), sum)
## 1 2
## 1 23 31
## 2 27 35
결과가 잘 나온것을 확인할 수 있습니다. 해당 방식은 clustering algorithm에 사용되니 이해하시길 바랍니다.
5. mapply()
마지막 mapply() 입니다. mapply()는 다수의 인자를 받아 처리하는 함수가 있고 함수에 넘겨줄 인자들이 데이터로 저장되어 있을 때, 데이터에 저장된 값들을 인자로 하여 함수를 호출합니다.
rnorm()을 사용하여 정규분포를 따르는 난수를 발생시킨 뒤 mapply()에 적용시켜 보겠습니다.
일단, rnorm(n = 1, mean = 0, sd = 1) / rnorm(n = 2, mean = 10, sd = 1) / rnorm(n = 3, mean = 100, sd = 1) 세 개의 값들을 호출해야 되는 상황이라고 가정해 봅시다.
첫 번째 방법은 위의 함수 3개를 각각 호출하는 것입니다.
rnorm(n = 1, mean = 0, sd = 1)
## [1] -0.9005462
rnorm(n = 2, mean = 10, sd = 1)
## [1] 8.737371 9.180634
rnorm(n = 3, mean = 100, sd = 1)
## [1] 101.21149 100.61278 99.10509
두 번째로는 mapply()를 활용하는 것입니다.
mapply(rnorm, c(1, 2, 3), c(0, 10, 100), c(1, 1, 1))
## [[1]]
## [1] -0.1427812
##
## [[2]]
## [1] 9.491927 9.473609
##
## [[3]]
## [1] 99.15836 101.11350 99.02827
맨 앞의 벡터에는 rnorm 3개에 해당하는 n 값을, 두 번째 벡터에는 rnorm 3개 각각의 mean 값을 넣는 식으로 진행하면 됩니다.
iris 데이터에 접목을 시켜보겠습니다.
mapply(mean, iris[ , 1:4])
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## 5.843333 3.057333 3.758000 1.199333
mapply()는 iris 데이터의 각 행의 첫 번째 행을 묶어 평균을 구하고, 다시 두번째 컬럼끼리 묶어서 평균을 구하는 작업을 반복합니다.
이렇게 apply() 함수들의 종류에 대해 알아보았습니다. 데이터 정리를 하다보면 필수적으로 자주 쓰이는 함수이니 정리하셔서 옆에 두고 계속 활용하시면 언젠가 손에 익숙해질 것입니다.