data.table 패키지와 국민건강정보DB 01

fastverse로 갈아타기
R
Author

Sungjun Park

Published

2026-03-12

개요

R이 언어라면 Tidyverse는 미국말. 사실상 표준이라고 봐도 무방하다. 그러나 대용량 데이터를 다루기엔 한계가 있다. 국민건강정보DB는 용량이 매우 크고 자료가 방대하여 Fastverse 사용이 필수적이다.

꼭 국민건강정보DB가 아니더라도, 10개년을 넘어가는 패널데이터만 해도 확연히 차이가 있다. 궁극적으로 데이터 분석을 위해서는 코드의 효율까지 생각해야 한다.

두 장점을 각각 섞은 tidytable 패키지가 있긴 한데, 이건 논외로 하자.

앞으로 기본적인건 넘어가고, 실무에서 막혀서 선뜻 생각나지 않았던 포인트만 기록한다.

library(data.table); library(fst); library(magrittr)
공단에서 제공하는 실습데이터 SAS 파일을 .csv 파일로 변환하자
library(haven)

sas_list <- list.files(
  "../../../rawdata/knhis", 
  pattern = "\\.sas7bdat$", 
  full.names = TRUE
)

lapply(sas_list, function(file) {
  temp_data <- read_sas(file)
  save_path <- gsub("\\.sas7bdat$", ".csv", file)
  fwrite(temp_data, save_path)
})

.csv 파일 읽어오기

.fst 파일이 속도와 효율면에서는 압도적이긴 한데, 우선 가장 범용적인 .csv를 사용해보자. fst는 fst 패키지만의 전용 확장자다.

basename()은 긴 파일 경로에서 디렉토리 부분은 떼어내고, 오직 파일명(또는 가장 하위 폴더명)만 추출할 때 사용한다. 이걸 활용해서 lapply()로 가져온 data_list 리스트에 이름을 붙여주자.

csv_list <- list.files(
  "../../../rawdata/knhis", 
  pattern = "\\.csv", 
  full.names = TRUE
)

data_list <- lapply(csv_list, fread)
names(data_list) <- basename(csv_list) %>% substr(., 1, nchar(.) - 4)

이중 2008-2012년 상병내역 T40을 하나로 합쳐 저장하자:

  • base 함수로 `rbind()``가 있고,
  • dplyr 함수로 bind_rows()가 있지만,
  • data.table 함수 rbindlist()를 사용하자.

각각 장단점이 있지만, rbindlist()가 수십배 빠르고 메모리효율도 압도적이다. use.names 옵션으로 같은 열끼리 맞춰주고, fill 옵션으로 혹 빈컬럼이 있다면 NA로 채워주자.

idcol이라는 옵션을 켜면 합쳐진 데이터셋에 “source”라는 컬럼이 생기고, 셀값으로 t40_2022 등 리스트 이름이 자동으로 채워진다. 유용한 기능이지만 실무에선 용량과 효율 문제로 사용하지 않는다.

t40 <- data_list[grep("t40_", names(data_list))] %>% 
  rbindlist(
    use.names = TRUE, 
    fill = TRUE
    # idcol = "source"
  )

잘 합쳐졌다. 속도도 매우 빠르다.

              KEY_SEQ SEQ_NO RECU_FR_DT DSBJT_CD SICK_SYM
                <i64>  <int>      <int>    <int>   <char>
      1: 200800000148      1   20081222        1     J303
      2: 200800000148      2   20081222        1     J060
      3: 200800000149      1   20081229        1     J060
      4: 200800000149      2   20081229        1     J303
      5: 200800000329      1   20081224       11     J219
     ---                                                 
1571230: 201227849358      1   20120309        3     F410
1571231: 201227849358      2   20120309        3     F510
1571232: 201227849917      1   20121026        1     B370
1571233: 201227849917      2   20121026        1     K291
1571234: 201227849917      3   20121026       14     L509

특정 조건에 맞는 행 제거하기

방법은 무궁무진하지만, 국민건강정보DB 분석은 코드의 효율도 고려해야 한다.

먼저 제외조건을 기록한 데이터를 만들어보자. 최종 목표는 앞서 만든 2008-2012년 상병내역 자료에서 2008년에 당뇨, 고혈압으로 진료받은 기록이 있는 환자를 제외하는 것. 여기서는 주상병, 부상병만 확인하자.

exclude <- data.table(
  SICK_SYM_EXC = c(
    "E11", "E12", "E13", "E14", # 당뇨
    "I10", "I11", "I12", "I13", "I15" # 고혈압
  )
)

이제 제외할 사람들의 목록(ID)을 만들자.

t20_2008_exc <- data_list$t20_2008 %>% 
  .[, .(PERSON_ID, MAIN_SICK, SUB_SICK)] %>% 
  .[substr(MAIN_SICK, 1, 3) %in% exclude$SICK_SYM_EXC] %>% 
  .[substr(SUB_SICK, 1, 3) %in% exclude$SICK_SYM_EXC] %>% 
  .[, .(PERSON_ID)] %>% 
  unique()
     PERSON_ID
         <int>
  1:  66536499
  2:  65507926
  3:  15707162
  4:  68569103
  5:  50583213
 ---          
225:  80692240
226:  33295034
227:  64399261
228:  71009305
229:  70543918

남은 일은 다음과 같다:

  • 전체기간 T20에서 PERSON_ID와 KEY_SEQ만 불러온뒤,
  • 위의 PERSON_ID에 해당하는 행을 제거한뒤,
  • 여기에 T40을 left join 하는 것
t2040_fin <- data_list[grep("t20_", names(data_list))] %>% 
  rbindlist(use.names = TRUE, fill = TRUE) %>% 
  .[, .(PERSON_ID, KEY_SEQ)] %>% 
  .[!t20_2008_exc, on = .(PERSON_ID)] %>% 
  unique() %>% 
  .[t40, on = .(KEY_SEQ), nomatch = NULL]

잘 됐나 확인해보자. 참고로 T40의 전체 행수는 1571234.

         PERSON_ID      KEY_SEQ SEQ_NO RECU_FR_DT DSBJT_CD SICK_SYM
             <int>        <i64>  <int>      <int>    <int>   <char>
      1:  89450360 200800000148      1   20081222        1     J303
      2:  89450360 200800000148      2   20081222        1     J060
      3:  89450360 200800000149      1   20081229        1     J060
      4:  89450360 200800000149      2   20081229        1     J303
      5:  74148596 200800000329      1   20081224       11     J219
     ---                                                           
1456471:  19747621 201227849358      1   20120309        3     F410
1456472:  19747621 201227849358      2   20120309        3     F510
1456473:  12739949 201227849917      1   20121026        1     B370
1456474:  12739949 201227849917      2   20121026        1     K291
1456475:  12739949 201227849917      3   20121026       14     L509

마무리

여전히 data.table의 join 방식이 헷갈린다.