Shinyアプリの基本構成を体験:複数タブ・グラフ・結果出力まで

Shiny

Shinyは、Rを使ってインタラクティブなWebアプリを簡単に作成できるパッケージです。プログラミングに不慣れでも、「やってみると意外と簡単に」UIと計算ロジックをRだけで記述でき、教育・研究・業務で活用できるツールが作れます。

この記事では、Shinyアプリを使って次のような基本構成を体験学習できるコードをサンプルとして紹介します。そのままRstudioにコピペして、試してみてください。


▶ Shiny構成の体験学習(このアプリで学べること)

  • タブ(tabsetPanel)で複数の画面を切り替え可能
  • 数値を入力(numericInput)し、項目の選択(radioButtons)もできる
  • 入力値に応じたグラフを描画(renderPlot
  • グラフ下に、計算結果を動的に出力(renderPrint
  • UIと計算ロジックが独立し、初心者でもカスタマイズしやすい構造

Shinyの基本的な部品(UI部品、サーバーロジック、出力との連携)をすべて含んでおり、Shinyで何ができるかを実感できるテンプレートになっています。


▶ コード(アプリ)に含まれる基本構成要素

機能使用するShiny部品
タブによる画面切り替えtabsetPanel() / tabPanel()
数値入力欄numericInput()
選択ボタンradioButtons()
計算に基づくプロットrenderPlot() + plotOutput()
数値出力renderPrint() + verbatimTextOutput()

▶ ベースとなっている計算式(応用例としての薬物動態シミュレーションの計算式)

このアプリでは、計算ロジックとして1コンパートメントモデルの濃度時間推移を例にしています。専門知識がなくても、以下の式をUI操作で体験できます:

経口投与モデル:

C(t) = F × (D × ka) / (Vd × (ka – ke)) × (e–ket – e–kat)

静脈投与モデル:

C(t) = D / Vd × e–ket

ここで:

  • D:投与量(mg)
  • Vd:分布容積(L)
  • ke:消失速度定数(1/hr)
  • ka:吸収速度定数(1/hr)※経口のみ
  • F:バイオアベイラビリティ(0〜1)※経口のみ

このような式をもとに、数値を入れると即座に結果がグラフに反映され、数値としても出力される体験ができます。


▶ コードと詳細な解説

以下のページでは、Shinyアプリの構成要素をすべて含んだコードを紹介しています。

コードは UI 部(入力画面のデザイン)と Server 部(計算処理)の2つに分かれます。

# 必要なパッケージを読み込み(未インストールなら自動インストール)
if (!requireNamespace("shiny", quietly = TRUE)) install.packages("shiny")

library(shiny)

# 半減期からKeを求める関数
to_ke <- function(t_half) log(2) / t_half
# CLtotとVdからKeを求める関数
from_cltot <- function(cl, vd) cl / vd

# ---- UIの定義 ----
ui <- fluidPage(
  titlePanel("Shinyで薬物動態シミュレーション"),
  
  tabsetPanel(
    tabPanel("経口投与",
             sidebarLayout(
               sidebarPanel(
                 numericInput("dose_po", "投与量 (mg)", 100, min = 1, max = 1000, step = 10),
                 numericInput("vd_po", "分布容積 Vd (L)", 10, min = 1, max = 100, step = 1),
                 numericInput("ka_po", "吸収速度定数 Ka (/hr)", 1.0, min = 0.01, max = 5, step = 0.01),
                 numericInput("f_po", "バイオアベイラビリティ F", 1.0, min = 0.01, max = 1.0, step = 0.01),
                 radioButtons("input_type_po", "入力するパラメータ:",
                              choices = c("Ke", "t1/2", "CLtot"), inline = TRUE),
                 numericInput("value_po", "数値を入力", 0.1, min = 0.01, max = 5, step = 0.01)
               ),
               mainPanel(
                 plotOutput("plot_po"),
                 verbatimTextOutput("result_po")
               )
             )
    ),
    tabPanel("静脈投与",
             sidebarLayout(
               sidebarPanel(
                 sliderInput("dose_iv", "投与量 (mg)", 100, min = 0, max = 1000, step = 10),
                 sliderInput("vd_iv", "分布容積 Vd (L)", 10, min = 0, max = 100, step = 1),
                 radioButtons("input_type_iv", "入力するパラメータ:",
                              choices = c("Ke", "t1/2", "CLtot"), inline = TRUE),
                 sliderInput("value_iv", "数値を選択", min = 0.01, max = 5, value = 0.1, step = 0.01)
               ),
               mainPanel(
                 plotOutput("plot_iv"),
                 verbatimTextOutput("result_iv")
               )
             )
    )
  )
)

# ---- サーバーロジック ----
server <- function(input, output) {
  # 経口投与グラフの描画
  output$plot_po <- renderPlot({
    dose <- input$dose_po
    vd <- input$vd_po
    ka <- input$ka_po
    f <- input$f_po
    ke <- switch(input$input_type_po,
                 "Ke" = input$value_po,
                 "t1/2" = to_ke(input$value_po),
                 "CLtot" = from_cltot(input$value_po, vd))
    time <- seq(0, 24, by = 0.1)
    conc <- f * dose * ka / (vd * (ka - ke)) * (exp(-ke * time) - exp(-ka * time))
    plot(time, conc, type = "l", col = "blue", lwd = 2,
         xlab = "Time (hr)", ylab = "Concentration (mg/L)",
         main = "経口投与モデル", ylim = c(0, max(conc, na.rm = TRUE) * 1.1))
  })
  
  # 経口投与の結果表示
  output$result_po <- renderPrint({
    vd <- input$vd_po
    ke <- switch(input$input_type_po,
                 "Ke" = input$value_po,
                 "t1/2" = to_ke(input$value_po),
                 "CLtot" = from_cltot(input$value_po, vd))
    t_half <- log(2) / ke
    cl <- ke * vd
    cat("Ke:", round(ke, 3), "1/hr\n")
    cat("t1/2:", round(t_half, 2), "hr\n")
    cat("CLtot:", round(cl, 2), "L/hr\n")
    cat("Ka:", input$ka_po, "/hr\n")
    cat("F:", input$f_po, "\n")
  })
  
  # 静脈投与グラフの描画
  output$plot_iv <- renderPlot({
    dose <- input$dose_iv
    vd <- input$vd_iv
    ke <- switch(input$input_type_iv,
                 "Ke" = input$value_iv,
                 "t1/2" = to_ke(input$value_iv),
                 "CLtot" = from_cltot(input$value_iv, vd))
    time <- seq(0, 24, by = 0.1)
    conc <- dose / vd * exp(-ke * time)
    plot(time, conc, type = "l", col = "darkgreen", lwd = 2,
         xlab = "Time (hr)", ylab = "Concentration (mg/L)",
         main = "静脈投与モデル", ylim = c(0, max(conc, na.rm = TRUE) * 1.2))
  })
  
  # 静脈投与の結果表示
  output$result_iv <- renderPrint({
    vd <- input$vd_iv
    ke <- switch(input$input_type_iv,
                 "Ke" = input$value_iv,
                 "t1/2" = to_ke(input$value_iv),
                 "CLtot" = from_cltot(input$value_iv, vd))
    t_half <- log(2) / ke
    cl <- ke * vd
    cat("Ke:", round(ke, 3), "1/hr\n")
    cat("t1/2:", round(t_half, 2), "hr\n")
    cat("CLtot:", round(cl, 2), "L/hr\n")
  })
}

# アプリ起動
shinyApp(ui = ui, server = server)

▶ Shinyコードの主な構成要素を詳しく解説

このアプリでは、Shinyの代表的なUI・出力機能をすべて使っています。
以下では、それぞれの部品の意味と使い方をより丁寧に解説していきます。


🔹 タブによる画面切り替え:tabsetPanel() / tabPanel()

tabsetPanel(
tabPanel("経口投与", ...),
tabPanel("静脈投与", ...)
)

役割:

  • 異なる表示内容を「タブ」で分けて見せるための仕組みです。
  • たとえば、「条件Aと条件Bで異なる計算式がある」「UIを分けてすっきり見せたい」場合にとても便利です。

補足:

  • tabsetPanel() の中に複数の tabPanel() を入れます。
  • タブの名前(例:”経口投与”)は、実際のタブの見出しに使われます。

🔹 数値入力欄:numericInput()

numericInput("dose_po", "投与量 (mg)", value = 100, min = 1, max = 1000, step = 10)

役割:

  • ユーザーが数値を入力するための部品です。スピンボタン(+−)も付いていて直感的に使えます。

引数の意味:

  • "dose_po":この値にアクセスするための名前(サーバー側では input$dose_po で取り出します)
  • "投与量 (mg)":表示されるラベル
  • value:初期値
  • min, max:選べる範囲の最小・最大
  • step:刻み幅(スピンで増減したときの幅)

使いどころ:

  • 実験条件の数値指定、スライダーよりも正確な入力が必要な場合に向いています。

🔹 スライダー入力欄:sliderInput()

sliderInput("dose_iv", "投与量 (mg)", min = 0, max = 1000, value = 100, step = 10)

役割:

  • ユーザーが マウス操作で直感的に値を選べる 入力部品です。
  • バーを左右に動かす形式なので、視覚的に分かりやすく、ユーザー体験も良好です。

引数の意味:

  • "dose_iv":この値にアクセスするための名前(サーバー側では input$dose_iv で取り出します)
  • "投与量 (mg)":表示されるラベル
  • min = 0:選べる値の最小値
  • max = 1000:選べる値の最大値
  • value = 100:初期値
  • step = 10:10刻みで選択できる(バーを動かしたときの移動幅)

使いどころ:

  • ざっくりした範囲で値を選ばせたいとき に便利
  • 入力ミスを防ぎたい場合 にも有効
  • 特に 教育用アプリや初心者向けのインターフェースに向いています

🔹 選択ボタン:radioButtons()

radioButtons("input_type_po", "入力するパラメータ:", choices = c("Ke", "t1/2", "CLtot"), inline = TRUE)

役割:

  • 複数の選択肢から「1つだけ」を選ばせるUI部品です。

引数の意味:

  • "input_type_po":サーバー側で使う名前
  • "入力するパラメータ:":ラベル
  • choices = c(...):選択肢のリスト(表示名と値が同じ場合はこのように簡略化可)
  • inline = TRUE:横並びに表示(デフォルトでは縦並び)

使いどころ:

  • 入力方式や計算モードの切り替えなど、明確な単一選択を必要とする場面で有効です。

🔹 計算に基づくプロット:renderPlot() + plotOutput()

# UI側
plotOutput("plot_po")

# サーバー側
output$plot_po <- renderPlot({
plot(x, y)
})

役割:

  • plotOutput()「ここにグラフを表示する」 というUIの場所の指定。
  • renderPlot()「どういうグラフを描くか」 のロジック。

しくみ:

  • サーバー関数 output$plot_po に、描画したいコード(ggplot()plot())を書きます。
  • UI側では plot_po という名前の出力が plotOutput() にバインドされます。

動的に変化:

  • 入力値が変わるたびにこの renderPlot() の中身が再実行され、グラフがリアルタイムで更新されます。

🔹 数値出力(計算結果の表示):renderPrint() + verbatimTextOutput()

# UI側
verbatimTextOutput("result_po")

# サーバー側
output$result_po <- renderPrint({
cat("Ke:", ke, "1/hr\\n")
})

役割:

  • 計算結果を「テキスト形式」で表示したいときに使います。

使いどころ:

  • グラフだけではわかりにくい「数値の詳細」を明示したいときに便利です。
  • renderPrint() 内で cat()print() を使って出力を調整できます。

リアクティブ:

  • こちらも input$... に反応するので、パラメータを変更するたびに自動で更新されます。

▶ 私の経験

実際私は、タブ(tabsetPanel)1では単回静脈内投与モデルでの計算式、2では単回経口投与モデルでの計算式、3では繰り返し静脈内投与モデルでの計算式、というように活用しました。

こうすることで、1つのShinyアプリを使用するだけで、複数のモデルの理解を深めることができ、便利です。

作成するうえではコードが煩雑になりますので、慣れてきたころにチャレンジするとよいと思います。


▶ まとめ

このアプリでは、Shinyを用いて以下のポイントを体験できます:

  • UIの構成:タブ、入力欄、選択肢の組み合わせ
  • リアクティブなロジック:入力値によって即座にグラフや結果を更新
  • Shinyアプリの基本構造を自分で編集可能な形で理解できる

このように、Shinyアプリの基本構成は「UIで入力」→「サーバーで処理」→「結果をUIに表示」という一連の流れで構成されます。

上記の部品を自由に組み合わせれば、統計解析、数理モデル、実験支援ツール、教育用シミュレータなど、幅広いアプリが構築可能です!


コメント

タイトルとURLをコピーしました