本記事は、Shiny Advent Calendar 2017の18日目の記事です。
Shiny100本ノック第18弾です!
そんな今回は、reactive関数を使ってShinyアプリの効率化を図って行きたいと思います。
アプリを動かす上で、なるべく計算負荷を減らして効率化させることはとても大事なことです。
ということで、まずは簡単にできる所から効率化を目指そうということでreactive関数について紹介します。
renderの挙動
Shinyの関数として、これまでrenderplotやrendertableをserver.Rの中で活用し、動的にテーブルやプロットを変えるという処理を行ってきました。これまではあまり内部でどのように処理が行われているか触れては来ませんでした。実はこのrender~関数の挙動をしっかり理解していないと、計算処理負荷が大変なことになってしまいます。
このrender~({ })で囲まれている部分ですが、ui.Rから変数の値を受取るたびに、{}で囲まれている処理が行われています。
例えば、server.R内で以下のような処理を書いたとしましょう。
output$plot<- renderPlot({ x1 = input$number_x y1 = input$number_y z1 = input$number_z x2 = x1 * 10 y2 = y1 * z1 plot (x2, y2) })
このとき、number_x・number_y・number_zのうちのどれかがui.Rから渡された場合、renderPlot({ })の全ての処理が再実行されてしまいます。実際には再実行する必要が無い処理がほとんどにも関わらず、です。
よって、このrender~({ })内に計算負荷の大きいコードを書いてしまうと、毎回その負荷の大きい処理を繰り返し実行してしまうため、アプリが重たくなることに繋がります。
何度も計算しなくていい処理はrender内に書いてはいけません。
具体的には、データのimport部分や、事前に行えるデータ整形などでしょうか。
reactive関数
reactive関数を上手に使うと、処理が重たくなることを防ぐことができます。reactive({ })関数で実行された結果は、メモリ内に保存されるようになっています。よって、reactive関数内の戻り値に変更がなければ、メモリから参照するようになるため、計算負荷が減ります。
具体例がないと少しわかりづらい部分だと思うので、次の章でコードを出しながら説明していきます。
reactive関数の具体例
さっそくですが、以下のapp.Rのスクリプトを見てみましょう。library(shiny) ui <- fluidPage( titlePanel("Old Faithful Geyser Data"), sidebarLayout( sidebarPanel( sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30), selectInput("color", "select color", c("red", "blue", "green", "black")) ), mainPanel( plotOutput("distPlot") ) ) ) server <- function(input, output) { output$distPlot <- renderPlot({ x = faithful[, 2] bins <- seq(min(x), max(x), length.out = input$bins + 1) hist(faithful[, 2], breaks = bins, col = input$color, border = 'white') }) }
こちらのコードはRstudioでshiny appを新規作成した時のデフォルトのコードを少しアレンジしたものになります。
ヒストグラムを作るアプリで、bins(階級幅)とヒストグラムの色を選択できるようにしています。
さて、こちらのコードのserver処理の部分に注目してみましょう。
server <- function(input, output) { output$distPlot <- renderPlot({ x <- faithful[, 2] bins <- seq(min(x), max(x), length.out = input$bins + 1) hist(faithful[, 2], breaks = bins, col = input$color, border = 'white') }) }
上のコードでは、renderPlot内で以下3つの処理を行っています。
- データのインポート(x <- faithful[,2])
- binsを可変的に動かす処理(bins <- seq~)
- 色を指定してヒストグラムの生成(hist~)
例えばここで、ui側でヒストグラムの色を変えた時の処理がどうなるか考えてみましょう。
3番目の処理のみ行われれば良いはずが、1,2の処理も同時に動いてしまいます。
色だけ変えたいのに、再びbinsの階級幅を計算して…という処理も一緒に動くため、無駄な負荷をかけていることになります。
そこで、色を変更する部分と階級幅を計算する部分を、reactive関数を使って切り離すことで、お互いを独立させてみましょう。そうすることで負荷を減らすことができます。
reactive関数を使って少しだけコードを変えてみると以下のようになります。
library(shiny) server <- function(input, output) { bins <- reactive({ x = faithful[, 2] return (seq(min(x), max(x), length.out = input$bins + 1)) }) output$distPlot <- renderPlot({ hist(faithful[, 2], breaks = bins(), col = input$color, border = 'white') }) }
上記のように、bins <- reactive({})という部分で、binsの計算処理を書いています。これにより、ヒストグラムの色を変える処理では、再び階級幅の計算処理を繰り返さないようになりました。
今回のサンプル例では元々の処理の負担がそこまで大きくないため、正直reactive関数を使っても処理負荷は大きくは改善しません。しかし、大規模データを扱う際や複雑な処理を複数行う場合などは、reactive関数を使うか使わないかで、処理負荷は大きく異なります。
終わりに
今回はアプリの計算負荷を減らすための一つの工夫として、reactive関数を紹介しました。render~({ })の中に処理を書きすぎると、せっかく便利なShinyも、カクカクした動きになって残念な結果にアプリケーションになることがあります。複雑な計算を行う際や、API経由でデータを取得する際、大規模データを読み込む際は、reactive関数を上手に使って効率良いShinyアプリケーションを作ることを心がけましょう。