iBetter Books
수정

설문조사 데이터 분석

대학교 수업 만족도 조사를 상상해보세요. 교수님, 커리큘럼, 시설, 과제 난이도, 전반적 만족도를 각각 1점에서 5점으로 평가했습니다. 응답지가 200장 쌓였을 때, 우리는 무엇을 알 수 있을까요. 이 챕터에서는 리커트 척도 설문 데이터를 시뮬레이션으로 만들고, 신뢰도를 검증하고, 집단 간 차이를 분석하고, 결과를 R Markdown 보고서로 완성하는 전 과정을 따라갑니다.

분석 목표와 가설 설정

먼저 분석하고자 하는 질문을 명확히 합니다. 막연하게 "뭔가 분석해보자"가 아니라 검증 가능한 가설로 바꿔야 합니다.

연구 질문

  • 이 설문의 다섯 항목은 하나의 개념(수업 만족도)을 일관되게 측정하고 있는가.
  • 성별에 따라 전반적 만족도에 차이가 있는가.
  • 학년에 따라 수업 만족도에 차이가 있는가.

가설

  • H1: Cronbach's α ≥ 0.7이면 내적 일관성이 충분하다.
  • H2: 성별 간 평균 만족도에 유의미한 차이가 없다 (귀무가설).
  • H3: 학년 간 평균 만족도에 유의미한 차이가 없다 (귀무가설).

데이터 시뮬레이션

실제 설문을 구하기 어려울 때는 시뮬레이션 데이터로 분석 파이프라인을 먼저 구축합니다. 나중에 실제 데이터가 생기면 같은 코드를 그대로 쓸 수 있습니다.

# 파일: survey_analysis.R

set.seed(42)  # 재현 가능한 결과를 위해 시드 고정
n <- 200      # 응답자 수

# 응답자 기본 정보
gender <- sample(c("남", "여"), n, replace = TRUE, prob = c(0.52, 0.48))
grade  <- sample(1:4, n, replace = TRUE)

# 리커트 5점 척도 항목 (1~5점)
# 성별 차이를 약간 넣어 현실감 있게 시뮬레이션
q1_professor   <- sample(1:5, n, replace = TRUE, prob = c(0.05, 0.10, 0.25, 0.40, 0.20))
q2_curriculum  <- sample(1:5, n, replace = TRUE, prob = c(0.05, 0.15, 0.30, 0.35, 0.15))
q3_facility    <- sample(1:5, n, replace = TRUE, prob = c(0.10, 0.20, 0.30, 0.30, 0.10))
q4_assignment  <- sample(1:5, n, replace = TRUE, prob = c(0.08, 0.18, 0.28, 0.32, 0.14))
q5_overall     <- sample(1:5, n, replace = TRUE, prob = c(0.05, 0.12, 0.28, 0.38, 0.17))

# 데이터프레임 통합
survey <- data.frame(
  id         = 1:n,
  gender     = gender,
  grade      = grade,
  professor  = q1_professor,
  curriculum = q2_curriculum,
  facility   = q3_facility,
  assignment = q4_assignment,
  overall    = q5_overall
)

# 처음 몇 행 확인
head(survey)

# 기술통계 요약
summary(survey[, 4:8])

데이터가 준비됐으니 먼저 각 항목의 분포를 눈으로 확인합니다.

library(ggplot2)
library(tidyr)
library(dplyr)

# 리커트 항목 전체 분포 시각화
survey_long <- survey %>%
  select(professor, curriculum, facility, assignment, overall) %>%
  pivot_longer(everything(), names_to = "항목", values_to = "점수")

# 항목명 한글화
survey_long$항목 <- factor(survey_long$항목,
  levels = c("professor", "curriculum", "facility", "assignment", "overall"),
  labels = c("교수 역량", "커리큘럼", "시설", "과제 난이도", "전반 만족도")
)

ggplot(survey_long, aes(x = factor(점수))) +
  geom_bar(fill = "steelblue", color = "white") +
  facet_wrap(~ 항목, ncol = 3) +
  labs(title = "수업 만족도 설문 항목별 응답 분포",
       x = "점수 (1~5점)", y = "응답자 수") +
  theme_minimal(base_family = "AppleGothic")

신뢰도 분석 — Cronbach's α

다섯 항목이 모두 "수업 만족도"라는 하나의 개념을 측정하고 있는지 확인합니다. Cronbach's α는 항목들 간의 상관관계를 종합한 내적 일관성 지수입니다. 0.7 이상이면 신뢰할 수 있는 척도로 봅니다.

# install.packages("psych")
library(psych)

# 리커트 항목만 선택
likert_items <- survey[, c("professor", "curriculum", "facility", "assignment", "overall")]

# Cronbach's α 계산
alpha_result <- alpha(likert_items)

# 결과 출력
print(alpha_result)

# 핵심 수치만 추출
cat("Cronbach's α:", round(alpha_result$total$raw_alpha, 3), "\n")
cat("표준화 α:", round(alpha_result$total$std.alpha, 3), "\n")

출력 결과 중 raw_alpha가 핵심입니다. 0.7 미만이면 특정 항목이 개념에서 벗어나 있다는 신호입니다. alpha_result$alpha.drop을 보면 어떤 항목을 제거할 때 α가 높아지는지 알 수 있습니다.

# 항목 제거 시 α 변화 — 어떤 항목이 일관성을 낮추는지 확인
cat("\n--- 항목 제거 시 α 변화 ---\n")
print(alpha_result$alpha.drop)

집단 간 비교

성별 비교 — 독립 표본 t-검정

두 집단(남/여)의 평균 만족도를 비교합니다. t-검정을 쓰기 전에 등분산 검정(Levene test)을 먼저 수행합니다.

library(car)

# 등분산 검정
levene_result <- leveneTest(overall ~ gender, data = survey)
cat("Levene 등분산 검정 p값:", levene_result$`Pr(>F)`[1], "\n")

# p값에 따라 var.equal 결정
equal_var <- levene_result$`Pr(>F)`[1] > 0.05

# t-검정 수행
t_result <- t.test(overall ~ gender, data = survey, var.equal = equal_var)
print(t_result)

# 효과크기 — Cohen's d
male_scores   <- survey$overall[survey$gender == "남"]
female_scores <- survey$overall[survey$gender == "여"]

pooled_sd <- sqrt((sd(male_scores)^2 + sd(female_scores)^2) / 2)
cohens_d  <- (mean(male_scores) - mean(female_scores)) / pooled_sd
cat("Cohen's d:", round(cohens_d, 3), "\n")

Cohen's d는 효과크기를 나타냅니다. 0.2 소(small), 0.5 중(medium), 0.8 대(large)로 해석합니다. 통계적으로 유의미해도 효과크기가 작으면 실질적 의미는 크지 않을 수 있습니다.

학년별 비교 — 일원 분산분석(ANOVA)

세 집단 이상을 비교할 때는 ANOVA를 씁니다. 여기서는 학년(1~4학년)에 따른 만족도 차이를 분석합니다.

# 학년을 요인(factor)으로 변환
survey$grade_f <- factor(survey$grade, labels = paste0(1:4, "학년"))

# ANOVA 수행
anova_result <- aov(overall ~ grade_f, data = survey)
summary(anova_result)

# 사후 검정 — 어떤 학년 쌍이 다른지 확인
tukey_result <- TukeyHSD(anova_result)
print(tukey_result)

# 사후 검정 시각화
par(family = "AppleGothic")
plot(tukey_result, las = 1)

ANOVA F-검정이 유의미하면 "적어도 한 집단은 다르다"는 것만 알 수 있습니다. 어떤 학년 쌍이 다른지 알려면 Tukey HSD 사후 검정이 필요합니다.

결과 시각화

# 성별별 만족도 박스플롯
p1 <- ggplot(survey, aes(x = gender, y = overall, fill = gender)) +
  geom_boxplot(alpha = 0.7) +
  geom_jitter(width = 0.1, alpha = 0.3, size = 1) +
  stat_summary(fun = mean, geom = "point", shape = 18, size = 4, color = "red") +
  scale_fill_manual(values = c("남" = "#4A90D9", "여" = "#E87C7C")) +
  labs(title = "성별에 따른 수업 만족도",
       x = "성별", y = "전반 만족도 (1~5점)") +
  theme_minimal(base_family = "AppleGothic") +
  theme(legend.position = "none")

# 학년별 만족도 평균 ± 표준오차
grade_summary <- survey %>%
  group_by(grade_f) %>%
  summarise(
    mean_score = mean(overall),
    se         = sd(overall) / sqrt(n()),
    .groups    = "drop"
  )

p2 <- ggplot(grade_summary, aes(x = grade_f, y = mean_score)) +
  geom_col(fill = "steelblue", alpha = 0.8) +
  geom_errorbar(aes(ymin = mean_score - se, ymax = mean_score + se),
                width = 0.2, color = "black") +
  coord_cartesian(ylim = c(0, 5)) +
  labs(title = "학년별 평균 수업 만족도",
       x = "학년", y = "평균 점수 (±SE)") +
  theme_minimal(base_family = "AppleGothic")

# 나란히 출력
library(patchwork)
p1 + p2

R Markdown 보고서 작성

분석 결과를 재현 가능한 보고서로 만듭니다. File → New File → R Markdown으로 새 파일을 만들고 아래 내용을 입력합니다.

---
title: "수업 만족도 설문조사 분석 보고서"
author: "분석자 이름"
date: "`r Sys.Date()`"
output:
  html_document:
    theme: flatly
    toc: true
    toc_float: true
    code_folding: show
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
library(psych)
library(car)
library(ggplot2)
library(dplyr)
library(patchwork)
```

## 1. 데이터 개요

응답자 `r nrow(survey)`명의 수업 만족도 설문 결과를 분석합니다.

```{r data-summary}
summary(survey[, c("professor", "curriculum", "facility", "assignment", "overall")])
```

## 2. 신뢰도 분석

```{r reliability}
alpha_result <- alpha(survey[, c("professor", "curriculum", "facility", "assignment", "overall")])
cat("Cronbach's α =", round(alpha_result$total$raw_alpha, 3))
```

## 3. 성별 비교

```{r gender-test}
t.test(overall ~ gender, data = survey)
```

## 4. 학년별 비교

```{r grade-anova}
anova_result <- aov(overall ~ grade_f, data = survey)
summary(anova_result)
TukeyHSD(anova_result)
```

## 5. 결론

- Cronbach's α = `r round(alpha_result$total$raw_alpha, 3)`으로 내적 일관성이 **`r ifelse(alpha_result$total$raw_alpha >= 0.7, "충분합니다", "다소 낮습니다")`**.
- 성별 간 만족도 차이는 통계적으로 **`r ifelse(t.test(overall ~ gender, data = survey)$p.value < 0.05, "유의미합니다", "유의미하지 않습니다")`**.

Knit 버튼을 누르면 HTML 보고서가 생성됩니다. 코드와 결과가 함께 담기므로 다른 사람에게 분석 과정을 투명하게 공유할 수 있습니다.

정리

설문 분석의 흐름은 단순합니다. 가설을 세우고, 신뢰도로 척도를 검증하고, 집단 비교로 가설을 판단하고, 보고서로 마무리합니다. 이 흐름은 마케팅 고객 만족도 조사부터 의학 연구 설문까지 어디서든 그대로 적용됩니다. 다음 챕터에서는 생존 분석이라는 좀 더 전문적인 분야로 나아갑니다.