require(data.table)
require(dplyr)
require(microbenchmark)
data('storms')
<- function(){
df_test aggregate(x=storms[,c('wind','pressure')],
by = list(storms$name,storms$year, storms$month,storms$day),
FUN = mean)
}
<- function(){
dplyr_test %>%
storms group_by(name, year, month, day) %>%
summarise(wind=mean(wind), pressure=mean(pressure),
)
}<- as.data.table(storms)
storm_dt <- function(){
dt_test wind=mean(wind), pressure=mean(pressure)), by=.(name, year,month,day)]
storm_dt[,.(
}
microbenchmark(df_test(), dplyr_test(), dt_test(), times=10)
1. data.table 소개
data.table
은 R의 data.frame
을 상속하여 만들어진 패키지입니다. 그렇기 때문에 대괄호([]
)를 쓰는 등의 문법은 대체로 data.frame
과 비슷합니다. 그러나 data.table
은 기존의 data.frame
과 다른 장점을 갖고 있습니다.
data.table
의 장점은 다음과 같습니다.
매우 빠른 속도
data.table
은 기본data.frame
구조보다 훨씬 빠르게 데이터를 연산합니다.위는 각각
data.frame
,dplyr
,data.table
로 동일한 결과를 불러오도록 실행했을 때 걸린 시간입니다.data.table
의 결과가 최소 10배는 더욱 빠른 것을 알 수 있습니다.효율적인 메모리 처리
data.table
은 다른 데이터 패키지보다 효율적으로 데이터연산을 처리합니다. 그렇기 때문에 메모리 사용에 있어서도 더 적은 양으로 더 빠르게 계산을 진행합니다.낮은 패키지 의존성
패키지 의존성이라는 것은 특정 패키지를 불러오기 위해 또다른 패키지를 불러오는 것입니다. 우리가 배울
data.table
은 R 사용자들에게 자주 활용되는tidyverse
계열의dplyr
패키지보다 의존성이 훨씬 낮습니다. 그렇기 때문에 번거롭게 하나의 패키지를 사용하기 위해 다른 패키지들을 설치해줄 필요가 없습니다.
2. data.table 함수
data.table
패키지에는 data.table
에서 사용할 수 있는 고유의 함수들이 존재합니다. 여기서는 우선 앞으로 사용할 데이터를 불러오고 설정하기 위한 함수들을 우선 배워봅시다.
fread()
: f(ast) + read의 의미입니다. 말그대로 빠르게 데이터를 불러오는 것(read)을 의미합니다..csv
,.txt
등의 확장자 이름을 가진 파일들을 불러올 수 있습니다.
require(NHANES)
<- as.data.table(NHANES) dt
fread()
를 통해 불러온 파일의 class
는 data.table
입니다.
class(dt)
[1] "data.table" "data.frame"
data.table
을 출력했을 때, data.frame
과 다른 점은 크게 두 가지가 있습니다.
data.table
은 우선 모든 column에 대해 첫 5개, 마지막 5개의 행을 출력합니다.
또한 행의 번호에 :
가 붙어 출력됩니다.
fwrite()
: f(ast) + write 입니다. 말그대로 빠르게 데이터를 저장(write)합니다..csv
,.txt
등의 확장자 파일로 저장할 수 있습니다.
setnames()
: column 이름을 사용자가 알아보기 쉽게끔 변경해야 할 때가 있습니다.setnames()
는 column의 이름을 변경하는 함수입니다. column의 이름을 하나만 바꾸고 싶은 경우에는 문자열 하나만 넣어주면 되고, 여러 개의 column 이름을 동시에 변경할 때는 문자 벡터를 넣어주면 됩니다.
# 하나의 column 이름을 변경할 때
setnames(dt, old='Gender', new='gender')
# 여러 개의 column 이름을 동시에 변경할 때
setnames(dt,
old = c('Age','Race1','Education'),# 바꿔줄 기존의 column 이름
new = c('age','race','education') # 새로운 column 이름
)
data.frame
등을data.table
로 변경하기새롭게 파일을 불러오는 것 뿐만 아니라 기존의
data.frame
을data.table
형태로 변경해줄 수 있습니다.setDT()
: 영구적으로data.table
형태로 저장합니다. 저장된 값을 따로 출력하지는 않습니다.setDT()
as.data.table()
: 일시적으로data.table
형태로 출력합니다. 출력된 데이터가 저장되지는 않습니다.require(data.table) head(as.data.table(iris))
3. i: data.table 행
data.table
에서 행(row)을 다루는 부분을 i
라고 부릅니다 (왜 i가 된건지는 모르겠습니다).
data.table
에서 행을 다룬다는 것은 특정한 조건을 만족하는 행들을 추출 또는 선택(filtering)한다는 것과 같습니다.
자, 이제 data.table
에서 행을 다루는 방법을 살펴보겠습니다.
1) 논리 연산자를 이용한 row filtering
data.frame
의 행을 다루는 부분에서도 배웠지만, 기본적으로 i에서는 논리 연산자를 이용해 행을 선택합니다. 논리 연산자의 조건을 만족하는 행들, 즉 논리 연산자의 실행 결과가 TRUE
인 행들만 추출하는 것입니다.
>=30] |> head() dt[age
=='male' & age>=45] |> head() # 남성이고 45세 이상 dt[gender
=='female' | age>=50] |> head() #여성 또는 50세 이상 dt[gender
이처럼 &
나 |
를 붙여주면, 여러 조건을 사용하여 원하는 행들을 추출할 수 있습니다.
data.frame
과 달리 data.table
문법에서는, 대괄호 안에서 column을 df$var
양식으로 사용하지 않아도 됩니다. 그냥 column의 이름만 사용하면 됩니다.
또한 data.frame
에서는 행 부분을 입력할 때, 꼭 ,
를 붙여줘야 했습니다. 그러나 data.table
의 경우 행만 filtering 할 때, 굳이 ,
을 붙여줄 필요가 없습니다.
어떤가요? data.table
이 훨씬 더 간단하죠? 😃
2) Infix 연산자를 이용한 row filtering
data.table
에서는 논리 연산자 뿐만 아니라 infix 연산자를 이용하여, 조건을 충족시키는 행을 선택할 수 있습니다. 파이프 연산자 역시 조건을 만족하는 경우인 TRUE
에 해당하는 값들만 선택합니다.
infix 연산자는 함수 피연산자의 양쪽에 있는 인수에 계산을 적용하는 기능을 제공합니다.
A %in% B
: A가 B 안에 있는지 확인합니다. 이 때 B는vector
가 옵니다. 문자를 확인하는 경우에는%chin%
을 이용해 더욱 빠르게 계산할 수 있습니다.%in% c('Black','White')] |> head() dt[race
A %like% B
: A가 B와 비슷한지 확인합니다. 이 때 B에는 보통 문자열(character
)이 옵니다.%like% 'Married'] |> head() dt[MaritalStatus
A %between% B
: A가 B 사이에 있는지 확인합니다. 이 때 B는c(0,10)
과 같은 범위로 지정합니다.%between% c(20,25)] |> head() dt[BMI
Tip위에서 소개해드린 %%을 이용한 infix 연산자 말고도 data.table 패키지 내의 함수를 이용할 수도 있습니다.
%between%
은between()
%like%
는like()
# 예시 like(race,'ite')] dt[
3) 함수를 이용한 row filtering
논리 연산자나 infix 연산자 뿐만 아니라 TRUE
/FALSE
를 반환하는 다른 함수들도 행을 선택하는 데 활용할 수 있습니다. 가장 대표적인 함수가 바로 is.na()
입니다.
is.na(Testosterone)] |> head() dt[
NA
가 아닌 데이터를 출력하는 방법은 !is.na()
입니다. Chapter 2의 논리 연산자 부분에서 NOT
을 의미하는 기호는 !
라고 배웠습니다.
!is.na(SleepHrsNight)] |> head() dt[
4) 행의 정렬
행의 정렬 역시 i
부분에서 담당합니다. 특정한 column을 기준으로 데이터를 오름차순 또는 내림차순 정렬을 할 때 사용할 수 있습니다.
data.table
에서 특정 column을 기준으로 데이터를 정렬하는 방법은 두 가지가 있습니다.
order()
order()
는 정렬한 값의 출력만 합니다.order(BMI)] |> head() dt[
만약 내림차순으로 정렬하고 싶은 경우는 변수 앞에 -를 붙여주면 됩니다.
order(-BMI)] |> head() dt[
setorder()
setorder()
는 데이터를 정렬하여 data.table에 저장합니다.order
와는 다르게 정렬된 결과를 출력하지는 않습니다.setorder(dt, # 정렬할 데이터 # 기준이 되는 변수 age )
data.table
에서 set
이 붙는 함수는 어떤 값을 출력없이 저장하는 함수입니다.
setnames() # column의 이름 변겅
setorder() # row 정렬
setDT() # data.table로 저장
4. j: data.table 열
j
부분은 데이터의 열 (column)을 다루는 부분을 의미합니다. j
부분을 통해 원하는 열들을 선택하거나, 특정 열을 계산할 수 있습니다. 또한 새로운 변수를 생성하거나 기존의 변수를 수정 또는 삭제할 수 있습니다.
j
를 활용하기 위해선 dt[,j]
처럼 앞에 ,
를 항상 붙여줘야 합니다.
1) column 선택
data.table
에서 열을 선택하는 방법은 다양합니다.
dt[,c('X','Y')]
dt[,list(X,Y)]
dt[,.(X,Y)]
여기서 list()
와 .()
은 동일한 기능입니다. 동일한 기능이라면 더 적은 코드를 입력하는 후자가 더 낫겠죠?
|> head() dt[,.(gender,age)]
문자 vector를 이용해서도 원하는 column을 선택할 수 있습니다. 이 때는 ..
기호를 이용해야 합니다.
<- c('gender','age','race')
target |> head() dt[,..target]
2) column 계산
column을 계산하는 것 역시 data.table
의 j
부분에서 담당합니다. 예를 들어 특정 column의 평균을 계산하거나 표준편차를 계산하는 경우가 있겠죠. 만약 하나의 column만 계산하는 경우 data.table
의 []
안에서 함수를 이용하면 됩니다.
mean(BMI,na.rm=T)] |> head() dt[,
[1] 26.66014
만약 여러 개의 column에 대해 계산하는 경우 또는 계산하는 값을 data.table
형태로 출력하고 싶은 경우, column 선택에서 배웠던 .()
를 활용하면 됩니다.
mean_BMI = mean(BMI, na.rm=T),
dt[,.(sd_BMI = sd(BMI,na.rm=T))] |> head()
3) column 생성 및 변경 :=
data.table
에는 특수한 기호가 있습니다. 바로 :=
입니다. walrus (바다코끼리) 연산자라고도 불리기도 합니다. 바다코끼리의 어금니를 닮아서 붙여진 이름 같습니다.
data.table
을 사용한다면 :=
연산자를 활용할 줄 아는 것이 굉장히 중요합니다. 새로운 column을 추가하거나, 기존의 column을 변경할 때, 또는 column을 삭제할 때 활용되는 연산자 입니다.
a. column의 생성
새로운 column을 생성하기 위해서는 dt[,column_name := value]
형식으로 코드를 작성합니다. :=
의 왼쪽에는 새로운 column의 이름이, 오른쪽에는 데이터를 입력합니다.
:= paste0((age %/% 10) * 10,'대')]
dt[,age_group table(age_group)] |> head() dt[,
age_group
0대 10대 20대 30대 40대 50대
1391 1374 1356 1338 1398 1304
age
를 이용하여 age_group
(연령대) column을 추가하였습니다.
만약 여러 개의 column을 동시에 추가하고 싶다면, 백틱(``
) 또는 따옴표(''
)과 함께 :=
를 사용해야 합니다.
이 때 ()
안의 등호들은 :=
가 아니라 그냥 등호(=
)를 사용해야 합니다.
`:=`(
dt[,age2 = age/5,
age3 = age/10
)]
b. column의 변경
column을 변경한다는 것은 기존의 column 값들을 다르게 바꿔준다는 것입니다. 예를 들면 1과 2로 이루어진 column을 ’no’와 ’yes’로 변경해주는 것처럼요.
column을 변경하는 것 역시 생성과 마찬가지로 dt[,column_name := value]
형식으로 코드를 작성하면 됩니다. column_name
에는 바꿔줄 column의 이름이, value
에는 새롭게 넣어줄 데이터를 입력합니다.
:= ifelse(gender=='male','m','f')] dt[,gender
위의 코드처럼 :=
를 이용해 male
, female
로 되어있는 Gender
를 각각 m
과 f
로 변경할 수 있습니다.
한편 여러 개의 column을 생성하거나 변경하는 경우, 새로운 column이 생성되는 []
안에서 그 column을 바로 사용할 수 없습니다.
#|eval: false
`:=`(
dt[,A2=age,
A3=A2
)]
# 에러메시지 발생
# Error in eval(jsub, SDenv, parent.frame()) : object 'A2' not found
위 코드는 A2와 A3 column을 동시에 생성하는 코드입니다. 그러나 A2가 아직 만들어지지 않았기 때문에, A3 column은 생성될 수 없습니다. 따라서,
:=age][,A3:=A2][] |> head() dt[,A2
위의 코드처럼 []
을 여러 번 붙여 순차적으로 column을 생성 또는 변경해주어야 합니다. 이를 chaining이라고 합니다.
c. column의 삭제
특정 데이터의 column을 삭제할 때는 dt[,column_name := NULL]
형식을 사용합니다. 삭제하려는 하는 column_name
만 선택하면 되겠죠.
:= NULL] # 삭제대상 열 이름 := NULL dt[,column_name
:=
연산자를 활용한 코드를 실행하게 되면 해당 데이터가 저장이 될 뿐, 출력이 되진 않습니다 (set
함수와 같은 역할). 변경한 column을 확인하기 위해선 대괄호를 한 번 더 붙여줍니다.
:= NULL][] |> head() dt[,column_name
5. by: 그룹 별 분석
by
: 특정 column에 따라 j
를 계산합니다. 예를 들어 성별에 따른 나이의 평균을 구하는 경우, 따라서 by
를 사용하기 위해서는 j
부분이 존재해야 합니다.
mean(age),by=MaritalStatus] dt[,
keyby
: by와 마찬가지로 특정 그룹에 따라 계산합니다. 하지만 by
와 다르게 그룹으로 선택된 column을 기준으로 정렬하여 결과값을 출력합니다.
mean(age),keyby=MaritalStatus] dt[,
혼인상태(MaritalStatus
)에 따른 나이(age
)의 평균 값이 혼인상태의 이름으로 오름차순 정렬이 되어 출력된 것을 확인할 수 있습니다.
6. 이외 유용한 data.table 전용 함수
fifelse()
: fast ifelse입니다.ifelse()
보다 더 빠르게 작업을 수행합니다.기본ifelse()
와 다르게 yes와 no 자리에 오는 인자들의 유형(type)이 반드시 동일해야 합니다.:= fifelse(gender=='m','남성','여성')] dt[,SEX := fifelse(age<10,NA,age)] # error 발생. dt[,Temp
fcase()
: fast case when입니다.ifelse()
처럼 조건을 사용해 데이터를 조작하지만, 반복적으로ifelse()
함수를 계속해서 사용할 필요가 없기 때문에 더 적은 코드를 사용합니다.:= fcase( dt[,age_group2 <20,'10대', age<30,'20대', age<40,'30대', age<50,'40대', age<60,'50대' age )]table(age_group2, useNA = 'always')] dt[,
age_group2 10대 20대 30대 40대 50대 <NA> 2765 1356 1338 1398 1304 1839
조건에 해당되지 않는 나머지 값들은 자동으로
NA
처리 됩니다. 나머지 값들에 값을 주고 싶다면default
인자를 활용하면 됩니다.:= fcase( dt[,age_group2 <20,'10대', age<30,'20대', age<40,'30대', age<50,'40대', age<60,'50대', agedefault='60대 이상' )]table(age_group2, useNA = 'always')] dt[,
age_group2 10대 20대 30대 40대 50대 60대 이상 <NA> 2765 1356 1338 1398 1304 1839 0
uniqueN()
: 고유한 데이터의 수를 확인하는 데 사용하는 함수입니다. 주로 사람의 ID 수를 카운트 하는 데 사용합니다.:=fifelse(is.na(BMI),'donno', fifelse(BMI>=30,'obese','normal'))][,uniqueN(BMI2)] |> head() dt[,BMI2
[1] 3