Shiny アプリは、実行速度が速く、素早く作れるのでユーザを驚かせます。しかし、アプリが多数重い計算をする必要がある場合はどうでしょうか?
このレッスンでは、reactive 式を使用した Shiny アプリの効率化方法について説明します。reactive 式を使用すると、アプリのどの部分で更新するかを制御できるため、処理速度を低下させる不要な計算を防ぐことができます。
始める前に:
- 作業ディレクトリに stockVis という名前の新しいフォルダを作成してください。
- 次のファイルをダウンロードして、それらを stockVis に配置してください:app.R と helpers.R
- runApp(“stockVis”) でアプリを起動します。
StockVis は、R の quantmod パッケージを使用しているので、まだインストールしていない場合は、install.packages(“quantmod”) で quantmod をインストールする必要があります。
(訳注)2018年3月、Googleが株価情報の提供を中止したため、app.Rにエラーが発生します。これを回避するには、app.R の 48行目を次の通り変更します。以下、「Google」と言う名称が出てきた場合は、全て「Yahoo」と読み替える必要があるでしょう(断定はできませんが)。
|
|
#getSymbols(input$symb, src = "google", (変更前) getSymbols(input$symb, src = "yahoo", (変更後) |
新アプリ:stockVis
stockVis アプリは、(株式)銘柄コードで株価を検索し、その結果を折れ線グラフとして表示します。 このアプリでは、
- 調べる株を選択する
- 評価する日付の範囲を選ぶ
- y 軸には、株価をプロットするのか、株価を対数でプロットするのかを選択し、
- インフレに対して価格を訂正するかどうかを決める。

“Adjust prices for inflation” チェックボックスはまだ機能しないので注意してください。このレッスンのタスクの 1 つは、このチェックボックスを修正することです。
デフォルトで、stockVis は SPY 銘柄( S&P 500 全体のインデクス)を表示します。別の株を探すには、Google ファイナンスが認識する株式の記号を入力します。ここで Yahoo の株式記号を調べることができます。一般的な記号の中には、GOOG(Google)、AAPL(Apple)、GS(Goldman Sachs)などがあります。
stockVis は、次の quantmod パッケージの 2 つの関数に大きく依存しています:
- getSymbols を使用し、Google ファイナンスやセントルイス連邦準備銀行などの Web サイトから、財務データを R に直接ダウンロードします。
- ChartSeries を使用し、価格を魅力あるチャートに表示します。
stockVis は、helpers.R という R スクリプトにも依存しています。これには、インフレに対する株価を調整する関数が含まれています。
チェックボックスと日付範囲
stockVis アプリは、いくつかの新しいウィジェットを使用しています。
- dateRangeInput で作成した日付範囲セレクタ
- checkboxInput で作成したいくつかのチェックボックス。チェックウィジェットは非常に簡単です。チェックボックスをチェックした場合は TRUE を、チェックを外した場合は FALSE を返します。
ui オブジェクト内で、このチェックボックスにはそれぞれ log と adjust という名前を付けていますので、server 関数内では input$log と input$adjust として参照します。ウィジェットとその値の使い方を再確認したい場合は、Lesson 3 と Lesson 4 をご覧ください。
計算の効率化
stockVis アプリには問題があります。
“Plot y axis on the log scale” をクリックすると、input$log の値が変わるため、renderPlot の式全体が再実行されます:
|
|
output$plot <- renderPlot({ data <- getSymbols(input$symb, src = "google", 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 を使用してGoogle ファイナンスからデータを再取得し、
- 軸を修正してでチャートを描画し直します。
プロットを再描画するのにデータを再フェッチする必要がないので、これは好ましくありません。 実際、データをあまりにも頻繁に再フェッチすると、Google ファイナンスは接続を切り離してしまいます(ボットのように見えてしまうためです)。しかし、もっと重要なことは、getSymbols の再実行は不要で、アプリの処理速度を低下させると共に、サーバの帯域幅が消費される可能性があると言うことです。
reactive 式
reactive 式で、リアクション中に再実行するものを制限することができます。
reactive 式は、ウィジェット入力を使用して値を返す R 式です。reactive 式は、オリジナルのウィジェットが変更されるたびに値を更新します。
reactive 式を作成するには、R 関数を波括弧で囲んだ reactive 関数を使用します(render* 関数と同じです)。
例えば、下には Google からのデータを取得するために stockVis のウィジェットを使用する reactive 式があります。
|
|
dataInput <- reactive({ #getSymbols(input$symb, src = "google", getSymbols(input$symb, src = "yahoo", from = input$dates[1], to = input$dates[2], auto.assign = FALSE) }) |
式を実行すると、getSymbols が実行され、結果として価格データのデータフレームが返されます。dataInput() を呼び出すことによって、renderPlot で価格データにアクセスするための式を使用することができます。
|
|
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 式が呼び出された場合は、保存されている値が古くなったかどうか(すなわち、依存するウィジェットが変更されたかどうか)をチェックする。
- 値が古い場合、reactive オブジェクトはそれを再計算する(そして新しい結果を保存する)。
- 値が最新の場合、reactive 式は計算を行わずに保存された値を返す。
この動作を使用して、Shiny がコードを不必要に再実行しないようにすることができます。下の新しい StockVis アプリでreactive 式がどのように動作するかを考えてみましょう。
|
|
server <- function(input, output) { dataInput <- reactive({ getSymbols(input$symb, src = "google", 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 は、日付ウィジェットと symb ウィジェットが変更されていないことをチェックする
- dataInput は、Google からデータを再取得せずに保存された株価データを返す
- renderPlotは、グラフを正しい軸で再描画する
依存関係
ユーザが symb ウィジェット内の銘柄コードを変更した場合はどうなりますか?
変更した場合、renderPlot で描いたプロットが古くなっていますが、renderPlot はもう input$symb を呼び出さなくなっています。Shinyは、input$symb のプロットが古くなったことを知っているのでしょうか?
はい、Shiny はそのことを知り、プロットを再描画します。 Shiny は、出力オブジェクトが依存している reactive 式やウィジェットの入力を追跡します。次の場合、 Shiny は自動的にオブジェクトを再構築します
-
-
- オブジェクトの render* 関数の入力値が変更されるか、または
- オブジェクトの render* 関数の reactive 式が古くなった場合
reactive 式は、入力値を出力オブジェクトに接続するチェーン内のリンクと考えることができます。出力内のオブジェクトは、チェーンの下流のどこで行った変更にも応答します。(reactive 式は他の reactive 式を呼び出すことができるので、長いチェーンを作ることができます)
reactive 式または render* 関数内から reactive 式を呼び出すだけです。どうして?これらの R 関数のみが reactive 出力を処理するために用意されており、警告なしで変更することができます。実際、Shiny はこれらの関数の外で reactive な式を呼び出さないようにします。
準備作業
“Adjust prices for inflation” という動作しないチェックボックスを修正していきます。ユーザは、インフレーションに対して調整した価格と、調整していない価格に切り換えることができます。
helpers.R の調整関数では、セントルイス連邦準備銀行が提供する消費者物価指数データを使用して、過去の価格を現在の価格に変換します。しかし、どのようにして、アプリにこれを実装できるでしょうか?
下に 1 つの解決方法がありますが、理想的なものではありません。理由がわかりますか?再び、input$log と関係してきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
server <- function(input, output) { dataInput <- reactive({ getSymbols(input$symb, src = "google", 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) }) } |
Reveal answer
adjust は renderPlot の内部で呼び出されています。adjust ボックスがチェックさた場合、y のスケールを通常のものと対数変換したもの(または、その逆)に切換えるたびに、すべての価格が再調整されます。この再調整は不要な計算です。
やってみよう
この問題を解決するには、新しい reactive 式をアプリに追加します。reactive 式は、dataInput の値を調整した(または調整していない)データのコピーを返す必要があります。
解答を思いついたら、下のモデル解答と比較してください。 ユーザが “Plot y axis on the log scale” をクリックしたときに、アプリ内でどの計算が行われ、どの計算が行われないのかを理解してください。
Reveal answer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
server <- function(input, output) { dataInput <- reactive({ getSymbols(input$symb, src = "google", from = input$dates[1], to = input$dates[2], auto.assign = FALSE) }) finalInput <- reactive({ if (!input$adjust) return(dataInput()) adjust(dataInput()) }) output$plot <- renderPlot({ chartSeries(finalInput(), theme = chartTheme("white"), type = "line", log.scale = input$log, TA = NULL) }) } |
各入力を、reactive 式または render* 関数に分離しました。入力が変更されると、古くなった式だけが再実行されます。
フローの例を次に示します。
-
-
- ユーザが “Plot y axis on the log scale.” をクリックする。
- renderPlot を再実行する。
- renderPlot は finalInput を呼び出す。
- finalInput は dataInput と input$adjust でチェックする。
- 何も変更されていない場合、finalInput は保存された値を返す。
- 何れかが変更された場合、finalInput は現在の入力値で新しい値を計算する。新しい値は renderPlot に渡され、将来のクエリに新しい値を格納する。
まとめ
reactive 式でコードをモジュール化することにより、アプリをより速くすることができます。
-
-
- reactive 式は、入力値または他の reactive 式からの値を取得し、新しい値を返します
- reactive 式は、結果を保存し、入力が変更された場合にのみ再計算します
- reactive 式は、reactive({ }) で作成します
- reactive 式は、式の名前の後に括弧 () を付けてを呼び出します
- reactive 式は、他の reactive 式か render* 関数内からのみ呼び出します
今やあなたは、洗練された効率的な Shiny アプリケーションを作成することができます。このチュートリアルの最後のレッスンでは、あなたのアプリケーションを他の人との共有方法を説明します。
Lesson7 へ