生存時間解析シミュレーター
if(!require(shiny)) install.packages(("shiny"))
::runGitHub("surv_sim", "sb8001at") shiny
データを解析した後には、その結果を用いて他の人にデータや解析の意味を説明する必要があります。説明するために用いるものの一つが文書やプレゼンテーションです。文書やプレゼンテーションはR markdown・Quarto(34章参照)で作成することができます。
もう一つの方法は、データを表示するためのウェブページを作成する方法です。ウェブページもR markdownで作成することはできますが、R markdownだけではインタラクティブなウェブページ、例えばデータを入力して、そのデータに応じたグラフを作成するようなもの、は作成することができません。このインタラクティブなウェブページを作成するためのRのツールがShiny(Chang et al. 2024)です。
Shinyはwebアプリケーションと呼ばれる、webページ(HTML)の生成(ユーザーインターフェース/フロントエンド)とサーバでの演算(サーバサイド)を行うアプリケーションの一種です。
通常のwebアプリケーションでは、ユーザーがwebページにアクセスし、情報を入力(例えば検索する文字を入力)すると、情報がサーバへと送られます。サーバでは入力された情報と、サーバー内のデータベースとを比較し、必要な情報を集めてHTMLファイルを作成します。このHTMLがユーザーに送られ、検索結果が表示される、といったような仕組みになっています。
このようなタイプのwebアプリケーションを作成するためのツールはたくさんあります。典型的な例として、Ruby on Rails、Djangoなどがあります。これらのツールはwebアプリケーションフレームワークと呼ばれており、我々がよく用いているwebページ・webサービスに用いられています。
Shinyはこれらのフレームワークと比較するとかなり機能が制限されたツールです。Shinyにはユーザーインターフェースとサーバサイドを構築する機能はありますが、演算は基本的にRで行われるためレスポンスは遅めです。また、データベースは必須ではないため、データベースを用いるのであればR上で別途ライブラリを用いる必要があります。データベースではなく、csvなどでデータを準備することもあります。また、セキュリティ面をサポートするツールはほとんどありません。
Shinyはデータを説明することに特化したwebアプリケーション開発ツールで、Rのツール、例えばggplot2
やplotly
、leaflet
などを用いてデータを表示し、統計結果を示すのに適したツールです。Shiny自体は2012年頃から開発され、2017年頃にver.1.0となった、古参のデータ用webアプリケーションツールです。
最近になってPlotly DashやStreamlitなどの、Pythonを用いる同様のツールが開発され、あっという間にShinyよりメジャーなツールになってしまいました。特にStreamlitは今(2024年)注目されているツールです。現状Shinyの方が完成度は高めですが、Streamlitはたくさんの開発者が寄ってたかって改善している最中ですので、いずれStreamlitの完成度がShinyを上回りそうです。Pythonを使ったShinyも開発されていますが、Streamlitの勢いに勝てるかどうかは疑わしいところです。
しかし、Rでウェブアプリケーションを作成するのであれば、Shinyはすぐに使えて、ある程度完成度の高いものを作るのも難しくない、良いツールです。ココではShinyでの基本的なWebアプリケーションの作成方法を簡単に説明します。
Shinyはかなり複雑なこともできる、高度なRライブラリの一つです。すべてをココで説明することはできませんので、詳しく学びたい方は教科書(日本語のものや英語のもの)をご一読されることをオススメいたします。
この教科書でもShinyで作成したいくつかのWebアプリケーションを統計解析手法の説明に用いています。以下のコードを実行することで、この教科書で用いているWebアプリケーションを起動することができます。また、Webアプリケーションを停止するには、コンソールを選択してEscを押します。
生存時間解析シミュレーター
if(!require(shiny)) install.packages(("shiny"))
::runGitHub("surv_sim", "sb8001at") shiny
k-meansのアニメーション
if(!require(shiny)) install.packages(("shiny"))
::runGitHub("kmeans_animated", "sb8001at") shiny
時系列:ARIMAシミュレーター
if(!require(shiny)) install.packages(("shiny"))
::runGitHub("ARIMAsim", "sb8001at") shiny
ShinyのWebアプリケーションをR Studioで作成する場合、まずは左上のNew Fileアイコンから「Shiny Web App…」を選択します。
「Shiny Web App…」を選択すると、以下のようなウインドウが表示されます。このウインドウでは、Webアプリケーションの名前、ファイルを作成するディレクトリ(フォルダ)に加えて、Web Appを1つのファイルで作成するか(Single File)、複数のファイルで作成するか(Multiple File)を選択します。「Single File」を選択すると、app.Rという1つのファイルが選択したディレクトリ内に作成されます。一方で「Multiple File」を選択すると、ui.Rとserver.Rという2つのファイルが作成されます。
どちらの形式を用いてもアプリケーションを作成するための関数などは共通していますが、アプリケーション作成に慣れるまでは「Multiple File」を用いるのがおすすめです。この章では、まず「Multiple File」での作成方法を一通り説明した後で、「Single File」で作成する方法を説明します。
上記のウインドウで「Create」をクリックすると、以下のようにディレクトリにui.R
とserver.R
という2つのファイルが作成されます。このファイルにはWebアプリケーションのスクリプト(サンプルアプリ)が書かれています。
作成されたui.R
やserver.R
をRstudioで開くと、右上に「Run App」というアイコンが出てきます。これを押すとサンプルアプリを実行することができます。
Webアプリケーション(Shinyアプリ)を実行すると、以下の図のようなページがWebブラウザに表示されます。このページのうち、左の部分(Number of bins:)はマウスで操作することができます。
実際にNumber of binsを変更し、50としたのが以下の図です。bins
、つまりヒストグラムの棒の数の設定を変えると、右のヒストグラムの表示が変化します。
このようなShinyアプリの動作は、以下の図のようなメカニズムで構成されています。まず、Shinyアプリのユーザーインターフェース(UI)で入力したデータは、Serverに送られます。Serverでは入力データを取り込んでggplot2
でグラフを作成し、グラフを含んだHTMLが作成されます。このHTMLがWebブラウザで表示されることで入力データに従ったグラフが表示されます。
通常のWebアプリケーションフレームワークでは、Rの部分に別の言語(RubyやPythonなど)が入り、さらにデータベースと接続されているのが一般的です。Shinyではデータベースは必須ではありませんが、もちろんデータベースを用いたWebアプリケーションを作成することもできます。データベースを用いたアプリケーションを作成する場合には、データベースを取り使うためのライブラリ(DBI
(R Special Interest Group on Databases (R-SIG-DB), Wickham, and Müller 2024)やdbplyr
(Wickham, Girlich, and Ruiz 2024)など)を用いることになります。
上記のUI、Serverをそれぞれ構築するためのファイルが、ui.R
とserver.R
です。ココからはまずui.R
の構築について説明し、その後server.R
について説明します。
ui.R
には以下の4点をそれぞれ準備します。
上記の4つのうち、Webページのレイアウトについては上の図には表現されていない部分です。HTMLの要素は文章のヘッダーや本文、画像などの表示に関わる部分です。入力と出力に関しては、上記の図に示した通り、入力データをserver.Rに送る部分と、server.Rで演算された結果をWebページ上に表示させる部分になります。以下にそれぞれの要素について説明していきます。
まずは、Webページ全体のレイアウトをui.Rに指定する必要があります。レイアウトの指定には、以下の表に示す関数が用いられます。
関数 | 主な引数 | 意味 |
---|---|---|
absolutePanel | fixed=FALSE | 絶対的な位置を指定したレイアウト(スクロールあり) |
fixedPanel | fixed=FALSE | 絶対的な位置を指定したレイアウト(スクロールされない) |
column | width | 列のレイアウト(widthは最大12で指定) |
fillPage | padding | ウインドウのサイズに合わせてコンテンツを表示 |
fixedPage | ― | 固定幅のページのレイアウト |
fixedRow | ― | 固定幅の行のレイアウト |
fluidPage | ― | 可変幅のページのレイアウト |
fluidRow | ― | 可変幅の行のレイアウト |
navbarPage | ― | ページ上にタブの表示を作成する |
sidebarLayout | ― | 左右にサイドバー、メインパネルを配置 |
sidebarPanel | width=4 | サイドバーのレイアウト |
mainPanel | width=8 | メインパネルのレイアウト |
tabsetPanel | ― | その列にタブの表示を作成する |
tabPanel | title | タブの中身のレイアウト |
レイアウトを決める関数にはある程度上下関係があります。最も上位に配置するレイアウトはnavbarPage
関数で、全体のページに渡るタブを作成します。
navbarPage
関数では、「タブを配置すること」だけしか設定できないため、タブの中身を別途準備する必要があります。タブの中身はtabPanel
関数を用いて記載していきます。tabPanel
関数の第一引数にはタブの名前(title
)を指定します。上記の図では、タブに記載された「textOutput」や「Plot・Table Output」というのがtitle
に当たります。
navbarPage
関数の下位にはfixedPage
関数・fluidPage
関数を配置するのが一般的です。navbarPage
関数はタブを用いない場合には使用しないため、タブなしのページの場合にはfixedPage
関数、fluidPage
関数が最上位のレイアウトとなります。fixedPage
関数、fluidPage
関数の差はレイアウトの幅を固定するか(fixedPage
関数)、ウインドウの幅に合わせて調整するか(fluidPage
関数)の違いです。
fixedPage
関数、fluidPage
関数の下位にはsidebarLayout
関数やcolumn
関数を配置します。sidebarLayout
関数の下位にはsidebarPanel
関数とmainPanel
関数を配置し、それぞれ1/3の幅を持つサイドバーと2/3の幅を持つメインパネルの2つを設定します。以下の図の左側(binsの入力)がサイドバー、右側(グラフ)がメインパネルとなります。
このサイドバーとメインパネルの幅はwidth
引数で変更することができます。Shinyでは、R markdownと同様にBootstrapによるデザインが採用されています。Bootstrapでは、Webページの幅全体を12とすることとされていますので、この幅12に従い、サイドバーとメインパネルのwidth
の和が12となるように調整します。デフォルトではサイドバーのwidth
が4、メインパネルのwidth
が8となっています。
column
関数は上記のsidebarPanel
関数やmainPanel
関数とよく似たものですが、上位にsidebarLayout
関数を必要としないものです。column
関数もwidth
引数で幅指定しますが、やはりすべてのcolumn
のwidth
の和が12となるように設定します。
column
関数やmainPanel
関数にタブを設定するためのレイアウトがtabsetPanel
関数です。ちょうどnavbarPanel
-tabPanel
の関係と同様に、tabsetPanel
関数の下にはtabPanel
関数を配置します。
以上のように、Shinyのレイアウトに関する関数はたくさんあり、上下関係が複雑です。上記で紹介していない関数もありますので、実際にはさらに複雑なレイアウトを取ることもできます。
レイアウトの上下関係は、関数をネストすることで指定することができます。例えば、以下の例ではfluidPage
の下にsidebarLayout
、sidebarLayout
の下にsidebarPanel
とmainPanel
を配置し、mainPanel
の下にtabsetPanel
、tabsetPanel
の下にtabPanel
を2枚配置しています。
レイアウトの例
fluidPage(
sidebarLayout(
sidebarPanel(
# サイドバーの中身を記載
),mainPanel(
# メインパネルの中身を記載
tabsetPanel(
tabPanel(
title = "パネル1"
# タブパネル1の中身を記載
),tabPanel(
title = "パネル2"
# タブパネル2の中身を記載
)
)
)
) )
Rは関数型言語ですので、すべてのレイアウトは関数、つまりカッコつきのスクリプトになります。また、sidebarPanel
や1枚目のtabPanel
のカッコの後にコンマがついています。1つのカッコの中に複数の要素がある場合にはコンマが必要です。ただし、カッコ内の最後の要素(上の例ではtabPanel(title = "パネル2")
)にはコンマは必要ありません。
Shinyのui.Rの記載では、レイアウトに関する関数が多層にネストされ、意味がわかりにくくなりやすいため、「Run App」でアプリを表示し、確認しながら作成するのがよいでしょう。
HTMLではヘッダーや改行、パラグラフは以下のように表現されます。
HTMLの記述
<h1> ヘッダー </h1>
<br> # 改行
<p> パラグラフ(文)</p>
Shinyも基本的にはHTMLをRから出力する仕組みとなっているため、Webアプリに文章を追加したい場合などにはHTMLを記述する必要があります。ShinyにはHTMLを直接挿入できるHTML
関数が備わっているため、HTMLをそのまま入力することができます。
HTML関数
HTML("<p>HTML直打ち</p>")
しかし、HTML
関数を用いるとHTMLのタグ(<p>
など)を入力しないといけないため、やや煩雑です。Shinyには、HTMLを追加するための専用の関数(shiny::tags
に登録されている関数)が備わっています。こちらを用いることで、簡単にヘッダーや文章を追加することができます。
HTMLを記載するための関数群
h1("ヘッダー1")
p("パラグラフ")
br() # 改行
hr() # 水平線
HTMLを追加する関数の一覧はnames(shiny::tags)
で確認できます。
shiny::tags
::tags |> names() |> head(10) # 10個だけ表示 shiny
[1] "a" "abbr" "address" "animate"
[5] "animateMotion" "animateTransform" "area" "article"
[9] "aside" "audio"
静的なHTMLとは異なり、Webアプリケーションではブラウザ上で入力した値に対して演算が行われ、表示が値によって変化するところに特徴があります。Shinyでは、このような入力(input)に対応した各種の部品(control widgets)が備わっています。control widgetsの一覧はShinyのギャラリーに示されています。
各control widgetsは以下のようなInput
関数群を用いることで設定することができます。
Input関数
numericInput(
inputId = "numeric_input",
label = "数値の入力",
value = 10,
min = 0,
max = 20,
step = 2
)
すべてのInput
関数は、inputId
を第一引数に取ります。このinputId
には文字列で指定し、server側で入力データを受け取るときに用います。inputId
の使い方についてはserverの解説時に詳しく説明します。第二引数にlabel
を文字列として取ります。label
はcontrol widgetsの題名としてwidgetに表示されます。3つ目以降の引数はそれぞれのInput
関数で異なっており、numericInput
では初期値(value
)、最小値(min
)、最大値(max
)、増加の幅(step
)を引数で設定することができます。
以下にInput
関数の一覧を示します。
関数 | 主な引数 | 意味 | 返り値 |
---|---|---|---|
actionButton | icon | ボタンを追加 | 数値 |
checkboxInput | choices, selected | チェックボックスを追加 | 論理型 |
checkboxGroupInput | choices, selected | チェックボックス群を追加 | 文字列 |
radioButtons | choices, selected | ラジオボタンを追加 | 文字列 |
selectInput | choices, selected | 選択リストを追加する | 文字列 |
dateInput | value, min, max | 日付の入力を追加 | 日付 |
dateRangeInput | start, end, format | 期間を指定する入力を追加 | 日付 |
numericInput | value, min, max | 数値を入力するボックスを追加 | 数値 |
sliderInput | value, min, max | 数値を選択するスライダーを追加 | 数値 |
textInput | value | 文字列を入力するボックスを追加 | 文字列 |
passwordInput | value | パスワードを入力するボックスを追加 | 文字列 |
Input
関数で設定したcontrol widgetsに従い、データがserver.Rに送られます。ui.Rには出力(output)も必要となりますが、outputに関してはサーバサイドの説明の後の方が分かりやすいと思いますので、まずはサーバサイドについて説明し、後ほどoutputについて説明します。
次に、server.Rを作成していきます。server.Rはfunction(input, output, session){}
という関数から始まります。この関数・引数はこのまま変更せずに使用し、function
の{}
の中に実行したいコードを記載していきます。
inputで入力されたデータをserver.Rで受け取ったら、server.Rに従い演算を行います。演算結果をoutputとしてHTMLに出力することで、ui.R側でinputに対応したoutputを表示します。server.Rにはこのoutputを準備するためのスクリプトを書くことになります。
outputを準備するときには、output$outputId
というオブジェクトにrender
関数を代入します。以下に例を示します。
outputをrender関数で準備する
$outputId <- renderText("Hello world!") output
このoutputId
はui側で読みだして利用します。上記のスクリプトの場合では、outputId
には"Hello world!"
が入っていますので、ui側でoutputId
を用いて文字列として"Hello world!"
を出力することができます。
とは言ってもこれでは単に文字列をui側で表示するだけになり、serverを介している意味がありません。Shinyが動的に動くようにするには、inputId
を用いて演算を行う必要があります。
server側でinputId
の情報を用いる場合には、input$inputId
と、リストに似た形の変数を指定します。
inputIdを用いてoutputを準備する
$outputId <- renderText(paste("Hello world", input$inputId, "!")) output
上記のような形でoutputId
を準備すると、renderText
内のスクリプトが実行されることで、input$inputId
の値(文字列や日付、数値等)に従ってoutput$outputId
が作成されます。この準備したoutputId
をui側で利用してやれば、inputの内容に従ったoutputを表示することができます。
ui側ではInput
関数を用いてinputId
を、server側ではrender
関数を用いてoutputId
を準備します。inputId
やoutputId
が重複していると、どれが正しいId
なのかわからなくなるため、inputId
やoutputId
が重複することは許されていません。単一の名前をinputId
、outputId
に指定する必要があります。また、input$inputId
をui.R内、output$outputId
をserver.R内で用いることはできません。
上記の通り、output$outputId
を準備する際には、render
関数群を用います。このrender
関数には、outputの種類(テキスト・グラフ・表など)によってそれぞれ専用の関数が準備されています。代表的なrender
関数の一覧を以下に示します。
関数 | 意味 |
---|---|
renderPlot | グラフ(プロット)を準備する |
renderText | テキスト(文字列)を準備する |
renderImage | 画像を準備する |
renderTable | テーブル(表)を準備する |
renderPrint | 文字列(consoleの出力)を準備する |
renderUI | UI(control widget)を準備する |
downloadHandler | ダウンロードファイルを準備する |
outputとして表示したいものがテキストならrenderText
、グラフならrenderPlot
、表ならrenderTable
関数を選択します。
server側では通常inputに従いデータを加工して、文字列やグラフ、表を準備します。ただし、このような演算は必ずしも1行のスクリプトで書けるとは限りません。render
関数では複数の行に渡るスクリプトを書くことができるようにするため、関数のカッコの内側に中カッコ({}
)を設定できるようになっています。この中カッコの中には複数行のスクリプトを書くことができます。
render関数内に複数のスクリプトを記載する
# 1行スクリプトの場合には中カッコが無くても問題ない
$output_text1 <- renderText("Hello world!")
output
# 2行以上の時に中カッコなしだとエラー
$output_text_error <-
outputrenderText(
<- paste("Hello world,", input$input_name, "!")
temp
temp
)
# 2行以上の時には中カッコを加える
$output_text2 <-
outputrenderText({
<- paste("Hello world,", input$input_name, "!")
temp
temp })
server.Rには、複数のoutput$outputId
を置くことができます。通常のRスクリプトとは異なり、server.Rの演算は上から下に順番に行われるわけではなく、inputの変更に従って必要な部分が実行される形となっています。また、server.Rでは、演算の部分とテキスト・グラフ・表を準備する部分を分離して記載する方法もあります。この分けて計算する部分(reactive
やobserve
などの関数を用いた演算)に関してはやや複雑ですので、後ほど説明します。
上記のように、server.Rでoutput$outputId
を準備します。このoutputId
を準備しただけではUIに表示されないため、ui.R側に出力を準備する必要があります。この出力の準備には、Output
関数群を用いることになります。Output
関数群の一覧を下の表に示します。
関数 | 対応するrender関数 | 意味 |
---|---|---|
textOutput | renderText | 文字列を出力する |
verbatimTextOutput | renderText・renderPrint | コード(文字列)を出力する |
plotOutput | renderPlot | 文字列(consoleの出力)を出力する |
tableOutput | renderTable | 表を出力する |
uiOutput | renderUI | control widgetsを出力する |
downloadButton | downloadHandler | ダウンロード出力用のボタンを準備する |
Output
関数はui.Rのレイアウト関連の関数内に記載し、Webページ上に配置していくことになります。Output
関数の使い方は単純で、引数にoutputId
を文字列として取るだけです。
Output関数
textOutput("outputID")
Input
関数→render
関数→Output
関数の流れができれば、入力に従う出力を返す、Shinyアプリが出来上がります。inputでinputId
を設定し、serverではinput$InputId
を変数として用いてoutput$outputId
を作成します。outputId
はOutput
関数の引数とすることでUIに配置します。一連の流れは以下の図のような形となります。
Shinyでは、ui.Rやserver.R内で変数や関数を設定したり、ライブラリを読み込んだりすることができます。ただし、Shinyのファイルが2つに分かれていることもあり、ui.Rやserver.R中にアプリの準備に関わるコードを書き込んでしまうと理解しにくくなります。
このような変数・関数の設定やライブラリの読み込みを分離して記載するために用いられるのがglobal.Rというファイルです。これは通常のRファイルであり、ui.Rやserver.Rを作成する時のようにRStudioの操作で作成するものではありません。作成中のアプリのフォルダ、ui.Rやserver.Rが保管されている場所にglobal.Rを作成し、変数・関数の設定、ライブラリの読み込み等を書き込みます。このglobal.RはShinyアプリを起動する際に実行されるため、ui.Rやserver.Rに余分なスクリプトを書き込む必要がなくなります。
また、Shinyで表示するための画像や動画、javascriptのコードなどは、ui.Rやserver.Rと同じフォルダ内にwwwという名前のフォルダを作成し、wwwフォルダ内に保存することで、Shinyで呼び出すことができます。必要に応じてglobal.Rやwwwフォルダを作成することで、コードを整理し、画像等をShinyで取り扱うことができるようになります。
Shinyでは上記の通り、inputが変更されるとserver.Rに書いたスクリプトが自動的に実行されます。Shinyでは、inputに対する変更を読み取り、自動的にそのinputが関与しているserver.R内のコードを実行するようになっています。このような仕組みはobserverと呼ばれるものによって行われています。observerはその名の通り、inputの変更を「観察」していて、変更に応じて必要なコードをすぐに実行するShinyの内部的な機能です。
このobserverの代表的なものが、output$outputId<-renderXX()
という表現と、observe
関数です。output$outputId<-renderXX()
は上に示した通り、output$outputId
にrender
関数を代入する表現です。この表現が書かれている部分はrender
関数内のinputId
の変更を捉えて、変更があればすぐにrender
関数内のコードが実行されます。observe
関数はそれ自体は何もしませんが、observe
関数内に存在するinputId
が変更されるとinputId
に関わるコードを即実行するという機能を持ちます。
render
関数、observe
関数のどちらのobserverもShinyを使っているときにも作成するときにも特に意識する必要はありません。ただし、重めの計算を含むShinyではこのobserverだけを用いていると、Shinyのレスポンスが悪くなってしまいます。
簡単な例として、以下のui.R、server.RからなるShinyアプリを実行するとします。
ui.R
# 実際にはuiにlayoutが必要
sliderInput("n1","N of rpois 1:",min = 1000000,max = 5000000,value = 2000000),
sliderInput("n2","N of rpois 2:",min = 1000000,max = 5000000,value = 2000000),
verbatimTextOutput("output_rpois")
server.R
#実際にはserverに`function`が必要
<- input$n1 |> rpois(5)
r1 <- input$n2 |> rpois(5)
r2
$output_rpois({
outputpaste(mean(r1), mean(r2))
})
上記は、平均値5のポアソン分布に従う乱数を100万~500万個、2セット作成し、その平均値を文字列として返すだけのShinyアプリです。返ってくる値はほぼ5になるので何の意味もないアプリですが、このShinyを走らせた場合、1つ目のsliderInput
(input$n1
)を変更すると、1つ目のsliderInput
の返り値(mean(r1)
)だけでなく、2つ目のsliderInput
の返り値(mean(r2)
)も計算され、値が変化します。
だから何なのか、という感じですが、input$n1
のみ変更しているのに、input$n1
が含まれるr1
の演算だけでなく、input$n2
のみが含まれるr2
の演算も勝手に行われている、ということになります。これがobserverが行っている演算の仕組みで、必要となるコードを自動的に検出し、すべての演算を実行します。図で示すと、以下のような流れで演算が行われていることになります。
input$n1
が変更されると、input$n1
に依存しているserver.R側のr1
が演算されます。r1
に依存しているoutput$output_rpois
が演算されるわけですが、このoutput$output_rpois
の演算にはr2
が必要です。r2
を読みに行きますが、r2
はinput$n2
に依存しています。input$n2
を読みに行きます。input$n2
は変更されていませんが、値はありますので、r2
を演算します。r1
、r2
がそろったので、output$output_rpois
を演算します。input$n2
には変更がなく、r2
を演算する必要はなさそうに見えますが、observerは必要なコードをすべて、即時に演算するため、input$n2
からr2
を演算することになります。
このような演算はr2
に当たる部分の演算が軽いうちは大きな問題にはなりませんが、r2
の演算がとても重いとき、例えばデータベースに問い合わせて大きなデータを読み出し、時系列分析した結果が代入されている、といった場合には演算に非常に時間がかかるようになり、Shinyアプリのレスポンスが悪くなります。ですので、重めの演算はできれば演算回数を減らしたいところです。
上記のように、observerでは必要のない演算を繰り返し行うことになります。このように演算を繰り返したくない場合に用いるのが、reactive
関数です。reactive
関数は、
reactive
関数内のinput$inputId
に変更があった場合には演算を行うreactive
関数内のinput$inputId
に変更がない場合は、以前に演算した値(cache)を返すという特徴を持った関数です。このreactive
関数を用いて以下のようにserver.Rを書き直します。
server.R(reactive関数を用いた方法)
<- reactive(input$n1 |> rpois(5))
r1 <- reactive(input$n2 |> rpois(5))
r2
$output_rpois({
outputpaste(mean(r1()), mean(r2()))
})
上のコードでは、reactive
関数の引数としてinput$n1 |> rpois(5)
を取り、r1
に代入しています。このr1
は関数オブジェクトになっており、値を呼び出す場合にはカッコを付ける(r1()
)必要があります。同様にr2
をreactive
関数を用いて準備しています。
このようなserver.Rを用いた場合、Shinyの初回起動時にはすべての変数(r1
、r2
、output$output_rpois
)が演算されます。これはreactive
を用いない場合と同じです。
Shinyアプリの起動後に、input$n1
を変更した場合には、上記のreactive
を用いない場合とは演算の経路が異なってきます。reactive
を用いた場合の演算の経路を以下の図に示します。
1、2に関してはobserverの時と同じです。output$output_rpois
を準備する際にr2
を読みに行きますが、この際にr2
がreactive
で包まれていると、前回演算した時のr2
(cache)の値をoutput$output_rpois
の準備に用いることができ、r2
をinput$n2
から再演算しなくなります。
上記のようなr1
、r2
の演算コストの低いShinyアプリではreactive
を設定する必要は必ずしもありませんが、reactive
関数をうまく用いることでよりレスポンスのよいアプリを作成することができるでしょう。
reactive
は比較的使い方を理解しやすいのですが、observe
は何も返さず、使い方のわからない関数です。これは、observe
が他の関数を準備するために作成されたprimitiveな(大元の)関数であるためです。observe
をより分かりやすく、実用的に利用するための関数がobserveEvent
関数です。observeEvent
関数は第一引数にinput$inputId
、第二引数にスクリプトを取ります。observeEvent
は主にactionButton
と共に用い、actionButton
を押した時に後ろに記載したコード実行されるような場合に用います。observeEvent
自体は返り値を返さないので、この関数自体をoutput$outputId
に代入することはできません。
server.R: observeEvent関数
observeEvent(input$inputButton1, {message("button pressed.")})
一方で、ボタンを押すなどのイベントによってreactiveに応答を求める時にはeventReactive
を用います。eventReactive
の返り値は関数オブジェクトで、reactive
と同じようにカッコをつけることでserver.R内で呼び出すことができます。
server.R: eventReactive関数
<- eventReactive(input$inputButton1, {print("button pressed.")}) buttonpressed
reavtive
の名前がついている関数は他にもあります(reactiveVal
、reactiveValue
、reactiveTimer
)。名前が紛らわしい上にレスポンスが分かりにくい関数群ですが、それぞれに特徴があります。
まず、reactiveVal
とreactiveValues
です。この2つは値を記録しておくために用いる関数ですが、reactive
の特徴(cacheなど)と、参照渡しの特徴を持ちます。
reactiveValとreactiveValue
<- y <- reactiveVal(5) # xとyにreactiveVal(5)を代入
x # xは5(Quartoでは演算されない)
x # yも5(Quartoでは演算されない)
y
x(10) # xの値を10に更新
# xは10(Quartoでは演算されない)
x # yも10になる(Quartoでは演算されない)
y
<- reactiveValues(a = 10, b = 15) # reactiveValuesには複数の値を設定できる
z $a # render関数などの中で呼び出さないとエラー z
値渡し(Rの変数はほぼすべてこちら)とは異なり、上記の例であればx
が変更されるとy
も変更される、参照渡しが行われます。
reactiveVal
は1つの値のみ、reactiveValues
はリストのように複数の値を保存することができます。reactiveVal
は通常のRのコンソールでも呼び出すことができますが、reactiveValues
はreactive
/observerの中でしか呼び出せません。reactiveValues
ではリストのように、$
を用いて値を呼び出します。
reactiveVal
もreactiveValues
もいまいち使い方が難しい関数ですが、数値を一時的に保存する場合には単なるreactive
や通常の変数ではなく、こちらを使うことを想定されているのかもしれません。
Shinyアプリの作成時には、単にinputに対して応答を求めるようなものだけではなく、一定時間ごとに演算や表示を行ってほしい場合もあります。このように、一定時間ごとに演算を行うような場合には、reactiveTimer
関数を用います。reactiveTimer
関数は引数に時間(intervalMs
、ミリ秒単位)を取ります。reactiveTimer
関数を用いることで、引数で指定した時間ごとに演算が行われるような仕組みをShinyに持ち込むことができます。
reactiveTimer
<- reactiveTimer(2000)
a
$outputId <- renderText({
outputa() # reactiveTimerをrender関数内で呼び出す
# 2秒ごとに表示される
paste0("Hello world, ", input$inputId, "! ", Sys.time())
})
上記の例では、input$inputId
の変更がoutput$outputId
の更新を引き起こさず、2秒(2000ミリ秒)おきにoutput$outputId
が更新されるようになります。このreactiveTimer
と類似した関数として、invalidateLater
関数があります。invalidateLater
関数はreactiveTimer
と同じく初期のShinyから実装されている関数で、より安全でシンプルな関数であるとされています。invalidateLater
関数は直接render
関数内に書いて用います。ですので、上のコードと同じ演算を以下のような形で実装することができます。
invalidatedLater
$outputId <- renderText({
outputinvalidatedLater(2000)
paste0("Hello world, ", input$inputId, "! ", Sys.time())
})
ココまではShinyアプリをui.R、server.Rの2つのファイルで開発する方法について説明してきました。複雑なShinyアプリを作成したい場合には、この2つのファイルを用いる方法が有効です。一方で、小規模のアプリを作る場合には2つのファイルを行き来するのが煩雑となる場合もあります。小規模アプリの開発では、app.Rという1つのファイルでShinyアプリを作成することもできます。
app.Rを準備する場合には、上記の図2に示したファイル作成のウインドウで、「Single file (app.R)」を選択します。
app.Rを作成すると、以下のように1ファイル内にui
とserver
が記載されたファイルが表示されます。ui
にはfluidPage
やfixedPage
、TabsetPanel
から始まるレイアウトを代入し、この中身にuiのレイアウト、input
、Output
等を記載していきます。
server
には、server.Rと同様にfunction(input, output)
を代入し、function
の{}
の中に実行したいコードを記載していきます。
最後のshinyApp(ui=ui, server=server)
はShinyアプリを実行するための関数です。ですので、app.Rでもui.R/server.Rでも、uiとserverの中身を作成していくことには違いはありません。
app.Rの構成
<- fluidPage(
ui # ....コードを記載....
)
<- function(input, output){
server # .....コードを記載....
}
shinyApp(ui = ui, server = server)
Shinyには、上記のlayout
、Input
、Output
、render
などの基本的な関数だけでなく、追加の機能を与えるためのライブラリ(Extensions)が豊富に存在します。以下に代表的なExtensionsの例を示します。
ShinyのデザインをBootstrapに従って変更するためのライブラリです。
Shinyでダッシュボードを簡単に作成するためのライブラリです。基本的にはShinyにダッシュボード用のレイアウト関数を追加するライブラリとなっています。
shinythemes
と同様にShinyのデザインを変更するためのライブラリです。こちらは前章で紹介したR markdown・QuartoのHTML出力のデザインを変更することもできます。また、レイアウトの関数をいくつか備えており、これらのレイアウト関数を用いることでダッシュボードを作成することもできます。shinythemes
とshinydashboard
はかなり前に開発されたライブラリであるため、このbslib
に統合されたような形となっているようです。デザイン性の高いShinyアプリを作成するのであれば、まずこのライブラリを用いることを検討するのがよいでしょう。
ShinyやR markdownにJavascript製の表を表示するためのライブラリです。Javascriptで開発され、用いられているDataTablesというライブラリをRに持ち込んだものです。Shinyで用いるための専用のOutput
、render
関数を備えています。
Shinyにデータを演算中(ローディング中)であることを示すアニメーションを追加するためのライブラリです。計算が重めのShinyを作成する際に用いるとよいでしょう。
ShinyにIDとパスワードによるログイン機構を追加するためのライブラリです。セキュリティ的には怪しいので、あまり信用はできませんが、社内のシステムで用いるぐらいなら耐えられるかもしれません。
Shinyにはこの他にもたくさんのExtensionsが存在します。このGithubのページに一覧が示されていますので、参考にされるとよいでしょう。
また、Shinyの開発を行っている企業であるappsilonは、Shiny開発のフレームワークやデザインに関するライブラリを多数開発しています。ハイエンドなShinyアプリを開発したいのであればチャレンジする価値があるかと思います。
Shinyアプリを作成したら、他の人に使ってもらってデータを共有・説明したいはずです。Shinyアプリを自分のPCで動かすだけであれば、Rから実行するのが最も簡単ですが、Webを通じてShinyアプリを公表したい場合には、アプリをデプロイする必要があります。
デプロイとは、インターネットでアクセスできるPC(Webサーバー)にWebアプリを実行する環境を整えることを指します。我々が使っている多くのWebサービス(Webアプリ)は、Amazon Web Services(AWS)やGoogle CloudなどのWebサーバーで実行されています。Shinyアプリも同様にWebサーバーにデプロイすることで、Webブラウザからアクセスし、利用することができます。
Shinyの開発元であるPositはShinyアプリを簡単にデプロイするサービスである、shinyapps.ioやPosit Connectを展開しています。また、同じくPositが開発したShiny serverを用いることで、AWSやGoogle Cloudに比較的簡単にデプロイすることができます。
Posit Connectはエンタープライズ版で、セキュリティ面などがしっかりしています。一方で価格が相当高いので(今は公開されていませんが、昔は$5000/monthぐらいからだったと思います)、個人で安価にデプロイしたいのであればShinyapps.ioかShiny serverの2択となります。
最も簡単に、無料、もしくは安価にShinyアプリを公開したいのであれば、Shinyapps.ioを用いるのがよいでしょう。Shinyapps.ioを用いるためには、まずShinyapps.ioのホームページ上でアカウントを作成する必要があります。shinyapps.ioに移動し、「sign up」からアカウントを作成しましょう。shinyapps.ioのアカウントを作らずに、Googleやfacebookのアカウントを用いてログインすることもできます。
アカウントが作成できたら、Rstudio上で、エディタの右上にある「Publish」のボタンをクリックします。
publishボタンを押すと、初回には2つのライブラリ(packrat
(Atkins et al. 2023)とrsconnect
(Atkins et al. 2024))のインストールが求められますので、RStudioの指示に従いライブラリをインストールします。
次に、接続するアカウントの種類を選択するウインドウが表示されますので、「shinyapps.io」を選択します。
shinyapps.ioを選択すると、Tokenを入力するよう指示が出ますので、shinyapps.ioにログインし、アカウント名が表示されている部分からTokenを表示、コピーして貼り付けます。
Tokenを貼り付け、Connect Accountを選択すると、shinyapps.ioのアカウントとRStudioが繋がり、簡単にShinyアプリをデプロイできるようになります。
この状態で再び「Publish」をクリックすると、下のようなダイアログが表示されます。Publishポタンを押せばデプロイは完了です。
Publishした後にWebブラウザからshinyapps.ioにログインすると、以下のようなページが表示されます。Publishしたアプリのリストが表示されていますので、以下の図の赤枠の部分をクリックするとアプリを起動することができます。
この方法で実行したShinyアプリはローカル(自分のPC)で演算するのではなく、Positが管理しているWebサーバでアプリを実行するようになっています。URLをコピーし、共有したい人にアクセスしてもらうことでShinyアプリをWeb上で実行してもらうことができます。
上記のshinyapps.ioを用いる方法は非常に簡単で、お金もかかりませんが、一方で演算速度は遅く、たくさんの人が同時に接続することもできません。また、月あたりの使用時間が制限されています。shinyapps.ioは基本的にすべての人にShinyアプリを公開する形でデプロイするため、秘密情報を含むShinyアプリに利用するのにも向いていません。
このようなサーバーの速度・接続時間・秘密情報の問題を解決するには、自前でWebサーバーを立ち上げ、管理する必要があります。自前でサーバーを立ち上げるには、個人や会社のサーバーを利用する方法とWebサーバー(Amazon Web ServicesやGoogle Cloud)を用いる方法がありますが、いずれの方法においてもShinyアプリのデプロイにはShiny serverを用いるのが一般的です。Shiny serverを用いたShinyアプリのデプロイにはコンテナを用いることが多いかと思います。Shiny server、コンテナやコンテナを用いたデプロイの方法はこの入門書の範囲を超えてしまいますので、こちらの記事やこの記事などを参照下さい。
上記の方法では、Webサーバーを用いてRでの演算をしており、自分のPC(ローカル)では演算を行いません。Webサーバーを用いる方法では、サーバーを借りる必要があり、お金がかかります。サーバーを借りず、アプリを使用する方のPCで直接Shinyアプリを実行する方法であれば、このようなサーバーを準備する必要はありません。
Shinyのコードだけを準備して、アプリを使用する方のPCでShinyアプリを実行する場合には、GitHubを用いるのが簡単です。GitHubはGitと呼ばれるバージョン管理システムのうち、リモートレポシトリと呼ばれるものを取り扱うWebサービスです。GitHubには公開・非公開のリモートレポジトリが多数登録されており、他者とコードを共有してプログラミングを行う際の重要なツールとなっています。
GitやGitHubの詳しい説明は他の参考資料をご参照下さい。
GitHubでは通常Gitのシステムを用いて、リモートレポジトリを作成・更新・アップロードするようにできていますが、Shinyアプリを共有する場合には、直接GitHubにコードを書き込んでも問題はありません。
GitHubにShinyアプリのコードを登録すると、Shiny::runGitHub
関数でそのアプリを実行できるようになります。この章の一番始めに紹介した以下のコードは、このrunGitHub
関数を用いてShinyアプリを実行するものとなっています。
runGitHub関数
if(!require(shiny)) install.packages(("shiny")) # shinyの読み込み
::runGitHub("surv_sim", "sb8001at") # runGitHub関数で"surv_sim"というレポジトリのアプリを読み込み、実行 shiny
上記の例では、"sb8001at"
というアカウントのGitHubのレポジトリである"surv_sim"
を読み込み、Rで実行させるためのコードです。GitHubではこのようなレポジトリを通常sb8001at/surv_sim
という形で表現します。このレポジトリは公開されているもので、このページでコードを確認することができます。
上記ページの「ui.R」を選択すると、以下のようにui.Rのコードを読むことができます。
上記のrunGitHub
関数はこのページのui.R、server.R、global.Rを読み込み、Shinyアプリを実行する関数です。GitHubでのアカウント作成・レポジトリの準備やコードの入力方法に関しては、この教科書の範囲から外れるためココでは詳細を説明しませんので、この記事などを参考にしていただければと思います。
最後に、Shinyアプリで用いられるUI、Serverのコードと実行時のページをイメージするためのShinyアプリを以下に示します。以下のコードをコピペしてRで実行していただければ、UIやServerのイメージがつかみやすいかと思います。
Shinyのサンプルアプリ
if(!require(shiny)) install.packages(("shiny")) # shinyの読み込み
::runGitHub("shiny_sample", "sb8001at") shiny