iBetter Books
수정

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의 반응형 프로그래밍입니다. 다음 챕터에서 이 반응형 동작이 어떻게 작동하는지 자세히 살펴봅니다.