はじめに
システムの稼働状況とかミドルウェアの統計情報とかを手っ取り早く可視化するためにRを使ってみようということで書き始めた連載企画です。
Rでは、集計結果をサクッとWebアプリケーションとして作成するためのShinyというステキなパッケージが提供されています。Shinyを使えば、Webアプリケーションの知識があまり無くても、割と簡単にWebアプリが作れて、きれいなWebページでRによる集計結果を表示させることができます。
今回はShiny基礎編です。
関連記事
インフラ屋さんのためのR言語: 環境構築編
オフラインでのR環境構築 on RHEL
z/OSにRを導入してみた
インフラ屋さんのためのR言語: プログラミング編
R Markdownによるレポート生成
R MarkdownのHTMLレポートをブラッシュアップ
R - ShinyによるWebアプリケーション作成: 基礎編 <= 当記事
R - ShinyによるWebアプリケーション作成: shinydashboard編
R - ShinyによるWebアプリケーション作成Tips: shinydashboardでの画面遷移制御
R - ShinyによるWebアプリケーション作成Tips: UIオブジェクトの動的制御
R - Shinyアプリ/管理サーバー テンプレート
R - ShinyアプリでJリーグの勝点推移グラフを作成してみた
Shiny概要
Shinyについては、チュートリアルが充実しているので、まずはそれを一通りやってみることをお勧めします。
Shiny Tutorial
日本語訳をしてくれているサイトもありますね。
R言語でWebアプリケーションを作るためのチュートリアルを翻訳しました
ギャラリーも充実しているので、こちらを見ると、どのような画面がどのようなコードでできあがるのかがよく分かります。
Shiny - Gallery
Shinyでは出来合いの部品を使ってWebアプリを作るイメージなので、一からHTMLやJavaScriptを駆使してアプリを作るのに比べたら細かいところをいろいろ制御しようとすると融通が利かない点は多々ありますが、そこは手軽さとのトレードオフです。しかも、HTMLやJavaScriptを埋め込んだりする抜け道も色々と用意されていますので、やろうと思えば色々できる余地はあります。
詳細は本家のサイトを見ていただいた方がよいので、ここでは要点を整理します。
Shinyの基本構造
ShinyによるWebアプリでは、UIを記述する部分(ui.R)と、ロジックを記述する部分(server.R)とに分かれています。
UI部分
ui.Rというファイル(ファイル名は固定)で、User Interface、すなわち、画面レイアウトやどんなオブジェクトを配置するのか、ということを指定します。Shinyでは、静的コンテンツ(見出しや単なる文字列、画像など)を配置するだけでなく、インタラクティブに操作可能なよく使われるウィジェット(テキスト入力、チェックボックス、ボタン、スライダーバーなど)も簡単に作成できるようになっています。
ui.Rでは、主に以下のような部品を組み合わせて画面情報を作っていきます。
静的オブジェクト
見出し / h1(), h2()...
文字列 / p()
画像 / img()
区切り線 / hr()
など
(HTMLのタグに近い関数が多数用意されています。)
入力用ウィジェット
テキスト入力 / textInput()
チェックボックス / checkboxGroupInput()
ボタン / actionButton()
スライダーバー / sliderInput()
プルダウンメニュー / selectInput()
など
出力用ウィジェット
テキスト出力 / textOutput()
表出力 / tableOtuput()
モーダルウィンドウ / modalDialog()
など
そのほかに画面レイアウトに関連する関数などが色々と提供されます。
ロジック部分
server.Rというファイル(ファイル名は固定)で、ロジック部分を記述していきます。この部分はRenderingと呼ばれる機能が中心となります。このあたりの考え方はWebアプリに慣れていないと最初戸惑うと思うので少し補足します(実際私がそうだったので...)。
UI部分で出力用ウィジェットを定義している場合、それは動的に生成させたモノを表示させたい、ということになるので、すなわち何らかの処理を行った結果を表示させることを想定しています。そしてその処理の入力変数となるものは、画面からユーザーが入力したなんらかのアクションであることが多いと思います。
たとえば、画面上でユーザーに花の種類を選択させて、その選択された花の分析結果を画面に表として出力するなどです。
もっと単純化して、画面上からある数字を入れると、その数字を2倍した結果が返ってくるというアプリを想定します。これを実現するには、UIとしては、テキスト入力のウィジェット(textIn01)と、結果出力用のテキスト出力用のウィジェット(textOut01)を用意して配置します。
ロジック部分としては、結果を生成してウィジェットに出力するための計算処理を記述します。
具体例を見てみましょう。
shinyUI(fluidPage(
textInput("textIn01",label="Input"),
hr(),
p("Result"),
textOutput("textOut01")
))
shinyServer(function(input, output) {
output$textOut01 <- renderText({
numInput <- as.numeric(input$textIn01)
result <- numInput * 2
})
})
server.Rでは、renderTextという関数を使って出力結果を生成しています。このrenderTextの中では、画面から入力した情報(`input$textIn01')を取得して、それを元に結果を生成しています。
{}でくくられた最後の処理結果が最終的な値となるので、resultに格納された値(入力値の2倍)が画面に返されるということになります。
ここで、このrenderText()が処理されるタイミングですが、それは、reactive variable(反応的な変数?)というものに依存します。reactive variable というのは、その変数の値の変更が他の処理に影響を与えるもの、というような捉え方が分かりやすいかと思います。入力用ウィジェットで与えられる変数(input$xxx
)はreactive variableとして認識されます。ここではinput$textIn01
がreactive variableとなります。つまり、上の例のrenderText()は、textIn01という入力用ウィジェットの値が変更されるたびに実行され、そのつど結果が出力しなおされる、ということになります。
この例はテキストを動的に生成するrenderTextという関数を使用しましたが、生成されるオブジェクトごとに、renderTableやrenderPlotなどのように各種render関数が提供されています。いずれも考え方は同様で、ロジック中で使用されているreactive variableに変更があると、その都度処理が実行されます。
逐次型処理しか馴染みが無いと、こういうイベントドリブンな感じの挙動は最初ちょっと分かりにくいですね。基本的にはこのような考え方でロジックとUIを記述していき、後はこれの応用という感じです。
上の例だと、テキスト入力が変わる度に処理が行われるので、処理が重たいもの(Rお得意の分析処理など)だと、操作性が悪くなってしまいますので、値入力後にボタンを押したら処理結果を返してほしい、というようなこともよくあります。
その場合は、例えば...
- ボタンをUIで定義し、そのボタンが押されたら処理を行い(
observeEvent()
)、処理結果をある変数に格納する - その結果が格納される変数をreactive variableとして定義しておく
- 結果変数を使ってrender関数内で処理結果を出力する
といった感じにすれば、ボタン起動で結果が出力されるようになります。
ボタン駆動で処理される例を追加してみます。
shinyUI(fluidPage(
textInput("textIn01",label="Input"),
hr(),
p("Result"),
textOutput("textOut01"),
hr(),
hr(),
textInput("textIn02",label="Input2"),
actionButton("button02", label="submit!"),
hr(),
p("Result2"),
textOutput("textOut02")
))
shinyServer(function(input, output) {
output$textOut01 <- renderText({
numInput <- as.numeric(input$textIn01)
result <- numInput * 2
})
result2 <- 0
makeReactiveBinding("result2")
observeEvent(input$button02, {
numInput <- as.numeric(input$textIn02)
result2 <<- numInput * 2
})
output$textOut02 <- renderText({
result2
})
})
observeEvent()の代わりにrenderReactive()を使用すると、reactive variableを生成してくれるようですが、上の例の方が明示的にどの変数がreactiveかが分かりやすいのと応用がきくので個人的には好きです。
参考: actionButton-demo
また、result2 <<- numInput * 2
と記載している箇所で、代入が<<-
記号を使用しているのは、result2がrender関数の外のスコープで定義されているものを扱いたいからです。
...と、こんな感じでUI、ロジック部分を書いていくことで、割とさくっときれいなWebアプリケーションが書けちゃいます。
shinyでの課題
shinyはサクッとWebアプリが簡単に作れるすばらしいパッケージなのですが、これだけだと実際使うにあたって色々と課題にぶち当たることがでてきます。
現時点で解が見つけられていないものもあるのですが、気になるところを整理しておきます。
複数画面の関連付け/画面遷移
単発の機能をひとつの画面で提供するだけならよいですが、複数の画面をまとめて管理したいということは普通にあると思います。普通はポータルとかダッシュボード的なものを作りたくなると思います。
解決策: shinydashboard
shinydashboardというshinyを拡張するパッケージがあるのでこれが使えます!shinyを使う場合にはshinydashboardもセットで使うくらいのイメージでよいと思います。これは別の記事でまとめようと思います。
アプリ稼働環境
Webアプリケーションなので、どこぞやかのサーバーにアプリを実行させておいて、ユーザーはブラウザからそのアプリを使用する、というのが想定される基本的な使い方になります。
オプションとしては、以下のパターンがあると思われます。
- スタンドアローン(RScriptでrunShinyAppを個別に実行)
- ShinyServerを立てる(Shinyアプリを実行するためのサーバー機能を利用)
- shiyapps.ioという外部のshinyアプリ専用ホスティングサービスを使う
これは用途に合わせてどのような稼働環境にするかは使い分けができそうです。
並行処理
Webアプリにするということは、複数のユーザーがそれぞれブラウザーからアクセスする環境が想定されます。複数セッションを維持することは可能なのですが、R処理部分はシングルプロセスで処理されるため、同時にリクエストがくるとシリアライズされてしまうらしいです。
Shiny-Serverをたった1行の変更でマルチプロセス化する方法 というのがあったのでこれが解決策になりそう。そのうち試してみたいと思います。
※2016/12/06追記
上の参考記事に従って、app-spec.jsファイルを編集してみました。ブラウザからshinyアプリ呼び出すと、確かにRプロセスが複数ポコポコ上がってくるのですが、うまくアプリが動いてくれませんでした...。画面が崩れたり動的に生成するオブジェクトがうまく表示されなかったり...。
ちょっと上の記事の日付が古い(2013年)ので、実装が変わってる部分があるのかもしれません。ちなみに試した環境は、CentOS7, shiny-server-1.4.2.786-1.x86_64 です。
並行処理をさせたい場合は、有償版のshiny server proというのがあるようなので、本来であればそちらを使うのが本筋なのでしょう。(ただ、$9,995 per year / concurrent user 20 とのことで、なかなかいいお値段しますね。参考: RStudio Shiny Server Pro Pricing)
重たい処理
並行処理の問題もあり、大量データを扱う重たい処理がある場合、処理に時間がかかってブラウザの応答がなかなか返らないとか、タイムアウトしてしまったりします。Webアプリとしてはあんまり時間がかかる処理はやっぱり不向きな気がします。そこで、処理に時間がかかるものについてだけは、裏でバッチ的に処理を実行させてR Markdownにより静的HTML文書を作り、後からブラウザでそれを見る、という合わせ技がよいのではないかと思っています(リアルタイムの分析はshinyで、週次集計はR Markdownで、みたいな使い分け)。この辺も別記事で紹介できればと思います。
日本語/文字コード
どうもまだ日本語環境は不備が多そうですね。
普段Windows版のR/RStudioを使っていますが、Windowsの場合はSJISでソースやデータを扱った方が扱いやすそうです(テストとかでローカルで動かすので)。一方デプロイ先のサーバーはLinuxを使いたいということもあると思いますが、LinuxはUTF-8で扱うのが一般的ですね。
何がベストな方法か悩ましい所ですが、Windows上ではSJISベースで開発して、Linux環境にデプロイする際には、デプロイ前にまるごとUTF-8にコード変換してからデプロイするってのが現実解なのかなと思ってそうしています。バッチで変換させればそれほど手間では無いので。
参考: コード変換用のサンプルバッチ
@echo off
setlocal enabledelayedexpansion
cd %~dp0
for %%i in (*.R) do (
echo %%i
iconv -f CP943 -t UTF-8 %%i > ..\R_UTF8\%%i
)
バッチと同フォルダにある*.Rファイルをiconvを使って変換し、R_UTF8という別フォルダに格納します。
iconvを使うにはMinGWなどの環境が必要です(参考: MinGW+MSYSインストール メモ)。
WebSocket???/ネットワーク???
1つ個人的に大きな課題として残っているのが、環境によってうまくアプリが動かないケースがありまして...
ブラウザとShiny間のネットワークで、ファイアーウォールやNAT変換が入るケースで、うまく画面が表示されないという事象に遭遇しています。タイトルや部品などの静的情報は表示されるのですが、動的に生成されるグラフや選択肢などが表示されません。試したところだと、以下のような環境で同様の事象に遭遇したことがあります。
- VirtualBox上に配置したShinyServer(ネットワーク構成はNAT変換で、ホストPCではなく別のPCからのアクセス)
- Bluemix上の仮想サーバー上に配置したShinyServer(これもなんらかのアドレス変換が行われているっぽい)
- ファイアーウォール内のサーバー上に配置したShinyServerを(Browser ← サーバーのコネクション確立はNGの環境)
ブラウザ<=>Shiny間はどうやらWebSocketというプロトコルで通信しているらしく、これがうまくいっていないのかなという気がしています。WebSocketは今のところの私の理解では、HTTP通信を利用したものなのでHTTPでの通信ができればOKだと思うのですが...。ちょっと判別に時間がかかりそうなので後回しにしてます。そのうち調査しないと....(どなたかお詳しい方、何かヒントになるようなことがあれば是非教えてください!)