Help us understand the problem. What is going on with this article?

RStudio Shiny チュートリアル レッスン6 reactiveな表現式の使用

More than 3 years have passed since last update.

この記事は 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 アプリは、株価をティッカーシンボルによって検索し、結果を折れ線グラフで表示するアプリです。
このアプリの使用手順は次のとおりです。

  1. 調査する株式を選択する
  2. 見たい日付の範囲を指定する
  3. y 軸(株価)を log 変換するかどうかを選ぶ
  4. インフレに対する価格補正をかけるどうかを決める

stockVis1.png

"Adjust prices for inflation"(インフレに対する価格補正) チェックボックスは、まだ機能しないことに注意して下さい。
このチェックボックスが動くように修正するのも、このレッスンでやることの一つです。

デフォルトでは、stockVis は SPY ティッカー(S&P 500 株価指数)を表示します。
別の株式を表示するには、Yahoo ファイナンスが認識できる銘柄記号を入力します。
Yahoo における銘柄記号は、ここから探せます。
よく使われる銘柄記号としては、GOOG(Google)、AAPL(Apple)、GS(Goldman Sachs) などが挙げられます。

stockVis アプリは、quantmod パッケージの 2 つの関数に依存しています:

  1. getSymbols 関数は、Yahoo ファイナンスセントルイス連邦準備銀行 のようなサイトから、R に金融データをダウンロードします。
  2. chartSeries 関数は、株価を魅力的なチャートにします。

stockVis アプリは、helpers.R スクリプトにも依存します。
このスクリプトには、インフレに対する株価補正の関数が含まれています。

チェックボックスと日付範囲選択カレンダー

stockVis アプリでは、新しい 2 つのウィジェットを使っています:

  • dateRangeInput 関数によって作られる「日付範囲選択カレンダー」
  • checkBoxInput 関数によって作られる「チェックボックス」。チェックボックスウィジェットは非常にシンプルです。チェックボックスにチェックが付いていたら TRUE を、チェックが付いていないなら FALSE を返します。

logadjust という名前の 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 が再実行されるたびに、

  1. getSymbols 関数によって Yahoo ファイナンスからデータが再取得されます。
  2. 指定された軸の方式でチャートが再描画されます。

これはいけません。
プロットを再描画するのに、データを再取得する必要はないからです。
何度も何度もデータを再取得していると、ボットと勘違いされて 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 が再実行されます。
このとき

  • renderPlotdataInput を呼び出します。
  • 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 アプリを作ることができるようになりました。
このチュートリアルの最後のレッスンでは、作成したアプリを他の人と共有する方法を紹介します。

hoxo_m
ホクソエム (hoxo_m) は架空のデータ分析者であり、日本の若手のデータ分析者集団のペンネームである。当初このデータ分析者集団は秘密結社として活動し、ホクソエムを一個人として活動させ続けた。
https://blog.hoxo-m.com/
hoxom
Machine Learning and Data Analysis Company for Your Smiles :)
http://hoxo-m.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした