生存時間解析シミュレーター
if(!require(shiny)) install.packages(("shiny"))
shiny::runGitHub("surv_sim", "sb8001at-oss")データを解析した後には、その結果を用いて他の人にデータや解析の意味を説明する必要があります。説明するために用いるものの一つが文書やプレゼンテーションです。文書やプレゼンテーションはR markdown・Quarto(35章~43章参照)で作成することができます。
もう一つの方法は、データを表示するためのウェブページを作成する方法です。ウェブページも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"))
shiny::runGitHub("surv_sim", "sb8001at-oss")k-meansのアニメーション
if(!require(shiny)) install.packages(("shiny"))
shiny::runGitHub("kmeans_animated", "sb8001at-oss")時系列:ARIMAシミュレーター
if(!require(shiny)) install.packages(("shiny"))
shiny::runGitHub("ARIMAsim", "sb8001at-oss")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を追加する関数の一覧はnames(shiny::tags)で確認できます。
静的な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関数で準備する
output$outputId <- renderText("Hello world!")このoutputIdはui側で読みだして利用します。上記のスクリプトの場合では、outputIdには"Hello world!"が入っていますので、ui側でoutputIdを用いて文字列として"Hello world!"を出力することができます。
とは言ってもこれでは単に文字列をui側で表示するだけになり、serverを介している意味がありません。Shinyが動的に動くようにするには、inputIdを用いて演算を行う必要があります。
server側でinputIdの情報を用いる場合には、input$inputIdと、リストに似た形の変数を指定します。
inputIdを用いてoutputを準備する
output$outputId <- renderText(paste("Hello world", input$inputId, "!"))上記のような形で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$output_text1 <- renderText("Hello world!")
# 2行以上の時に中カッコなしだとエラー
output$output_text_error <-
renderText(
temp <- paste("Hello world,", input$input_name, "!")
temp
)
# 2行以上の時には中カッコを加える
output$output_text2 <-
renderText({
temp <- paste("Hello world,", input$input_name, "!")
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")上記は、平均値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を書き直します。
上のコードでは、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関数
buttonpressed <- eventReactive(input$inputButton1, {print("button pressed.")})reavtiveの名前がついている関数は他にもあります(reactiveVal、reactiveValue、reactiveTimer)。名前が紛らわしい上にレスポンスが分かりにくい関数群ですが、それぞれに特徴があります。
まず、reactiveValとreactiveValuesです。この2つは値を記録しておくために用いる関数ですが、reactiveの特徴(cacheなど)と、参照渡しの特徴を持ちます。
reactiveValとreactiveValue
x <- y <- reactiveVal(5) # xとyにreactiveVal(5)を代入
x # xは5(Quartoでは演算されない)
y # yも5(Quartoでは演算されない)
x(10) # xの値を10に更新
x # xは10(Quartoでは演算されない)
y # yも10になる(Quartoでは演算されない)
z <- reactiveValues(a = 10, b = 15) # reactiveValuesには複数の値を設定できる
z$a # render関数などの中で呼び出さないとエラー値渡し(Rの変数はほぼすべてこちら)とは異なり、上記の例であればxが変更されるとyも変更される、参照渡しが行われます。
reactiveValは1つの値のみ、reactiveValuesはリストのように複数の値を保存することができます。reactiveValは通常のRのコンソールでも呼び出すことができますが、reactiveValuesはreactive/observerの中でしか呼び出せません。reactiveValuesではリストのように、$を用いて値を呼び出します。
reactiveValもreactiveValuesもいまいち使い方が難しい関数ですが、数値を一時的に保存する場合には単なるreactiveや通常の変数ではなく、こちらを使うことが想定されているのかもしれません。
Shinyアプリの作成時には、単にinputに対して応答を求めるようなものだけではなく、一定時間ごとに演算や表示を行ってほしい場合もあります。このように、一定時間ごとに演算を行うような場合には、reactiveTimer関数を用います。reactiveTimer関数は引数に時間(intervalMs、ミリ秒単位)を取ります。reactiveTimer関数を用いることで、引数で指定した時間ごとに演算が行われるような仕組みをShinyに持ち込むことができます。
reactiveTimer
a <- reactiveTimer(2000)
output$outputId <- renderText({
a() # 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
output$outputId <- renderText({
invalidatedLater(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の中身を作成していくことには違いはありません。
Shinyには、上記のlayout、Input、Output、renderなどの基本的な関数だけでなく、追加の機能を与えるためのライブラリ(Extensions)が豊富に存在します。以下に代表的なExtensionsの例を示します。
shinythemes(Chang 2021):ShinyのデザインをBootstrapに従って変更するためのライブラリです。
shinydashboard(Chang and Borges Ribeiro 2021):Shinyでダッシュボードを簡単に作成するためのライブラリです。基本的にはShinyにダッシュボード用のレイアウト関数を追加するライブラリとなっています。
bslib(Sievert, Cheng, and Aden-Buie 2024):shinythemesと同様にShinyのデザインを変更するためのライブラリです。こちらは前章で紹介したR markdown・QuartoのHTML出力のデザインを変更することもできます。また、レイアウトの関数をいくつか備えており、これらのレイアウト関数を用いることでダッシュボードを作成することもできます。shinythemesとshinydashboardはかなり前に開発されたライブラリであるため、このbslibに統合されたような形となっているようです。デザイン性の高いShinyアプリを作成するのであれば、まずこのライブラリを用いることを検討するのがよいでしょう。
DT(Xie, Cheng, and Tan 2024):ShinyやR markdownにJavascript製の表を表示するためのライブラリです。Javascriptで開発され、用いられているDataTablesというライブラリをRに持ち込んだものです。Shinyで用いるための専用のOutput、render関数を備えています。
shinycssloaders(Sali and Attali 2020):Shinyにデータを演算中(ローディング中)であることを示すアニメーションを追加するためのライブラリです。計算が重めのShinyを作成する際に用いるとよいでしょう。
shinymanager(Thieurmel and Perrier 2022):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の読み込み
shiny::runGitHub("surv_sim", "sb8001at-oss") # runGitHub関数で"surv_sim"というレポジトリのアプリを読み込み、実行上記の例では、"sb8001at-oss"というアカウントのGitHubのレポジトリである"surv_sim"を読み込み、Rで実行させるためのコードです。GitHubではこのようなレポジトリを通常sb8001at-oss/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の読み込み
shiny::runGitHub("shiny_sample", "sb8001at-oss")