読者です 読者をやめる 読者になる 読者になる

まだ厨二病

RとPyhtonを使ったデータ分析・統計解析らへんの話題をしていくだけ

🔧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)
}

この関数はabという2つの引数を持つ、ごくごく簡単な関数です。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を使っている)

という感じでしょうか。

より詳しく学びたい人は1月にLondonRで発表された資料(zipファイル)もコード付きで参考になります。

🔗 参考

広告を非表示にする