군집분석(Clustering Analysis)은 데이터를 유사한 그룹으로 나누는 비지도 학습 기법입니다. 데이터의 숨겨진 구조를 발견하고, 각 군집 내의 데이터 관측점들이 최대한 유사하도록 합니다. 대표적인 기법으로는 k-평균(K-means) 클러스터링이 있으며, 사전에 정해진 k개의 중심을 기준으로 데이터를 반복적으로 군집화합니다.
#| label: shinylive-lln-coin
#| viewerHeight: 700
#| standalone: true
library(shiny)
library(ggplot2)
library(dplyr)
library(DT)
library(RColorBrewer)
library(showtext)
showtext_auto()
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,
titlePanel('K-평균 군집분석과 PCA 시각화'),
sidebarLayout(
sidebarPanel(
radioButtons('dataset', '데이터셋 선택',
choices = c('USArrests', 'iris', '업로드된 데이터셋')),
conditionalPanel(
condition = "input.dataset == '업로드된 데이터셋'",
fileInput('file1', 'CSV 파일 선택',
accept = c('text/csv',
'text/comma-separated-values,text/plain',
'.csv'))
),
uiOutput("varselect_ui"),
sliderInput('clusters', '군집 개수', 3, min = 1, max = 10)
),
mainPanel(
tabsetPanel(
tabPanel("그래프", plotOutput('plot')),
tabPanel("군집별 관측점수", tableOutput('table1')),
tabPanel("군집별 요약통계", tableOutput('table2'))
)
)
)
)
server <- function(input, output, session) {
dataInput <- reactive({
switch(input$dataset,
'iris' = iris,
'USArrests' = USArrests,
'업로드된 데이터셋' = {
req(input$file1)
read.csv(input$file1$datapath)
})
})
observe({
req(dataInput())
num_vars <- names(dataInput())[sapply(dataInput(), is.numeric)]
updateSelectInput(session, 'xcol', choices = num_vars)
updateSelectInput(session, 'ycol', choices = num_vars, selected = num_vars[2])
})
output$varselect_ui <- renderUI({
req(dataInput())
num_vars <- names(dataInput())[sapply(dataInput(), is.numeric)]
list(
selectInput('xcol', 'X 변수', num_vars),
selectInput('ycol', 'Y 변수', num_vars, selected = num_vars[2])
)
})
selectedData <- reactive({
req(input$xcol, input$ycol)
dataInput()[, c(input$xcol, input$ycol)]
})
pca <- reactive({
req(selectedData())
prcomp(selectedData(), scale. = TRUE)
})
cluster_results <- reactive({
req(pca())
kmeans(pca()$x[, 1:2], input$clusters)
})
output$plot <- renderPlot({
req(cluster_results(), pca())
df <- data.frame(pca()$x[, 1:2])
ggplot(df, aes(x = PC1, y = PC2, color = factor(cluster_results()$cluster))) +
geom_point(size = 3, show.legend = TRUE) +
geom_text(data = data.frame(cluster_results()$centers),
aes(label = "X"), size = 8, color = "black") +
scale_color_brewer(type = "qual", palette = "Set2") +
scale_shape_manual("클러스터", values = c(1:input$clusters)) +
theme_tufte_bit() +
labs(title = paste("K =", input$clusters, "인 K-평균 군집분석"),
color = "군집")
})
output$table1 <- renderTable({
req(cluster_results(), dataInput())
df_res <- dataInput()
df_res$cluster <- cluster_results()$cluster
df_res %>%
group_by(cluster) %>%
tally(name = "개수") # 각 클러스터의 샘플 수 계산
})
output$table2 <- renderTable({
req(cluster_results(), dataInput())
df_res <- dataInput()
df_res$cluster <- cluster_results()$cluster
df_res %>% group_by(cluster) %>%
summarise(관측점수 = n(),
X_평균 = mean(get(input$xcol)),
Y_평균 = mean(get(input$ycol)))
})
}
shinyApp(ui = ui, server = server)