본문 바로가기

R/R을 이용한 데이터 처리&분석 실무

apply 계열 함수 - 1

반응형

R에는 벡터, 행렬 또는 데이터 프레임에 임의의 함수를 적용한 결과를 얻기 위한 apply 계열 함수가 있다. 이 함수들은 데이터 전체에 함수를 한 번에 적용하는 벡터 연산을 수행하므로 속도가 빠르다. 다음은 apply 계열 함수를 요약한 것이다.

함수 설명 다른 함수와 비교했을 때의 특징
apply() 배열 또는 행렬에 주어진 함수를 적용한 뒤 그 결과를 벡터, 배열 또는 리스트로 반환 배열 또는 행렬에 적용
lapply() 벡터, 리스트 또는 표현식에 함수를 적용하여 그 결과를 리스트로 반환 결과가 리스트
sapply() lapply와 유사하지만 결과를 벡터, 행렬 또는 배열로 반환 결과가 벡터, 행렬 또는 배열
tapply() 벡터에 있는 데이터를 특정 기준에 따라 그룹으로 묶은 뒤 각 그룹마다 주어진 함수를 적용하고 그 결과를 반환 데이터를 그룹으로 묶은 뒤 함수를 적용
mapply() sapply의 확장된 버전으로, 여러 개의 벡터 또는 리스트를 인자로 받아 함수에 각 데이터의 첫째 요소들을 적용한 결과, 둘째 요소들을 적용한 결과, 셋째 요소들을 적용한 결과 등을 반환 여러 데이터를 함수의 인자로 적용

 

apply()

apply()는 행렬의 행 또는 열 반향으로 특정 함수를 적용하는 데 사용한다.

 

-apply : 배열 또는 행렬에 함수 FUN을 MARGIN 반향으로 적용하여 결과를 벡터, 배열 또는 리스트로 반환한다.

apply(

   x,                 #배열 또는 행렬

   MARGIN,       #함수를 적용하는 방향. 1은 행 방향, 2는 열 방향 

                      #c(1,2)는 행과 열 반향 모두를 의미

   FUN             #적용할 함수
)

 

반환 값은 FUN이 길이 1인 벡터들을 반환한 경우 벡터, 1보다 큰 벡터들을 반환한 경우 행렬, 서로 다른 길이의 벡터를 반환한 경우 리스트다.

 

apply()가 적용된 결과가 벡터, 배열, 리스트 중 어떤 형태로 반환될 것인지는 데이터 X의 데이터타입과 함ㅁ수 FUN의 반환 값에 따라 대부분 자연스럽게 예상할 수 있으므로, 반환 값의 데이터 타입에 대해 크게 걱정할 필요는 없다. 또, 반환 값을 str()로 검토하면 데이터 타입을 알아낼 수 있다는 점을 기억하기 바란다.

 

합을 구하는 함수 sum()을 apply()에 적용하는 예에 대해 알아보자. sum()은 인자로 주어진 값들의 합을 구하는 간단한 함수다. 예를 들어, 다음은 1부터 10까지의 합을 계산한다.

>sum(1:10)
[1]55

 

이를 사용해 apply()로 행렬에 저장된 데이터의 합을 구해보자. 예를 들어, 다음ㅁ과 같은 행렬이 있다고 하자.

>d<-matrix(1:9, ncol=3)
>d
     [,1] [,2] [,3]
[1,]  1   4   7
[2,]  2   5   8
[3,]  3   6   9  

 

이 행렬의 각 행의 합(즉, 1+4+7, 2+5+8, 3+6+9)을 구하려면 apply를 행별로(즉, MARGIN에 1을 지정) 처리하되 각 행에 대해 sum 함수를 호출하면 된다.

>apply(d,1,sum)
[1] 12 15 18

 

마찬각지로 열 방향 합(1+2+3, 4+5+6, 7+8+9)은 MARGIN=2를 지정해 구한다.

>apply(d,2,sum)
[1] 6 15 24

 

apply()를 사용하여 아이리스 데이터의 Sepal.Length, Sepal.Width, Petal.Length, Petal.Width 컬럼의 합을 구해보자. 다음 코드에서 iris[, 1:4]는 아이리스 데이터 프레임의 모든 행에서 1~4 컬럼만 가져온다는 의미다.

>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
...
>apply(iris[, 1:4], 2, sum)
Sepal.Length, Sepal.Width, Petal.Length, Petal.Width
       876.5         458.6          563.7           179.9

 

이와 같은 행 또는 열의 합 또는 평균의 계산은 번번히 사용되므로 rowSums(), rowMeans(), colSums(), colMeans() 함수가 정리 정의되어 있다. 아래에 행 방향의 합과 평균에 대한 합수를 정리했다. colSums(), colMeaans() 역시 같은 형식으로 사용하면 된다.

 

-rowSums : 숫자 배열 또는 데이터 프레임에서 행의 합을 구한다.

rowSums(

   x,                        #배열 또는 숫자를 저장한 데이터 프레임

   na.rm=FALSE,        #NA를 제외할지 여부
)

 

rowMeans : 숫자 배열 또는 데이터 프레임에서 행의 평균을 구한다.

rowMeans(

   x,                        #배열 또는 숫자를 저장한 데이터 프레임

   na.rm=FALSE,        #NA를 제외할지 여부
)

 

다음은 앞서 apply()를 통해 구한 계산을 colSums()로 수행하는 예다.

>colSums(iris[, 1:4])
Sepal.Length, Sepal.Width, Petal.Length, Petal.Width
         876.5         458.6          563.7          179.9

 

lapply()

lapply()는 리스트를 반환하는 특징이 있는 apply 계열 함수다.

 

-lapply : 벡터, 리스트, 표현식, 데이터 프레임 등에 함수를 적용하고 그 결과를 리스트로 반환한다.

lapply(

   x,                #벡터, 리스트, 표현식 또는 데이터 프레임

   FUN,            #적용할 함수
   ...                #추가인자. 이 인자들은 FUN에 전달된다.

)

 

리스트보다는 벡터 또는 데이터 프레임이 사용하기에 직관적인 면이 있으므로 lapply()의 결과를 벡터 또는 데이터 프레임으로 변환할 필요가 있다. 이 경우 다음과 같은 함수를 사용한다.

 

-unlist : 리스트 구조를 벡터로 변환한다.

unlist(

    x,                            #R  객체. 보통 리스트 또는 벡터

   recursive=FLSE,           #x에 포함된 리스트 역시 재귀적으로 변환할지 여부

   use.names=TRUE         #리스트 내 값의 이름ㅁ을 보존할지 여부

)

 

-do.call : 하수를 리스트로 주어진 인자에 적용하여 결과를 반환한다.

do.call(

   what,                        #호출할 함수

   args,                         #함수에 전달할 인자의 리스트

)

 

c(1,2,3) 벡터가 있을 때, 각 숫자를 2배한 값을 laaapply()를 통해 구해보자. 앞에서 살펴봤듯이, 리스트의 각 값은 [[n]] 형태로 접근한다는 점을 기억하기 바란다. (이때n은 접근할 요소의 색인이다.)

>(result<-lapply(1:3, function(x) { x*2 }))
[[1]]
[1]2

[[2]]
[1]4

[[3]]
[1]6

>result[[1]]
[1]2

 

위의 예처럼 lapply()의 결과는 리스트다. 이 결과를 다시 벡터로 변환하고 싶다면 unlist()를 사용한다.

>unlist(result)
[1]2 4 6

 

lpply()는 인자로 리스트를 받을 수 있다. 다음은 aa에는 c(1,2,3), b에는 c(4,5,6)이 저장된 리스트에서 각 변수마다 평균을 계산한 예다.

>(x<-list(=1:3, b=4:6))
$a
[1] 1 2 3

$b
[1] 44 5 6

>lapply(x,mean)
$a
[1]2

$b
[1]5

 

데이터 프레임에도 곧바로 lapply()를 적용할 수 있다. 아이리스 데이터의 숫자형 데이터들에 대한 평균을 구해보자.

>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

 

앞서 설명했듯이 각 컬럼ㅁ의 평균은 colMeans()로도 계산할 수 있다.

>colMeans(iris[, 1:4])
Sepal.Length, Sepal.Width, Petal.Length, Petal.Width
   5.843333       3.057333       3.758000      1.199333

 

데이터 프레임을 처리한 결과를 리스트로 얻은 뒤 해당 리스트를 다시 데이터 프레이으로 변환할 필요가 있을 수 있다. 이 변환은 몇 단계를 거쳐서 처리해야 한다.

 

1.unlist()를 통해 리스트를 벡터로 변환한다.

2.matrix()를 사용해 벡터를 행렬로 변환한다.

3.as.data.frame()을 사용해 행렬을 데이터 프레임으로 변환한다.

4.names()를 사용해 리스트로부터 변수명을 얻어와 데이터 프레임의 각 컬럼에 이름을 부여한다.

다음은 아이리스 데이터의 각 컬럼에 대한 평균을 lapply()로 구한 뒤 이 결과를 다시 데이터 프레임으로 변환한 예다.

>d<-as.data.frame(matrix(unlist(lapply(iris[, 1:4], mean)),
+                                         ncol=4, byrow=TRUE))
>names(d) <- names(iris[, 1:4])
>d
       Sepal.Length, Sepal.Width, Petal.Length, Petal.Width
1        5.843333      3.57333           3.758        1.199333

 

또는 do.call()을 사용해 변환할 수도 있다. 지금 살펴보는 예제의 경우에는 lapply()가 반환한 리스트 내의 각 컬럼별 계산 결과가 들어 있다. 따라서 이를 새로운 데이터 프레임의 컬럼들로 합치기 위해 cnind()를 사용한다. 다음ㅁ 코드는 do.call()을 사용해 lapply()의 결과로 나온 리스트 내 요소 각각을 cbind()의 인자들로 넘겨준다.

>data.frame(do.call(cbind, lapply(iris[, 1:4], mean)))
      Sepal.Length, Sepal.Width, Petal.Length, Petal.Width
1       5.843333       3.57333      3.758          1.199333

앞에서 살펴본 두 가지 방법 중 unlist()후 matrix()를 거쳐 데이터 프레임으로 변환하는 방법에는 한 가지 문제가 있다. unlist()는 벡터를 반환하는데, 벡터에는 한 가지 데이터 타입만 저장할 수 있기 때문에 변환 과정에서 하나의 데이터 타입으로 데이터가 모두 형 변환되어버리기 때문이다.

 

다음 예에서는 문자열과 숫자가 혼합된 경우  unlist()가 문자열을 모두 엉뚱한 값으로 바꿔버리는 것을 볼 수 있다.

>x<-list(data.frame(name="foo", value=1),
+         data.frame(name="bar", value=2))
>unlist(x)
  name  value  name  value
      1       1        1       2

 

따라서 데이터 타입이 혼합된 경우에는 do.call()을 사용해야 한다. 다음 예에서는 리스트의 각 요소가 한 행에 해당하므로 rbind를 호출했다.

>x<-list(data.frame(name="foo", value=1),
+         data.frame(name="bar", value=2))
>do.call(rbind,x)
    name  value
1   foo       1
2   bar       2

 

이것만으로 끝이라면 좋겠지만 아쉽게도 do.call(rbind,...)방식은 속도가 느리다는 단점이 있다. 따라서 대량의 데이터를 변환해야 한다면 "데이터 조작 2"에서 설명할 rbindlist()를 사용해야 한다.

 

R을 이용한 데이터 처리&분석 실무 中

반응형

'R > R을 이용한 데이터 처리&분석 실무' 카테고리의 다른 글

데이터 분리 및 병합  (0) 2020.02.03
apply 계열 함수 - 2  (0) 2020.02.02
데이터 프레임의 행과 컬럼 합치기  (0) 2020.01.31
파일 입출력  (0) 2020.01.31
아이리스 데이터  (0) 2020.01.31