R reactable 완전 정복: 기본 표부터 Shiny 연동, data bars, CSV 다운로드까지

R reactable 패키지의 기본 사용법, colDef와 colFormat, 필터링과 검색, 그룹화와 nested table, reactablefmtr, Shiny 연동, CSV 다운로드까지 실무 관점에서 자세히 정리한 가이드입니다.
데이터·통계
저자

Ikmyungterran

공개

2026년 3월 8일

0. 이 글의 목표

reactable은 R에서 데이터 프레임을 단순한 표가 아니라, 정렬·검색·필터링·선택·확장·시각화까지 가능한 인터랙티브 테이블로 바꿔 주는 패키지입니다. 초보자 입장에서는 “보기 좋은 표 패키지”로 시작하지만, 실제로는 대시보드와 분석 리포트, Shiny 앱에서 매우 넓게 쓰입니다.

이 글은 data 폴더의 reactable 관련 정리 자료를 바탕으로, 아래 흐름으로 설명합니다.

  • reactable이 무엇인지
  • 가장 기본적인 테이블을 만드는 법
  • colDef()colFormat()으로 열을 제어하는 법
  • 검색, 필터, 페이지네이션, 그룹화, nested table
  • reactablefmtr로 데이터 바, 아이콘, 타일을 넣는 법
  • Shiny와 연동하는 법
  • CSV 다운로드와 실무 팁

즉, 이 글 하나를 읽으면 reactable을 “예쁘게 보여 주는 표” 수준이 아니라, 분석용 인터랙티브 테이블을 설계하는 도구로 이해할 수 있게 구성했습니다.


1. reactable은 어떤 패키지인가

reactable은 R 데이터 프레임을 HTML 기반의 인터랙티브 테이블로 렌더링합니다. 기본적으로 아래 기능을 매우 쉽게 붙일 수 있습니다.

기능 설명
정렬 열 머리글 클릭으로 정렬
검색 전체 테이블 검색
필터링 열별 필터 UI 제공
페이지네이션 많은 행을 페이지 단위로 나눠 보기
열 정의 colDef()로 열별 표시 방식 제어
포맷팅 숫자, 통화, 날짜, 퍼센트 형식 지정
확장 행 details로 하위 테이블 또는 상세 정보 표시
Shiny 연동 선택 상태 조회, 테이블 갱신, 다운로드 버튼 연결

핵심 철학은 간단합니다. 데이터 프레임 전체를 보여 준 뒤, 필요한 열에만 세밀한 동작을 덧붙이는 구조입니다.


2. 시작하기 전에 설치할 패키지

실무에서는 보통 아래 조합을 많이 씁니다.

install.packages("reactable")
install.packages("reactablefmtr")
install.packages("htmltools")
install.packages("shiny")

불러올 때는 상황에 따라 최소한으로 로드하면 됩니다.

library(reactable)
library(reactablefmtr)
library(htmltools)

여기서 역할은 다음과 같습니다.

패키지 역할
reactable 기본 테이블 생성
reactablefmtr 데이터 바, 아이콘, 타일, 제목, 소스 등 시각 보강
htmltools 링크, 이미지, HTML 태그 삽입
shiny Shiny 앱 안에서 reactable 사용

3. 가장 기본적인 reactable부터 만들기

가장 작은 예제는 데이터 프레임 하나를 그대로 넘기는 것입니다.

library(reactable)

data_tb <- data.frame(
  year = c(2021, 2022, 2023, 2024),
  num = c(14, 21, 18, 29)
)

reactable(data_tb)

이 상태만으로도 기본 정렬 기능이 붙은 테이블이 생성됩니다.

reactable로 만든 가장 단순한 2열 인터랙티브 테이블 예시

조금 더 익숙한 예제로 iris를 써 보면:

reactable(iris[1:10, ])

처음에는 이 단계를 충분히 익히는 것이 좋습니다. reactable()은 결국 데이터 프레임을 입력받아 인터랙티브 테이블로 바꾸는 함수라는 감각을 먼저 잡아야, 이후 columns, theme, details 같은 옵션이 자연스럽게 들어옵니다.


4. 자주 쓰는 기본 옵션 먼저 익히기

reactable()은 기본 표에서 시작해서 옵션을 차례로 붙이는 방식으로 사용합니다. 아래 옵션은 가장 자주 쓰입니다.

인수 의미 자주 쓰는 상황
searchable 전체 검색창 표시 테이블에서 키워드 검색이 필요할 때
filterable 필터 UI 표시 열 단위 조건 검색이 필요할 때
defaultPageSize 한 페이지 행 수 기본 페이지 크기 지정
showPageSizeOptions 페이지 크기 변경 UI 사용자가 직접 10/20/50행 선택
pagination 페이지 분할 여부 한 페이지 전체 표시 여부
minRows 최소 높이 유지 데이터가 적어도 표 높이를 안정적으로 유지
highlight hover 강조 행 가독성 개선
striped 줄무늬 행 배경 행 구분 강화
compact 조밀한 레이아웃 많은 정보를 작은 공간에 배치

예시는 아래와 같습니다.

library(reactable)

reactable(
  iris,
  searchable = TRUE,
  filterable = TRUE,
  highlight = TRUE,
  striped = TRUE,
  defaultPageSize = 5,
  showPageSizeOptions = TRUE,
  pageSizeOptions = c(5, 10, 20),
  minRows = 5
)

이 예제는 실무에서 가장 흔한 “검색 가능한 데이터 목록 테이블”의 기본형입니다.


5. colDef()를 이해하면 reactable이 본격적으로 쉬워진다

reactable의 진짜 핵심은 colDef()입니다. 테이블 전체 옵션은 reactable()에서 주고, 각 열별 동작은 columns = list(...) 안에서 colDef()로 정의합니다.

기본 형태는 다음과 같습니다.

reactable(
  iris,
  columns = list(
    Sepal.Length = colDef(name = "Sepal Length"),
    Petal.Length = colDef(name = "Petal Length")
  )
)

자주 쓰는 colDef() 인수는 아래와 같습니다.

인수 의미
name 열 제목 변경
show 열 숨김 여부
align 왼쪽/가운데/오른쪽 정렬
minWidth, maxWidth, width 열 너비
sortable 정렬 허용 여부
filterable 필터 허용 여부
format 표시 형식 지정
cell 셀 렌더링 함수 지정
style 스타일 지정
aggregate 그룹 집계 방식
na 결측값 표시 방식

예를 들어 일부 열만 세밀하게 조정하는 코드는 아래처럼 씁니다.

reactable(
  iris,
  columns = list(
    Species = colDef(
      name = "품종",
      minWidth = 140
    ),
    Sepal.Length = colDef(
      name = "꽃받침 길이",
      align = "right",
      maxWidth = 120
    ),
    Petal.Width = colDef(
      name = "꽃잎 너비",
      align = "right",
      sortable = TRUE
    )
  )
)

초보자는 reactable() 전체 옵션보다 colDef()를 먼저 익히는 편이 좋습니다. 실제 보고서와 대시보드 품질은 열 제어에서 갈립니다.


6. colFormat()으로 숫자, 날짜, 퍼센트를 읽기 쉽게 바꾸기

보고서용 테이블에서 가장 중요한 것 중 하나는 값이 읽히는 방식입니다. 예를 들어 0.9525556을 그대로 보여 주는 것보다 95.3%로 보여 주는 편이 훨씬 낫습니다.

이때 사용하는 것이 colFormat()입니다.

library(reactable)

data_fmt <- data.frame(
  price_usd = c(123456.56, 132, 5650.12),
  percent = c(0.9525556, 0.5, 0.112),
  temp = c(22, NA, 31),
  date = as.Date(c("2019-01-02", "2019-03-15", "2019-09-22"))
)

reactable(
  data_fmt,
  columns = list(
    price_usd = colDef(
      format = colFormat(prefix = "$", separators = TRUE, digits = 2)
    ),
    percent = colDef(
      format = colFormat(percent = TRUE, digits = 1)
    ),
    temp = colDef(
      format = colFormat(suffix = " °C")
    ),
    date = colDef(
      format = colFormat(date = TRUE, locales = "en-GB")
    )
  )
)

colFormat()에서 자주 쓰는 옵션은 다음과 같습니다.

옵션 의미
prefix 값 앞에 문자 붙이기
suffix 값 뒤에 문자 붙이기
digits 소수점 자리수
separators 천 단위 구분
percent 퍼센트 형식
currency 통화 형식
date, time, datetime 날짜/시간 표시
locales 로케일 지정

특히 다국어 리포트나 글로벌 데이터에서는 통화와 날짜 형식 차이가 크게 느껴지므로, locales를 의식적으로 쓰는 편이 좋습니다.


7. 결측값과 사용자 정의 셀 렌더링

실무 데이터에서는 결측값과 사용자 정의 출력이 거의 항상 등장합니다.

7.1 결측값 표시 바꾸기

reactable(
  data.frame(
    n = c(1, 2, NA, 4, 5),
    x = c(55, 27, NA, NaN, 19),
    y = c(1, NA, 0.25, 0.55, NA)
  ),
  columns = list(
    x = colDef(na = "–", format = colFormat(prefix = "$")),
    y = colDef(na = "NA", format = colFormat(percent = TRUE))
  )
)

na = "–"처럼 두면 표가 훨씬 깔끔해집니다.

7.2 링크, 텍스트 변환, 조건부 출력

cell = function(value, index) { ... } 형태를 쓰면 셀을 자유롭게 렌더링할 수 있습니다.

library(reactable)
library(htmltools)

data_custom <- MASS::Cars93[1:5, c("Manufacturer", "Model", "Type", "AirBags", "Price")]

reactable(
  data_custom,
  columns = list(
    Model = colDef(cell = function(value, index) {
      url <- sprintf("https://wikipedia.org/wiki/%s_%s", data_custom[index, "Manufacturer"], value)
      tags$a(href = url, target = "_blank", as.character(value))
    }),
    AirBags = colDef(cell = function(value) {
      if (value == "None") "No" else "Yes"
    }),
    Price = colDef(cell = function(value) {
      paste0("$", format(value * 1000, big.mark = ","))
    })
  )
)

이 방식은 아래 상황에 특히 유용합니다.

  • 링크 걸기
  • 아이콘 또는 이미지 삽입
  • 조건부 텍스트
  • 단위나 약어를 사용자 정의 규칙으로 변환

8. 검색, 필터링, 페이지네이션은 어떻게 설계해야 하나

데이터 탐색형 테이블에서는 searchable, filterable, pagination 조합이 중요합니다.

8.1 전체 검색과 열 필터

data_cars <- MASS::Cars93[1:20, c("Manufacturer", "Model", "Type", "AirBags", "Price")]

reactable(
  data_cars,
  searchable = TRUE,
  filterable = TRUE,
  columns = list(
    Price = colDef(filterable = FALSE)
  ),
  defaultPageSize = 5
)

이 코드는 전체 검색창을 보여 주되, Price 열은 필터 입력을 숨깁니다.

8.2 페이지네이션 방식

reactable(
  iris[1:50, ],
  paginationType = "jump",
  defaultPageSize = 4
)

reactable(
  iris[1:50, ],
  paginationType = "simple",
  defaultPageSize = 4
)

reactable(
  iris[1:20, ],
  pagination = FALSE,
  highlight = TRUE,
  height = 250
)

페이지네이션을 고를 때는 목적이 중요합니다.

목적 추천 방식
탐색형 목록 기본 pagination
빠른 페이지 이동 paginationType = "jump"
단순 UI paginationType = "simple"
한 화면 전체 비교 pagination = FALSE

Palmer Penguins 데이터를 reactable로 표시한 페이지네이션 테이블 예시


9. 그룹화와 집계는 reactable의 강력한 장점이다

groupByaggregate를 쓰면 요약 테이블과 상세 테이블 사이를 자연스럽게 오갈 수 있습니다.

9.1 기본 그룹화

data_grp <- MASS::Cars93[10:22, c("Manufacturer", "Model", "Type", "Price", "MPG.city")]

reactable(data_grp, groupBy = "Manufacturer")

9.2 집계 추가

data_grp2 <- MASS::Cars93[14:38, c("Type", "Price", "MPG.city", "DriveTrain", "Man.trans.avail")]

reactable(
  data_grp2,
  groupBy = "Type",
  columns = list(
    Price = colDef(aggregate = "max"),
    MPG.city = colDef(aggregate = "mean", format = colFormat(digits = 1)),
    DriveTrain = colDef(aggregate = "unique"),
    Man.trans.avail = colDef(aggregate = "frequency")
  )
)

자주 쓰는 집계 방식은 다음과 같습니다.

  • "sum"
  • "mean"
  • "max"
  • "median"
  • "unique"
  • "frequency"

9.3 다중 그룹화

data_multi <- data.frame(
  State = state.name,
  Region = state.region,
  Division = state.division,
  Area = state.area
)

reactable(
  data_multi,
  groupBy = c("Region", "Division"),
  columns = list(
    Division = colDef(aggregate = "unique"),
    Area = colDef(aggregate = "sum", format = colFormat(separators = TRUE))
  ),
  bordered = TRUE
)

이 패턴은 부서-팀, 지역-지점, 제품군-상품 같은 계층형 데이터에서 매우 유용합니다.

groupBy와 aggregate를 적용한 reactable 그룹 요약 테이블 예시


10. Nested table과 details 행 확장

행을 클릭하면 하위 테이블을 열어 주는 방식도 reactable의 대표 기능입니다.

library(dplyr)
library(reactable)

data_nested <- MASS::Cars93[1:30, c("Type", "Make", "Model", "MPG.city", "MPG.highway")]

averages <- data_nested %>%
  group_by(Type) %>%
  summarize(
    MPG.city = mean(MPG.city),
    MPG.highway = mean(MPG.highway),
    .groups = "drop"
  )

reactable(
  averages,
  columns = list(
    Type = colDef(maxWidth = 250),
    MPG.city = colDef(format = colFormat(digits = 1)),
    MPG.highway = colDef(format = colFormat(digits = 1))
  ),
  onClick = "expand",
  details = function(index) {
    data_sub <- data_nested[data_nested$Type == averages$Type[index], ]
    reactable(
      data_sub,
      columns = list(
        Type = colDef(show = FALSE)
      )
    )
  }
)

이 구조는 아래처럼 쓰기 좋습니다.

  • 요약 KPI 행 -> 상세 원본 행 보기
  • 부서 합계 -> 직원별 내역 펼치기
  • 제품군 요약 -> 상품별 세부 목록 보기

행 확장과 하위 테이블이 결합된 reactable nested table 예시


11. 테마를 적용하면 표 분위기가 크게 달라진다

reactablefmtrreactable을 꾸미는 데 유용한 테마를 제공합니다.

library(reactable)
library(reactablefmtr)

reactable(iris, theme = fivethirtyeight())
reactable(iris, theme = nytimes())
reactable(iris, theme = dark())
reactable(iris, theme = superhero())

테마는 단순한 색상 변경을 넘어서, 헤더, 글꼴 느낌, 여백, 대비를 한 번에 바꿔 줍니다. 다만 실무에서는 테마만 의존하기보다, 필요한 부분만 커스터마이징하는 편이 더 안정적입니다.


12. reactablefmtr로 시각화 요소 넣기

reactablefmtrreactable을 “표”에서 “분석형 시각 테이블”로 확장해 줍니다.

대표 기능은 다음과 같습니다.

함수 역할
data_bars() 셀 안에 막대 그래프
color_tiles() 값 크기에 따라 배경 타일 색 표시
icon_assign() 값 크기를 아이콘 개수로 표현
icon_sets() 미리 정의된 아이콘 세트 사용
gauge_chart() 게이지 스타일 표현
add_title(), add_subtitle(), add_source() 테이블 상단/하단 메타 정보 추가

12.1 Data bars 예제

library(dplyr)
library(reactable)
library(reactablefmtr)

cars <- mtcars %>%
  tibble::rownames_to_column("model") %>%
  select(model, cyl, hp, qsec)

reactable(
  cars,
  columns = list(
    qsec = colDef(
      name = "1/4 mile time",
      minWidth = 220,
      cell = data_bars(
        data = cars,
        fill_color = c("#FAFAFA", "#D3D3D3", "#BFBFBF"),
        fill_gradient = TRUE,
        text_position = "inside-base",
        number_fmt = scales::number_format(accuracy = 0.1, suffix = "s"),
        bar_height = 12
      )
    )
  )
)

숫자를 막대 길이로 바꾸면, 정렬 없이도 상대적 차이가 즉시 보입니다.

12.2 Color tiles 예제

reactable(
  mtcars[1:10, c("mpg", "hp", "wt")],
  columns = list(
    mpg = colDef(cell = color_tiles(mtcars[1:10, ], bias = 1.2)),
    hp = colDef(cell = color_tiles(mtcars[1:10, ], bias = 0.8)),
    wt = colDef(cell = color_tiles(mtcars[1:10, ], bias = 1.0))
  )
)

12.3 아이콘 기반 표시

cars_icon <- mtcars %>%
  tibble::rownames_to_column("model") %>%
  dplyr::select(model, cyl, mpg)

reactable(
  cars_icon,
  columns = list(
    cyl = colDef(
      cell = icon_assign(
        cars_icon,
        fill_color = "slategrey",
        empty_color = "lightgrey",
        icon_size = 12
      )
    ),
    mpg = colDef(
      cell = icon_assign(
        cars_icon,
        icon = "envira",
        fill_color = "forestgreen",
        buckets = 5,
        show_values = "right"
      )
    )
  )
)

이런 표현은 단순히 “예쁜 표”를 넘어서, 한눈에 값의 상대적 수준을 읽게 해 준다는 점에서 의미가 있습니다.

reactablefmtr로 게이지, 순위, 아이콘, 데이터 바를 결합한 고급 테이블 예시

아이콘과 텍스트를 함께 사용해 연비와 가격을 표시한 reactable 셀 예시


13. 이미지, 아이콘, 제목, 소스 표시

보고서형 테이블에서는 텍스트만 있는 표보다 이미지와 메타 정보가 있는 표가 훨씬 읽기 쉽습니다.

13.1 로컬 이미지 삽입

library(reactable)
library(htmltools)

animals <- data.frame(
  Animal = c("beaver", "cow", "wolf", "goat"),
  Body = c(1.35, 465, 36.33, 27.66),
  Brain = c(8.1, 423, 119.5, 115)
)

reactable(
  animals,
  columns = list(
    Animal = colDef(cell = function(value) {
      image <- img(src = sprintf("./images/%s.png", value), height = "24px", alt = value)
      tagList(
        div(style = list(display = "inline-block", width = "45px"), image),
        value
      )
    })
  )
)

13.2 제목, 부제목, 출처 추가

library(reactablefmtr)

reactable(iris) %>%
  add_title("Iris Summary Table") %>%
  add_subtitle("Built with reactable and reactablefmtr") %>%
  add_source("Source: datasets::iris")

이런 메타 정보는 단순 장식이 아니라, 테이블이 독립된 리포트 컴포넌트처럼 읽히게 만들어 줍니다.

선수 사진과 팀 로고 이미지를 셀 안에 포함한 reactable 표 예시


14. Shiny 안에서 reactable 쓰기

reactable은 Shiny와 결합할 때 진가가 커집니다.

14.1 기본 렌더링

library(shiny)
library(reactable)

ui <- fluidPage(
  titlePanel("reactable example"),
  reactableOutput("table")
)

server <- function(input, output, session) {
  output$table <- renderReactable({
    reactable(iris)
  })
}

shinyApp(ui, server)

이 패턴은 plotOutput() / renderPlot()과 동일한 구조입니다.

14.2 선택 상태 읽기

library(shiny)
library(reactable)

ui <- fluidPage(
  reactableOutput("table"),
  verbatimTextOutput("selected")
)

server <- function(input, output, session) {
  selected <- reactive(getReactableState("table", "selected"))

  output$table <- renderReactable({
    reactable(iris, selection = "multiple", onClick = "select")
  })

  output$selected <- renderPrint({
    selected()
  })
}

shinyApp(ui, server)

getReactableState()는 테이블을 단순 출력이 아니라 사용자 상호작용이 있는 상태 객체로 다루게 해 줍니다.

14.3 테이블 업데이트

library(shiny)
library(reactable)

data <- MASS::Cars93[, 1:7]

ui <- fluidPage(
  actionButton("select_btn", "Select rows"),
  actionButton("clear_btn", "Clear selection"),
  actionButton("page_btn", "Change page"),
  reactableOutput("table")
)

server <- function(input, output, session) {
  output$table <- renderReactable({
    reactable(
      data,
      filterable = TRUE,
      searchable = TRUE,
      selection = "multiple"
    )
  })

  observeEvent(input$select_btn, {
    updateReactable("table", selected = c(1, 3, 5))
  })

  observeEvent(input$clear_btn, {
    updateReactable("table", selected = NA)
  })

  observeEvent(input$page_btn, {
    updateReactable("table", page = 3)
  })
}

shinyApp(ui, server)

updateReactable()를 쓰면 선택, 데이터, 확장, 페이지 상태를 서버에서 제어할 수 있습니다.


15. 다른 위젯과 연동하기

원문 자료에서는 leafletreactable을 연결하는 예시도 등장합니다. 핵심은 crosstalk::SharedData()나 상태 조회를 통해 “표에서 고른 행”과 “지도에서 보여 줄 위치”를 연결하는 것입니다.

이 구조가 중요한 이유는 reactable이 단독 위젯이 아니라, 대시보드 전체의 상호작용 허브가 될 수 있기 때문입니다.

예를 들어:

  • 표에서 선택한 행만 지도에 표시
  • 표에서 클릭한 항목 기준으로 상세 차트 갱신
  • 필터링된 테이블 결과를 다른 카드 지표와 동기화

즉, reactable은 결과 출력 위젯이면서 동시에 사용자 상호작용 입력원 역할도 합니다.


16. CSV 다운로드는 생각보다 실무적이다

표를 보여 주는 것만큼 자주 요구되는 기능이 다운로드입니다.

16.1 기본 다운로드 방식

library(shiny)
library(reactable)
library(htmltools)

csvDownloadButton <- function(id, filename = "data.csv", label = "Download as CSV") {
  tags$button(
    label,
    onclick = sprintf("Reactable.downloadDataCSV('%s', '%s')", id, filename)
  )
}

ui <- fluidPage(
  csvDownloadButton("cars_table", filename = "cars.csv"),
  reactableOutput("cars_table")
)

server <- function(input, output) {
  output$cars_table <- renderReactable({
    reactable(
      MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")],
      searchable = TRUE
    )
  })
}

shinyApp(ui, server)

이 방식의 장점은 클라이언트에서 필터링한 결과가 반영된 CSV를 바로 내려받을 수 있다는 점입니다.

16.2 한글 CSV 깨짐을 피하는 방식

원문에서도 강조한 것처럼 Reactable.downloadDataCSV()는 엑셀에서 인코딩 문제가 보일 수 있습니다. 이럴 때는 Reactable.getDataCSV()로 데이터를 가져와 BOM을 붙여 내려받는 방식이 더 안전합니다.

library(htmltools)
library(reactable)

my_csv_download <- tags$script(HTML("
  function mydownloadDataCSV(tableId, outfName = 'data.csv'){
    const data = Reactable.getDataCSV(tableId)
    var blob = new Blob(['\\uFEFF' + data], { type: 'text/csv;charset=UTF-16;' });
    var link = document.createElement('a');
    var url = URL.createObjectURL(blob);
    link.setAttribute('href', url);
    link.setAttribute('download', outfName);
    link.style.visibility = 'hidden';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }
"))

htmltools::browsable(
  tagList(
    my_csv_download,
    tags$button("Download as CSV", onclick = "mydownloadDataCSV('cars-table')"),
    reactable(MASS::Cars93, elementId = "cars-table")
  )
)

한국어 보고서를 다루는 경우 이 차이는 꽤 중요합니다.


17. 실무에서 많이 쓰는 설계 원칙

reactable은 기능이 많아서, 처음에는 무엇을 켜고 무엇을 끌지 헷갈리기 쉽습니다. 실무적으로는 아래 원칙이 유용합니다.

17.1 탐색형 테이블과 보고형 테이블을 구분하기

유형 특징 추천 옵션
탐색형 테이블 검색, 필터, 페이지 이동이 중요 searchable, filterable, defaultPageSize
보고형 테이블 한눈에 읽히는 시각 구조가 중요 pagination = FALSE, reactablefmtr, columnGroups

17.2 숫자 열은 시각화 요소를 적극 고려하기

정렬 가능한 숫자 열이라도, 사용자는 항상 정렬을 누르지 않습니다. 상대 비교가 중요한 열은 data_bars()color_tiles()를 고려하는 편이 좋습니다.

17.3 colDef()를 일관되게 유지하기

열마다 이름, 너비, 정렬, 포맷이 제각각이면 표가 산만해집니다. 자주 쓰는 패턴을 정해서 일관되게 적용하는 것이 좋습니다.


18. 자주 하는 실수

18.1 모든 열에 너무 많은 기능을 한 번에 넣기

검색, 필터, 데이터 바, 아이콘, 그룹화, 확장 행을 전부 동시에 넣으면 오히려 읽기 어려워집니다. 핵심 열 몇 개부터 강화하는 편이 낫습니다.

18.2 포맷팅 없이 원시 숫자를 그대로 노출하기

특히 금액, 비율, 날짜는 colFormat() 없이 보여 주면 표 품질이 급격히 떨어집니다.

18.3 Shiny 상태를 안 쓰고 단순 출력 위젯처럼만 보기

getReactableState()updateReactable()를 모르고 있으면 reactable의 절반만 쓰는 셈입니다.

18.4 다운로드 인코딩 문제를 나중에야 발견하기

한글이 포함된 경우라면 CSV 다운로드는 초반부터 실제 엑셀 열기까지 확인하는 편이 좋습니다.


19. 빠르게 시작하는 추천 학습 순서

  1. reactable(data)로 기본 테이블 만들기
  2. searchable, filterable, defaultPageSize 적용해 보기
  3. colDef()로 열 이름, 정렬, 너비, 숨김 제어해 보기
  4. colFormat()으로 숫자/날짜 포맷 맞추기
  5. groupByaggregate로 요약 테이블 만들기
  6. details로 nested table 만들기
  7. reactablefmtr로 data bars와 아이콘 넣기
  8. Shiny에서 renderReactable() / reactableOutput() 사용하기
  9. getReactableState()updateReactable() 연동하기
  10. CSV 다운로드까지 붙여 보기

이 순서대로 가면 reactable의 구조를 무리 없이 이해할 수 있습니다.


20. 자주 묻는 질문

Q1. reactableDT는 무엇이 다른가

둘 다 인터랙티브 테이블 패키지이지만, reactable은 상대적으로 더 현대적인 표현과 세밀한 셀 커스터마이징, Shiny 연동의 유연성이 강점입니다.

Q2. reactablefmtr는 꼭 써야 하나

아닙니다. 기본 테이블은 reactable만으로 충분합니다. 다만 데이터 바, 아이콘, 테마, 제목/소스 같은 시각 보강이 필요하면 매우 유용합니다.

Q3. 대용량 데이터에서도 괜찮은가

규모와 환경에 따라 다릅니다. 브라우저에 한 번에 너무 많은 데이터를 보내는 구조라면 부담이 생길 수 있으므로, 필터링이나 페이지네이션 전략을 함께 고려해야 합니다.

Q4. Shiny 없이 Quarto 문서에서도 쓸 수 있나

그렇습니다. reactable()은 HTML 위젯이므로 Quarto 문서 안에서도 잘 동작합니다. 다만 Shiny의 상태 조회와 업데이트 함수는 Shiny 환경에서 의미가 커집니다.


21. 마무리

reactable은 단순히 “예쁜 표 패키지”가 아닙니다. 데이터 탐색, 보고서형 요약, 대시보드 UI, Shiny 상호작용까지 한 흐름으로 연결하는 매우 강력한 도구입니다. 핵심은 reactable()로 시작하되, colDef()colFormat()으로 기본기를 다지고, 필요에 따라 groupBy, details, reactablefmtr, getReactableState(), updateReactable()까지 확장하는 것입니다.

처음에는 기능이 많아 보이지만, 실제로는 “기본 표 -> 열 제어 -> 시각화 -> 상호작용” 순서로 쌓아 가면 됩니다. 이 순서를 지키면 reactable을 훨씬 안정적으로 익힐 수 있습니다.

함께 읽으면 좋은 글