JS and python in R (R-adv week4)

by Minho Lee — on  ,  , 

cover-image

R에서 js, 파이썬 코드 사용하기

라이브러리를 구성할 때, 원하는 기능을 R로 직접 구현할 수 있다면 제일 좋겠지만 현실은 그렇지 않을 때가 많다. 이미 널리 알려진 구현체가 존재할 수도 있고, 다른 언어를 사용해서 성능 향상을 시도해 볼 수도 있다. R 라이브러리를 보면 속도를 끌어올리기 위해 Rcpp을 많이 사용하고, 자바의 풍부한 라이브러리를 활용하기 위해 rJava를 쓰는 경우가 많다. 하지만 여기서는 C++이나 자바 대신 js와 파이썬을 활용하는 방법을 알아보려고 한다.

왜 하필 자바스크립트와 파이썬일까? 라고 하면, 사실 가장 큰 이유는 스터디를 함께하는 구성원들이 많이 사용할 법한 언어라는 점이었다. 데이터 분석과 모델링, 시각화를 주로 공부하다보니 나만해도 거의 R, Python, JS 세 가지 언어만을 활용하고 있다. 아무리 C++ 이나 자바가 필요한 경우가 있다고 한들, 그 언어들을 잘 활용하지 못하는 이상 적어도 지금 단계에서 공부할 내용은 아니라고 생각했다.

어찌됐든, 자바스크립트와 파이썬이 널리 쓰이고 있는 것은 사실이고, npm과 pypi라는 거인의 어깨에 올라타는 것은 R 패키지를 만드는 사람에게도 한 번쯤은 꼭 필요한 과정일 것이다.

참고로 스터디 멤버 전원이 맥북을 사용하고 있기 때문에! 아래의 코드들은 윈도우에서 동작한다는 보장이 없다… (윈도우에서 npm이나 browserify 잘 되나요..????)



Javascript in R : V8

V8은 구글에서 만든 자바스크립트 엔진이다. R의 V8 라이브러리를 설치하면 v8 엔진을 통해 자바스크립트 코드를 직접 동작시킬 수 있다.


Basic

V8::v8() 함수를 사용하면 자바스크리트 코드를 동작시킬 context를 생성한다. context는 브라우저로 치면 탭 하나라고 생각하면 되는데, 특정 context에서 실행되는 코드는 다른 context에 영향을 주지 않는다.

# 예제를 실행하기 전에 먼저 V8 라이브러리를 설치한다
# install.packages('V8')
ct = V8::v8()


자바스크립트 라이브러리를 불러오기 위해서는 컨텍스트 객체의 $source() 메서드를 사용하면 된다. V8 라이브러리는 설치될 때 crossfilterunderscore 두 개의 라이브러리를 포함하고 있다. system.file() 함수를 통해 underscore.js의 위치를 찾고, $source() 메서드에 해당 파일의 주소를 인자로 넣는다.

# V8 패키지가 설치된 위치에 존재하는 js/underscore.js 파일의 full path를 받는다
path_to_underscore = system.file("js/underscore.js", package="V8")
ct$source(path_to_underscore) # 컨텍스트에 라이브러리를 불러온다


v8 컨텍스트 내에 올라가 있는 자바스크립트 함수는 $call() 메서드를 통해 사용할 수 있다. call 메서드 안에 있는 모든 argument를 (첫 번째 인자는 제외하고) json으로 변환하기 때문에 R의 데이터프레임을 인자로 넣더라도 아래와 같이 잘 동작한다. 인자에 js 함수를 넣기 위해서는 V8::JS 함수로 문자열을 감싸주어야 한다.

ct$call("_.filter", mtcars, V8::JS("function(x){return x.mpg < 15}"))
##                      mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Duster 360          14.3   8  360 245 3.21 3.570 15.84  0  0    3    4
## Cadillac Fleetwood  10.4   8  472 205 2.93 5.250 17.98  0  0    3    4
## Lincoln Continental 10.4   8  460 215 3.00 5.424 17.82  0  0    3    4
## Chrysler Imperial   14.7   8  440 230 3.23 5.345 17.42  0  0    3    4
## Camaro Z28          13.3   8  350 245 3.73 3.840 15.41  0  0    3    4


컨텍스트에서 $console() 메서드를 사용하면 R console에서 자바스크립트를 직접 구동해 볼 수 있다.

ct$console()


npm 라이브러리 사용하기

Browserify

node.js 등 환경에서 npm을 통해 내려받은 라이브러리를 불러올 때는 disk에 직접 접근한다. 하지만 V8 엔진에는 해당 기능이 없다. 따라서 npm을 통해 받은 라이브러리 중 파일 단위로 모듈이 쪼개져있는 경우에는 번들러를 통해 하나의 js파일로 통합하여 사용해야 한다. 자바스크립트 파일을 번들링해주는 도구는 여러 가지가 있지만, 여기서는 간단하게 사용할 수 있는 browserify를 써보자! http://browserify.org/


번들링 과정

(0) npm 설치

node.js를 설치하면 npm이 함께 설치된다. ( https://nodejs.org/ko/ )


(1) npm을 통해 d3 다운로드 받는다

글로벌 설치가 아니므로 원하는 폴더로 이동해서 아래 명령어를 실행시킨다. node_modules 폴더 아래에 라이브러리와 관련 디펜던시를 다운받게 된다.

npm install d3


(2) npm에서 글로벌 옵션으로 browserify 설치

-g 옵션을 추가해서 browserify를 글로벌로 설치한다. 혹시 권한 문제로 잘 동작하지 않는다면 앞에 sudo를 추가해서 실행시켜보면 된다.

npm install -g browserify
# 권한 문제로 설치되지 않는다면 sudo npm install -g browserify


(3) d3.color, d3.format을 단일 파일로 번들링

먼저, d3가 설치된 node_module 폴더와 같은 곳에 (node_modules/in.js) 아래와 같이 in.js 파일을 작성한다.

// in.js
global.d3_color = require('d3-color');
global.d3_format = require('d3-format');

그리고 browerify를 통해 bundle.js 파일로 번들링한다. node_modules 폴더에서 다음 명령을 실행한다.

browserify in.js -o bundle.js


(4) V8에서 불러서 사용하는 함수를 만들어보자

컨텍스트에 번들링한 파일을 올려보자. $call() 메서드를 사용해서 제대로 동작하는지 확인해보자.

# bundle.js 파일을 적당한 곳에 옮겨놓거나, 해당 파일의 위치를 직접 입력한다
ct$source('bundle.js')
# $eval 메서드는 문자열 전체를 파싱하여 실행시킨다
# ct$eval("d3_format.format('.1%')(0.1234)")
# ct$eval("d3_color.color('steelblue')")

# $call 메서드는 js 함수를 먼저 받고, R 객체를 받아서 js 함수에 넘겨준다.
ct$call("d3_format.format('.1%')", 0.1234)
## [1] "12.3%"
ct$call('d3_color.color', 'steelblue')
## $r
## [1] 70
## 
## $g
## [1] 130
## 
## $b
## [1] 180
## 
## $opacity
## [1] 1



Python in R : reticulate

Rstudio에서 만든 reticulate는 R 코드 형태로 파이썬을 사용할 수 있게 해주는 라이브러리다. R의 텐서플로우와 케라스가 바로 이 reticulate를 바탕으로 작성되었다.

# install.packages('reticulate')
library('reticulate')

특정 파이썬 가상환경 사용하기

아나콘다를 사용할 경우, reticulate::conda_list() 함수를 실행시켜보면 현재 로컬에 설치된 콘다 가상환경의 종류가 보인다. 특정 환경을 사용할 경우 reticulate::use_condaenv() 함수에 원하는 env name을 입력하면 된다. virtualenv를 사용할 경우에는 use_virtualenv()를 쓰면 되고, 파이썬이 설치된 위치를 직접 입력하려면 use_python을 쓰자.

conda_list()
#       name                                          python
# 1 anaconda             /Users/username/anaconda/bin/python
# 2    env1    /Users/username/anaconda/envs/env1/bin/python
# 3    env2    /Users/username/anaconda/envs/env2/bin/python

# conda env를 사용할 경우
use_condaenv('anaconda', required = TRUE)

# python path를 직접 입력할 경우
use_python('/Users/miika/anaconda/bin/python', required = TRUE)


이대로 잘 따라했는데 이상하게 제대로 동작하지 않는다면 파이썬 환경이 제대로 설정되었는지 확인해보자. py_config()를 실행시켜보고 python 위치가 제대로 잡혀있는지 보면 된다. 만약 첫 줄에서 python: /usr/bin/python 이라고 잡혀있다면 아나콘다나 다른 환경이 아닌 맥 기본 파이썬이 잡혀있는 것이다. 내 경우에는 한 번 Terminate R 해주고 다시 시도하면 잘 동작했다.


파이썬 스크립트 실행하기

이미 파이썬 코드로 작성된 스크립트는 py_run_file() 함수로 실행시킬 수 있다. 아래 파이썬 코드를 작성해서 py_test.py 라고 저장하고 R에서 실행시켜보자.

#### py_test.py ####
from datetime import datetime, timedelta

d = datetime(2018, 1, 1)

date_list = []
for i in range(0, 10):
  target_dt = d + timedelta(days=i)
  date_list.append(target_dt.strftime('%Y-%m-%d'))


스크립트를 실행시킨 객체에서 프로퍼티 형태로 결과를 조회할 수 있다.

py_run_result = py_run_file('py_test.py')
py_run_result$date_list
##  [1] "2018-01-01" "2018-01-02" "2018-01-03" "2018-01-04" "2018-01-05"
##  [6] "2018-01-06" "2018-01-07" "2018-01-08" "2018-01-09" "2018-01-10"


파이썬 라이브러리 불러오기

reticulate를 사용하면 파이썬 라이브러리를 R에서 직접 불러서 사용할 수도 있다. 아래 코드는 numpy를 이용해 np.sum([1,2,3,4]) 코드를 R에서 작성한 결과다.

np = import('numpy')
np$sum(c(1,2,3,4))
## [1] 10


이번에는 Scikit-Learn 라이브러리를 가지고 cars 데이터에 대해 간단한 회귀분석을 시도해보자.

sklearn = import('sklearn', convert = FALSE)

regr = sklearn$linear_model$LinearRegression()
regr$fit(X = array_reshape(cars$speed, dim = c(-1, 1)), 
         y = array_reshape(cars$dist, dim = c(-1, 1)))

print(sprintf('slope: %f', py_to_r(regr$coef_)[1]))
print(sprintf('intercept: %f', py_to_r(regr$intercept_)[1]))
## [1] "slope: 3.932409"
## [1] "intercept: -17.579095"


사실 reticulate는 이전에 다룬 적이 있다. R에서 flask가 돌아가는지 간단하게 확인만 해 본 정도지만, 궁금한 분들은 확인해보시길! http://lumiamitie.github.io/r/flask-on-r/



마무리

사실 이렇게 외부의 디펜던시를 끼워넣게 되면, 다른 모든 사용자에게 동일한 경험을 주기 위해 필요한 작업이 점점 많아지게 된다. V8의 경우에는 패키지를 배포할 때 js 파일을 직접 담아 보내면 되니 V8엔진만 잘 설치되면 된지만 파이썬의 경우에는 기본으로 설치된 파이썬 환경과 충돌하는 경우도 있고 다양한 문제가 생길 수 있다. rJava는 정말이지 잘못 걸리면 극한의 지옥을 맛볼 수도 있다. 하지만 그렇게 고생 끝에 잘 동작하는 라이브러리 하나만 만들어낼 수 있다면, 이 생태계에 기여할 수 있는 패키지 작성자가 된다. 겁먹지 말고 할 수 있는 것부터 차근차근 시도해보자. :)

Comments