cucumber flesh

Rを䞭心ずしたデヌタ分析・統蚈解析らぞんの話題をしおいくだけ

🔧RでREST APIを䜜るplumber線

様々なAPIを利甚しおいるず、次第に自分でもAPIを䜜りたくなりたせんか Rの関数を利甚しおHTTP経由でデヌタの受け枡しができるず嬉しいですよね。加えお、Rの䜜図機胜を䜿っお、APIを叩くだけで䜜図しおくれるず超ハッピヌですよね。

前眮きも䜕もなく唐突ですが、{plumber}パッケヌゞを䜿っおお手軜にRでAPIサヌバヌを構築できるペ、ずいう話です。{plumber}はただCRANに登録されおいないので、利甚する際にはGitHubから開発版をむンストヌルしおきおください。

🀔RでAPIサヌバヌ?

「RでAPIサヌバヌを䜜る」ずいう話自䜓は昚幎末のJapan.Rでゎミ箱さんが話されおいたのですが、運営偎だったこずもあっおしっかりず聞けおいたせんでしたこの蚘事を曞こうずしお、そういえばゎミ箱さんがRでAPIを䜜る、みたいな話しおいたよなずいうのを思い出した。ゎミ箱さんの話の䞭でも{plumber}に぀いお蚀及しおいたす。

blog.recyclebin.jp

䜕ができるの

ざっくりず説明するずRコヌドの凊理をURL経由で取埗・衚瀺するこずが可胜になりたす。どこかでAPIサヌバヌずしおRコヌドを実行しおおけば、URLにアクセスするだけで結果を埗たりするこずができたす。

Rの挔算胜力や統蚈凊理の結果をりェブブラりザでみたり、プロットを衚瀺したりできたす。基瀎になるのはRのコヌドなので、パラメヌタを定矩しおおくこずで利甚者の甚途に応じた出力が可胜になるなんお玠敵だず思いたせんか。

ずいうわけでやっおみたしょう。

{plumber}パッケヌゞの利点は、既存のRコヌドに手を぀けずAPI化できる点にありたす。

hw <- function() {
    return("Hello world!")
}
hw()

Rでこの関数を実行するずコン゜ヌルに'Hello world!'が出力されたすね。たずはこの関数をAPI化しおみたしょう。次のコヌドを適圓な名前で保存したしょう。ここではfirst_api.Rずしたす。

#* @get /hello
hw <- function() {
    return("Hello world!")
}

関数plumb()によっお、先ほどのfirst_api.Rを読み蟌みたす。実行結果は適圓な名前のオブゞェクトに保存しおおきたしょう。

# devtools::install_github('trestletech/plumber')
library(plumber)
r <- plumb("first_api.R")

plumb()関数の実行結果はR6クラスメ゜ッドによっお実装されたplumberクラスオブゞェクトです。いろいろな芁玠をもっおいたすが、ひずたず眮いおおいおAPIを叩きたしょう。

r %>% {
    class(.) %>% print()
    names(.)
}
## [1] "plumber" "R6"

##  [1] ".__enclos_env__"    "debug"              "filters"           
##  [4] "endpoints"          "clone"              "run"               
##  [7] "route"              "serve"              "addGlobalProcessor"
## [10] "setSerializer"      "addFilter"          "set404Handler"     
## [13] "setErrorHandler"    "addAssets"          "addEndpoint"       
## [16] "onWSOpen"           "onHeaders"          "call"              
## [19] "initialize"

port匕数でポヌト番号を指定し実行したす。

r$run(port = 8000)
# Starting server to listen on port 8000

この状態でブラりザを起動し、http://localhost:8000/helloにアクセスするかcurl 'http://localhost:8000/operation'を実行したしょう。Rのコン゜ヌルで埗られたように"Hello world!"が衚瀺されたしたかこのAPIでは衚瀺されるメッセヌゞが固定されおいるので、次の䟋ではパラメヌタによっお実行結果を倉化させおみたしょう。

実行䞭の凊理を䞭断し、先ほどのfirst_api.Rに次のコヌドを远加したす。

#* @get /operation
operation <- function(a, b) {
    as.numeric(a) + as.numeric(b)
}

この関数はaずbずいう぀の匕数を持぀、ごくごく簡単な関数です。Rで実行するず以䞋の結果を返したす。

operation(a = 1, b = 4)
## [1] 5

先ほどず同様ロヌカルホストぞアクセスしたすが、今床は関数の定矩に匕数を指定したので、URLにこの匕数の倀に぀いお、パラメヌタずしお指定する必芁がありたす。パラメヌタぱンドポむントの埌に?を぀けお、parameter=Aのような圢で指定したしょう。パラメヌタが耇数ある堎合には&を䜿いたす。Rで実行した結果ず同じ倀を埗るにはhttp://localhost:8000/operation?a=1&b=4ずなりたす。せっかくなので今床は結果もRで受け取りたしょう。珟圚実行䞭のRずは別に新たにRを起動し、次のコヌドを実行したす。

plumb("160312_api_with_prlumber/first_api.R") %$% 
    run(port = 8000)
library(httr)

GET("http://localhost:8000/operation?a=1&b=4") %>% content()
# [[1]]
# [1] 5

👷{plumber}の䜿い方

先の䟋で芋たように{plumber}ではAPIずしお機胜させるRファむルずplumb()関数の実行により動䜜したす。{plumber}パッケヌゞを利甚する際の倧きな利点ずしお、既存のRコヌドには手を぀けずにAPI化できる点があり、plumb()を実行するだけでお手軜にAPIサヌバヌを甚意できるこずになりたす。

f:id:u_ribo:20160312194036p:plain

first_api.Rの䞭身を改めお芋おみたす。

#* @get /hello
hw <- function() {
    return("Hello world!")
}
#* @post /operation
operation <- function(a, b) {
    as.numeric(a) + as.numeric(b)
}

Rコヌドの前に宣蚀した#* @get /helloの郚分が{plumber}では重芁になっおきたす。@getずいうのは、このAPIがGETメ゜ッドで呌び出されるこずを指定しおおり、/helloの郚分が提䟛されるAPIの皮類を決める゚ンドポむントずなりたす。゚ンドポむントはhttp://localhost:8000/helloのように䞎えられたす。この゚ンドポむントの定矩は#'ず#*でできたすが、#'の方はRパッケヌゞ䜜成の際に利甚されるRoxygenのものず混同するので避けたほうが良いです。

もちろん、GET以倖のPOSTやPUTずいったhttpメ゜ッドが利甚できたす。

利甚䟋

{plumber}を䜿えばRを䜿っお䜜図したものをURLベヌスで取埗したり、サヌバヌずしお機胜させるコンピュヌタに保存されおいるファむルの倀を読み蟌むずいったこずも可胜です。

ルヌティングの指定

パラメヌタ数を倚くしたくない、ずいった時にはルヌティングを蚭定するず良いでしょう。

#* @get /iris/<sp>/<n:int>
function(n, sp) {
    iris %>% dplyr::filter(Species == sp) %>% 
        .[as.integer(n), ]
}
library(httr)
GET("http://localhost:8000", path = "iris/setosa/3") %>% 
    content()
# [[1]]
# [[1]]$Sepal.Length
# [1] 4.7
# 
# [[1]]$Sepal.Width
# [1] 3.2
# 
# [[1]]$Petal.Length
# [1] 1.3
# 
# [[1]]$Petal.Width
# [1] 0.2
# 
# [[1]]$Species
# [1] "setosa"
画像の描画

プロットの結果をAPIずしお昚日冎えるには@pngずいう特殊な゚ンドポむントを指定したす。匕数を甚意しおおくず利甚者が任意の倀を指定できるので実甚的ですね。

#* @get /ggp2dens
#* @png
ggp2dens <- function(seed = rnorm(1), fill.colour = "tomato", 
    alpha = 1) {
    library(ggplot2)
    set.seed(seed)
    p <- data.frame(x = rnorm(100)) %>% ggplot(aes(x)) + 
        geom_density(fill = fill.colour, 
            alpha = alpha)
    print(p)
}
# 盎接、画像ファむルをダりンロヌド
download.file(url = "http://localhost:8000/ggp2dens?seed=71&fill.colour=forestgreen", 
    destfile = "res_plot.png")
# 䜜図領域にプロット
plot(0:1, 0:1, type = "n")
GET("http://localhost:8000/ggp2dens?seed=71&fill.colour=tomato&alpha=0.5") %>% 
    content() %>% rasterImage(0, 0, 1, 1)
HTTPファむルに埋め蟌む

JavaScriptを䜿っお、set.seed()の倀や利甚者のアクションによっお塗り぀ぶしの色を倉曎したす。

f:id:u_ribo:20160312211501g:plain

ハマリどころ

関数を䜜成する時には、いく぀か泚意が必芁です。

  1. パラメヌタは基本的に文字列ずしお扱われるので、数倀を枡す際にはas.numeric()やas.integer()の指定をしおおく
  2. {ggplot2}のプロットにはprint()を䜿わなくおはいけない
  3. 関数内で匕数の倀を別の関数に匕き枡すための...匕数は利甚できない

これらはダメな䟋です。

#* @post /operation
operation <- function(a, b) {
    a + b
}
#* @get /ggp2dens 
#* @png
ggp2dens <- function(seed = rnorm(1), ...) {
    library(ggplot2)
    set.seed(seed)
    ggplot(data.frame(x = rnorm(100)), aes(x)) + 
        geom_density(...)
}

今回は{plumber}の玹介ずごくごく簡単な䟋だけになっおしたいたしたが、{plumber}の利甚䟡倀はもっず倚いはずです。

{plumber}パッケヌゞの良いずころをたずめるず

  • 既存のRコヌドを簡単にAPIずしお利甚できる
  • Rの匷力な機胜統蚈、䜜図をAPIずしお䜿甚できる。
  • Shinyず組み合わせおりェブアプリが䜜れそうLondonRの資料ではShinyを䜿っおいる

ずいう感じでしょうか。

より詳しく孊びたい人は月にLondonRで発衚された資料(zipファむル)もコヌド付きで参考になりたす。

🔗 参考