내장 데이터셋과 외부 CSV 파일에서 단변량 변수를 선택하면 범주형, 숫자형을 인식하고 변수에 따라 적절한 기술통계를 계산하고 시각화할 수 있다.
#| label: shinylive-desc-stat-univariate
#| viewerHeight: 600
#| standalone: true
library(shiny)
library(ggplot2)
library(showtext)
showtext_auto()
# 내장 데이터셋 리스트 (iris 제외)
datasets <- c("mtcars", "faithful", "ChickWeight")
# UI 정의
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("단변량 기술통계"),
sidebarLayout(
sidebarPanel(
fileInput("file", "CSV 파일 업로드", accept = ".csv"),
radioButtons("dataset", "데이터셋 선택", choices = c("Uploaded Dataset", datasets)),
uiOutput("variableInput"),
verbatimTextOutput("variableType")
),
mainPanel(
tabsetPanel(
tabPanel("시각화",
fluidRow(
column(6, plotOutput("plot1")),
column(6, plotOutput("plot2"))
)
),
tabPanel("요약 통계", verbatimTextOutput("summary"))
)
)
)
)
# 서버 로직
server <- function(input, output, session) {
# 업로드된 CSV 파일을 데이터프레임으로 변환
uploaded_data <- reactive({
req(input$file)
read.csv(input$file$datapath)
})
# 선택된 데이터셋
selected_data <- reactive({
if (input$dataset == "Uploaded Dataset") {
req(input$file)
uploaded_data()
} else {
get(input$dataset)
}
})
# 선택된 변수
selected_variable <- reactive({
req(input$variable)
input$variable
})
# 변수 선택 UI 생성
output$variableInput <- renderUI({
selectInput("variable", "변수 선택", choices = names(selected_data()))
})
# 변수 유형 출력
output$variableType <- renderPrint({
variable <- selected_variable()
data <- selected_data()
if (is.numeric(data[[variable]])) {
"선택된 변수는 연속형 변수입니다."
} else {
"선택된 변수는 범주형 변수입니다."
}
})
# 요약 통계 출력
output$summary <- renderPrint({
data <- selected_data()
variable <- selected_variable()
if (is.numeric(data[[variable]])) {
# 연속형 변수의 경우
summary_stats <- summary(data[[variable]])
names(summary_stats) <- c("최솟값", "1분위수", "중앙값", "평균", "3분위수", "최댓값")
cat("요약 통계:\n")
print(summary_stats)
cat("\n")
cat("결측값 개수:", sum(is.na(data[[variable]])), "\n")
} else {
# 범주형 변수의 경우
freq_table <- table(data[[variable]])
prop_table <- prop.table(freq_table)
cat("범주별 빈도와 비율:\n")
print(cbind(freq_table, round(prop_table, 4)))
cat("\n")
cat("결측값 개수:", sum(is.na(data[[variable]])), "\n")
}
})
# 시각화 출력 - 연속형 변수
output$plot1 <- renderPlot({
data <- selected_data()
variable <- selected_variable()
if (is.numeric(data[[variable]])) {
# Boxplot + Dot
ggplot(data, aes(x = factor(0), y = .data[[variable]])) +
geom_boxplot(fill = "#c9c6b8", color = "black", outlier.shape = 16, outlier.size = 2) +
geom_point(position = position_jitter(width = 0.1), size = 2, alpha = 0.6) +
labs(title = "Boxplot + Dot", x = "", y = variable) +
theme_tufte_bit()
} else {
# 범주형 변수의 경우 - 막대 그래프
ggplot(data, aes(x = .data[[variable]])) +
geom_bar(fill = "#c9c6b8", color = "black") +
labs(title = "Bar Plot", x = variable, y = "Count") +
theme_tufte_bit() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
}
})
output$plot2 <- renderPlot({
data <- selected_data()
variable <- selected_variable()
if (is.numeric(data[[variable]])) {
# Density Plot + Histogram
ggplot(data, aes(x = .data[[variable]])) +
geom_density(fill = "#c9c6b8", alpha = 0.6) +
geom_histogram(aes(y = ..density..), color = "black", fill = "white", alpha = 0.6, bins = 30) +
labs(title = "Density Plot + Histogram", x = variable, y = "Density") +
theme_tufte_bit()
} else {
# 범주형 변수의 경우 - 원 그래프
ggplot(data, aes(x = "", fill = .data[[variable]])) +
geom_bar(width = 1) +
coord_polar("y", start = 0) +
labs(title = "Pie Chart", fill = variable) +
theme_void() +
theme(legend.position = "bottom")
}
})
}
# 앱 실행
shinyApp(ui, server)