워드 클라우드

단변량
기술통계
텍스트
워드 클라우드
저자

이광춘

공개

2026-07-06

내장 데이터셋과 외부 CSV 파일에서 단변량 변수를 선택하면 범주형, 숫자형을 인식하고 변수에 따라 적절한 기술통계를 계산하고 시각화할 수 있다.

#| label: shinylive-desc-wordcloud
#| viewerHeight: 600
#| standalone: true

library(shiny)
library(wordcloud2)
library(colourpicker)
library(tidytext)
library(dplyr)
library(DT)
library(stringr)
library(janeaustenr)

# 워드 클라우드 생성 함수
create_wordcloud <- function(text, min_freq, num_words, background, language, filter_single_char) {
  # 티블 생성
  text_df <- tibble(text = text)

  # 텍스트 전처리
  tidy_words <- text_df %>%
    unnest_tokens(word, text, token = "words", drop = TRUE) %>%
    filter(
      if (language == "english") str_detect(word, "[a-zA-Z]")
      else if (language == "korean") str_detect(word, "[가-힣]")
    ) %>%
    filter(!word %in% stop_words$word) %>%
    count(word, sort = TRUE) %>%
    ungroup()

  # 한글에서 한 글자 단어 필터링
  if (language == "korean" && filter_single_char) {
    tidy_words <- tidy_words %>%
      filter(str_length(word) > 1)
  }

  # 최소 빈도에 따른 단어 필터링
  filtered_words <- tidy_words %>%
    filter(n >= min_freq) %>%
    top_n(num_words, wt = n)

  # 필터링된 데이터프레임과 워드 클라우드 반환
  if (nrow(filtered_words) <= 1) {
    return(list(
      wordcloud = NULL,
      table = data.frame()
    ))
  } else {
    return(list(
      wordcloud = wordcloud2(filtered_words, backgroundColor = background),
      table = filtered_words
    ))
  }
}

# 워드 클라우드용 샘플 데이터
sample_data <- list(
  "없음" = "",
  "오만과 편견" = paste(janeaustenr::prideprejudice, collapse = " ")
)

library(bslib)
library(showtext)
tryCatch(sysfonts::font_add_google("Nanum Myeongjo", "kr"), error = function(e) NULL)
showtext_auto()

# ── Tufte 팔레트 · 테마 (자동 주입) ──────────────────────────
bit_cream <- "#fffff8"; bit_ink <- "#111111"; bit_rust <- "#8a1500"
bit_muted <- "#4a4a44"; bit_grid <- "#e8e6dc"; bit_axis <- "#8a8578"
theme_tufte_bit <- function(base_size = 13) {
  ggplot2::theme_minimal(base_size = base_size) +
    ggplot2::theme(
      text = ggplot2::element_text(family = "kr", colour = bit_ink),
      plot.title = ggplot2::element_text(family = "kr", size = base_size, hjust = 0),
      plot.background = ggplot2::element_rect(fill = bit_cream, colour = NA),
      panel.background = ggplot2::element_rect(fill = bit_cream, colour = NA),
      panel.grid.major = ggplot2::element_line(colour = bit_grid, linewidth = 0.3),
      panel.grid.minor = ggplot2::element_blank(),
      axis.line = ggplot2::element_line(colour = bit_axis, linewidth = 0.3),
      axis.ticks = ggplot2::element_line(colour = bit_axis, linewidth = 0.3)
    )
}
if (requireNamespace("ggplot2", quietly = TRUE)) ggplot2::theme_set(theme_tufte_bit())
bit_theme <- bs_theme(
  version = 5, bg = bit_cream, fg = bit_ink, primary = bit_rust,
  base_font = font_collection("Palatino Linotype", "Georgia", "Times New Roman", "serif"),
  "card-box-shadow" = "none"
)
# ─────────────────────────────────────────────────────────────

ui <- fluidPage(
  theme = bit_theme,
  h1("워드 클라우드"),
  sidebarLayout(
    sidebarPanel(
      selectInput("dataset", "데이터셋 선택:", choices = names(sample_data)),
      textAreaInput("text", "텍스트 입력:", "", rows = 5),
      radioButtons("language", "언어:",
                   choices = c("영어" = "english", "한글" = "korean"),
                   selected = "english"),
      conditionalPanel(
        condition = "input.language == 'korean'",
        checkboxInput("filter_single_char", "한 글자 단어 제외", value = TRUE)
      ),
      sliderInput("min_freq", "최소 빈도:", min = 1, max = 10, value = 1),
      sliderInput("num", "최대 단어 수:", min = 5, max = 100, value = 50),
      colourInput("col", "배경색:", value = "white")
    ),
    mainPanel(
      splitLayout(
        cellWidths = c("70%", "30%"),
        wordcloud2Output("cloud"),
        DTOutput("table")
      )
    )
  )
)

server <- function(input, output) {
  wordcloud_data <- reactive({
    req(input$text != "" | input$dataset != "없음") # 텍스트 입력 또는 데이터셋이 비어있지 않은지 확인
    text <- if (input$dataset != "없음") sample_data[[input$dataset]] else input$text
    create_wordcloud(text, input$min_freq, input$num, input$col, input$language, input$filter_single_char)
  })

  output$cloud <- renderWordcloud2({
    wordcloud_data()$wordcloud
  })

  output$table <- renderDT({
    datatable(wordcloud_data()$table, options = list(pageLength = 10))
  })
}

shinyApp(ui = ui, server = server)

라이센스