R 을 더욱 효율적으로 사용하는 방법 3가지



프로그래밍에서의 효율성 


프로그래밍에서의 효율성이란 아래와 같이 두 가지로 나누어볼 수 있다. 



- Algorithmic efficiency

: 컴퓨터가 어떠한 일을 얼마나 빠르게 수행할 수 있을지에 관한 것


- Programmer productivity

: 단위 시간동안 사람이 할 수 있는 일의 양 


부연 설명을 하자면, 만약 어떠한 코드를 R 이 아니라 C로 구현을 하면 100배 빠르다고 하자, 하지만 그 코드를 짜는데 100배 넘는 시간이 들고, 그것이 여러번 사용되지 않고 딱 한 번 사용되고 버려지는 코드라면 그 일은 아무런 의미가 없게 된다.



R 의 특수성


우선 효율적인 R 프로그래밍을 알아보기 전에 R 언어의 특징을 알아보자. 


- R 언어는 무언가를 해결하기 위한 다양한 방법을 제공한다. 

그것은 장점이 되기도 하고 단점이 되기도 한다. 단점이란 명확한 '정답' 이 없어 헤멜 수 있다는 것이고, 장점은 다양한 접근법을 시도할 수 있기 때문에 개인의 창의성이 발휘될 수 있다는 것이다. R 에서는 모든것이 생성된 후에 변형될 수 있다라는 말처럼 (“anything can be modified after it is created” - 해들리 위컴) R 은 매우 유연한 언어이다. 


- R은 컴파일 언어가 아니다. 하지만 컴파일된 코드를 call 할 수 있다. 

따라서 코드를 컴파일링하는 작업을 하지 않아도 된다는 장점이 있다. 한 편, 최대한 C나 FORTRAN 으로 컴파일된 코드를 call 하면 그만큼 빠른 속도의 증가를 불러올 수 있다. 또한 R 은 함수형 언어이며, 객체지향언어이기 때문에 같은 작업을 하는 code 를 짧게 쓸 수 있다는 특징이 있다.



Algorithmic efficiency in R


R 은 컴파일된 코드를 call 할 수 있기 때문에 R 의 Algorithmic efficiency 를 향상시키기 위한  Golden-rule  C/Fortran 에 최대한 빠르게 접근하는 것이다. 그걸 어떻게 하는지에 관한 것은 Efficient R Programming 에서 자세히 다루고 있다. 본 포스팅에서는 Programmer efficiency 에 대해서 간단하게 다루어 보려고 한다. 



Programmer efficiency in R


R에는 하나의 문제를 해결하기 위한 다양한 해결법이 있다. 예를 들어, data frame 의 subsetting 에서도 data$x 도 쓸 수 있고 data[['x']] 도 쓸 수 있다. 하지만 '결과를 중시하는 코딩' 을 하는 경우, 어떠한 문제를 해결할 '가장 효율적인 방법' 이 무엇인지 알기만 하면 된다. 본 포스팅에서는 이 관점에서 어떻게하면 R 을 통해 결과를 빠르고 생산성 있게 작성할 수 있는지에 대해 간단하게 정리해보려고 한다. 



1. Conding convention 을 사용해보자 


Good coding style is like using correct punctuation. You can manage without

it, but it sure makes things easier to read. - Hadley Wickham


위 말처럼 좋은 코딩 스타일을 갖는 것은 구두점을 정확하게 사용하는 것과 같다. 구두점이 없더라도 문장을 읽을 수는 있지만 구두점이 있다면 문장을 더 쉽게 읽을 수 있게 된다. 특히 일관적인 코딩 스타일은 협업을 할 때 중요하다. 협업을 하지 않고, 혼자 작업을 하더라도 자신의 코드를 오랜만에 보았을 때, 코딩 스타일이 일관적이라면 더 쉽게 이해할 수 있다. 


R 에는 공식 스타일 가이드가 없다. 하지만 R 커뮤니티는 이미 성숙도가 높기 때문에 유명한 coding convention 이 존재한다. 


Bioconductor’s coding standards (https://bioconductor.org/developers/how-to/coding-style/)

- Hadley Wickham’s style guide (http://stat405.had.co.nz/r-style.html )

- Google’s R style guide (http://google-styleguide.googlecode.com/ svn/trunk/google-r-style.html )

- Colin Gillespie’s R style guide (http://csgillespie.wordpress.com/2010/ 11/23/r-style-guide/)

The State of Naming Conventions in R (by Rasmus Bååth)


참고하면 좋을 자료 

https://www.r-bloggers.com/%F0%9F%96%8A-r-coding-style-guide/ 



2. 반복 작업을 짧고, 간결하게 작성하자


R Apply Family


R 에서의 반복작업을 할 때, Apply 계열의 함수 (Apply family 라고 부르기도 한다.) 를 이용하면 좋다. Apply 계열의 함수는 함수의 인자로 함수를 넣어주는 특징을 갖고 있다. 인자로 들어온 함수를 데이터에 대해 여러번 적용 시켜서 결과를 반환하는 것이다. Apply 계열의 함수를 이용하면 1) 빠르고, 2) 읽기 쉽다. 


1. Apply 계열의 함수는 C 코드로 되어있다. 비록, for 문의 성능이 최근들어 좋아졌다고 하더라도, Apply 계열의 함수가 더 빠르다.


2. '어떻게' 하는지가 아니라 '무엇을' 하는지에 대해서 강조할 수 있다. R 의 목적은, 프로그래밍이 아니라, 데이터를 Interactive 하게 탐구하는 것이다. Apply 계열의 함수를 이용하는 것은 R 언어의 본질적 특징인 Functional Programming 의 원칙에 부합하는 코딩 스타일이라고 할 수 있다. 


이와 관련해서는 이전 포스팅에 정리한적이 있다. 또한 다양한 책과 블로그에서 Apply family 에 관하여 잘 정리가 되어있으니 참고하면 좋다. 또한 이러한 Apply 계열의 함수는 R 고유의 문법이 아니다. Python 에서도 이와 비슷하게 함수를 input 으로 받아들이는 함수들이 있다. Map, Filter, Reduce 가 그것이다. 이에 대해서도 이전 포스팅에 정리한 적이 있다. 따라서 이러한 문법에 익숙해 진다면 다양한 프로그래밍 언어를 사용할 때, 훨씬 생산성을 높일 수 있다. 


Overview

FunctionDescription
applyApply functions over array margins
byApply a function to a data frame split by factors
eapplyApply a function over values in an environment
lapplyApply a function over a list or vector
mapplyApply a function to multiple list or vector arguments
rapplyRecursively apply a function to a list
tapplyApply a function over a ragged array


Purrr 


Tidyverse 의 멤버중 하나인 Purrr 을 이용하면 반복작업을 Apply family 에 비해 더욱 직관적이고 쉽게 할 수 있다.


Purrr 패키지는 고양이의울음소리를 의미하는 purr 과 r을 합친 의미로, 위와 같이 귀여운 로고를 가졌다. 아래는 공식 홈페이지에서 purrr 에 대한 설명이다. 


purrr enhances R’s functional programming (FP) toolkit by providing a complete and consistent set of tools for working with functions and vectors. If you’ve never heard of FP before, the best place to start is the family of map() functions which allow you to replace many for loops with code that is both more succinct and easier to read. The best place to learn about the map() functions is the iteration chapter in R for data science. 


Purrr 에서 가장 기본적인 함수는 map() 이다. map 이 어떻게 사용되는 지를 간단하게 알아보자. 


df 에서 a,b,c,d 의 평균을 출력하는 작업을 해보자. 


mean(df$a)

mean(df$b)

mean(df$c)

mean(df$d)


이를 반복문을 사용하면 아래와 같이 할 수 있다. 


for var in c("a", "b", "c", "d") {

print(mean(var), trim = 0.5)

}


map 함수를 사용하면 아래와 같이 할 수 있다. 


map_dbl(df, mean, trim = 0.5)

#>      a      b      c      d 
#>  0.237 -0.218  0.254 -0.133

두 접근법의 차이는 무엇일까? 


for 문을 사용한 것은 how 에 관한 정보를 포함하고 있지만 map 함수는 what 에 집중한다. 즉, 이 코드에서 하고자하는 것은 평균을 내는 것이다. R 은 그 작업을 "어떻게" 하는지에는 관심이 없다. map 을 사용하면 하고자 하는 작업인 '평균내기' 에 집중한 코드를 작성할 수 있다. 이와 관련해서는 Hadley Wickham 의 강의를 참고하기 바란다. 



3. 효율적인 Data Carpentry 를 하자 


데이터 처리를 일컫는 수많은 말이 있다. clean, hack, manipulation, munge, refine, tidy 등이다. 이 과정 이후에 modeling 과 visualization 을 수행하게 된다. 데이터 처리는 실제 재미있고, 의미 있는 작업을 하기 전에 수행되는 dirty work 로 생각되어지기도 한다. 왜냐하면 시간만 있으면 그것을 할 수 있다는 것이 한 가지 이유일 것이다. 하지만 데이터를 아주 깔끔하게 정돈하고, 원하는 형태를 빠르고 정확하게 만들어내는 것은 필수적인 능력이다.  또한 데이터처리 과정을 추상화해서 재생산 가능한 (reproducible) 코드를 만드는 작업은 단순 노동과는 다른 높은 숙련도를 가져야만 할 수 있는 것이다. 따라서 이러한 사람들의 인식은 틀렸다고 할 수 있다. 또한 미국에는 데이터 처리만 해서 판매하는 회사도 존재하며, 이 회사의 가치는 매우 높다! 이러한 데이터 처리 과정에 대해 Efficient R Programming 의 저자이자 유명한 R 교육자인 Colin Gillespie 와 Robin Lovelace는 Data Carpentry 라는 이름을 붙였다. 


Colin Gillespie 와 Robin Lovelace 가 정리한 효율 적인 Data Carpentry 를 위한 5가지 팁은 아래와 같다. 


1. Time spent preparing your data at the beginning can save hours of frustration in the long run.

2. ‘Tidy data’ provides a concept for organising data and the package tidyr provides some functions for this work.

3. The data_frame class defined by the tibble package makes datasets efficient to print and easy to work with.

4. dplyr provides fast and intuitive data processing functions; data.table has unmatched speed for some data processing applications.

5. The %>% ‘pipe’ operator can help clarify complex data processing workflows.


Data carpentry 는 크게 두 가지로 나눌 수 있다.  


Tidying : Raw 데이터를 "Tidy data" 로 만드는 작업 

Transformation: "Tidy data" 를 원하는 형태로 만드는 작업 (subsetting, adding column, summarizing 등)


대표적으로, Tidying 을 위한 패키지는 tidyr, Transformation 을 위한 패키지는 dplyr, data.table 패키지가 있다. 이 과정에서 Tibble 이라는 데이터 프레임을 상속한 클래스를 사용하면 데이터를 print 하거나, 처리할 때 있어 더욱 효율적이며 magrittr 의 pipe (%>%) 는 더욱 읽기 쉽고 직관적인 코드를 만든다. R 기본 패키지에 비해 dplyr 를 사용했을 때의 장점에 대해서는 이전 포스팅에 정리한 적이 있다. 필자 의견으로는 한 번도 dplyr 를 사용하지 않은 사람은 있어도, 한 번만 dplyr 를 사용한 사람은 없다고 생각될 정도로 dplyr 를 사용했을 때 높은 생산성을 가질 수 있다. 


예를 들어, 특정 행을 subsetting 하는 코드를 작성한다고 해보자. 


flights 라는 data frame 에서 arr_delay 가 120 미만인 행을 골라낸다고 하면, R 기본 subsetting 방법으로 하면 아래와 같다. [] 안에 logical vector 를 넣는 subsetting 방법이다. 문제는, is.na 를 이용해 arr_delay 가 na 인 경우도 고려해야한다는 것이다. 


flights[(flights$arr_delay < 120) & !is.na(flights$arr_delay), ]

하지만 dplyr 를 사용하면, 이러한 기술적인 것과 상관없이 직관적으로 코드를 작성할 수 있다. 


filter(flights, arr_delay < 120