shiny × backpipeは鬼に金棒

今回はRのお話です。



backpipeパッケージという便利なものを見つけたので、備忘録としてまとめておきたいと思います。

目次

backpipeとは?

簡単に言うとパイプ%>%とは逆に後ろから前の順に関数を実行していく二項演算子%<%を提供してくれるパッケージのことです。

ここまで読んで、「なるほど」と思った方はもう使えます。


パイプ?

まず、パイプ%>%のお話から。


tidyverseを普段から使っている人にとって%>%は必要不可欠ですよね。
直感的に思考の流れに沿って書けますし、理解しやすく、デバッグもしやすい書き方が自然とできると思います。


超簡単な例。

# install.packages("tidyverse") # パッケージを入れていない方はこちら
library(tidyverse)
# library(dplyr)でも良い

tasu <- function(x, y) x+y
kakeru <- function(x, y) x*y  

1 %>% tasu(2) %>% kakeru(3)
# (1+2)*3
1 %>% kakeru(3) %>% tasu(2)
# (1*3)+2

iris %>%
  filter(Species == "setosa") %>% 
  select(starts_with("Sepal"), Species)
# irisのデータフレームを
# Species == "setosa"の行に絞り
# "Sepal"で始まる列とSpeciesの列にしぼる

わかりやすい。

※tidyverseについてはここでは触れないので、別途ググってみてください

backpipeの場合


上のプログラムをbackpipeで書くと

# 1 %>% tasu(2) %>% kakeru(3)が
kakeru(3) %<% tasu(2) %<% 1
# (1+2)*3

# 1 %>% kakeru(3) %>% tasu(2)が
tasu(2) %<%  kakeru(3) %<%  1
# (1*3)+2

# iris %>%
#   filter(Species == "setosa") %>% 
#   select(starts_with("Sepal"), Species)が
select(starts_with("Sepal"), Species) %<% 
  filter(Species == "setosa") %<% 
  iris
# irisのデータフレームを
# Species == "setosa"の行に絞り
# "Sepal"で始まる列とSpeciesの列にしぼる



読みづらい!





「いつ使うんだよ、こんなの」と思うかもしれません。



じゃあ、いつ使うか。

shinyでしょ!(古い)



shinyとは?

Rには shinyパッケージというものがあって、

リンク先の言葉を引用すると

Shiny is an R package that makes it easy to build interactive web apps straight from R.
(shinyはRパッケージで、ボタンをポチポチしただけで動かせるwebアプリを簡単に作成することができます)

と書いてあります。


Rすげぇ。




Rすげぇ。




大事なことなので、2回言いました。

百聞は一見に如かず


百聞は一見に如かずということで、作ってみましょう。

一番簡単にやる方法は、RStudioから

[File]→[New Project]→[New Directory]→[Shiny Web Application]→ディレクトリ名を指定

を実行する方法です。

実行すると、app.Rが作成されると思いますが、それがwebアプリの本体となります。

本体は大きく分けて2つのかたまりに分かれていて、以下の2つが含まれています。

  • ui:User Interface、見た目を決めるプログラム
  • server:(ボタンを押したときの動きなどの)処理全般を司るプログラム



では、実際にそのプログラムの中身を見てみます。

#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
# これはShiny webアプリだよ。上にある"Run App"ボタンをクリックすると、アプリを起動できるよ。
#
# Find out more about building applications with Shiny here:
#
#    http://shiny.rstudio.com/
#

library(shiny)

# Define UI for application that draws a histogram
# ヒストグラムを描くアプリのUIを定義する
ui <- fluidPage(

    # Application title
    titlePanel("Old Faithful Geyser Data"),

    # Sidebar with a slider input for number of bins 
    sidebarLayout(
        sidebarPanel(
            sliderInput("bins",
                        "Number of bins:",
                        min = 1,
                        max = 50,
                        value = 30)
        ),

        # Show a plot of the generated distribution
        mainPanel(
           plotOutput("distPlot")
        )
    )
)

# Define server logic required to draw a histogram
# ヒストグラムを描くために必要な処理を定義する
server <- function(input, output) {

    output$distPlot <- renderPlot({
        # generate bins based on input$bins from ui.R
        x    <- faithful[, 2]
        bins <- seq(min(x), max(x), length.out = input$bins + 1)

        # draw the histogram with the specified number of bins
        hist(x, breaks = bins, col = 'darkgray', border = 'white')
    })
}

# Run the application 
# アプリ実行!
shinyApp(ui = ui, server = server)


UI作って、処理書いて、実行してるだけなので、構造としてはシンプルですね。

uiオブジェクトの中身を見てみる

ui, serverのオブジェクトのうちuiに注目してみてみます。

uiは下記のようなHTMLコードの文字列を持っています。

> ui
<div class="container-fluid">
  <h2>Old Faithful Geyser Data</h2>
  <div class="row">
    <div class="col-sm-4">
      <form class="well">
        <div class="form-group shiny-input-container">
          <label class="control-label" for="bins">Number of bins:</label>
          <input class="js-range-slider" id="bins" data-min="1" data-max="50" data-from="30" data-step="1" data-grid="true" data-grid-num="9.8" data-grid-snap="false" data-prettify-separator="," data-prettify-enabled="true" data-keyboard="true" data-data-type="number"/>
        </div>
      </form>
    </div>
    <div class="col-sm-8">
      <div id="distPlot" class="shiny-plot-output" style="width: 100% ; height: 400px"></div>
    </div>
  </div>
</div>


なるほど。



これを、Rコードと対応させてみると以下のようになります。

<div class="container-fluid"> # <- fluidPage()
  <h2>Old Faithful Geyser Data</h2> # <- titlePanel()
  <div class="row">
    <div class="col-sm-4"> # <- sidebarPanel()
      <form class="well">
        <div class="form-group shiny-input-container"> # <- sliderInput()
          <label class="control-label" for="bins">Number of bins:</label>
          <input class="js-range-slider" id="bins" data-min="1" data-max="50" data-from="30" data-step="1" data-grid="true" data-grid-num="9.8" data-grid-snap="false" data-prettify-separator="," data-prettify-enabled="true" data-keyboard="true" data-data-type="number"/>
        </div>
      </form>
    </div>
    <div class="col-sm-8"> # <- mainPanel()
      <div id="distPlot" class="shiny-plot-output" style="width: 100% ; height: 400px"></div> # <- plotOutput()
    </div>
  </div>
</div>

uiを作り出すコードで出てくる関数の順番にはなっているが、
それゆえに、関数が実行される順番にはなっていないということがわかります。



では、この状態でパイプを使って簡潔に書けるかやってみます。

uiを作り出すコードの関数部分だけ取り出して書くと

fluidPage(
  titlePanel,
  sidebarLayout(
    sliderInput %>% 
      sidebarPanel, # わかりづらい

    plotOutput %>% 
      mainPanel # わかりづらい
  )
)


カッコの数が減って構成要素の塊を判別しやすくなりましたが、
パイプ%>%を書いたことによって直観的な順番と逆になってしまいました。


ここでbackpipeの出番です。

backpipeで書き換え

> fluidPage(
+   
+   # Application title
+   titlePanel("Old Faithful Geyser Data"),
+   
+   # Sidebar with a slider input for number of bins 
+   sidebarLayout(
+     sidebarPanel %<% 
+       sliderInput("bins",
+                   "Number of bins:",
+                   min = 1,
+                   max = 50,
+                   value = 30),
+     
+     # Show a plot of the generated distribution
+     mainPanel %<% 
+       plotOutput("distPlot")
+   )
+ )
<div class="container-fluid">
  <h2>Old Faithful Geyser Data</h2>
  <div class="row">
    <div class="col-sm-4">
      <form class="well">
        <div class="form-group shiny-input-container">
          <label class="control-label" for="bins">Number of bins:</label>
          <input class="js-range-slider" id="bins" data-min="1" data-max="50" data-from="30" data-step="1" data-grid="true" data-grid-num="9.8" data-grid-snap="false" data-prettify-separator="," data-prettify-enabled="true" data-keyboard="true" data-data-type="number"/>
        </div>
      </form>
    </div>
    <div class="col-sm-8">
      <div id="distPlot" class="shiny-plot-output" style="width: 100% ; height: 400px"></div>
    </div>
  </div>
</div>

先ほどの出力結果と一致し、なおかつ、直観的な順番になりました。
ただ、2項演算子なので、

> sidebarLayout %<% 
+   list(
+     sidebarPanel %<% 
+       sliderInput("bins",
+                   "Number of bins:",
+                   min = 1,
+                   max = 50,
+                   value = 30),
+     
+     mainPanel %<% 
+       plotOutput("distPlot")
+   )
 sidebarLayout(.) でエラー: 
   引数 "mainPanel" がありませんし、省略時既定値もありません 

2つ以上の引数を一気にこのようにlist形式で渡したとしても、
引数が足りませんと怒られます。



HTMLの構成要素が切り分けられれば十分キレイに見えるし、
デバッグもしやすいので、このままでOKだと思います。

まとめ

今回の例だと恩恵が少ないように見えますが、
shinyで難しいカスタマイズをしていきたいときにはかなり役立ちます。

例)

div(class="outer-outer",
    div( class="outer",
         div( class="inner",
              h1( "content", role="heading" )
         )
    )
)

div( class="outer-outer")  %<%
  div( class="outer") %<% 
    div( class="inner") %<% 
      h1( "content", role="heading" ) 

shinyの詳しい書き方については最近、日本語の本も出ましたし、
興味のある方は下記の本を要チェックです!!

RとShinyで作るWebアプリケーション

RとShinyで作るWebアプリケーション

  • 作者: 梅津雄一,中野貴広
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2018/11/07
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る