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に表示」という一連の流れで構成されます。
上記の部品を自由に組み合わせれば、統計解析、数理モデル、実験支援ツール、教育用シミュレータなど、幅広いアプリが構築可能です!
コメント