この記事は Shiny 公式サイトのチュートリアルを翻訳したものです。
http://shiny.rstudio.com/tutorial/lesson6/
チュートリアル目次:http://d.hatena.ne.jp/hoxo_m/20151222/p1
LESSON6 reactiveな表現式の使用
Shiny アプリは、その高速な動きと素早い反応によってユーザを感嘆させます。
しかし、アプリが遅い計算を必要とする場合にはどうすればいいでしょうか?
このレッスンでは、reactive な表現式を使って Shiny アプリを効率化する方法を見ていきます。
Reactive な表現式を使うと、アプリのどの部分がどのタイミングで更新されるのかを制御することができます。
これにより、不要な計算を防ぐことができます。
始めるために、
- ワーキングディレクトリに
stockVis
フォルダを新しく作成して下さい。 - 次の 3 つのファイル ui.R, server.R, helpers.R をダウンロードし、
stockVis
フォルダに入れて下さい。 -
runApp("stockVis")
を実行して、アプリを起動して下さい。
stockVis アプリは quantmod
パッケージを使用するため、インストールしていない場合は install.packages("quantmod")
を実行して quantmod
パッケージをインストールしておく必要があります。
runApp("stockVis")
新しいアプリ:stockVis
stockVis アプリは、株価をティッカーシンボルによって検索し、結果を折れ線グラフで表示するアプリです。
このアプリの使用手順は次のとおりです。
- 調査する株式を選択する
- 見たい日付の範囲を指定する
- y 軸(株価)を log 変換するかどうかを選ぶ
- インフレに対する価格補正をかけるどうかを決める
"Adjust prices for inflation"(インフレに対する価格補正) チェックボックスは、まだ機能しないことに注意して下さい。
このチェックボックスが動くように修正するのも、このレッスンでやることの一つです。
デフォルトでは、stockVis は SPY ティッカー(S&P 500 株価指数)を表示します。
別の株式を表示するには、Yahoo ファイナンスが認識できる銘柄記号を入力します。
Yahoo における銘柄記号は、ここから探せます。
よく使われる銘柄記号としては、GOOG(Google)、AAPL(Apple)、GS(Goldman Sachs) などが挙げられます。
stockVis アプリは、quantmod
パッケージの 2 つの関数に依存しています:
-
getSymbols
関数は、Yahoo ファイナンス や セントルイス連邦準備銀行 のようなサイトから、R に金融データをダウンロードします。 -
chartSeries
関数は、株価を魅力的なチャートにします。
stockVis アプリは、helpers.R
スクリプトにも依存します。
このスクリプトには、インフレに対する株価補正の関数が含まれています。
チェックボックスと日付範囲選択カレンダー
stockVis アプリでは、新しい 2 つのウィジェットを使っています:
-
dateRangeInput
関数によって作られる「日付範囲選択カレンダー」 -
checkBoxInput
関数によって作られる「チェックボックス」。チェックボックスウィジェットは非常にシンプルです。チェックボックスにチェックが付いていたらTRUE
を、チェックが付いていないならFALSE
を返します。
log
と adjust
という名前の 2 つのチェックボックスが ui.R
スクリプトの中で定義されています。
これらの値は、server.R
の中で input$log
および input$adjust
として使えます。
ウィジェットとその値の使用方法については、レッスン 3 とレッスン 4 を確認して下さい。
計算の効率化
stockVis アプリには問題があります。
"Plot y axis on the log scale" をクリックした時に何が起こるでしょうか。
input$log
の値が変化するため、renderPlot
関数の中のすべてのコードが再実行されます:
output$plot <- renderPlot({
data <- getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
chartSeries(data, theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
renderPlot
が再実行されるたびに、
-
getSymbols
関数によって Yahoo ファイナンスからデータが再取得されます。 - 指定された軸の方式でチャートが再描画されます。
これはいけません。
プロットを再描画するのに、データを再取得する必要はないからです。
何度も何度もデータを再取得していると、ボットと勘違いされて Yahoo ファイナンスからアクセスを拒否されるかもしれません。
しかし、もっと重要なのは、getSymbols
関数の再実行は本来不要であり、アプリの速度を低下させ、サーバ帯域を浪費するということです。
reactive な表現式
reactive な表現式を使えば、コードの再実行される部分を限定することができます。
reactive な表現式とは、ウィジェットの入力値を使用した、値を返す R の表現式のことです。
reactive な表現式は、元になるウィジェットが変更されたときに限り値を更新します。
reactive な表現式を作るには、reactive
関数を使います。
reactive
関数は、波括弧によって囲まれた R の表現式を受け取ります(render*
関数と同様です)。
例えば、下記は stockVis アプリのウィジェットを使って Yahoo からデータを取得する reactive な表現式です。
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
この表現式が実行されると、getSymbols
関数が実行され、結果(価格データのデータフレーム)が返されます。
renderPlot
の中で dataInput()
を呼び出すことによって、価格データにアクセスすることが可能です。
output$plot <- renderPlot({
chartSeries(dataInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
reactive な表現式は、普通の R 関数よりも、ちょっぴり優秀です。
reactive な表現式は、自分の返り値をキャッシュしていて、その値が古くなってしまったかどうかを知っています。
これはどういう意味でしょうか?
reactive な表現式を最初に実行したとき、その返り値はコンピュータメモリの中に保存されます。
そしてそれをもう一度実行したとき、その保存された値を返すことができます(つまり、再計算しなくて済むのでアプリは速くなります)。
reactive な表現式は、保存された結果が最新である場合のみ、その値を返します。
もし、結果が古くなっていたら(すなわち、ウィジェットが変更されていたら)、reactive な表現式は結果を再計算します。
そして、新しい結果を返し、新しい結果を保存します。
再びその値が古くなるまでは、reactive な表現式はその値を返し続けます。
これらの動作をまとめると、
- reactive な表現式は最初に実行されたとき、結果を保存する
- 次に実行されたとき、保存された値が最新かどうか(すなわち、依存するウィジェットが変更されたかどうか)をチェックする
- 保存された値が最新でないならば、再計算する(そしてその新しい結果を保存する)
- 保存された値が最新ならば、再計算せずに保存された値を返す
この動作により、不必要なコードの再実行を防ぐことができます。
reactive な表現式を使った stockVis アプリが、どのように動作するのかを考えてみましょう。
# server.R
library(quantmod)
source("helpers.R")
shinyServer(function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
output$plot <- renderPlot({
chartSeries(dataInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
})
"Plot y axis on the log scale" をクリックすると、input$log
が変更され、renderPlot
が再実行されます。
このとき
-
renderPlot
はdataInput
を呼び出します。 -
dataInput
は dates と symb が変更されていないことを確認します。 -
dataInput
は保存された株価を返します。Yahoo からデータを再取得しません。 -
renderPlot
は軸のスケールが変更されたチャートを再描画します。
依存関係
ユーザが symb ウィジェットの銘柄記号を変更した場合はどうなるでしょうか?
このとき、renderPlot
によって描かれたプロットが更新されて欲しいのですが、renderPlot
の中ではもはや input$symb
が使われていません。
input$symb
が変更されたためにプロットがもう古くなってしまったことを、Shiny は知ることができるのでしょうか?
答えは Yes です。
Shiny はプロットが古くなったことを検知し、プロットを再描画します。
Shiny は、output
オブジェクトが依存する reactive な表現式を監視しています。
Shiny は次のいずれかの場合に、自動的にオブジェクトを再構築します。
-
render*
関数の中で使われているinput
の値が変更される -
render*
関数の中で使われている reactive な表現式が古くなる
input
の値から output
オブジェクトまでをつなげている鎖を想像してください。
reactive な表現式は、その鎖を構成する輪っかです。
reactive な表現式の中で別の reactive な表現式を使うことで、長い鎖を作ることも可能です。
鎖の中のどの位置で変化が起こっても、output
オブジェクトは反応します。
reactive な表現式を呼び出すことができるのは、reactive
または render*
関数の中だけです。
これはなぜでしょうか?
R の関数の中で、これらの関数だけが、reactive な出力を扱う機能を備えているからです。
事実、Shiny はこれらの関数の外で reactive な表現式を呼び出そうとすると、警告を出します。
準備運動
"Adjust prices for inflation"(インフレに対する価格補正) のチェックボックスの機能を実装する時がやってきました。
ユーザがインフレによる価格補正ありのグラフと無しのグラフを切り替えることができるようにしましょう。
helper.R
ファイル中で定義されている adjust
関数は、セントルイス連邦準備銀行の提供する消費者物価指数データを使って、過去の株価を現在の水準での値に変換します。
ところでこれをどうやってアプリに実装するのでしょうか?
一つの答えを下記に提示します。
しかし、これは理想の答えではありません。
なにが悪いか分かるでしょうか?
ヒント:input$log
が変更されたときどうなるでしょうか?
# server.R
library(quantmod)
source("helpers.R")
shinyServer(function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
output$plot <- renderPlot({
data <- dataInput()
if (input$adjust) data <- adjust(dataInput())
chartSeries(data, theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
})
やってみよう
上記で問題となった部分を修正するために、アプリのコードに reactive な表現式を追加して下さい。
その reactive な表現式は、dataInput
の値を受け取り、補正された(または補正されていない)データを返します。
できたと思ったら、模範解答と比べてみて下さい。
(訳注:模範解答は公式サイトの "Reveal answer" をクリックすると見れます)
ユーザが “Plot y axis on the log scale” をクリックしたとき、どの計算が行われ、どこ計算が行われないかをしっかりと理解して下さい。
まとめ
reactive な表現式を使ってコードをモジュール化することで、アプリを高速にすることができます。
- reactive な表現式は、
input
の値または別の reactive な表現式の値を受け取り、新しい値を返します - reactive な表現式は、実行結果を保存し、入力が変化したときだけ再計算を行います
- reactive な表現式を作るには、
reactive({ })
と書きます - reactive な表現式を呼び出すには、名前の後ろに括弧
()
を付けます - reactive な表現式は、別の reactive な表現式か、
render*
関数の中でしか呼び出すことはできません
以上により、洗練された、効率的な Shiny アプリを作ることができるようになりました。
このチュートリアルの最後のレッスンでは、作成したアプリを他の人と共有する方法を紹介します。