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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143058/639e2b1a-26f8-7afd-0a0b-32163529432c.png)

## 背景

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. できた


![image.png](https://qiita-image-store.s3.amazonaws.com/0/143058/4dd55550-5df5-b26d-9934-79be8e1465e0.png)




## 実装

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

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

```

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

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

```r: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))
  } 
}
```


<details><summary>細かい解説</summary><div>

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

```r:api.R
plot(hc(learning.test))
```
`/bayesiannetwork?type=type2`なら↓が、

```r:api.R
graphviz.plot(alarm.net)
```
`/bayesiannetwork?type=type3`なら↓が返されるapiができます！

```r:api.R
graphviz.plot(moral(alarm.net))
```

</div></details>

- 上の`api.R`を実行するscriptを用意します.

```r:run.R
library(plumber)

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

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

```shell
$ 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`で選んだ内容が、クエリパラメータに反映されたリンクを生成するような処理を書きます

```python: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のサーバーを起動します

```shell
$ python ¥app.py
```

### 3. できた

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.amazonaws.com/0/143058/eaf8dcce-0566-e9b2-931f-23d7135aed2c.gif)


## おわりに

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


### 参考

- [Rのコードをweb APIに {plumber}](https://qiita.com/masaki_kubota/items/a5ccc6504a697c0e89f8)
- [Visualizing Performance In Bayesian Network Structure Learning
](https://robertness.github.io/2015/03/12/Visualizing-Performance-in-Bayesian-Network-Structure-Learning.html)
- [Creating APIs in R with Plumber](https://www.rplumber.io/docs/index.html)