사용자가 선택한 데이터에 대해 연속확률분포의 적합도를 분석하고 시각화할 수 있는 도구입니다. 이 애플리케이션을 통해 통계학적 분석을 수행하고 결과를 실시간으로 탐색할 수 있습니다.
Shiny 앱: 데이터가 어떤 이론적 분포를 따르는지 확인하기 위해 히스토그램과 이론적 확률밀도함수를 비교합니다. Q-Q 플롯을 통해 데이터의 분위수가 이론적 분위수와 얼마나 일치하는지 시각적으로 평가합니다. 또한, 데이터의 누적분포함수와 이론적 누적분포함수를 비교하여 데이터의 적합도를 평가합니다. 균일분포는 Kolmogorov-Smirnov 검정을 통해, 정규분포는 Shapiro-Wilk 검정을 통해, 지수분포는 Kolmogorov-Smirnov 검정을 통해 데이터의 적합도를 평가하고 검정 결과의 p-값 통해 분포의 적합성 여부를 결정합니다.
코딩:
#| label: shinylive-continous-distribution-fitting
#| viewerHeight: 700
#| standalone: true
library(ggplot2)
library(moments)
library(palmerpenguins)
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(
radioButtons("data_source", "데이터 출처 선택",
choices = c("CSV 업로드" = "Upload CSV", "penguins", "mtcars"),
inline = TRUE),
conditionalPanel(
condition = "input.data_source == 'Upload CSV'",
fileInput("file", "CSV 파일 선택",
accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv"))
),
selectInput("variable", "변수 선택", choices = NULL),
radioButtons("distribution", "분포 선택",
choices = c("균등" = "Uniform", "정규" = "Normal", "지수" = "Exponential"))
),
mainPanel(
tabsetPanel(
tabPanel("요약", verbatimTextOutput("summary")),
tabPanel("히스토그램", plotOutput("histogram")),
tabPanel("Q-Q 그래프", plotOutput("qqplot")),
tabPanel("CDF 그래프", plotOutput("cdfplot"))
),
h4("사용 방법:"),
tags$ol(
tags$li("데이터 출처를 선택합니다(CSV 업로드, penguins, mtcars)."),
tags$li("'CSV 업로드'를 선택하면 업로드할 CSV 파일을 고릅니다."),
tags$li("드롭다운에서 연속형 변수를 선택합니다."),
tags$li("적합할 분포를 고릅니다(균등, 정규, 지수)."),
tags$li("선택에 따라 적합 결과가 자동으로 갱신됩니다."),
tags$li("각 탭에서 요약통계·히스토그램·Q-Q 그래프·CDF 그래프를 살펴봅니다.")
)
)
)
)
server <- function(input, output, session) {
data <- reactive({
if (input$data_source == "Upload CSV") {
req(input$file)
read.csv(input$file$datapath)
} else if (input$data_source == "penguins") {
penguins[, c("bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g")]
} else if (input$data_source == "mtcars") {
mtcars[, c("mpg", "disp", "hp", "drat", "wt", "qsec")]
}
})
observe({
req(data())
updateSelectInput(session, "variable", choices = names(data()))
})
variable <- reactive({
req(input$variable)
data()[[input$variable]]
})
fit_result <- reactive({
req(input$distribution, variable())
distribution <- tolower(input$distribution)
if (distribution == "uniform") {
min_val <- min(variable(), na.rm = TRUE)
max_val <- max(variable(), na.rm = TRUE)
test_result <- ks.test(variable(), "punif", min = min_val, max = max_val)
} else if (distribution == "normal") {
test_result <- shapiro.test(variable())
} else if (distribution == "exponential") {
rate_val <- 1/mean(variable(), na.rm = TRUE)
test_result <- ks.test(variable(), "pexp", rate = rate_val)
}
list(
distribution = distribution,
test_result = test_result,
params = switch(distribution,
"uniform" = list(min = min_val, max = max_val),
"normal" = list(mean = mean(variable(), na.rm = TRUE), sd = sd(variable(), na.rm = TRUE)),
"exponential" = list(rate = rate_val))
)
})
output$summary <- renderPrint({
req(fit_result())
cat("선택한 분포:", fit_result()$distribution, "\n\n")
cat("기술통계:\n")
print(summary(variable()))
cat("\n왜도:", skewness(variable()), "\n")
cat("첨도:", kurtosis(variable()), "\n\n")
if (fit_result()$distribution == "uniform") {
cat("균등분포에 대한 Kolmogorov-Smirnov 검정\n")
} else if (fit_result()$distribution == "normal") {
cat("Shapiro-Wilk 정규성 검정\n")
} else if (fit_result()$distribution == "exponential") {
cat("지수분포에 대한 Kolmogorov-Smirnov 검정\n")
}
cat("p-값:", fit_result()$test_result$p.value, "\n")
})
output$histogram <- renderPlot({
req(fit_result())
distribution <- fit_result()$distribution
params <- fit_result()$params
ggplot(data.frame(x = variable()), aes(x)) +
geom_histogram(aes(y = ..density..), bins = 30, color = "black", fill = "#c9c6b8") +
stat_function(fun = switch(distribution,
"uniform" = dunif,
"normal" = dnorm,
"exponential" = dexp),
args = params,
color = "#8a1500", size = 1) +
labs(title = "적합된 분포와 히스토그램",
x = input, y = "밀도")
})
output$qqplot <- renderPlot({
req(fit_result())
distribution <- fit_result()$distribution
params <- fit_result()$params
ggplot(data.frame(x = variable()), aes(sample = x)) +
stat_qq(distribution = switch(distribution,
"uniform" = qunif,
"normal" = qnorm,
"exponential" = qexp),
dparams = unlist(params),
color = "#4a4a44") +
stat_qq_line(distribution = switch(distribution,
"uniform" = qunif,
"normal" = qnorm,
"exponential" = qexp),
dparams = unlist(params),
color = "#8a1500") +
labs(title = "Q-Q 그래프",
x = "이론적 분위수", y = "표본 분위수")
})
output$cdfplot <- renderPlot({
req(fit_result())
distribution <- fit_result()$distribution
params <- fit_result()$params
ggplot(data.frame(x = variable()), aes(x)) +
stat_ecdf(geom = "step", color = "#4a4a44", size = 1) +
stat_function(fun = switch(distribution,
"uniform" = punif,
"normal" = pnorm,
"exponential" = pexp),
args = unlist(params),
color = "#8a1500", size = 1) +
labs(title = "경험적·이론적 CDF",
x = input$variable, y = "Cumulative Probability")
})
}
shinyApp(ui, server)