내장 데이터셋과 외부 CSV 파일에서 범주형, 숫자형을 인식하고 변수 유형에 맞춰 적절한 기술통계를 계산하고 시각화할 수 있다.
- 선택한 변수에 따른 시각화 기능
- 연속형 변수 1개: 히스토그램과 밀도 그래프
- 연속형 변수 2개: 산점도
- 연속형 변수 1개 + 범주형 변수 1개: 상자 그림
- 연속형 변수 2개 + 범주형 변수 1개: 그룹별 색상이 적용된 산점도
- 범주형 변수 2개: 모자이크 플롯
- 범주형 변수 1개: 막대 그래프와 파이 차트
- 3개 이상의 변수: 페어 플롯 (연속형 변수가 2개 이상인 경우)
- 선택한 변수에 따른 요약 통계 기능
- 범주형 변수 1개와 연속형 변수 1개: 그룹별 요약 통계량 (평균, 중앙값, 표준편차, 분산, 최소값, 최대값, 사분위수)
- 범주형 변수 2개: 분할표와 카이제곱 검정 결과
- 연속형 변수 2개 이상: 상관계수 행렬
1 Shiny 앱
#| label: shinylive-desc-stat-multivariate
#| viewerWidth: 800
#| viewerHeight: 600
#| standalone: true
library(shiny)
library(ggplot2)
library(GGally)
library(vcd)
library(dplyr)
# 내장 데이터셋 리스트 (iris 제외)
datasets <- c("mtcars", "faithful", "ChickWeight")
# UI 정의
ui <- fluidPage(
titlePanel("2변량 이상 기술통계 분석"),
sidebarLayout(
sidebarPanel(
fileInput("file", "CSV 파일 업로드", accept = ".csv"),
radioButtons("dataset", "데이터셋 선택",
choices = c("내장 데이터셋", "업로드한 CSV 파일"),
selected = "내장 데이터셋"),
uiOutput("datasetInput"),
uiOutput("categoricalInput"),
uiOutput("numericInput")
),
mainPanel(
tabsetPanel(
tabPanel("시각화", plotOutput("plot")),
tabPanel("요약 통계",
verbatimTextOutput("summary"),
verbatimTextOutput("correlation")
)
)
)
)
)
# 서버 로직
server <- function(input, output, session) {
# 데이터셋 선택 UI 생성
output$datasetInput <- renderUI({
if (input$dataset == "내장 데이터셋") {
selectInput("selectedDataset", "내장 데이터셋 선택", choices = datasets)
} else {
selectInput("selectedDataset", "업로드한 CSV 파일 선택", choices = input$file$name)
}
})
# 선택된 데이터셋
selected_data <- reactive({
if (input$dataset == "내장 데이터셋") {
if (!is.null(input$selectedDataset)) {
get(input$selectedDataset)
} else {
NULL
}
} else {
if (!is.null(input$file)) {
read.csv(input$file$datapath)
} else {
NULL
}
}
})
# 범주형 변수 선택 UI 생성
output$categoricalInput <- renderUI({
data <- selected_data()
if (!is.null(data)) {
vars <- names(data)
categorical_vars <- vars[sapply(data, is.factor) | sapply(data, is.character)]
checkboxGroupInput("categoricalVariables", "범주형 변수 선택", choices = categorical_vars)
}
})
# 숫자형 변수 선택 UI 생성
output$numericInput <- renderUI({
data <- selected_data()
if (!is.null(data)) {
vars <- names(data)
numeric_vars <- vars[sapply(data, is.numeric)]
checkboxGroupInput("numericVariables", "연속형 변수 선택", choices = numeric_vars)
}
})
# 선택된 범주형 변수
selected_categorical_vars <- reactive({
input$categoricalVariables
})
# 선택된 숫자형 변수
selected_numeric_vars <- reactive({
input$numericVariables
})
# 선택된 모든 변수
selected_vars <- reactive({
c(selected_categorical_vars(), selected_numeric_vars())
})
# 요약 통계 출력
output$summary <- renderPrint({
req(selected_vars())
data <- selected_data()
categorical_vars <- selected_categorical_vars()
numeric_vars <- selected_numeric_vars()
if (length(categorical_vars) == 1 && length(numeric_vars) == 1) {
# 범주형 변수 1개와 연속형 변수 1개 선택 시
grouped_summary <- data %>%
group_by(across(all_of(categorical_vars))) %>%
summarise(
mean = mean(get(numeric_vars), na.rm = TRUE),
median = median(get(numeric_vars), na.rm = TRUE),
sd = sd(get(numeric_vars), na.rm = TRUE),
variance = var(get(numeric_vars), na.rm = TRUE),
min = min(get(numeric_vars), na.rm = TRUE),
max = max(get(numeric_vars), na.rm = TRUE),
q1 = quantile(get(numeric_vars), 0.25, na.rm = TRUE),
q3 = quantile(get(numeric_vars), 0.75, na.rm = TRUE)
)
print(grouped_summary)
} else if (length(selected_vars()) >= 1) {
summary(data[, selected_vars(), drop = FALSE])
} else {
"1개 이상의 변수를 선택해주세요."
}
})
# 상관관계 출력
output$correlation <- renderPrint({
req(selected_vars())
data <- selected_data()
categorical_vars <- selected_categorical_vars()
numeric_vars <- selected_numeric_vars()
if (length(categorical_vars) == 2) {
# 범주형 변수 2개 선택 시
contingency_table <- table(data[, categorical_vars], useNA = "ifany")
chisq_test <- chisq.test(contingency_table)
cat("Contingency Table:\n")
print(contingency_table)
cat("\nChi-squared Test:\n")
print(chisq_test)
} else if (length(categorical_vars) > 0 && length(numeric_vars) > 0) {
"범주형 변수와 연속형 변수 간의 상관관계는 계산할 수 없습니다."
} else if (length(categorical_vars) >= 2) {
"범주형 변수가 너무 많습니다. 2개만 선택해 주세요."
} else if (length(numeric_vars) >= 2) {
cor_matrix <- cor(data[, numeric_vars, drop = FALSE], use = "pairwise.complete.obs")
print(cor_matrix)
} else {
"2개 이상의 동일한 유형의 변수를 선택해주세요."
}
})
# 범주형 변수를 factor로 변환
data_with_factors <- reactive({
data <- selected_data()
categorical_vars <- selected_categorical_vars()
if (length(categorical_vars) > 0) {
data[, categorical_vars] <- lapply(data[, categorical_vars, drop = FALSE], as.factor)
}
data
})
# 시각화 출력
output$plot <- renderPlot({
req(selected_vars())
data <- data_with_factors()
categorical_vars <- selected_categorical_vars()
numeric_vars <- selected_numeric_vars()
if (length(numeric_vars) == 1 && length(categorical_vars) == 0) {
# 연속형 변수 1개 선택 시
ggplot(data, aes_string(x = numeric_vars[1])) +
geom_histogram(aes(y = ..density..), fill = "lightblue", color = "black", bins = 30) +
geom_density(color = "red") +
labs(title = "Histogram and Density Plot", x = numeric_vars[1], y = "Density") +
theme_minimal()
} else if (length(numeric_vars) == 2 && length(categorical_vars) == 0) {
# 연속형 변수 2개 선택 시
ggplot(data, aes_string(x = numeric_vars[1], y = numeric_vars[2])) +
geom_point() +
labs(title = "Scatter Plot", x = numeric_vars[1], y = numeric_vars[2]) +
theme_minimal()
} else if (length(numeric_vars) == 1 && length(categorical_vars) == 1) {
# 연속형 변수 1개 + 범주형 변수 1개 선택 시
ggplot(data, aes_string(x = categorical_vars[1], y = numeric_vars[1])) +
geom_boxplot() +
labs(title = "Box Plot", x = categorical_vars[1], y = numeric_vars[1]) +
theme_minimal()
} else if (length(numeric_vars) == 2 && length(categorical_vars) == 1) {
# 연속형 변수 2개 + 범주형 변수 1개 선택 시
ggplot(data, aes_string(x = numeric_vars[1], y = numeric_vars[2], color = categorical_vars[1])) +
geom_point() +
labs(title = "Scatter Plot with Group Color", x = numeric_vars[1], y = numeric_vars[2], color = categorical_vars[1]) +
theme_minimal()
} else if (length(categorical_vars) == 2) {
# 범주형 변수 2개 선택 시
if (nrow(table(data[, categorical_vars])) == 0) {
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "")
text(1, 1, "선택한 범주형 변수의 조합에 해당하는 데이터가 없습니다.")
} else {
mosaic(table(data[, categorical_vars]), shade = TRUE, legend = TRUE)
}
} else if (length(categorical_vars) == 1) {
# 범주형 변수 1개 선택 시
p1 <- ggplot(data, aes_string(x = categorical_vars[1])) +
geom_bar(fill = "lightblue", color = "black") +
labs(title = "Bar Plot", x = categorical_vars[1], y = "Count") +
theme_minimal()
p2 <- ggplot(data, aes_string(x = factor(1), fill = categorical_vars[1])) +
geom_bar(width = 1) +
coord_polar("y", start = 0) +
labs(title = "Pie Chart", fill = categorical_vars[1]) +
theme_void() +
theme(legend.position = "right")
gridExtra::grid.arrange(p1, p2, ncol = 2)
} else if (length(selected_vars()) >= 3) {
# 3개 이상의 변수 선택 시
if (length(numeric_vars) < 2) {
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "")
text(1, 1, "Pair Plot을 그리려면 연속형 변수가 2개 이상 필요합니다.")
} else {
ggpairs(data[, selected_vars(), drop = FALSE], title = "Pair Plot")
}
} else {
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "")
text(1, 1, "1개 이상의 변수를 선택해주세요.")
}
})
}
# 앱 실행
shinyApp(ui, server)
2 코딩
라이센스
CC BY-SA-NC & GPL-3