설문조사 데이터 분석
대학교 수업 만족도 조사를 상상해보세요. 교수님, 커리큘럼, 시설, 과제 난이도, 전반적 만족도를 각각 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 보고서가 생성됩니다. 코드와 결과가 함께 담기므로 다른 사람에게 분석 과정을 투명하게 공유할 수 있습니다.
정리
설문 분석의 흐름은 단순합니다. 가설을 세우고, 신뢰도로 척도를 검증하고, 집단 비교로 가설을 판단하고, 보고서로 마무리합니다. 이 흐름은 마케팅 고객 만족도 조사부터 의학 연구 설문까지 어디서든 그대로 적용됩니다. 다음 챕터에서는 생존 분석이라는 좀 더 전문적인 분야로 나아갑니다.