チュートリアルの目次はこちら。
shinyアプリは、驚くべきほど即座に高速に実行されます。しかし、アプリで大量の計算負荷の高い計算を実行する必要がある場合はどうなるでしょうか?
このレッスンでは、リアクティブな表現を使用してShinyアプリを効率化する方法を説明します。リアクティブな表現を使用すると、アプリのどの部分をいつ更新するかを制御できるため、アプリの速度を低下させる可能性がある不必要な計算を防ぐことができます。
始めるためには以下のことをしておきます:
- 作業ディレクトリに
stockVis
という名前を付けた新しいフォルダーを作成します。 - 次のファイルをダウンロードして、
stockVis
内に配置します:app.R,
helpers.R
runApp("stockVis")
でアプリを起動します。
stockVis
はRのquantmod
パッケージを使用するため、まだお持ちでない場合はinstall.packages("quantmod")
でインストールする必要があります。
runApp("stockVis")
新たなアプリ: StockVis
StockVisアプリはティッカーシンボルで株価を検索し、結果を折れ線グラフで表示します。アプリを使用して、
- 調べる銘柄を選択してください
- 確認する日付の範囲を選択してください
- y 軸に株価をそのままプロットするか、株価の対数をプロットするかを選択します。
- インフレを考慮して価格を修正するかどうかを決定します。
“Adjust prices for inflation”というチェックボックスはまだ機能しないことに注意してください。このレッスンのタスクの1つは、このチェック ボックスを修正することです。
デフォルトでは、stockVisはSPYティッカー (S&P 500全体の指数) を表示します。別の銘柄を検索するには、Yahoo Finance が認識する銘柄記号を入力します。ここでYahooの銘柄記号を検索できます。一般的なシンボルには、GOOG (Google)、AAPL (Apple)、GS (Goldman Sachs) などがあります。
StockVisは、quantmod
パッケージの2つの関数に大きく依存しています。
- Yahooファイナンスやセントルイス連邦準備銀行などのWeb サイトから金融データを R に直接ダウンロードするために
getSymbols
関数が使用されます。 - 価格を魅力的なチャートで表示するために
chartSeris
関数を使用します。
また、stockVisはhelpers.R
というRスクリプトにも依存しています。このスクリプトには、インフレに合わせて株価を調整する関数が含まれています。
チェックボックスと日付範囲
StockVis アプリは、いくつかの新しいウィジェットを使用します。
dateRangeInput
で作成された日付範囲セレクターcheckboxInput
で作られたいくつかのチェックボックス。チェックボックスウィジェットは非常にシンプルです。チェックボックスがチェックされている場合はTRUE
を返し、チェックボックスがチェックされていない場合はFALSE
を返します。
チェックボックスはui
オブジェクト内にlog
とadjust
として保存され,server
関数内で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 Finance からデータを再取得し- 正しい軸でチャートを再描画します。
プロットを再描画するためにデータを再取得する必要がないため、これは良くありません。実際、データを頻繁に再取得すると、Yahoo ファイナンスから遮断されてしまいます (ボットのように見え始めるため)。しかし、さらに重要なのは、getSymbols
関数の再実行は不必要な作業であり、アプリの速度が低下し、サーバーの帯域幅が消費される可能性があります。
リアクティブな表現
リアクティブな表現を使用すると、リアクティブな動作中に再実行される内容を制限できます。
リアクティブな表現は、ウィジェットの入力値を使用して出力値を返すRコードです。リアクティブな表現は、元のウィジェットが変更されるたびにこの値を更新します。
リアクティブな表現を作成するには、reactive
関数を使用します。この関数は、(render*
関数と同様に)中括弧{}で囲まれたRコードを受け取ります。
たとえば、次のリアクティブな表現は、stockVisのウィジェットを使用して Yahoo からデータを取得します。
dataInput <- reactive({
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)
})
リアクティブな表現は、通常のR関数よりも少し賢いです。値をキャッシュし、その値がいつ古くなったかを認識します。これはどういう意味でしょうか?リアクティブな表現を初めて実行すると、コードの結果がコンピュータのメモリに保存されます。次にリアクティブな表現を呼び出すときは、計算を行わずにこの保存された結果を返すことができます (これにより、アプリが高速化されます)。
リアクティブな表現は、結果が最新である (変更されていない) ことがわかっている場合にのみ、保存された結果を返します。リアクティブな表現が結果が捨てられたことを学習した場合 (ウィジェットの値が変更されたため)、コードは再計算をします。次に、新しい結果を返し、新しい結果を保存します。リアクティブな表現は、この新しいコピーも期限切れになるまで使用します。
この挙動を要約してみましょう。
- リアクティブな表現は、最初に実行したときに結果を保存します。
- 次にリアクティブな表現が呼び出されたときに、保存された値が古くなったかどうか (つまり、依存するウィジェットが変更されたかどうか) がチェックされます。
- 値が古い場合、リアクティブオブジェクトは値を再計算します (その後、新しい結果が保存されます)。
- 値が最新の場合、リアクティブな表現は計算を行わずに保存された値を返します。
この動作を使用すると、Shiny がコードを不必要に再実行するのを防ぐことができます。以下の新しいStockVisアプリでリアクティブな表現がどのように機能するかを考えてみましょう。
server <- 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
を呼び出しません。shinyはinput$symb
によってプロットが古くなったことに気づくでしょうか?
shinyはそれを知っており、プロットを描き直します。Shinyは、output
オブジェクトがどのリアクティブな表現に依存しているかを、どのウィジェットの入力が依存しているかを追跡するのと同様に,追跡します。 Shinyは、次の場合にオブジェクトを自動的に再構築します。
render*
関数内のinput
の値が変更される、またはrenderPlot
関数のリアクティブな表現が古くなる
リアクティブな表現は、input
値をoutput
オブジェクトに接続するチェーン内のリンクとして考えてください。output
オブジェクトは、チェーンの下流の任意の場所で行われた変更に反応します。 (リアクティブな表現は他のリアクティブな表現を呼び出すことができるため、長いチェーンを形成できます。)
リアクティブな表現は、reactive
またはrender*
関数内からのみ呼び出します。なぜでしょうか?これらのR関数のみが、警告なしで,変化する可能性があるリアクティブ出力を処理する機能を備えています。実際、Shinyでは、これらの関数の外でリアクティブな表現を呼び出すことができません。
ウォームアップ
“Adjust prices for inflation”の機能しないチェックボックスを修正するタイミングが来ました。ユーザーは、インフレに合わせて調整された価格と調整されていない価格を切り替えることができる必要があります。
helper.R
の中のadjust
関数は、セントルイス連邦準備銀行によって提供される消費者物価指数データを使用して、過去の価格を現在の価格に変換します。しかし、これをアプリにどのように実装できるのでしょうか?
以下に解決策の 1 つを示しますが、これは理想的ではありません。その理由がわかりますか?もう一度言いますが、それはinput$log
と関係があります。
server <- 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)
})
}
adjust
関数はrenderPlot
関数の中で呼び出されます。調整ボックスがチェックされている場合、通常のY軸スケールから記録された対数Y軸スケールに切り替えるたびに、アプリはすべての価格を再調整します。この再調整は不要な作業です。
Your turn
この問題を解決するには、新しいリアクティブな表現をアプリに追加します。リアクティブな表現は、dataInput
の値を受け取り、データの調整された (または調整されていない) コピーを返す必要があります。
理解できたと思ったら、自分の解決策を以下の模範解答と比較してください。ユーザーが“Plot y axis on the log scale” をクリックしたときにアプリでどのような計算が行われ、どのような計算が行われないかを必ず理解してください。
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
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)
})
}
これで、各入力を独自のリアクティブな表現またはrender*
関数に分離しました。入力が変更された場合、古い式のみが再実行されます。
例題のフローを次に示します。
- ユーザーが“Plot y axis on the log scale” をクリックします。
renderPlot
を再実行します。renderPlot
関数はfinalInput
を呼び出します。finalInput
はdataInput
とinput$adjust
をチェックします。- どちらも変更されていない場合は、
finalInput
は保存された値を返します。 - いずれかが変更されていた場合は、
finalInput
は現在の入力値を使用して新しい値を計算します。新しい値をrenderPlot
に渡し、将来のクエリのために新しい値を保存します。
要約
リアクティブな表現を使用してコードをモジュール化することで、アプリを高速化できます。
- リアクティブな表現は
input
値、または他のリアクティブな表現からの値を受け取り、新しい値を返します。 - リアクティブな表現は結果を保存し、入力が変更された場合にのみ再計算します。
- リアクティブな表現を
reactive({})
によって作成します。 - 式の名前の後にかっこ「()」を付けてリアクティブな表現を呼び出します。
- 他のリアクティブな表現または
render*
関数内からのみリアクティブな表現を呼び出します
洗練され効率化されたShinyアプリを作成できるようになりました。このチュートリアルの最後のレッスンでは、アプリを他の人と共有する方法を説明します。
コメント