data.table 패키지 기초

데이터를 빠르게 가공할 수 있는 data.table에 대하여 패키지 설치부터, 기본 구조 및 데이터를 가공하여 재구조화 하는 방법에 대해서 소개합니다.
R
data.table
lecture
Author
Published

July 13, 2022

1. data. table

1-1. data.table 특징

data.table은 빠른 속도와 메모리 효율성에 가장 적합한 패키지입니다.

대용량의 데이터를 분산처리 시스템 없이 처리할 수 있습니다.

데이터 프레임(data.frame)을 대신하여 더 빠르고 편리하게 사용할 수 있는 데이터 타입입니다.

  • 장점으로는, 상대적으로 메모리 요구량이 적고, 속도가 매우 빠르다는 특징이 있습니다.

  • 단점으로는, 다소 난해한 문법으로 널리 사용되지 못하고 있다는 특징이 있습니다.

1-2. 생성하기

본격적으로 data.table에 대해서 알아보기 전에, Setup 과정에 대해서 먼저 소개하려고 합니다. data.table은 R에서 기본적으로 제공되는 데이터 구조가 아니기 때문에, package 설치가 필요합니다.

## Setup
# install.packages("data.table")
# install.packages("curl")
library(data.table)
library(curl)

위의 과정을 통해 pacakge 설치 및 불러오기를 실행합니다.

data.table을 생성하는 데는 두 가지 방법이 있습니다.

첫번째는, data.table() 함수를 통해 직접 생성하는 방식입니다. 다음의 예시로 살펴보겠습니다.

EX=data.table(
  ID=c("A","B","C","D","E"),
  MATH=c(100,96,94,88,92),
  ENGLISH=c(96,86,97,92,93),
  HISTORY=c(85,92,87,92,94))
ID MATH ENGLISH HISTORY
A 100 96 85
B 96 86 92
C 94 97 87
D 88 92 92
E 92 93 94

ID, MATH, ENGLISH, HISTORY를 변수로 한, data.table이 형성된 것을 확인할 수 있습니다.

두번째는, 기존의 데이터를 불러오는 방법이 있습니다.

fread 함수는 대용량 파일을 빠르게 가져올 수 있는 함수입니다. 파일을 읽어와서 data.table 형식의 자료로 만들 때, 로컬 file path를 입력하거나, http://로 시작하는 URL을 입력하는 방법을 사용할 수 있습니다.

library(data.table) ; library(magrittr)
df <- read.csv("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv")
dt <- fread("https://raw.githubusercontent.com/jinseob2kim/lecture-snuhlab/master/data/example_g1e.csv")

09-15년 공단 건강검진 데이터에서 실습용으로 32명을 뽑은 자료를 이용하여,

df에는 data.frame 형식으로 데이터를 불러왔고, dt에는 fread 함수를 이용하여 data.table의 형식으로 데이터를 불러온 것을 확인할 수 있습니다.

fread 함수로 파일을 불러오면 그 class는 data.frame에 data.table이 추가되며, 문법이 원래의 data.frame과 달라지는 점을 유의해야 합니다.

class 함수를 통해 df와 dt의 속성을 확인해보겠습니다.

print(class(df)) ; print(class(dt))
[1] "data.frame"
[1] "data.table" "data.frame"

dt의 class에 data.table이 추가된 것을 확인할 수 있습니다.

지금까지 data.table을 생성하는 두 가지 방법에 대해서 알아보았습니다.

다음으로 data.table이 data.frame과 다른 점은, 행(Row)의 이름을 받지 않는 것을 기본값으로 한다는 것입니다.

예시로 알아보도록 하겠습니다.

R에 기본적으로 저장되어 있는 mtcars 데이터를 이용하도록 하겠습니다.

# mtcars
EX1<-as.data.frame(mtcars)
EX2<-as.data.table(mtcars)

실행하였을 때, EX1과 EX2의 행의 이름에서 차이점이 있음을 확인할 수 있습니다.

만약, data.table에서도 행의 이름을 남겨 놓고 싶을 때는 다음과 같이 실행하면 됩니다.

EX3<-as.data.table(mtcars,keep.rownames=T)

data 값 뒤에 keep.rownames=T로 설정하였을 때,

각 행의 이름이 rn 컬럼에 남아 있는 것을 확인할 수 있습니다.

1-3. 기본문법

data.table의 기본 문법은 DT[i, j, by] 형태입니다.

  • i는 행(row)과 관련되어, 행에 대해서 subset 하는 역할을 수행합니다.

  • j는 열(column)을 선택하거나, 열 또는 테이블 전체(.SD)에 함수를 적용합니다.

  • by는 집단을 나눕니다. j에서 지정한 열과 함수에 대한 실행을 그룹 별로(by group) 수행합니다.

  • 맨 마지막에 [order]를 붙여 오름차순이나 내림차순으로 정렬할 수 있습니다.

1-4. 특수기호

data.table에서만 확인할 수 있는 특수기호들이 있습니다.

각 특수기호의 자세한 기능과 사용법은 이하에서 설명하기로 하고, 여기에서는 간단히 개념정도만 다뤄보려고 합니다.

  • .SD : Subset of Data(by=로 나눠진 부분 데이터). 특수 기호를 사용하여 그룹 칼럼(by grouping columns)을 제외한 모든 칼럼을 대상으로 연산을 수행할 때 사용합니다.

  • .SDcols : 특수 기호를 사용하여 특정 다수의 칼럼을 지정하여 처리할 때 사용합니다.

  • .N : 부분 데이터의 행의 수를 나타낼 때 사용합니다. 특정한 열을 잡아서 length() 함수를 이용해도 되지만 좀 더 간편하게 구할 수 있습니다.

  • := : DT[i, j, by]에서 칼럼 j를 추가/갱신/삭제할 때 특수기호 := 연산자를 사용하여 수행할 수 있습니다.

이상에서 data.table을 이용하면서 가장 많이 쓰이는 특수기호들에 대해서 알아보았습니다. 각각의 특수기호들이 어떻게 실제로 쓰이는지에 대해서는 이하에서 등장할 때마다 자세하게 설명하도록 하겠습니다.

2. data.table에 접근하기

이하에서는 위에서 불러온 dt(=09-15년 공단 건강검진 데이터)와 EX3(=mtcars) 데이터를 이용해서 실습하려고 합니다.

2-1. 행(Row)에 접근하기

data.table에서 행(Row)에 접근하는 방법은 DT[i, j, by]에서 i 자리에 값을 넣는 것입니다. 즉, DT[c(row1, row2, …)]의 방식입니다.

mtcars 데이터로 예시를 들어보겠습니다.

여러 개의 자동차 종류 중, Datsun 710과 Hornet Sprotabout에 대해서만 알아보고 싶을 때는 다음과 같이 작성하면 됩니다.

EX3[c(3,5)]
rn mpg cyl disp hp drat wt qsec vs am gear carb
Datsun 710 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1
Hornet Sportabout 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2

만약 Mazda RX4 부터 Hornet Sportabout까지 알아보고 싶다면, 범위로 지정할 수도 있습니다.

EX3[1:5]
rn mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2

여기서 중요한 것은 DT[i, j, by]라는 기본적인 형식에서 i의 자리에 지금 내용을 채워 넣는 것인데, i 자리에 내용을 작성한 후 꼭 콤마(,)를 찍지 않아도 된다는 것입니다. 꼭 콤마(,)를 찍지 않아도 뒤에 특정한 열 을 선택하지 않으면 모든 열에 대해서 알아서 필터링을 하기 때문입니다.

다음으로는 특정 조건을 만족하는 행(row)을 선택하는 방법에 대해서 알아보려고 합니다.

DT[조건]의 형식을 이용하면 됩니다.

mtcars 데이터에서 cyl>=6이면서, carb==4인 조건을 만족하는 데이터를 찾고 싶은 경우, 다음과 같이 작성하면 됩니다.

EX3[cyl>=6 & carb==4]

KEY를 미리 설정해놓으면 더 빠르게 검색할 수 있는데, 이 내용에 대해서는 뒤에서 자세하게 다루도록 하겠습니다.

다음으로는 특정 행(row)을 제외하는 방법에 대해서 알아보려고 합니다. 제외하려는 행 혹은 행의 범위 앞에 -! 를 붙여주면 됩니다.

EX3[!1:5]
EX3[-2]

위와 같이 실행했을 때, 제외하려는 데이터가 사라진 것을 확인할 수 있습니다.

2-2. 열(Column)에 접근하기

행(Row)을 선택할 때와 유사합니다. 기본적인 형식은 DT[i, j, by]의 j 자리에 넣는 것입니다.

mtcars 데이터에서 ‘cyl’ 열(Column)을 가져오고 싶을 때는 다음과 같이 가져올 수 있습니다.

EX3[,3] ; EX3[,.(cyl)] ; EX3[,"cyl"]
cyl
6
6
4
6
8
6

열(column)의 숫자로 불러와도 되고, 변수의 이름으로 불러오는 것도 가능합니다. 그런데, 여기서 중요하게 봐야할 점은 변수의 이름으로 가져올 때 앞에 .()의 형식을 이용했다는 점입니다.

.()list()와 동일한 기능을 하는데, 조금 더 간편하게 쓸 수 있는 형식이라 생각하면 됩니다.

data.table에서는 변수의 이름만 넣었을 경우, 벡터의 형식으로 값을 불러옵니다. 그렇기 때문에 data.table의 형식을 유지하면서 데이터를 불러오려면 .() 혹은 list() 형식을 이용해야 합니다. 혹은 변수를 따옴표를 이용하여 작성하는 것도 동일한 결과를 가져옵니다.

열(column)을 선택할 때, DT[,.(new_col_name=col)] 형식을 사용하여 새로운 열 이름을 지정해서 출력할 수도 있습니다.

EX3[,.(MPG=mpg, CYL=cyl)]

위와 같이 mpg와 cyl에 대해서 변수 이름을 대문자로 바꿔준 것을 확인할 수 있습니다.

다음으로는 변수로 열을 선택하는 방법에 대해서 알아보려고 합니다.

예시를 위해 mpg, cyl, disp 세 변수를 묶는 VARS라는 새로운 변수를 임의로 설정하겠습니다.

VARS<-c("mpg","cyl","disp")
EX3[,..VARS]

VARS 라는 변수를 넣었을 때, 위에서 설정한 것처럼 mpg, cyl, disp에 대한 값들만 추출한 것을 확인할 수 있습니다. 여기서 중요한 것은, 변수 앞에 .. 을 넣어줬다는 것입니다.

data.table의 약속이라고 보면 되는데, 같은 결과를 도출하는 다른 형식들에 대해서 소개하려고 합니다.

우선은, with = F가 있습니다.

EX3[,VARS,with=F]

VARS 앞에 ..을 붙이지 않아도, with=F를 추가한다면 같은 결과를 도출하는 것을 확인할 수 있습니다.

다음으로는 앞서 배운 .SD.SDcols를 이용하는 방법에 대해 알아보겠습니다.

EX3[,.SD,.SDcols=VARS]

.SD를 통해 전체 변수를 대상으로 하되, .SDcols로 특정 변수만을 설정하는 메커니즘입니다.

또한 특정조건을 만족하는 값들에 대해 VARS의 변수 값을 알고 싶으면 다음과 같이 실행하면 됩니다.

EX3[hp>=130 & gear>=4, ..VARS]

hp가 130을 넘고, gear가 4를 넘는 값들 중 VARS(mpg,cyl,disp) 변수에서 해당하는 값들을 보여주는 것을 확인할 수 있습니다.

다음으로는 열을 제거하는 방법에 대해서 알아보려고 합니다. 행(row)을 제거할 때와 유사하게 - , ! 을 통해서 실행하면 됩니다. 그리고 같은 결과를 도출하는 다른 형식들에 대해서도 소개하려고 합니다.

EX3[,-..VARS] ; EX3[,!..VARS] ; EX3[,.SD,.SDcols=-VARS]

마지막으로 열(column)의 값에 대해서 함수들을 이용해 값들을 가공하는 방법입니다.

mpg와 hp의 평균에 대해서 구해보겠습니다.

EX3[,.(mean(mpg), mean(hp))]

값을 실행할 경우, V1, V2라는 변수 아래에 값이 도출되는 것을 확인할 수 있습니다.

위에서 배웠던, 변수에 새로운 이름을 부여하는 방식을 이용해보겠습니다.

EX3[,.(MEAN_mpg=mean(mpg), MEAN_hp=mean(hp))]

위와 동일한 값에 변수의 이름이 생긴 것을 확인할 수 있습니다.

.SD, .SDcols를 이용해서도 도출할 수 있습니다.

EX3[,lapply(.SD,mean), .SDcols=(c("mpg", "hp"))]
mpg hp
20.09062 146.6875

또한 행(row)의 자리에 특정 조건을 입력하여, 특정 조건을 만족하는 변수들에 대해서만 특정 함수를 적용할 수도 있습니다.

EX3[gear==4, lapply(.SD,mean), .SDcols=c("mpg","hp")]
mpg hp
24.53333 89.5

2-3. by에 접근하기

by는 집단을 나눕니다. 정확히는 옵션을 이용하여 그룹별로 함수를 적용할 수 있습니다.

by = (그룹1, 그룹2, …)의 형식으로 두 개 이상의 그룹별로 함수를 적용할 수도 있는데, 이 때 괄호 앞에 있는 점(.)은 list를 의미하므로 꼭 포함시켜야 합니다. (ex. by=.(EXMD_BZ_YYYY, Q_SMK_YN) 와 같이 두 개 이상의 그룹으로 묶을 때는 .()의 형식을 이용해야 합니다.)

어떻게 쓰이는지 바로 알아보도록 하겠습니다.

여기에서는 dt(=09-15 공단 건강검진 데이터)를 이용해서 실습해보려고 합니다. EXMD_BZ_YYYY을 기준으로 집단을 분리한 후, 각 집단의 HGHT와 WGHT, BMI 평균을 구하는 방법은 다음과 같습니다.

dt[,.(HGHT=mean(HGHT), WGHT=mean(WGHT), BMI=mean(BMI)), by= EXMD_BZ_YYYY]

EXMD_BZ_YYYY에 따라 각 연도를 기준으로, HGHT, WGHT, BMI가 정렬이 되었고, 그 값들의 평균을 그룹별로 구하여 나타낸 데이터 값입니다.

만약 특정한 변수가 아닌, 모든 변수에 대해서 평균을 구하고 싶다면 .SD를 이용하면 됩니다.

dt[,lapply(.SD,mean), by=EXMD_BZ_YYYY]

위의 값은 평균을 낼 수 없는 변수들에 대해서도 일괄적으로 평균을 돌렸기 때문에 NA 값이 도출되었습니다. 값에 집중하기보다, 전체에 대한 함수를 적용하는 방식에 대해서 알아두면 좋을 것 같습니다. 만일 전체가 아닌 특정 변수에만 함수를 적용하고 싶다면 .SDcols 을 이용하면 됩니다.

다음으로는 두 개 이상의 그룹 변수를 지정해 행(row)의 개수를 구해보겠습니다.

키가 175cm 이상인 사람들에 대해서, 연도(EXMD_BZ_YYYY)와 흡연 여부(Q_SMK_YN)로 구분해보려고 합니다.

dt[HGHT>=175, .N, by=.(EXMD_BZ_YYYY, Q_SMK_YN)]

위에서 잠깐 언급한 .N 을 이용하여 특정조건에 부합하며 각 변수값에 해당되는 행(row)의 수를 구해보았습니다. 그러나, 여기에서 도출된 결과값의 문제는 Q_SMK_YN의 값이 섞여 있다는 것입니다.

조금 더 정렬된 결과값으로 나타내고 싶을 때는, by 대신에 keyby를 이용하면 됩니다. keyby는 기존의 by에 오름차순/내림차순 기능이 포함되었다고 생각하면 됩니다. 만약 by를 이용하면서 정렬을 시키고 싶다면 마지막에 [order(정렬기준)]를 붙이면 됩니다.

dt[HGHT>=175, .N, keyby=.(EXMD_BZ_YYYY, Q_SMK_YN)]
dt[HGHT>=175, .N, by=.(EXMD_BZ_YYYY, Q_SMK_YN)][order(EXMD_BZ_YYYY)]

연도를 기준으로 정렬을 하고 싶은 경우, 위와 같이 뒤에 [order(EXMD_BZ_YYYY)]를 붙여주면, 위의 값과 다르게 정렬된 것을 확인할 수 있습니다.

다음으로, 특정 조건(HGHT>=175)를 만족시키면서, 하나의 기준을 더 추가하여 분류하고 싶을 때는 다음과 같은 방식을 이용하면 됩니다.

dt[HGHT>=175, .N, keyby=.(Y2015 = ifelse(EXMD_BZ_YYYY>=2015, ">=2015", "<2015"))]
Y2015 N
<2015 206
>=2015 36

HGHT가 175 이상인 사람들을 우선으로 뽑아놓고, 거기에서 Y=2015를 기준으로 행(row)의 갯수를 확인하였습니다.

3. 다른 기능들

3-1. setkey()

키를 설정합니다. 키를 활용하는 이유는 자료를 찾을 때, 그 탐색 및 처리 시간을 단축시키기 위함입니다.

setkey(DT,col)로 키를 설정하며 키가 문자열 벡터일 경우 setkeyv을 활용합니다.

만일 설정된 키를 제거할 경우, setkey(DT, NULL)를 활용합니다.

dt 데이터를 이용하여, key를 설정하고 활용해보겠습니다.

setkey(dt, EXMD_BZ_YYYY)
key(dt)
[1] "EXMD_BZ_YYYY"

dt의 키로 EXMD_BZ_YYYY가 설정된 것을 확인할 수 있습니다. 다른 변수들도 setkey 함수에 추가로 입력하면, 그 변수들이 key로 저장된 것을 확인할 수 있습니다.

다음으로는 키를 활용한 행(row) 선택에 대해서 알아보려고 합니다. dt[.(a)], dt[J(a)], dt[list(a)], dt[col==a] 중에서 아무거나 사용하여 행을 선택할 수 있습니다. 위에서 EXMD_BZ_YYYY를 key로 설정하였기 때문에, dt[J(a)]에서 a의 자리에 EXMD_BZ_YYYY에 포함되어 있는 값을 넣으면 그 값을 기준으로 데이터를 정리합니다.

예시로,

dt[J(2013)]

이 값의 경우, EXMD_BZ_YYYY가 2013인 값에 대하여 정리한 것을 확인할 수 있습니다.

만약, key를 두 개 이상 설정해놓은 경우, a의 자리에 다른 조건을 연결하면 그 조건도 포함하고 있는 값이 도출됩니다.

setkey(dt, EXMD_BZ_YYYY, Q_HBV_AG)
key(dt)
[1] "EXMD_BZ_YYYY" "Q_HBV_AG"    
dt[J(2013,2)]

위의 과정은 key에 Q_HBV_AG를 추가한 뒤, 2013년도에 Q_HBV_AG가 2인 값들에 대해서 정리한 것입니다.

3-2. Merge : data.table 병합

다음으로는 두 개의 data.table에 대해서 공통된 column을 기준으로,

하나의 data.table로 만드는 방법에 대해서 소개하려고 합니다.

dt 파일의 설문조사에 관한 데이터(Q_)들을 하나의 변수 colvars로 편의를 위해 설정하였습니다.

colvars<-grep("Q_", names(dt), value=T)

다음으로는 dt 데이터를 임의로 분리하여 dt1, dt2를 설정하겠습니다.

dt1<-dt[1:10, .SD, .SDcols=c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM", colvars)]
dt2<-dt[6:15, -..colvars]

본격적인 분석을 하기에 앞서, dt1 과 dt2에 대해서 간단히 살펴보겠습니다.

행(row)을 기준으로는 6:10행까지가 겹치고,

열(column)을 기준으로는 “EXMD_BZ_YYYY”, “RN_INDI”, “HME_YYYYMM” 이 공통입니다.

dt1, dt2 데이터를 이용해 merge 함수에 대해서 알아보도록 하겠습니다.

merge 에는 inner_join, full_join, left_join, right_join, anti_join 등이 있습니다.

하나하나씩 예시와 함께 알아보도록 하겠습니다.

처음으로 알아볼 것은 inner_join 입니다. 집합의 교집합 개념과 유사하지만, 약간의 차이점은 존재합니다.

inner_join(dt1,dt2)
merge(dt1, dt2, by=c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"), all=F)

우선 inner_join을 실행함에 있어 단순하게 inner_join 함수를 이용해도 되지만, merge 함수에서 공통 변수인 “EXMD_BZ_YYYY”, “RN_INDI”, “HME_YYYYMM”을 직접 merge의 매개체로 설정할 수도 있습니다. 그리고 inner_join의 경우, merge 함수의 뒤에 all=F가 들어간다는 것을 유의하시면 될 것 같습니다. (뒤에 full_join과 비교)

inner_join을 실행하였습니다. dt1과 dt2의 공통 행(row)에 속하는 6:10행까지를 기준으로 정렬하되, 각 값들이 dt1, dt2에서 가지고 있던 변수 값들도 그대로 가져온 것을 확인할 수 있습니다.

결과값의 RN_INDI가 714509인 값을 살펴보겠습니다.

이 변수값은 원래 dt1에서는 HGHT와 WGHT 등의 값을 가지고 있지 않았습니다. 그러나, inner_join을 하면서 dt2의 값을 그대로 받아와, HGHT, WGHT 등의 값을 부여받은 것을 확인할 수 있습니다.

다음으로는 full_join 입니다. 집합의 합집합 개념과 유사합니다.

full_join(dt1, dt2)
merge(dt1, dt2, by=c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"), all=T)

full_join을 실행하였습니다. dt1과 dt2의 모든 행이 나열된 것을 확인할 수 있습니다. (공통된 행(row)은 한번만 표시되었습니다. 또한 inner_join과 다르게 all=T 임을 확인할 수 있습니다.)

여기서 유심히 봐야할 것은 1:5, 11:15행입니다.

1:5행의 경우에는 dt에는 속해 있지만, dt2에는 속해있지 않습니다. 그렇기 때문에 1:5행은 HGHT, WGHT 등 dt2에만 있는 값들에 대해서는 받을 값이 존재하지 않아, NA로 표시된 것을 확인할 수 있습니다.

반면, 11:15행의 경우에는 dt2에는 속해 있지만, dt1에는 속해있지 않습니다. 그렇기 때문에 Q_로 시작하는 변수값에 대해서 받을 수가 없어서 NA로 나온 것을 확인할 수 있습니다.

다음으로는 left_join과 right_join 입니다.

left_join(dt1,dt2)
merge(dt1, dt2, by=c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"), all.x=T)
dt2[dt1, on = c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM")]

left_join을 실행하였습니다. 변수의 값이 dt1의 row를 기준으로 설정된 것을 확인할 수 있습니다. 그러나, dt2의 column이 추가되어, HGHT, WGHT 등 기존의 dt1에 없던 변수들이 생긴 것을 확인할 수 있습니다. inner_join과 유사하게, dt2에 있는 변수들에 대해서는 left_join을 했을 때도, 원래 dt1에는 없었던 HGHT, WGHT 등의 값이 새로 생긴 것을 확인할 수 있습니다.

right_join으로도 직접 실습하여 차이를 확인하시기 바랍니다.

하나의 차이가 있다면, left_join을 했을 때는 all.x = T 였지만, right_join의 경우에는 all.y =T 를 사용합니다.

right_join(dt1, dt2)
merge(dt1, dt2, by=c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"), all.y=T)
dt1[dt2, on = c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM")]

마지막으로 anti_join이 있습니다. 예시부터 보이고 설명하도록 하겠습니다.

anti_join(dt1,dt2)
dt1[!dt2, on = c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM")]

직관적으로 확인할 수 있듯, dt2와 겹치지 않는 dt1의 값들에 대해서만 나타낸 것을 확인할 수 있습니다. 또한 다른 join들이 dt2의 column(variable)을 가져왔던 것과 다르게, anti_join은 오직 dt1의 변수들로만 구성된 것을 확인할 수 있습니다.

만약에 anti_join(dt2,dt1) (또는, dt2[!dt1, on = c(“EXMD_BZ_YYYY”, “RN_INDI”, “HME_YYYYMM”))은 이라고 작성한다면 위와 반대로 dt2를 기준으로 데이터의 값이 도출됩니다.

3-3. 수정 연산자 :=

data.table에서 열 j를 추가하거나 갱신 또는 삭제할 때 특수 기호 := 연산자를 사용합니다.

수정 또는 생성하는 column이 두 개 이상이라면, DT[,c(“cola”, “colb”) := list(valA,valB)] 또는, DT[, “:=”(cola, colb)]의 형식을 사용합니다.

즉, := 는 새로운 data.table을 생성하지 않고 기존의 데이터 테이블에 덮어씌우거나(수정), 새로운 컬럼을 추가합니다.

다음 예시로 알아보겠습니다.

BMI2 라는 새로운 변수를 data.table에 추가하려고 합니다.

(BMI2 = WGHT/(HGHT/100)^2 를 하고, 소수점 첫째자리까지 반영)

dt[, BMI2 := round(WGHT/(HGHT/100)^2, 1)]

열(column)의 맨 마지막에 BMI2가 새롭게 생긴 것을 확인할 수 있습니다.

다음으로는 특정 조건을 충족하는 값들에 대해서 새로운 변수를 만들어 확인하는 것에 대해서 알아보려고 합니다.

두 가지 조건을 설정하겠습니다. 하나는, BP_SYS가 140이 넘는지 그리고 BMI가 25가 넘는지에 대해서, factor로 바꾸어 0과 1로 나타내는 column에 대해서 추가하려고 합니다.

dt[, ':=' (BP_SYS140 = factor(as.integer(BP_SYS>=140)), BMI25 = factor(as.integer(BMI>=25)))]

dt의 column에 새롭게 BP_SYS140과 BMI25 컬럼이 생성되어, 0과 1로 TRUE/FALSE를 보여주고 있습니다.

그리고 := 을 이용해서 column을 삭제할 수도 있습니다. 위에서 만들어본 BMI25 column에 대해서 제거해보려고 합니다.

dt[,BMI25 := NULL]

3-4. 데이터 재구조화

마지막으로, data의 형태를 바꾸는 melt(wide to long), dcast(long to wide) 함수에 대해서 알아보겠습니다.

우선 melt 함수입니다.

melt 함수는 일부 고정 칼럼을 제외한 나머지 칼럼을 stack 처리할 수 있습니다. melt 함수의 기본 구조는 다음과 같습니다.

melt(data, id.vars, measure.vars, variable.name, value.name)의 구조를 가지고 있습니다.

  • id.vars에는 data에서 그대로 유지할, 고정될 column에 대해 작성하면 됩니다.

  • measure.vars에는 새로운 변수에 포함될 기존의 데이터 값들에 대해서 넣으면 됩니다.

  • variable.name에는 measure.vars에서 추출한 데이터들을 모은 변수에 대한 이름을 설정하면 됩니다.

  • value.name에는 그 variable.name의 값들이 적히는 곳의 이름을 설정한다고 보면 됩니다.

예시로 쉽게 설명해보겠습니다.

melt_EX<-melt(dt,
              id.vars=c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"),
              measure.vars = c("TOT_CHOL", "TG", "HDL", "LDL"),
              variable.name = "Lipie",
              value.name = "Value")
melt_EX

위의 예시에서 EXMD_BZ_YYYY, RN_INDI, HME_YYYYMM 세 변수는 고정되어 있는 것을 확인할 수 있습니다. 그리고 TOT_CHOL, TG, HDL, LDL 값들이 Lipid라는 새로운 변수에 묶여있고, 그것들의 값이 Value에 나타나는 것을 확인할 수 있습니다.

melt 함수는 또한 동시에 여러 개의 칼럼들을 묶어서 사용할 수도 있습니다. melt 함수에 meausre = list(col1, col2, …) 형식으로 여러 개의 칼럼 이름을 list() 형태로 넣습니다. 이 때 공통의 value.name을 지정할 수 있습니다.

다음의 예시를 보겠습니다.

col1<-c("BP_SYS", "BP_DIA")
col2<-c("VA_LT", "VA_RT")
melt_EX2 <- melt(dt,
                 id.vars = c("EXMD_BZ_YYYY", "RN_INDI", "HME_YYYYMM"),
                 measure.vars = list(col1, col2),
                 value.name = c("BP", "VA"))
melt_EX2

예시가 직관적이진 않지만 간단하게 설명을 하자면, col1(BP_SYS, BP_DIA)과 col2(VA_LT, VA_RT)의 내용이 매칭 되는 것입니다. 그래서 BP_SYS와 VA_LT일 때, variable에서 1로 나타나고, BP_DIA와 VA_RT일 때, 2로 나타나는 것입니다.

(따라서, BP_SYS와 VA_RT의 값이 매칭되는 경우는 없습니다.)

(또한 만약에 col1과 col2에 각각 새로운 변수가 하나씩 추가되었다면 그 값은 variable에 3으로 표시될 것입니다.)

다음은 dcast 입니다.

dcast 함수는 melt 함수를 통해 길게 쌓여진 칼럼을 각 항목별로 분리하기 위해 사용합니다. 쉽게 설명하면, 방금 melt에서 행한 작업을 정확히 반대로 수행한다고 보면 됩니다.

dcast의 기본 구조는 다음과 같습니다.

dcast(data, formula, value.var, fun.aggregate)

조금 더 실용적으로 작성하면 dcast(data, ID1+ID2+ID3+…~ variable, value.var = “val”)의 구조입니다.

구조에 대해서 설명하자면,

  • data에는 dcast 함수를 실행할 데이터를 의미하고

  • ID1+ID2+ID3+…는 기존의 data부터 dcast 이후에도 고정적으로 유지될 변수들을 의미합니다.

  • variable은 melt 함수로 모아진 변수들을 다시 long to wide 하게 하기 위해 해당 변수들을 작성하는 곳입니다.

  • value.var = ‘val’ 은 dcast 함수로 long to wide 하게 될 변수 값을 작성하는 공간입니다.

  • 그리고 variable과 value.var = 칸에는 melt를 하면서 지정한 변수 이름을 넣어주면 됩니다.

예시로 알아보도록 하겠습니다.

dcast_EX <- dcast(melt_EX, EXMD_BZ_YYYY+RN_INDI+HME_YYYYMM ~ Lipid, value.var="Value")
dcast_EX

Lipid 변수에 하나로 묶어뒀던 TOT_CHOL, TG, HDL, LDL 변수가 다시 각각의 변수로 바뀐 것을 확인할 수 있습니다.

다음은 dcast 함수를 조금 더 응용해서, 재구조화를 할 때 sum, mean 등의 집계함수를 이용해서 그룹별 요약 통계량을 나타내는 과정을 소개하겠습니다.

dcast_EX2 <- dcast(melt_EX, EXMD_BZ_YYYY ~ Lipid, value.var = "Value", fun.aggregate = mean, na.rm = T)
dcast_EX2

EXMD_BZ_YYYY, 즉 연도 변수를 기준으로 Lipid에 묶여있던 TOT_CHOL, TG, HDL, LDL 변수들의 평균에 대해서 (결측치를 제거하고) 나타낸 것을 확인할 수 있습니다.

조금 더 구체적으로 fun.aggregate 뒤에는 기존에 존재하는 함수가 아닌, fun.aggregate = function(x){}의 형식으로 어떠한 함수도 이용할 수 있습니다.

다음으로는 여러 개의 칼럼들을 묶어서 melt 한 data.table에 대해서도 dcast를 하는 것에 대해 설명하겠습니다. 기본적으로 dcast 구조와 동일하지만 약간의 차이가 있습니다. variable 칸에는 그대로 long to wide 하려는 변수 이름만을 작성하면 되지만, value.var = 칸에는 각각의 데이터 값의 이름을 다 적어야 한다는 차이점이 존재합니다.

예시로 보이겠습니다.

dcast_EX3 <- dcast(melt_EX2, EXMD_BZ_YYYY+RN_INDI+HME_YYYYMM ~ variable, value.var = c("BP", "VA"))
dcast_EX3

4. 마치며

이상에서 R에서 데이터를 쉽고 빠르게 가공할 수 있는 data.table에 대하여 알아보았습니다.

배운 내용을 가볍게 정리하고 마무리하도록 하겠습니다.

1) 생성하기 : data.table은 기본적으로 설치되어 있는 프로그램이 아니기 때문에, package 설치가 필요하다.

2) 기본구조 : data.table의 기본 문법은 DT [ i, j, by ] 형태이며 각각의 쓰임새는 다음과 같다.

  • i : 행(row)을 선택

  • j : 열(column)을 선택, data.table 전반에 함수를 적용

  • by: 그룹을 구성, j에서 행한 함수를 by 그룹 별로 수행시킬 수 있음.

3) 특수기호: data.table 에서만 확인할 수 있는 특수기호들이 있다. (.SD, .SDcols, .N, :=)

4) KEY를 설정하여, 데이터에 조금 더 빠르게 접근할 수 있다.

5) merge, melt, dcast 등의 함수를 통해 데이터를 보기 쉽게 가공할 수 있다.

감사합니다.

Citation

BibTeX citation:
@online{ko2022,
  author = {Ko, Junhyuk},
  title = {Data.table {패키지} {기초}},
  date = {2022-07-13},
  url = {https://blog.zarathu.com/posts/2022-07-13-r-datatable2},
  langid = {en}
}
For attribution, please cite this work as:
Ko, Junhyuk. 2022. “Data.table 패키지 기초.” July 13, 2022. https://blog.zarathu.com/posts/2022-07-13-r-datatable2.