iBetter Books
수정

Ch 02. DT로 인터랙티브 테이블

renderTable()로 만든 표는 보기엔 깔끔하지만 정렬이나 검색이 안 됩니다. DT 패키지의 datatable()은 클릭 한 번으로 정렬하고, 검색창에 입력하면 실시간으로 필터링되며, 페이지를 넘길 수 있는 완성도 높은 테이블을 제공합니다.

DT 설치와 기본 사용

install.packages("DT")

renderTable/tableOutput 대신 renderDT/DTOutput을 씁니다.

library(shiny)
library(DT)

ui <- fluidPage(
  titlePanel("DT 기본 예제"),
  DTOutput("my_table")  # tableOutput 대신 DTOutput
)

server <- function(input, output, session) {
  output$my_table <- renderDT({  # renderTable 대신 renderDT
    datatable(mtcars)
  })
}

shinyApp(ui, server)

이것만으로 정렬, 검색, 페이징이 모두 작동하는 테이블이 만들어집니다.

datatable 주요 옵션

datatable()options 인수로 동작을 세밀하게 제어합니다.

library(shiny)
library(DT)

ui <- fluidPage(
  DTOutput("options_table")
)

server <- function(input, output, session) {
  output$options_table <- renderDT({
    datatable(
      data      = iris,
      rownames  = FALSE,           # 행 번호 숨기기
      filter    = "top",           # 각 열 위에 필터 입력란 표시
      selection = "multiple",      # 다중 행 선택 허용
      options   = list(
        pageLength  = 10,          # 페이지당 행 수
        lengthMenu  = c(5, 10, 25, 50),  # 페이지 크기 선택 옵션
        scrollX     = TRUE,        # 가로 스크롤 활성화
        searching   = TRUE,        # 검색창 표시
        ordering    = TRUE,        # 열 정렬 허용
        info        = TRUE,        # "1~10 of 150 rows" 표시
        language    = list(        # 한국어 UI
          search     = "검색:",
          info       = "_START_~_END_ / 전체 _TOTAL_건",
          paginate   = list(
            previous = "이전",
            `next`   = "다음"
          ),
          lengthMenu = "_MENU_ 행씩 보기"
        )
      )
    )
  })
}

shinyApp(ui, server)

열 스타일링

formatStyle(), formatCurrency(), formatRound(), formatPercentage() 등으로 특정 열의 표시 형식과 스타일을 지정합니다.

server <- function(input, output, session) {
  output$styled_table <- renderDT({
    datatable(mtcars[, c("mpg", "cyl", "hp", "wt")],
              rownames = TRUE,
              options  = list(pageLength = 10)) %>%

      # 숫자 반올림
      formatRound(columns = c("mpg", "wt"), digits = 1) %>%

      # 색상 강조: 값에 따라 배경색 변경
      formatStyle(
        columns    = "mpg",
        background = styleColorBar(mtcars$mpg, "#3498db"),
        backgroundSize     = "100% 80%",
        backgroundRepeat   = "no-repeat",
        backgroundPosition = "center"
      ) %>%

      # 조건부 스타일: hp가 200 이상이면 빨간색 텍스트
      formatStyle(
        columns = "hp",
        color   = styleInterval(200, c("black", "red")),
        fontWeight = styleInterval(200, c("normal", "bold"))
      )
  })
}

styleColorBar()는 셀 안에 막대 그래프를 그려 값의 크기를 시각적으로 보여줍니다.

편집 가능한 테이블

editable = TRUE로 테이블 셀을 직접 편집할 수 있게 만들 수 있습니다.

library(shiny)
library(DT)

ui <- fluidPage(
  DTOutput("edit_table"),
  hr(),
  h4("수정된 데이터"),
  verbatimTextOutput("changed_cells")
)

server <- function(input, output, session) {
  # 반응형 데이터프레임
  df <- reactiveVal(
    data.frame(
      이름 = c("홍길동", "이순신", "강감찬"),
      점수 = c(85, 92, 78),
      등급 = c("B", "A", "C"),
      stringsAsFactors = FALSE
    )
  )

  output$edit_table <- renderDT({
    datatable(
      df(),
      editable = list(
        target  = "cell",         # 셀 단위 편집
        disable = list(columns = 0)  # 첫 번째 열(행 번호) 편집 비활성화
      ),
      rownames  = TRUE,
      selection = "none"
    )
  })

  # 셀 편집 이벤트 처리
  observeEvent(input$edit_table_cell_edit, {
    info <- input$edit_table_cell_edit
    # info$row: 행 번호, info$col: 열 번호, info$value: 새 값
    updated <- df()
    updated[info$row, info$col] <- DT::coerceValue(info$value,
                                                     updated[info$row, info$col])
    df(updated)
  })

  output$changed_cells <- renderPrint({
    print(df())
  })
}

shinyApp(ui, server)

편집 이벤트는 input${outputId}_cell_edit으로 받습니다. output$edit_table이면 input$edit_table_cell_edit입니다.

선택된 행 활용

사용자가 테이블에서 행을 선택하면 서버에서 그 정보를 활용할 수 있습니다.

library(shiny)
library(DT)
library(ggplot2)

ui <- fluidPage(
  fluidRow(
    column(6,
      DTOutput("car_table")
    ),
    column(6,
      plotOutput("detail_plot")
    )
  ),
  p("테이블에서 행을 클릭하면 해당 차량의 정보가 차트에 강조됩니다.")
)

server <- function(input, output, session) {
  output$car_table <- renderDT({
    datatable(
      mtcars[, c("mpg", "cyl", "hp", "wt")],
      selection = "single",   # 단일 행 선택
      options   = list(pageLength = 8)
    )
  })

  output$detail_plot <- renderPlot({
    selected_row <- input$car_table_rows_selected

    df <- data.frame(
      항목 = c("연비(mpg)", "실린더", "마력(hp)", "무게(wt)"),
      값   = c(0, 0, 0, 0)
    )

    if (length(selected_row) > 0) {
      row_data <- mtcars[selected_row, c("mpg", "cyl", "hp", "wt")]
      df$값 <- c(row_data$mpg, row_data$cyl, row_data$hp, row_data$wt)
      title_text <- paste("선택된 차량:", rownames(mtcars)[selected_row])
    } else {
      df$값 <- colMeans(mtcars[, c("mpg", "cyl", "hp", "wt")])
      title_text <- "전체 평균"
    }

    ggplot(df, aes(x = 항목, y = 값, fill = 항목)) +
      geom_col(show.legend = FALSE) +
      geom_text(aes(label = round(값, 1)),
                vjust = -0.3, size = 5) +
      labs(title = title_text, x = NULL, y = NULL) +
      theme_minimal() +
      theme(axis.text.y = element_blank())
  })
}

shinyApp(ui, server)

선택된 행 인덱스는 input${outputId}_rows_selected로 받습니다. 필터링 후 현재 보이는 행은 input${outputId}_rows_current, 전체 행 순서는 input${outputId}_rows_all로 접근합니다.

버튼 확장 기능

DT는 Excel 내보내기, CSV 다운로드, 프린트 버튼을 버튼 확장으로 추가할 수 있습니다.

output$table_with_buttons <- renderDT({
  datatable(
    mtcars,
    extensions = "Buttons",
    options    = list(
      dom     = "Bfrtip",  # B: Buttons, f: 검색, r: 처리 중 표시, t: 테이블, i: 정보, p: 페이징
      buttons = list(
        list(extend = "csv",   text = "CSV 저장"),
        list(extend = "excel", text = "Excel 저장"),
        list(extend = "print", text = "인쇄")
      )
    )
  )
})

extensions = "Buttons"dom = "Bfrtip"을 함께 설정해야 버튼이 나타납니다.