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()가 FALSE나 NULL을 받으면 렌더링을 조용히 멈춥니다. 오류 메시지 대신 빈 공간이 표시됩니다.