iBetter Books
수정

Ch 02. 출력 렌더링 (renderPlot, renderTable)

입력 위젯이 사용자의 말을 듣는 귀라면, 출력 함수는 결과를 보여주는 입입니다. Shiny에는 서버에서 값을 만드는 render* 함수와, UI에서 자리를 잡아두는 *Output 함수가 쌍을 이룹니다. 이 짝을 정확히 맞추는 것이 Shiny 출력의 핵심입니다.

render*와 *Output 짝 맞추기

출력은 항상 두 곳에 코드가 들어갑니다. UI에는 자리(placeholder)를 만들고, 서버에는 값을 계산하는 로직을 씁니다. 아래 표처럼 함수가 정확히 대응됩니다.

UI 함수 (*Output) 서버 함수 (render*) 출력 종류
plotOutput() renderPlot() ggplot2, base R 그래프
tableOutput() renderTable() 정적 HTML 테이블
dataTableOutput() renderDataTable() 인터랙티브 테이블 (기본 DT)
textOutput() renderText() 인라인 텍스트
verbatimTextOutput() renderPrint() 콘솔 출력 형식
uiOutput() renderUI() 동적 UI 요소
imageOutput() renderImage() 이미지 파일
htmlOutput() renderUI() HTML 코드 직접

outputId는 UI와 서버에서 같은 문자열을 써야 합니다. UI에서는 plotOutput("my_plot")처럼 넣고, 서버에서는 output$my_plot <- renderPlot({...})처럼 할당합니다.

renderPlot과 plotOutput

그래프를 그리는 가장 기본적인 조합입니다. renderPlot() 안에 ggplot2 코드나 base R 그래프 코드를 넣으면 됩니다.

library(shiny)
library(ggplot2)

ui <- fluidPage(
  sliderInput("bins", "구간 수", min = 5, max = 50, value = 20),
  plotOutput("hist_plot", height = "400px")  # 높이 지정
)

server <- function(input, output, session) {
  output$hist_plot <- renderPlot({
    ggplot(faithful, aes(x = eruptions)) +
      geom_histogram(bins = input$bins, fill = "#3498db", color = "white") +
      labs(title = "간헐천 분출 시간 분포",
           x = "분출 시간(분)", y = "빈도") +
      theme_minimal()
  })
}

shinyApp(ui, server)

plotOutput()의 주요 인수를 알아두면 유용합니다.

인수 설명 예시
width 너비 "100%", "600px"
height 높이 "400px"
click 클릭 이벤트 ID "plot_click"
brush 브러시(범위 선택) ID "plot_brush"
hover 호버 이벤트 ID "plot_hover"

renderPlot()에는 res 인수로 해상도(기본 72 dpi)를 지정하거나, width/height를 함수로 지정해 동적으로 크기를 변경할 수 있습니다.

renderTable과 tableOutput

renderTable()은 데이터프레임을 깔끔한 HTML 테이블로 바꿉니다. 인터랙션이 필요 없는 작은 요약 테이블에 알맞습니다.

ui <- fluidPage(
  selectInput("n_rows", "행 수", choices = c(5, 10, 20), selected = 10),
  tableOutput("summary_table")
)

server <- function(input, output, session) {
  output$summary_table <- renderTable({
    head(mtcars[, c("mpg", "cyl", "hp", "wt")],
         n = as.integer(input$n_rows))
  },
  rownames    = TRUE,    # 행 이름 표시
  striped     = TRUE,    # 줄무늬 배경
  hover       = TRUE,    # 마우스 호버 강조
  bordered    = TRUE,    # 테두리
  digits      = 2        # 소수점 자리수
  )
}

shinyApp(ui, server)

renderText와 renderPrint

텍스트 출력에는 두 가지 함수가 있고 용도가 다릅니다.

renderText()는 단일 문자열을 인라인으로 보여줍니다. textOutput()과 짝을 이루며, 결과가 문장 안에 자연스럽게 들어갑니다.

renderPrint()는 R 콘솔에서 값을 프린트한 것처럼 보여줍니다. verbatimTextOutput()과 짝을 이루며, summary() 결과나 구조 출력에 씁니다.

ui <- fluidPage(
  numericInput("x", "숫자 입력", value = 42),

  # 인라인 텍스트
  p("입력한 값의 제곱: ", textOutput("square", inline = TRUE), "입니다."),

  # 콘솔 스타일 출력
  verbatimTextOutput("summary_out")
)

server <- function(input, output, session) {
  output$square <- renderText({
    input$x ^ 2
  })

  output$summary_out <- renderPrint({
    cat("입력값:", input$x, "\n")
    cat("제곱:", input$x ^ 2, "\n")
    cat("세제곱:", input$x ^ 3, "\n")
  })
}

shinyApp(ui, server)

textOutput(inline = TRUE)로 설정하면 <span> 태그로 감싸져 문장 중간에 값이 들어갑니다. 기본값은 <div> 블록 요소입니다.

여러 출력 함께 사용하기

실제 앱에서는 여러 종류의 출력을 함께 배치합니다. 각 출력마다 고유한 outputId를 쓰면 됩니다.

library(shiny)
library(ggplot2)
library(dplyr)

ui <- fluidPage(
  titlePanel("mtcars 탐색기"),
  sidebarLayout(
    sidebarPanel(
      selectInput("x_var", "X축 변수",
                  choices = c("hp", "wt", "disp", "qsec"),
                  selected = "hp"),
      selectInput("y_var", "Y축 변수",
                  choices = c("mpg", "hp", "wt"),
                  selected = "mpg"),
      checkboxInput("add_smooth", "추세선 추가", value = TRUE)
    ),
    mainPanel(
      plotOutput("scatter"),
      hr(),
      h4("요약 통계"),
      tableOutput("stat_table"),
      verbatimTextOutput("corr_text")
    )
  )
)

server <- function(input, output, session) {
  output$scatter <- renderPlot({
    p <- ggplot(mtcars, aes(x = .data[[input$x_var]],
                             y = .data[[input$y_var]])) +
      geom_point(size = 3, alpha = 0.7, color = "#2c3e50") +
      labs(x = input$x_var, y = input$y_var) +
      theme_minimal()

    if (input$add_smooth) {
      p <- p + geom_smooth(method = "lm", se = TRUE, color = "#e74c3c")
    }
    p
  })

  output$stat_table <- renderTable({
    mtcars %>%
      summarise(
        평균_x = mean(.data[[input$x_var]]),
        평균_y = mean(.data[[input$y_var]]),
        최솟값_x = min(.data[[input$x_var]]),
        최댓값_x = max(.data[[input$x_var]])
      )
  }, digits = 2)

  output$corr_text <- renderPrint({
    r <- cor(mtcars[[input$x_var]], mtcars[[input$y_var]])
    cat(sprintf("상관계수(r): %.4f\n", r))
    cat(sprintf("결정계수(r²): %.4f\n", r^2))
  })
}

shinyApp(ui, server)

.data[[input$x_var]] 문법은 tidyverse에서 변수 이름을 문자열로 동적으로 참조할 때 사용하는 방식입니다. aes_string()은 구버전 방식이므로 이 방법을 권장합니다.

출력 조건부 표시와 숨기기

출력이 준비되지 않았을 때 빈 공간이나 오류 메시지 대신 아무것도 보이지 않게 하려면 req()를 씁니다.

output$result_plot <- renderPlot({
  req(input$run_btn > 0)  # 버튼을 눌러야만 실행
  req(nrow(filtered_data()) > 0)  # 데이터가 있어야만 실행

  ggplot(filtered_data(), aes(x = x, y = y)) +
    geom_point()
})

req()FALSENULL을 받으면 렌더링을 조용히 멈춥니다. 오류 메시지 대신 빈 공간이 표시됩니다.