UI와 Server 구조
모든 Shiny 앱은 단 두 가지로 이루어져 있습니다. 사용자가 보는 화면인 UI, 그리고 그 뒤에서 계산을 처리하는 Server입니다. 레스토랑으로 비유하면 UI는 홀이고, Server는 주방입니다.
Shiny 앱의 뼈대
모든 Shiny 앱은 아래 세 줄로 시작합니다.
library(shiny)
ui <- fluidPage()
server <- function(input, output, session) {}
shinyApp(ui, server)
ui를 정의하고, server를 정의하고, shinyApp(ui, server)로 둘을 연결해 앱을 실행합니다. 이 구조는 어떤 Shiny 앱을 만들더라도 변하지 않습니다.
ui — 사용자 인터페이스
ui는 브라우저에 표시되는 HTML을 정의합니다. R 함수들이 내부적으로 HTML을 생성하기 때문에 HTML을 직접 작성할 필요가 없습니다.
ui <- fluidPage(
titlePanel("학생 성적 조회"),
sidebarLayout(
sidebarPanel(
selectInput(
inputId = "dept",
label = "학과 선택",
choices = c("통계", "수학", "컴퓨터")
),
sliderInput(
inputId = "min_score",
label = "최소 점수",
min = 0, max = 100, value = 70
)
),
mainPanel(
tableOutput(outputId = "result_table"),
plotOutput(outputId = "score_plot")
)
)
)
UI에서 중요한 두 가지 개념이 있습니다.
inputId — 입력 위젯의 이름입니다. Server에서 input$dept, input$min_score처럼 이 이름으로 값을 읽어옵니다.
outputId — 출력 영역의 이름입니다. Server에서 output$result_table, output$score_plot처럼 이 이름으로 값을 채워 넣습니다.
server — 서버 함수
server는 계산 로직을 담은 함수입니다. Shiny가 앱을 시작할 때 이 함수를 호출합니다.
server <- function(input, output, session) {
output$result_table <- renderTable({
students |>
filter(dept == input$dept, score >= input$min_score) |>
arrange(desc(score))
})
output$score_plot <- renderPlot({
filtered <- students |>
filter(dept == input$dept, score >= input$min_score)
ggplot(filtered, aes(x = name, y = score)) +
geom_col(fill = "steelblue") +
theme_minimal() +
labs(title = paste(input$dept, "학과 성적"))
})
}
server 함수는 세 가지 인자를 받습니다.
| 인자 | 역할 |
|---|---|
input |
사용자가 입력한 값들의 목록 |
output |
브라우저에 표시할 결과를 채워 넣는 목록 |
session |
현재 세션 정보 (사용자 한 명의 연결 정보) |
input과 output의 흐름
UI와 Server가 어떻게 연결되는지 그림으로 이해해봅시다.
[브라우저] [R 서버]
─────────────────────────────────────────────────────
selectInput(inputId="dept") ──→ input$dept
sliderInput(inputId="min_score") ──→ input$min_score
output$result_table ──→ tableOutput(outputId="result_table")
output$score_plot ──→ plotOutput(outputId="score_plot")
UI에서 정의한 inputId는 Server에서 input$아이디 형태로 읽습니다. Server에서 output$아이디에 값을 넣으면 UI의 대응하는 outputId에 표시됩니다.
render*() 함수들
Server에서 출력을 생성할 때는 반드시 해당 출력 유형에 맞는 render*() 함수를 사용해야 합니다.
| UI 출력 함수 | Server 렌더 함수 | 출력 유형 |
|---|---|---|
tableOutput() |
renderTable() |
기본 테이블 |
plotOutput() |
renderPlot() |
ggplot2 그래프 |
textOutput() |
renderText() |
텍스트 문자열 |
verbatimTextOutput() |
renderPrint() |
콘솔 출력 형태 |
uiOutput() |
renderUI() |
동적 UI |
UI 함수와 render 함수는 반드시 쌍을 이뤄야 합니다. plotOutput()에는 renderPlot()이, tableOutput()에는 renderTable()이 대응합니다.
fluidPage() — 유동적 레이아웃
fluidPage()는 브라우저 창 크기에 맞게 자동으로 조절되는 레이아웃을 만듭니다. 모바일 화면이든 큰 모니터든 자연스럽게 적응합니다.
ui <- fluidPage(
# 앱 제목
titlePanel("제목"),
# 사이드바 + 메인 레이아웃
sidebarLayout(
sidebarPanel(
# 입력 위젯들
),
mainPanel(
# 출력 영역들
)
)
)
sidebarLayout()은 왼쪽에 좁은 사이드바, 오른쪽에 넓은 메인 패널을 배치하는 가장 일반적인 레이아웃입니다.
완전한 앱 예제
지금까지 배운 내용을 하나로 합친 완전한 앱입니다.
library(shiny)
library(tidyverse)
# 실습 데이터
students <- tibble(
name = c("Alice", "Bob", "Charlie", "Diana", "Eve"),
dept = c("통계", "수학", "통계", "컴퓨터", "수학"),
score = c(85, 92, 78, 95, 88)
)
ui <- fluidPage(
titlePanel("학생 성적 조회"),
sidebarLayout(
sidebarPanel(
selectInput("dept", "학과 선택",
choices = c("통계", "수학", "컴퓨터")),
sliderInput("min_score", "최소 점수",
min = 0, max = 100, value = 70)
),
mainPanel(
tableOutput("result_table"),
plotOutput("score_plot")
)
)
)
server <- function(input, output, session) {
output$result_table <- renderTable({
students |>
filter(dept == input$dept, score >= input$min_score)
})
output$score_plot <- renderPlot({
students |>
filter(dept == input$dept, score >= input$min_score) |>
ggplot(aes(x = name, y = score)) +
geom_col(fill = "steelblue") +
theme_minimal() +
labs(title = paste(input$dept, "학과 성적"))
})
}
shinyApp(ui, server)
이 앱을 실행하고 학과를 바꾸거나 슬라이더를 조절해보세요. 표와 그래프가 즉시 갱신됩니다. 이것이 Shiny의 반응형 프로그래밍입니다. 다음 챕터에서 이 반응형 동작이 어떻게 작동하는지 자세히 살펴봅니다.