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 |