R 데이터 매니지먼트: tidyverse

파일을 읽는 readr, 읽기 쉬운 코드를 만드는 %>% 연산자, 데이터를 다루는 dplyr 그리고 반복문을 다루는 purrr 패키지를 중심으로 tidyverse 생태계에서 데이터를 다루는 방법을 정리하였습니다.

Jinseob Kim https://github.com/jinseob2kim (ANPANMAN Co.,Ltd)https://www.anpanman.co.kr
01-23-2019

Table of Contents


김진섭 대표는 1월 28일(월) 성균관의대 사회의학교실를 방문, tidyverse 생태계에서의 데이터 매니지먼트 방법을 강의할 예정입니다. 강의 내용을 미리 정리하였습니다.

시작하기 전에

R 데이터 매니지먼트 방법은 크게 3 종류가 있다.

  1. 원래의 R 문법을 이용한 방법으로 과거 홈페이지1에 정리했었다.

  2. tidyverse는 직관적인 코드를 작성할 수 있는 점을 장점으로 원래의 R 문법을 빠르게 대체하고 있다.

  3. data.table 패키지는 빠른 실행속도를 장점으로 tidyverse 의 득세 속에서 살아남았으며, 역시 과거 홈페이지2에 정리한 바 있다.

본 강의는 이중 두 번째의 기초에 해당하며

  1. readr 패키지의 read_csv 함수로 데이터를 빠르게 읽은 후

  2. magrittr 패키지의 %>% 연산자와 dplyr 패키지의 select, mutate, filter, group_by, summarize 함수로 직관적인 코드를 작성하고

  3. purrr 패키지의 map 함수로 쉽게 반복문을 처리하는 것을 목표로 한다.

각각의 패키지를 따로 설치할 수도 있고 install.packages("tidyverse")tidyverse 생태계의 패키지를 모두 설치할 수도 있다.

데이터 읽기: readr

readr 패키지에서 csv 파일을 읽는 함수는 read_csv이며, 구분자(ex: 공백, 탭)가 다를 때는 read_delim 함수를 이용하여 구분자를 설정할 수 있다. 예제 데이터를 읽어보자.


library(readr)
a <- read_csv("https://raw.githubusercontent.com/jinseob2kim/jinseob2kim.github.io/master/smc_example.csv")

Parsed with column specification:
cols(
  Patient_ID = col_double(),
  Sex = col_character(),
  Age = col_double(),
  Height = col_double(),
  Weight = col_double(),
  BMI = col_double(),
  DM = col_double(),
  HTN = col_double(),
  Smoking = col_double(),
  MACCE = col_double(),
  Death = col_double(),
  MACCE_date = col_double(),
  Death_date = col_double(),
  STRESS_EXIST = col_character(),
  Number_stent = col_double()
)

데이터를 읽으면 각 변수들을 어떤 형태(숫자형, 문자형)로 읽었는지 표현되는데, 바꾸고 싶은 것이 있으면 아래와 같이 col_types 옵션을 이용하면 된다.


## Character: col_character() or "c"
a <- read_csv("https://raw.githubusercontent.com/jinseob2kim/jinseob2kim.github.io/master/smc_example.csv", 
              col_types = cols(Patient_ID = col_character(), HTN = "c"))                                              

이제 데이터를 살펴보면 Patient_ID, HTN 변수가 문자형이 된것을 볼 수 있다.


a

기본 함수인 read.csv 와 비교했을 때 아주 작은 데이터에서는 장점이 없으나, 그 크기가 커질수록 read_csv가 더 좋은 성능을 보임이 알려져 있다(Figure 1).

파일 읽기 함수 비교^[https://csgillespie.github.io/efficientR/5-3-importing-data.html]

Figure 1: 파일 읽기 함수 비교3

read_csv로 읽은 데이터는 tibble이라는 새로운 클래스로 저장된다. 직접 확인해보자.


class(a)

[1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame" 

기존의 data.frame 외에도 tbl_df, tbl 와 같은 것들이 추가되어 있다. tibbledata.frame보다 좀 더 날 것(?)의 정보를 보여주는데, 범주형 변수를 factor가 아닌 character 그대로 저장하고 변수명을 그대로 유지하는 것(data.frame에서 변수명의 특수문자나 공백은 .으로 바뀜)이 가장 큰 특징이다. tibble의 추가 내용은 jumpingrivers 블로그4를, readr의 다른 함수들은 아래의 Data Import Cheat Sheet5 를 참고하기 바란다.

직관적인 코드: %>% 연산자

tidyverse 생태계에서 가장 중요한 것을 하나만 고르라면 magrittr 패키지의 %>% 연산자를 선택하겠다. %>%은 Rstudio에서 단축키 Ctrl + Shift + M (OS X: Cmd + Shift + M)로 입력할 수 있는데, 이것을 이용하면 의식의 흐름대로 코딩을 할 수 있어 직관적인 코드를 작성할 수 있다. head함수를 통해 %>%의 장점을 알아보자.


library(magrittr)

## head(a)
a %>% head

a %>% head(n = 10)

head(a)a %>% head는 같은 명령어 “ahead를 보여줘”로, a %>% head가 생각의 흐름이 반영된 코드임을 알 수 있다. 일반적으로 %>% 연산자는 함수의 맨 처음 인자를 앞으로 빼오는 역할을 하고 f(x, y)x %>% f(y)로 바꿀 수 있다. 맨 처음 인자가 아닐 때에는 y %>% f(x, .)과 같이 앞으로 빼온 변수의 자리에 .를 넣으면 된다. 예를 들면 아래에 나오는 세 명령어는 모두 같다.


a %>% head(n = 10)
10 %>% head(a, .)   
10 %>% head(a, n = .)

%>%의 진가는 여러 함수를 한 번에 사용할 때 나타나는데, headsubset을 동시에 쓰는 경우를 예로 살펴보겠다.


## head(subset(a, Sex == "M"))
a %>% subset(Sex == "M") %>% head

이 명령어는 a에서 남자만 뽑아서 head를 보여줘로 기존의 head(subset(a, Sex == "M"))보다 훨씬 직관적이며 함수가 3개, 4개로 늘어날수록 비교가 안될 정도의 가독성을 보여준다. 남자만 뽑아 회귀분석을 수행하고 그 계수와 p-value를 구하는 예를 살펴보자. 먼저 기존의 코딩 스타일대로 명령을 수행하면 아래와 같다.


b <- subset(a, Sex == "M")
model <- glm(DM ~ Age + Weight + BMI, data = b, family = binomial)
summ.model <- summary(model)
summ.model$coefficients

다음은 %>% 를 이용한 코딩이다.


a %>% 
  subset(Sex == "M") %>% 
  glm(DM ~ Age + Weight + BMI, data = ., family = binomial) %>% 
  summary %>% 
  .$coefficients

%>% 연산자로 쓴 코드는 읽기 쉬운 것은 물론 불필요한 중간변수(b, model, summ.model)가 없어 깔끔하고 메모리의 낭비도 없다. 딱 하나 주의할 점은 코드를 내려쓸 때 각 줄은 반드시 %>%로 끝나야 한다는 점이다. 예를 들어 아래의 코드를 실행하면 맨 윗줄만 실행되는 것을 확인할 수 있다.


a %>% subset(Sex == "M")
  %>% head 

본 강의 후 다른 것은 까먹어도 %>% 만 익숙해지면 성공이라고 생각한다.

데이터 정리: dplyr

dplyr 는 데이터를 효과적으로 다룰 수 있는 일련의 함수들을 제공한다. 이중 group_bysummarize는 쉽게 그룹 별 요약통계량을 보여줌으로서 기존 R 문법과 차별화된 가치를 제공하므로 꼭 익혀두자.

filter

filtersubset 함수와 같은 기능으로 특정 조건으로 데이터를 필터링하는 데 이용된다. 아래는 데이터에서 남자만 추출하는 예시이다.


library(dplyr)
a %>% filter(Sex == "M") 

filter에서는 & 외에 ,으로도 AND 조건을 쓸 수 있어 가독성이 좋다. between 함수를 이용하면 연속변수의 특정 범위를 선택할 수도 있는데, 이것 역시 기존의 &를 활용하는 것보다 직관적이다. 50~60세 사이를 필터링하는 예시를 살펴보자.


## Age between 50 and 60.
a %>% filter(between(Age, 50, 60))

아래의 &,로 표현한 조건도 between을 이용한 것과 같은 결과를 보여준다.


a %>% filter(Age >= 50 & Age <= 60)
a %>% filter(Age >= 50, Age <= 60)

arrange: 정렬

arrange는 특정 순서에 따라 데이터를 정렬하는 함수로, 정렬 순서만 알려주는 order 함수와는 달리 정렬된 데이터를 보여주는 것이 특징이다.


## a[order(a$Age), ]
a %>% arrange(Age)

정렬 조건이 2개 이상이면 ,로 같이 적을 수 있으며 내림차순 정렬은 desc 명령어를 이용한다. 아래는 Age에 대해 오름차순, BMI에 대해 내림차순 정렬을 수행하는 예시이다.


## a[order(a$Age, -a$BMI), ]
a %>% arrange(Age, desc(BMI))

조건에 Age 대신 "Age"와 같이 문자열을 넣을 때는 언더바(_)가 붙은 arrange_ 함수를 이용하며, 이것은 나머지 함수들에서도 마찬가지이다.


a %>% arrange_("Age")

select: 변수 선택

select는 데이터에서 특정 변수들을 선택하는 함수로 기본 R 에는 없는 유용한 기능들을 제공한다.


## a[, c("Sex", "Age")]
a %>% select(Sex, Age, Height)

변수명 대신에 열 번호를 넣어도 되며 Sex:Height을 이용해서 SexHeight 사이의 모든 변수를 선택할 수도 있다. arrange 함수와는 달리 "Sex"와 같은 문자열도 그대로 입력 가능하며, 아래의 방법들은 모두 a %>% select(Sex, Age, Height)와 같은 코드이다.


a %>% select(Sex:Height)
a %>% select("Sex":"Height")
a %>% select(2, 3, 4)
a %>% select(c(2, 3, 4))
a %>% select(2:4)

특정 변수들을 빼려면 -Sex-(Sex:Height)와 같이 적으면 된다.


## a[, -c("Sex", "Age", "Height")]
a %>% select(-Sex, -Age, -Height)

아래의 코드도 a %>% select(-Sex, -Age, -Height)와 같은 결과를 준다.


## a[, -c("Sex", "Age", "Height")]
a %>% select(-2, -3, -4)
a %>% select(-(2:4))
a %>% select(-c(2, 3, 4))

a %>% select(-(Sex:Height))
a %>% select(-"Sex", -"Age", -"Height")
a %>% select(-("Sex":"Height"))

만약 MACCE_date, Death_date와 같이 _date로 끝나는 변수들을 선택하고 싶다면 end_with 함수를 이용하면 된다.


## a[, grep("_date", names(a))]
a %>% select(ends_with("date"))

이외에 select와 함께 쓸 수 있는 유용한 함수들을 정리하면 아래와 같다.

mutate: 변수 생성

mutate는 새로운 변수를 만드는 함수이다. AgeBMI 변수에서 고령과 비만을 뜻하는 Old, Overweight 변수를 만들어 보자.


## a$old <- as.integer(a$Age >= 65); a$overweight <- as.integer(a$BMI >= 27)
a %>% mutate(Old = as.integer(Age >= 65),
             Overweight = as.integer(BMI >= 27)
             )