data.table 기초 문법

data.table 문법, 연산자, 함수
data.table
R
Published

January 18, 2023

1. data.table 소개

data.table은 R의 data.frame을 상속하여 만들어진 패키지입니다. 그렇기 때문에 대괄호([])를 쓰는 등의 문법은 대체로 data.frame과 비슷합니다. 그러나 data.table은 기존의 data.frame과 다른 장점을 갖고 있습니다.

data.table의 장점은 다음과 같습니다.

  • 매우 빠른 속도

    data.table은 기본 data.frame 구조보다 훨씬 빠르게 데이터를 연산합니다.

    require(data.table)
    require(dplyr)
    require(microbenchmark)
    data('storms')
    df_test <- function(){
      aggregate(x=storms[,c('wind','pressure')],
                by = list(storms$name,storms$year, storms$month,storms$day),
                FUN = mean)
    }
    
    dplyr_test <- function(){
      storms %>% 
        group_by(name, year, month, day) %>% 
        summarise(wind=mean(wind), pressure=mean(pressure),
                  )
    }
    storm_dt <- as.data.table(storms)
    dt_test <- function(){
      storm_dt[,.(wind=mean(wind), pressure=mean(pressure)), by=.(name, year,month,day)]
    }
    
    microbenchmark(df_test(), dplyr_test(), dt_test(), times=10)

    위는 각각 data.frame, dplyr, data.table로 동일한 결과를 불러오도록 실행했을 때 걸린 시간입니다. data.table의 결과가 최소 10배는 더욱 빠른 것을 알 수 있습니다.

  • 효율적인 메모리 처리

    data.table은 다른 데이터 패키지보다 효율적으로 데이터연산을 처리합니다. 그렇기 때문에 메모리 사용에 있어서도 더 적은 양으로 더 빠르게 계산을 진행합니다.

  • 낮은 패키지 의존성

    패키지 의존성이라는 것은 특정 패키지를 불러오기 위해 또다른 패키지를 불러오는 것입니다. 우리가 배울 data.table은 R 사용자들에게 자주 활용되는 tidyverse 계열의 dplyr패키지보다 의존성이 훨씬 낮습니다. 그렇기 때문에 번거롭게 하나의 패키지를 사용하기 위해 다른 패키지들을 설치해줄 필요가 없습니다.

    dplyr vs data.table 패키지 의존성 비교

2. data.table 함수

data.table 패키지에는 data.table에서 사용할 수 있는 고유의 함수들이 존재합니다. 여기서는 우선 앞으로 사용할 데이터를 불러오고 설정하기 위한 함수들을 우선 배워봅시다.

  • fread(): f(ast) + read의 의미입니다. 말그대로 빠르게 데이터를 불러오는 것(read)을 의미합니다. .csv, .txt 등의 확장자 이름을 가진 파일들을 불러올 수 있습니다.
require(NHANES)
dt <- as.data.table(NHANES)

fread()를 통해 불러온 파일의 classdata.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.framedata.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 인 행들만 추출하는 것입니다.

dt[age>=30] |> head()
dt[gender=='male' & age>=45] |> head() # 남성이고 45세 이상
dt[gender=='female' | age>=50] |> head() #여성 또는 50세 이상

이처럼 &|를 붙여주면, 여러 조건을 사용하여 원하는 행들을 추출할 수 있습니다.

행을 다룰 때 data.table과 data.frame의 차이점

data.frame과 달리 data.table 문법에서는, 대괄호 안에서 column을 df$var 양식으로 사용하지 않아도 됩니다. 그냥 column의 이름만 사용하면 됩니다.

또한 data.frame에서는 행 부분을 입력할 때, 꼭 , 를 붙여줘야 했습니다. 그러나 data.table의 경우 행만 filtering 할 때, 굳이 ,을 붙여줄 필요가 없습니다.

어떤가요? data.table이 훨씬 더 간단하죠? 😃

2) Infix 연산자를 이용한 row filtering

data.table에서는 논리 연산자 뿐만 아니라 infix 연산자를 이용하여, 조건을 충족시키는 행을 선택할 수 있습니다. 파이프 연산자 역시 조건을 만족하는 경우인 TRUE 에 해당하는 값들만 선택합니다.

Note

infix 연산자는 함수 피연산자의 양쪽에 있는 인수에 계산을 적용하는 기능을 제공합니다.

  • A %in% B: A가 B 안에 있는지 확인합니다. 이 때 B는 vector가 옵니다. 문자를 확인하는 경우에는 %chin% 을 이용해 더욱 빠르게 계산할 수 있습니다.

    dt[race %in% c('Black','White')] |> head()
  • A %like% B: A가 B와 비슷한지 확인합니다. 이 때 B에는 보통 문자열(character)이 옵니다.

    dt[MaritalStatus %like% 'Married'] |> head()
  • A %between% B: A가 B 사이에 있는지 확인합니다. 이 때 B는 c(0,10)과 같은 범위로 지정합니다.

    dt[BMI %between% c(20,25)] |> head()
    Tip

    위에서 소개해드린 %%을 이용한 infix 연산자 말고도 data.table 패키지 내의 함수를 이용할 수도 있습니다.

    %between%between()

    %like%like()

    # 예시
    dt[like(race,'ite')]

3) 함수를 이용한 row filtering

논리 연산자나 infix 연산자 뿐만 아니라 TRUE/FALSE를 반환하는 다른 함수들도 행을 선택하는 데 활용할 수 있습니다. 가장 대표적인 함수가 바로 is.na() 입니다.

dt[is.na(Testosterone)] |> head()

NA가 아닌 데이터를 출력하는 방법은 !is.na() 입니다. Chapter 2의 논리 연산자 부분에서 NOT을 의미하는 기호는 !라고 배웠습니다.

dt[!is.na(SleepHrsNight)] |> head()

4) 행의 정렬

행의 정렬 역시 i 부분에서 담당합니다. 특정한 column을 기준으로 데이터를 오름차순 또는 내림차순 정렬을 할 때 사용할 수 있습니다.

data.table에서 특정 column을 기준으로 데이터를 정렬하는 방법은 두 가지가 있습니다.

  • order()

    order()는 정렬한 값의 출력만 합니다.

    dt[order(BMI)] |> head()

    만약 내림차순으로 정렬하고 싶은 경우는 변수 앞에 -를 붙여주면 됩니다.

    dt[order(-BMI)] |> head()
  • setorder()

    setorder()는 데이터를 정렬하여 data.table에 저장합니다. order와는 다르게 정렬된 결과를 출력하지는 않습니다.

    setorder(dt, # 정렬할 데이터
             age # 기준이 되는 변수
             )
set이 들어가는 함수

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().()은 동일한 기능입니다. 동일한 기능이라면 더 적은 코드를 입력하는 후자가 더 낫겠죠?

dt[,.(gender,age)] |> head()

문자 vector를 이용해서도 원하는 column을 선택할 수 있습니다. 이 때는 ..기호를 이용해야 합니다.

target <- c('gender','age','race')
dt[,..target] |> head()

2) column 계산

column을 계산하는 것 역시 data.tablej 부분에서 담당합니다. 예를 들어 특정 column의 평균을 계산하거나 표준편차를 계산하는 경우가 있겠죠. 만약 하나의 column만 계산하는 경우 data.table[] 안에서 함수를 이용하면 됩니다.

dt[,mean(BMI,na.rm=T)] |> head()
[1] 26.66014

만약 여러 개의 column에 대해 계산하는 경우 또는 계산하는 값을 data.table 형태로 출력하고 싶은 경우, column 선택에서 배웠던 .()를 활용하면 됩니다.

dt[,.(mean_BMI = mean(BMI, na.rm=T),
      sd_BMI = sd(BMI,na.rm=T))] |> head()

3) column 생성 및 변경 :=

data.table에는 특수한 기호가 있습니다. 바로 := 입니다. walrus (바다코끼리) 연산자라고도 불리기도 합니다. 바다코끼리의 어금니를 닮아서 붙여진 이름 같습니다.

바다코끼리(Walrus)의 어금니가 := 와 닮았습니다.

data.table을 사용한다면 := 연산자를 활용할 줄 아는 것이 굉장히 중요합니다. 새로운 column을 추가하거나, 기존의 column을 변경할 때, 또는 column을 삭제할 때 활용되는 연산자 입니다.

a. column의 생성

새로운 column을 생성하기 위해서는 dt[,column_name := value] 형식으로 코드를 작성합니다. :=의 왼쪽에는 새로운 column의 이름이, 오른쪽에는 데이터를 입력합니다.

dt[,age_group := paste0((age %/% 10) * 10,'대')]
dt[,table(age_group)] |> head()
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에는 새롭게 넣어줄 데이터를 입력합니다.

dt[,gender := ifelse(gender=='male','m','f')]

위의 코드처럼 :=를 이용해 male, female로 되어있는 Gender 를 각각 mf로 변경할 수 있습니다.

한편 여러 개의 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은 생성될 수 없습니다. 따라서,

dt[,A2:=age][,A3:=A2][] |> head()

위의 코드처럼 []을 여러 번 붙여 순차적으로 column을 생성 또는 변경해주어야 합니다. 이를 chaining이라고 합니다.

c. column의 삭제

특정 데이터의 column을 삭제할 때는 dt[,column_name := NULL] 형식을 사용합니다. 삭제하려는 하는 column_name만 선택하면 되겠죠.

dt[,column_name := NULL] # 삭제대상 열 이름 := NULL

:= 연산자를 활용한 코드를 실행하게 되면 해당 데이터가 저장이 될 뿐, 출력이 되진 않습니다 (set함수와 같은 역할). 변경한 column을 확인하기 위해선 대괄호를 한 번 더 붙여줍니다.

dt[,column_name := NULL][] |> head()

5. by: 그룹 별 분석

by: 특정 column에 따라 j 를 계산합니다. 예를 들어 성별에 따른 나이의 평균을 구하는 경우, 따라서 by를 사용하기 위해서는 j 부분이 존재해야 합니다.

dt[,mean(age),by=MaritalStatus]

keyby: by와 마찬가지로 특정 그룹에 따라 계산합니다. 하지만 by와 다르게 그룹으로 선택된 column을 기준으로 정렬하여 결과값을 출력합니다.

dt[,mean(age),keyby=MaritalStatus]

혼인상태(MaritalStatus)에 따른 나이(age)의 평균 값이 혼인상태의 이름으로 오름차순 정렬이 되어 출력된 것을 확인할 수 있습니다.


6. 이외 유용한 data.table 전용 함수

  • fifelse(): fast ifelse입니다. ifelse()보다 더 빠르게 작업을 수행합니다.기본 ifelse()와 다르게 yes와 no 자리에 오는 인자들의 유형(type)이 반드시 동일해야 합니다.

    dt[,SEX := fifelse(gender=='m','남성','여성')]
    dt[,Temp := fifelse(age<10,NA,age)] # error 발생.
  • fcase() : fast case when입니다. ifelse()처럼 조건을 사용해 데이터를 조작하지만, 반복적으로 ifelse() 함수를 계속해서 사용할 필요가 없기 때문에 더 적은 코드를 사용합니다.

    dt[,age_group2 := fcase(
      age<20,'10대',
      age<30,'20대',
      age<40,'30대',
      age<50,'40대',
      age<60,'50대'
    )]
    dt[,table(age_group2, useNA = 'always')]
    age_group2
    10대 20대 30대 40대 50대 <NA> 
    2765 1356 1338 1398 1304 1839 

    조건에 해당되지 않는 나머지 값들은 자동으로 NA 처리 됩니다. 나머지 값들에 값을 주고 싶다면 default 인자를 활용하면 됩니다.

    dt[,age_group2 := fcase(
      age<20,'10대',
      age<30,'20대',
      age<40,'30대',
      age<50,'40대',
      age<60,'50대',
      default='60대 이상'
    )]
    dt[,table(age_group2, useNA = 'always')]
    age_group2
         10대      20대      30대      40대      50대 60대 이상      <NA> 
         2765      1356      1338      1398      1304      1839         0 
  • uniqueN() : 고유한 데이터의 수를 확인하는 데 사용하는 함수입니다. 주로 사람의 ID 수를 카운트 하는 데 사용합니다.

    dt[,BMI2:=fifelse(is.na(BMI),'donno', fifelse(BMI>=30,'obese','normal'))][,uniqueN(BMI2)] |> head()
    [1] 3
Back to top