1.7 자료 다루기 (Data Manipulation)
읽어들인 자료를 자르고, 붙이고, 변형하는 작업을 자유자재로 할 수 있어야 한다.
1.7.1 새로운 열(column, 컬럼) 만들기
앞의 d1 data.frame에서 ageGrp라는 컬럼을 새로 만드는데, Age 컬럼을 이용하여 5세 단위로 잘라 어느 군에 속하는지 표시하고 싶다.
d1 data.frame 속의 Age 라는 컬럼을 가리킬 때는 $를 이용하여 어떤 object (여기서는 data.frame) 속의 object (여기서는 column vector)를 표시할 수 있다. 즉, d1$Age라고 하면 d1이라는 list (즉, 여기서는 data.frame) 속의 Age라는 column vector를 의미하게 된다.
아래는 seq 함수로 잘리는 위치(breaks)를 만들어서 지정해 주고 있다.
$ageGrp = cut(d1$Age, seq(0, 100, by=5), right=F) ; head(d1) d1
ID Sex Age WT ageGrp
1 1 M 63 58.3 [60,65)
2 2 M 52 67.7 [50,55)
3 3 M 62 70.5 [60,65)
4 4 M 55 72.9 [55,60)
5 5 M 84 68.1 [80,85)
6 6 M 70 75.5 [70,75)
cut 함수는 구간을 나눌 때 “( , ]” “~초과 ~이하”로 하기 때문에 “~이상 ~미만”으로 하고 싶을 때는 right=F라는 옵션을 써야 한다.
위에서 첫 행의 사람은 55세이어서 55세 이상 60세미만 군, 즉 “[55, 60)”에 속하게 된다. 만약 right=F라는 옵션을 사용하지 않았다면, 첫 행의 사람은 50세 초과 55세 이하 군, 즉 ”(50, 55]”에 속하게 된다.
위에서 자른 것을 그림으로 나타내고 싶으면 plot 함수를 쓰면 된다.
plot(d1$ageGrp)
Figure 1.1: Distribution of Age Group
is() 함수를 이용해서 ageGrp 컬럼은 factor type으로 생성된 것을 확인할 수 있다.
is(d1$ageGrp)
[1] "factor" "integer" "oldClass"
[4] "double" "numeric" "vector"
[7] "data.frameRowLabels" "atomicVector" "index"
[10] "replValue" "numLike" "number"
[13] "replValueSp"
R에서 factor type은 통계에서의 범주형(categorical type) 변수 가운데에서도 명목형(nominal type)을 의미하며, 순서형(ordered type)을 나타내는 ordered라는 type도 있다.
사실 나이군(age group)은 명목형이라기보다 순서형이기 때문에 다음과 같이 as.ordered()라는 type cast함수를 써서 순서형으로 바꾸어 줄 수도 있다.
$ageGrp = as.ordered(d1$ageGrp)
d1is(d1$ageGrp)
[1] "ordered" "factor" "integer"
[4] "oldClass" "double" "numeric"
[7] "vector" "data.frameRowLabels" "atomicVector"
[10] "index" "replValue" "numLike"
[13] "number" "replValueSp"
as. 뒤에 type이름을 쓰면 그 type으로 변형해 주는 함수 이름이 된다.
1.7.2 특정 행(row)만 고르기
data.frame에서 행을 나타내는 인덱스(index)에 행 이름, 행 번호, 또는 행의 수와 같은 TRUE, FALSE들의 vector를 대입해 줌으로써 특정 행만 골라낼 수 있다.
인덱스란 data.frame 이름 바로 뒤에 오는 [ , ]를 의미하고, 쉼표 앞부분이 행에 대한 index, 뒷 부분이 열에 대한 index를 의미한다.
행 인덱스를 비워 두면 모든 행, 열 인덱스를 비워 두면 모든 열을 의미한다.
d1 data.frame에서 5 번째, 10번째 행만 보고 싶을 때는 다음과 같이 할 수 있다.
c(5, 10), ] d1[
ID Sex Age WT ageGrp
5 5 M 84 68.1 [80,85)
10 10 M 63 71.9 [60,65)
d1 data를 load할 때 행 이름을 지정하지 않았으므로, 행 번호가 행 이름 역할을 한다.
만약 남자만 보고 싶다면 다음과 같이 할 수 있다.
$Sex == "M", ] d1[d1
위에서 d1$sex == “M”에 의해 수식을 만족하는지 여부에 따라서, TRUE, FALSE들의 vector 생성되었기 때문에 그 TRUE, FALSE vector가 index 역할을 하게 된다.
TRUE, FALSE들의 vector가 제대로 생성되었는지는 다음과 같이 확인해 볼 수 있다.
$Sex == "M" d1
여러 조건들은 AND 연산자 (“&”)나 OR 연산자 (“|”)를 이용해서 결합할 수도 있다.
예를 들어, 남자이면서 60세 이상인 사람들의 행만 고른다면 다음과 같다.
$Sex == "M" & d1$Age > 85, ] d1[d1
위 조건을 만족하는 사람이 몇 명인지는 위의 결과를 nrow() 함수에 입력으로 쓰면 된다. 즉,
nrow(d1[d1$Sex == "M" & d1$Age > 85, ])
1.7.3 특정 열(컬럼)만 고르기
data.frame에서 특정 열의 이름들의 vector를 컬럼 인덱스에 넣어 주면 된다.
예를 들어, d1 data.frame에서 pat, sex, age 컬럼들만 보고 싶다면 다음과 같이 한다.
c("ID", "Sex", "Age")] d1[,
위의 결과를 d2라는 data.frame을 만들어 저장하고 싶다면 다음과 같이 한다.
= d1[, c("ID", "Sex", "Age")] d2
위의 경우에 자료의 복제가 일어나므로 매우 큰 dataset을 자꾸 복사하면 메모리의 낭비가 심해지며, 어떤 경우에는 메모리 부족으로 오류가 날 수도 있다. 따라서, 위와 같은 복제는 메모리 낭비를 감안해서 하는 것이 바람직하다.
열의 순서는 위에서 지정해 준 열 이름의 순서이다. 이것을 이용해서 (표시되는) 열 순서를 바꿀 수 있다. 다른 data.frame에 대입했다면 새 data.frame에서는 바뀐 순서로 들어간다.
1.7.4 행 순서 바꾸기
order() 함수를 이용해서 특정 컬럼으로 정렬(sorting)할 수 있다.
예를 들어, 성별, 나이별로 sorting하고 싶다면 다음과 같이 한다.
order(d1$Sex, d1$Age), ] d1[
1.7.5 동일한 형태의 data.frame 합하기 (행 결합, row binding)
만약 열의 이름과 형태가 동일한 두 개의 data.frame 들의 행을 합할 때는 rbind() 함수를 사용한다.
= rbind(d1, d2) d3
1.7.7 공통 컬럼들을 이용한 결합 (Merge)
이것은 database에서의 join operation과 유사하다.
= merge(d1, d2) d3
Default는 이름이 같은 모든 컬럼들을 matching시키지만, 특정 컬럼만 matching시키고 싶다면 by=c(컬럼명들의 comma list)로 하면 된다.
공통 컬럼들에서 어느 한 data.frame에는 있는 값들이 다른 data.frame에는 없는 경우에는 어떻게 할 지(left join or right join)는 옵션들을 이용해서 조절할 수 있다.
예들 들어, d1에만 있고, d2에 없는 경우에도 결과에 모두 포함시키고 싶은 경우에는 all.x=T라는 옵션을 쓰고, 반대의 경우에는 all.y=T의 옵션을 쓴다.
어느 한 쪽에라도 있으면 모두 포함시키고 싶을 때는 all=T 옵션을 사용한다.
이제 새로운 data.frame d1, d2를 생성하여 예를 보이면 다음과 같다.
= data.frame(ID=c(101, 102, 103), sex=c("F", "F", "M"), age=c(50, 60, 70)) ; d1 d1
ID sex age
1 101 F 50
2 102 F 60
3 103 M 70
= data.frame(ID=c(101, 101, 103, 103, 104), hgb=c(12, 11, 10, 9, 8)) ; d2 d2
ID hgb
1 101 12
2 101 11
3 103 10
4 103 9
5 104 8
merge(d1, d2)
ID sex age hgb
1 101 F 50 12
2 101 F 50 11
3 103 M 70 10
4 103 M 70 9
양쪽 테이블(table)에 모두 있는 ID들만 포함되었다.
merge(d1, d2, all.x=T)
ID sex age hgb
1 101 F 50 12
2 101 F 50 11
3 102 F 60 NA
4 103 M 70 10
5 103 M 70 9
첫 번째 테이블에 있는 ID들은 모두 포함되었다.
merge(d1, d2, all.y=T)
ID sex age hgb
1 101 F 50 12
2 101 F 50 11
3 103 M 70 10
4 103 M 70 9
5 104 <NA> NA 8
두 번째 테이블에 있는 ID들은 모두 포함되었다.
merge(d1, d2, all=T)
ID sex age hgb
1 101 F 50 12
2 101 F 50 11
3 102 F 60 NA
4 103 M 70 10
5 103 M 70 9
6 104 <NA> NA 8
어느 한 쪽 테이블에라도 있는 ID들은 모두 포함되었다.
잘 normalize된 database인 경우에는 전체 database가 많은 table로 나누어져 있다. 이 경우 분석을 위해 merge해야 하는 경우가 많은데, database언어인 SQL을 아는 경우에는 그것을 사용해도 되고, (R 에는 여러가지가 있는데, RSQLite package가 1인용으로 크기가 작다.) R의 merge() 함수만 잘 사용해도 되지만, 그렇더라도 database의 primary key (PK) - foreign key (FK) referential integrity, Entity-Relationship diagram, normalization의 개념을 알고 사용하는 것이 좋다.
1.7.8 Reshape (rotation)
첵에 인쇄할 때는 wide format이 보기 좋지만, 컴퓨터에서 자료 처리할 때는 long format이 좋다.
다음은 6명의 대상자를 1개월 간격으로 3개월에 걸쳐 측정한 것을 wide format으로 정리한 예이다.
= data.frame(trt = c(rep("Active", 3), rep("Placebo", 3)),
d8 pat = c(101, 104, 107, 102, 105, 108),
Mo1 = c(7, 6, 8, 9, 5, 7),
Mo2 = c(1, 3, 3, 4, 5, 2),
Mo3 = c(0, 2, 3, 5, 4, 3))
몇몇 자료 분석 도구는 위의 형태를 바로 input으로 받을 수 있지만, 좀 더 바람직하게는 아래와 같이 long format을 사용하는 것이다.
d8
trt pat Mo1 Mo2 Mo3
1 Active 101 7 1 0
2 Active 104 6 3 2
3 Active 107 8 3 3
4 Placebo 102 9 4 5
5 Placebo 105 5 5 4
6 Placebo 108 7 2 3
= reshape(d8, direction = "long",
d9 varying = list(names(d8)[3:5]),
v.names = "score",
idvar = c("pat"),
timevar = "visit",
times = 1:3) ; d9
trt pat visit score
101.1 Active 101 1 7
104.1 Active 104 1 6
107.1 Active 107 1 8
102.1 Placebo 102 1 9
105.1 Placebo 105 1 5
108.1 Placebo 108 1 7
101.2 Active 101 2 1
104.2 Active 104 2 3
107.2 Active 107 2 3
102.2 Placebo 102 2 4
105.2 Placebo 105 2 5
108.2 Placebo 108 2 2
101.3 Active 101 3 0
104.3 Active 104 3 2
107.3 Active 107 3 3
102.3 Placebo 102 3 5
105.3 Placebo 105 3 4
108.3 Placebo 108 3 3
reshape() 함수의 사용법은 외우기가 어렵지만 매우 유용하다. dplyr, tidyr package의 여러 함수로도 같은 일을 할 수 있다.