다변량 기술통계

다변량
기술통계
분위수
평균
표준편차
범주형
연속형
막대그래프
원그래프
히스토그램
상자그림
산점도
상관계수
그룹별 기술통계
시각화
저자

이광춘

공개

2024-05-17

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

  1. 선택한 변수에 따른 시각화 기능
    • 연속형 변수 1개: 히스토그램과 밀도 그래프
    • 연속형 변수 2개: 산점도
    • 연속형 변수 1개 + 범주형 변수 1개: 상자 그림
    • 연속형 변수 2개 + 범주형 변수 1개: 그룹별 색상이 적용된 산점도
    • 범주형 변수 2개: 모자이크 플롯
    • 범주형 변수 1개: 막대 그래프와 파이 차트
    • 3개 이상의 변수: 페어 플롯 (연속형 변수가 2개 이상인 경우)
  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