【Python】DashでRの出力を呼び出して描画する


背景

Dashで可視化アプリケーションを作りたいが、出力する内容の一部をRのpackageから出力したい場面がありました。

UI(dropdown)はDashで用意して、計算の部分はRで行うような簡単なサンプルを作ってみます。

  • 環境

    • macOSX Mojave version 10.14,3
    • Python 3.6.1
    • R version 3.5.3
    • 実行環境はlocalで
  • 仕様

    • Dropdownで表示するBayesian networkを選んで,Rでinputに応じたネットワークが返されて描写される

方法

PythonからRを実行するとか、やり方は無数にありそうですが、ひとまず以下の流れで1パターン実装してみます。

  • 目次
    • 1. R(plumber)でAPIserverを準備
    • 2. Dashで、inputの内容をクエリパラメータに反映させてhtml.Img()で1.のエンドポイントにGETリクエストを送る
    • 3. できた

実装

ファイル構造は以下のようにします

$ tree
.
├── R
│   ├── run.R
│   └── api.R
├── app
│   └── app.py
└── README.md

1. R(plumber)でAPI serverを準備

GETリクエストのクエリパラメータ?type=に応じて描くBNが変わるようにfunctionを書きます。

api.R

library(plyr)
library(dplyr)
library(bnlearn)
library(plumber)
library(magrittr)

# BNを定義
alarm.model.string <- paste("[HIST|LVF][CVP|LVV][PCWP|LVV][HYP][LVV|HYP:LVF]",
                            "[LVF][STKV|HYP:LVF][ERLO][HRBP|ERLO:HR][HREK|ERCA:HR][ERCA]",
                            "[HRSA|ERCA:HR][ANES][APL][TPR|APL][ECO2|ACO2:VLNG][KINK]",
                            "[MINV|INT:VLNG][FIO2][PVS|FIO2:VALV][SAO2|PVS:SHNT][PAP|PMB][PMB]",
                            "[SHNT|INT:PMB][INT][PRSS|INT:KINK:VTUB][DISC][MVS][VMCH|MVS]",
                            "[VTUB|DISC:VMCH][VLNG|INT:KINK:VTUB][VALV|INT:VLNG][ACO2|VALV]",
                            "[CCHL|ACO2:ANES:SAO2:TPR][HR|CCHL][CO|HR:STKV][BP|CO:TPR]", sep = "") 
alarm.net <- alarm %>%
  names %>%
  empty.graph %>%
  `modelstring<-`(value = alarm.model.string)

#* @get /bayesiannetwork
#* @png
bn <- function(type="type1") {
  if(type=="type1"){
    plot(hc(learning.test))
  } else if(type=="type2"){
    graphviz.plot(alarm.net)
  } else {
    graphviz.plot(moral(alarm.net))
  } 
}

細かい解説



これによって
/bayesiannetwork?type=type1なら↓が返されて、

api.R
plot(hc(learning.test))

/bayesiannetwork?type=type2なら↓が、

api.R
graphviz.plot(alarm.net)

/bayesiannetwork?type=type3なら↓が返されるapiができます!

api.R
graphviz.plot(moral(alarm.net))

  • 上のapi.Rを実行するscriptを用意します.
run.R
library(plumber)

rapi <- plumb("api.R")
rapi$run(port=8888)

ここまで準備できたら、以下のコマンドでAPIサーバーを立ち上げます

$ Rscript run.R

Running plumber API at http://127.0.0.1:8888

2. Dashで、inputの内容をクエリパラメータに反映させてhtml.Img()で1.のエンドポイントにGETリクエストを送る

  • dashのdash_core_components.Dropdownで選んだ内容が、クエリパラメータに反映されたリンクを生成するような処理を書きます
app.py

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State


app = dash.Dash()
app.title = 'dash app with python and R'
app.scripts.config.serve_locally = True
app.config.suppress_callback_exceptions = True

app.layout = html.Div([
    html.H1(children='dash app with python and R'),
    dcc.Dropdown(
        id='my-dropdown',
        options=[
            {'label': 'Bayesian network 1', 'value': 'type1'},
            {'label': 'Bayesian network 2', 'value': 'type2'},
            {'label': 'Bayesian network 3', 'value': 'type3'}
        ],
        value='type1'
    ),

    html.Div(id='output-container')
])


@app.callback(
    dash.dependencies.Output('output-container', 'children'),
    [dash.dependencies.Input('my-dropdown', 'value')])
def render_link_with_parameter(value):
    url = 'http://localhost:8888/bayesiannetwork' + "?type=" + value
    render_div = html.Div(
        html.Div([
            html.P(url),
            html.Img(src=url)
        ])
    )
    return render_div



if __name__ == '__main__':
    app.run_server(debug=True)

  • dropdownで選んだ内容が、のちに生成するlinkのurlに反映されるようになっています。
  1. dropdownで描画するネットワークを選ぶ
  2. render_link_with_parameterが実行される
  3. 先ほど準備したAPIにGETリクエストが飛んで、RがBNを描画する
  4. htmlにネットワークが表示される
  • ここまで準備できたら、別なterminalからdashのapplicationのサーバーを起動します
$ python ¥app.py

3. できた

おわりに

  • PythonとRの連携方法として、API経由でやりとりするのは便利そうでした
  • 画像の出力だけじゃなく、htmlwidgetsなどもRから返せるとのことです
  • よりスマートな方法があればぜひ教えてください

参考