본문 바로가기

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

데이터 구조의 변형과 요약

반응형

reshape2(참고자료 [4], [5] 참고)는 데이터의 모양을 바꾸거나 그룹별 요약 값을 계산하는 함수들을 담고 있는 패키지다. 변환된 데이터는 측정치를 variable과 value라는 두 컬럼으로 표현하므로 데이터의 통계치 계산이 편리해진다.

 

reshape2가 제공하는 볂환은 크게 melt()와 cast() 두 함수로, 이 둘은 4장에서 다른 stack(), unstack() 함수와 유사한 기능을 한다. 다음 표에 이 두 함수의 역할에 대해 정리하였다.

함수 의미
melt() 여러 컬럼으로 구성된 데이터를 데이터 식별자(id), 측정 변수(variable), 측정값(value)이라는 3개 컬럼으로 변환한다. 만약 한 데이터에 대해 다수의 측정 변수와 측정값이 있다면 이들은 여러 행으로 표현된다. 이렇게 변환된 결과는 variable 컬럼에 측정 대상이 기록되어 있으므로 각 variable마다 value의 통계 값을 계산하는 것이 편리하다.
cast()

melt()된 데이터를 다시 여러 컬럼으로 변환한다. 데이터에 여러 측정 변수와 측정 값이 존재한다면 이들은 모두 새로운 컬럼으로 변환된다.
cast()로 변환된 결과는 마치  스프레드시트에 입력한 데이터 모양과 유사하므로 분석자가 읽기 쉽다. 또한, cast()시 melt()된 데이터의 여러 행이 한 셀에 대응하는 경우 데이터의 요약 값을 자동으로 계산해준다.

 

reshape2 패키지를 설치 후 로드해보자.

>install.packages("reshape2")
>library(reshape2)

 

이 절에서는 reshape2 패키지에 포함된 french_fries 데이터를 통해 melt()와 cast()의 역할에 대해서 알아본다. french_fries는 2014년 아이오와 주립대학교에서 프렌치프라이를 만드는 식용유가 프렌치 프라이의 맛에 어떤 영향을 주는지 조사한 결과를 저장하고 있는 데이터다. 다음은 이 데이터의 일부를 보여준다.

>head(french_fries)
   time treatment subject rep potato buttery grassy rancid painty
61    1         1       3       1    2.9     0.0      0.0      0.0      5.5
25    1         1       3       2   14.0     0.0      0.0      1.1     0.0
62    1         1      10       1   11.0     6.4      0.0      0.0     0.0
26    1         1      10       2    9.9     5.9      2.9      2.2      0.0
63    1         1      15       1    1.2     0.1      0.0      1.1      5.1
27    1         1      15       2    8.8     3.0      3.6      1.5      2.3

 

french_fries 데이터의 각 컬럼의 의미는 다음과 같다.

컬럼 의미
tim 몇 주차 실험인가
treatmente 사용한 식용유의 종류
subject 실험 대상자
rep 같은 내용을 여러 번 측정했을 때, 이 측정이 몇 번째 반복에 해당하는가. 예를 들어, 앞서 보인 french_fries의 1,2행은 1번 식용유로 조리한 프렌치프라이를 3번 실험 대상자에게 맛보게 했을 때의 결과들로 각각 첫 번째 맛을 물어봤을 때의 응답과 두 번째 물어봤을 때의 응답을 담고 있다.
potato 감자 맛
buttery 버터 맛
grassy 풀 맛
rancid 신맛
painty 안 좋은 냄새

 

french_fries의 각 행을 봄면 한 행에 여러 측정치가 여러 컬럼으로 나열된 것을 볼 수 있다. 따라서 french_fires 데이터는 이미 cast()된 형태의 데이터에 해당한다. 다음 절에서는 melt()를 사용해 여러 컬럼으로 표현된 측정치를 id, variable, value의 컬럼을 사용해 여러 행으로 푷현해본다. 그 다음 이를 다시 원래 형태로 바꾸는 cast()를 살펴볼 것이다.

 

melt()

melt()함수는 식별자(id), 측정 변수(variable), 측정치(value) 형태로 데이터를 재구성하는 함수다.

 

help(melt)를 살펴봄면 melt()는 주어진 데이터에 따라 melt.data.frame(), melt.array(), melt.list()를 내부적으로 호출함을 알 수 있다. 여기서는 가장 자주 사용하는 데이터 타입인 데이터 프레임의 경우에 대해서만 살펴본다.

 

-reshape2::melt.data.frame : 데이터를 식별자, 측정 변수, 측정치 형태로 재구성한다.

reshape2::melt.data.frame(

   data,        #melt할데이터

   id.vars,     #식별자 컬럼들

   mesure.vars  # 측정치 컬럼들. 이 값이 생략되면 id.vars에 해당하지 않는 모든 컬럼이 측정치 컬럼으로 취급된다.

   na.rm=FALSE   # NA인 행을 결과에 포함시킬지 여부. FALSE는 NA를 제거하지 않음을 뜻한다.

 

french_fries에 melt()를 적용해보자. french_fries에서 데이터를 식별하는 식별자로 볼 수 있는 부분은 time, treatment, subject, rep의 1:4 컬럼임며 potato, buttery, grassy, rancid, painty는 측정 변수와 측정값들로 볼 수 있다.

 

이런 사실을 이용해 1:4 컬럼을 id, 나머지 컬럼들을 측정치로 놓고 melt() 변환을 수행하는 코드는 다음과 같다.

>m<-melt(french_fries, id.vars=1:4)
>head(m)
  time   treatment subject  rep   variable   value
1    1         1         3        1    potato        2.9
2    1         1         3        2    potato       14.0
3    1         1         10       1    potato       11.0
4    1         1         10       2    potato       9.9
5    1         1         15       1    potato       1.2
6    1         1         15       2    potato       8.8

 

이 결과에서 알 수 있듯이 여러 컬럼으로 나열된 측정치들을 variable, value의 두 개 컬럼을 사용해 여러 행으로 변환하는 것이 melt()의 역할이다.

 

이렇게 변환된 결과는 측정 변수가 variable이라는 컬럼에 값으로 저장되어 variable마다 그룹 지어 통계치를 계산하는 작업이 간단해진다. 예를 들어, 식용유 종류(treatment)마다 측정 변수의 평균을 구하는 코드는 다음과 같이 작성할 수 있다.

>library(plyr)
>ddply(m, .(variable), summarise, mean=mean(value, na.rm=TRUE))
     variable      mean
1   potato       6.9525180
2   buttery      1.8236994
3   grassy       0.6641727
4   rancid       3.8522302
5   painty       2.5217579

 

위 코드에서 na.rm=TRUE는 melt()된 결과에서 value가 NA 값인 행들이 있어서 필요했다. NA를 포함한 행들을 확인하기 위해 complete.cases()를 사용해보자. complete.cases() 함수는 해당 행의 모든 값이 NA가 아닌 경우 TRUE, 해당 행의 값이 하나라도 NA를 포함하고 있는 경우 FALSE를 반환하므로, 이를 사용해 NA를 포함하는 행을 찾을 수 있다.

>french_fries[!complete.cases(french_fries), ]
      time   treatment subject rep potato buttery grassy rancid painty
315    5         3          15     1     NA       NA     NA     NA     NA
455    7         2          79     1     7.3       NA     0.0     0.7      0
515    8         1          79     1     10.5      NA    0.0      0.5      0
...

 

NA를 포함하는 측정치를 melt()시 제외하려면 na.rm에 TRUE를 지정한다. 다음에 그 예를 보였다.

>m<-melt(id=1:4, french_fries, na.rm=TRUE)
>head(m)
  time treatment subject rep variable value
1    1         1        3      1   potato   2.9
2    1         1        3      2   potato  14.0
3    1         1       10      1   potato  11.0
4    1         1       10      2   potato   9.9
5    1         1       15      1   potato   1.2
6    1         1       15      2   potato   8.8

 

cast()

cast()는 결과로 얻고자 하는 데이터 타입에 따라 dcast(), acast()로 구분하여 사용한다. dcast()는 결과로 데이터 프레임을 반환하며, acast()는 벡터, 행렬, 배열을 반환한다. 여기서는 앞에서와 마찬가지로 데이터 프레임에 집중하여 dcast()만 살펴보도록 하겠다.

 

-reshape2::dcast : melt()된 데이터(molten data)를 측정치를 컬럼으로 나열한 데이터 프레임으로 변환한다.

reshape2::dcast(

   data,     #melt()된 데이터

              #변환 포뮬로

              #포뮬러를 작성ㅎ하는 규칙은 다음과 같다.

              #1) "id 변수~ variable 변수" 형태로 적는다.

              #2)아무 변수도 지정ㅎ하지 않으려면 .을 사용한다.

              #3)formula에 명시적으로 나열되지 않는 모든 변수를 표현하려면 ...을 사용한다.

   formula,

   fun.aggregate=NULL      #데이터 재구성 시 여러 행이 한 셀에 모일 경우 사용할 집합 함수

)         

 

다음 코드는 french_fries 데이터를 melt() 후 dcast()를 사용해 다시 원 데이터로 변환하는 예를 보여준다. 코드 마지막에 사용된 함수인 identical()은 두 데이터가 완전히 동일한 객체인지를 알려주는 함수다. rownames()에 NULL을 부여한 것은 french_fries에 특이하게 부여된 행 번호(61, 25, 62, … 형태로 행 번호가 부여되어 있음)를 무시하기 위함이다.

>m<-melt(french_fries, id.vars=1:4)
>r<-dcast(m, time + treatment + subject + rep ~ ...)
>rownames(r) <- NULL
>rownames(french_fries) <- NULL
>identical(r, french_fries)
[1] TRUE

 

데이터 요약

cast()의 또 다른 유용성은 데이터의 요약 값을 계산하는 기능이 있다는 점이다. 그러나 melt()된 데이터에 plyr 패키지만 잘 적용해도 이 절에서 설명할 대부분의 요약치 계산은 쉽게 수행할 수 있다. 따라서 cast()가 제공하는 요약 기능은 reshape2가 제공하는 하나의 보너스 정도로 생각할 수 있다.

reshape2에서 데이터 요약을 수행하려면 melt()에서 사용한 것보다 적은 개수의 식별자를 dcast()의 formula에 지정하면 된다. 예를 들어, 앞에서 melt() 시 사용한 french_fries의 식별자는 time, treatment, subject, rep였고, 이를 dcast()에서 복구할 때는 이들을 모두 time+treatment+subject+rep ~ … 형태로 지정했다. 그러나 이들 식별자 중 하나를 포뮬러 ‘~’ 왼편에서 제외한다면 dcast() 시 여러 행이 하나의 셀로 모이게 될 것이다. 바로 이렇게 모인 값들에 요약치를 계산하는 것이 핵심이다.

다음 예는 dcast() 시 time만 포뮬러에서 ~ 왼쪽에 적고 측정 변수를 오른쪽에 적은 예를 보여준다. 이 경우 같은 time 값에 해당하는 여러 개의 데이터가 결과 데이터 프레임의 한 셀에 모이게 되며, dcast()는 자동으로 length()를 적용해 같은 셀에 모인 행의 개수를 센다.

>m<-melt(french_fries, id.vars=1:4)
>dcast(m, time ~ variable)
Aggregation function missing: defaulting to length5

   time potato buttery grassy rancid painty
1      1     72      72     72     72     72
2      2     72      72     72     72     72
3      3     72      72     72     72     72
4      4     72      72     72     72     72
5      5     72      72     72     72     72
6      6     72      72     72     72     72
7      7     72      72     72     72     72
8      8     72      72     72     72     72
9      9     60      60     60     60     60
10   10     60      60     60     60     60

 

좀 더 재미있는 통계는 length보다는 sum, mean 또는 임의의 함수를 적용해 구할 수 있다. 다음 데이터는 time에 따라 평균값이 어떻게 달라지는지를 보여준다.

>dcast(m, time ~ variable, mean, na.rm=TRUE)
  time       potato        buttery          grassy         rancid         painty
1    1      8.562500      2.236111      0.9416667      2.358333      1.645833
2    2      8.059722      2.722222      1.1819444      2.845833      1.444444
3    3      7.797222      2.102778      0.7500000      3.715278      1.311111
4    4      7.713889      1.801389      0.7416667      3.602778      1.372222
5    5      7.328169      1.642254      0.6352113      3.529577      2.015493

 

dcast() 호출 시 포뮬러에서 ~ 우측은 측정 변수를 적는 곳으로, 이곳에 적은 변수는 결과에서 새로운 컬럼이 된다. 따라서 식별자를 ~ 오른쪽에 적으면 해당 식별자 값마다 variable의 요약치를 새로운 컬럼으로 한 결과를 얻을 수 있다. 다음은 각 time마다 (treatment, variable) 순서쌍에 해당하는 value의 평균을 계산한 예다.

>dcast(m, time ~ treatment + variable, mean, na.rm=TRUE)
  time      1_potato      1_buttery       1_grassy        1_rancid       1_painty         2_potato       2_buttery
1    1      7.925000      1.7958333      0.9041667      2.758333      2.1500000      8.775000       2.491667
2    2      7.591667      2.5250000      1.0041667      3.900000      1.9750000      8.537500       3.125000
3    3      7.770833      2.2958333      0.8166667      4.650000      1.1166667      7.637500       2.079167
4    4      8.404167      1.9791667      1.0250000      2.079167      0.4666667      8.204167       1.608333
5    5      7.741667      1.3666667      0.7708333      4.279167      3.0083333      6.933333       1.858333
...

    2_grassy      2_rancid       2_painty        3_potato        3_buttery      3_grassy       3_rancid      3_painty
1  0.9958333    1.716667      0.8083333      8.987500       2.420833      0.9250000      2.600000      1.979167
2  0.9500000    2.141667      0.6625000      8.050000       2.516667      1.5916667      2.495833      1.695833
3  0.7250000    2.895833      1.5625000      7.983333       1.933333      0.7083333      3.600000      1.254167
4  0.6416667    3.512500      1.8583333      6.533333       1.816667      0.5583333      5.216667      1.791667
5  0.5833333    3.641667      0.7375000      7.308696       1.704348      0.5478261      2.630435      2.313043
...

 

위 결과에서 1_potato는 treatment가 1일 때 potato 값을, 2_potato는 treatment가 2일 때 potato 값을 의미한다. 다른 컬럼들도 같은 방식으로 이해할 수 있다.

비교를 위해 같은 연산을 ddply()를 사용해 계산해보자. 이처럼 같은 연산을 서로 다른 함수나 패키지로 해보는 것도 R 프로그래밍 실력을 높이는 데 많은 도움이 된다.

>ddply(m, .(time, treatment, variable), function(rows) {
+    return(mean(rows$value, na.rm=TRUE))
+    })
   time  treatment    variable        V1
1    1         1         potato       7.9250000
2    1         1         buttery      1.7958333
3    1         1         grassy       0.9041667
4    1         1         rancid       2.7583333
5    1         1         painty       2.1500000

 

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

반응형

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

난수 생성 및 분포 함수  (0) 2020.02.04
코드 수행 시간 측정  (0) 2020.02.03
데이터 정렬  (0) 2020.02.03
데이터 분리 및 병합  (0) 2020.02.03
apply 계열 함수 - 2  (0) 2020.02.02