19대 대선 다층 모델(lmer) 분석 결과와 시뮬레이션 결과를 해석하겠습니다. 이 해석은 데이터의 변수 정의와 모델링 결과를 기반으로 이루어지며, K 값의 의미와 시뮬레이션 결과의 차이를 중심으로 설명하겠습니다.

주요 변수 정의:
M1: 문재인 득표수H1: 홍준표 득표수M2: 문재인 미분류표H2: 홍준표 미분류표K: R2/R1로 정의되며, 여기서 R1 = M1/H1 (문재인/홍준표 득표 비율), R2 = M2/H2 (문재인/홍준표 미분류표 비율). 즉, K는 미분류표 비율과 득표 비율 간의 상대적 차이를 나타냅니다.다층 모델 (lmer):
K (미분류표와 득표 비율의 비율).poly(elderly_ratio, 2): 노인 비율 (elderly_ratio)의 2차 다항식으로, 노인 비율의 비선형적 영향을 모델링.unclassified_rate: 미분류표 비율.ballot_design_bias: 투표 용지 디자인 편향.scanner_error: 스캐너 오류.manipulation_factor: 조작 요인.elderly_conservative_interaction: 노인 비율과 보수 성향 간 상호작용.unclassified_conservative_interaction: 미분류표 비율과 보수 성향 간 상호작용.manipulation_conservative_interaction: 조작 요인과 보수 성향 간 상호작용.region (지역별 랜덤 절편).시뮬레이션 변수:
sim_alpha, sim_beta, sim_K: 관측된 K 값을 시뮬레이션으로 예측하기 위해 생성된 변수.sim_alpha: 여러 변수의 가중치 합으로 계산.sim_beta: sim_alpha를 1.5로 나눈 값.sim_K: sim_alpha, sim_beta, unclassified_rate_raw를 사용해 계산.region (16개 지역).region):K 값의 변동은 비교적 작음. 즉, 지역별로 K 값의 차이가 크지 않음.고정 효과는 각 독립 변수가 K 값에 미치는 영향을 나타냅니다. Estimate는 회귀 계수, Std. Error는 표준 오차, t value는 통계적 유의성을 판단하는 기준입니다.
절편 (Intercept):
K 값의 평균이 약 0.645로 추정됨. 매우 유의미함 (t 값이 매우 큼).노인 비율 (poly(elderly_ratio, 2)):
poly(elderly_ratio, 2)1 (1차 항): 계수 0.61268, t 값 1.986.K 값이 증가하는 경향.poly(elderly_ratio, 2)2 (2차 항): 계수 0.26587, t 값 3.662.K 값의 증가 속도가 가속화됨 (2차 곡선 형태).미분류표 비율 (unclassified_rate):
K 값이 약간 증가함.투표 용지 디자인 편향 (ballot_design_bias):
K 값에 미치는 영향이 거의 없음 (t 값이 2보다 작아 유의미하지 않음).스캐너 오류 (scanner_error):
K 값이 감소하는 경향이 있지만, 통계적으로 유의미하지 않음 (t 값이 작음).조작 요인 (manipulation_factor):
K 값에 미치는 영향은 미미하며, 유의미하지 않음.노인-보수 성향 상호작용 (elderly_conservative_interaction):
K 값을 약간 감소시키는 방향으로 작용하지만, 유의미하지 않음.미분류표-보수 성향 상호작용 (unclassified_conservative_interaction):
K 값에 큰 영향을 미치지 않음 (유의미하지 않음).조작-보수 성향 상호작용 (manipulation_conservative_interaction):
K 값을 감소시키는 방향으로 작용하며, 통계적으로 유의미함 (t 값 < -2).K 값이 감소하는 경향이 있음.poly(elderly_ratio, 2)1과 unclassified_rate 간 상관계수가 -0.849로 매우 높음.unclassified_rate와 unclassified_conservative_interaction 간 상관계수가 -0.875로 높음.manipulation_factor와 manipulation_conservative_interaction 간 상관계수가 -0.767로 높음.K와 시뮬레이션 sim_K 비교관측된 K:
K 값은 대부분 0.5~0.7 사이에 분포하며, 평균은 0.638로 모델의 절편(0.64486)과 유사함.시뮬레이션 sim_K:
K 값은 1.2~1.7 사이에 분포하며, 평균은 1.3589로 관측된 K보다 훨씬 큼.K와 sim_K 간 차이 분석차이: 시뮬레이션 sim_K 값이 관측 K 값보다 약 2배 정도 큽니다.
K의 평균: 0.6380.sim_K의 평균: 1.3589.원인 분석:
sim_K는 다음과 같이 계산됨:sim_K = 1.3 + 3.5 * sim_alpha * unclassified_rate_raw - 0.2 * sim_beta
- 절편이 1.3으로 설정되어 있어, 기본적으로 `sim_K` 값이 1.3에서 시작함. 이는 관측 `K`의 평균(0.638)보다 훨씬 큼.
- `3.5 * sim_alpha * unclassified_rate_raw` 항이 `sim_K` 값을 추가로 증가시킴.
- `-0.2 * sim_beta`는 감소 요인이지만, 그 영향이 크지 않음.
시뮬레이션 공식의 과대 추정: 시뮬레이션 공식에서 사용된 가중치(예: 3.5, 0.2 등)가 실제 데이터의 패턴을 과대 추정했을 가능성이 있음.
변수 스케일링 문제: sim_alpha와 sim_beta 계산에 사용된 변수(elderly_ratio, unclassified_rate 등)의 스케일이 적절히 조정되지 않았을 수 있음. 예를 들어, unclassified_rate_raw 값이 크다면 sim_K 값이 과도하게 증가할 수 있음.
sim_K의 절편을 1.3에서 관측 K의 평균(0.638)에 맞추기 위해 0.6 정도로 낮출 수 있음.3.5와 -0.2 가중치를 줄여 sim_K 값이 관측 K 값에 더 가까워지도록 조정.3.5를 1.5 정도로 낮추고, -0.2를 -0.1 정도로 조정.조정된 공식 예시:
sim_K = 0.6 + 1.5 * sim_alpha * unclassified_rate_raw - 0.1 * sim_beta
K 값에 비선형적으로 영향을 미치며, 특히 2차 항이 유의미함. 노인 비율이 높아질수록 K 값이 더 빠르게 증가하는 경향이 있음.K 값에 긍정적인 영향을 미치며, 유의미한 변수임.K 값이 감소하는 경향이 있으며, 이 상호작용은 유의미함.sim_K 값이 관측 K 값보다 과대 추정됨.sim_K 값을 관측 K 값에 더 가깝게 만들 필요가 있음.모델 개선:
unclassified_rate와 unclassified_conservative_interaction)를 재검토하거나 변수 선택 기법(예: VIF 계산)을 사용할 수 있음.시뮬레이션 공식 개선:
sim_K 계산 공식의 절편과 가중치를 데이터 기반으로 재조정.unclassified_rate_raw와 같은 변수의 스케일을 표준화(예: 0~1로 정규화)하여 과대 추정을 방지.이상치 추가 분석:

K 값에 주요한 영향을 미치며, 조작-보수 성향 상호작용도 유의미한 영향을 미침.K 값을 과대 추정하고 있으며, 시뮬레이션 공식의 절편과 가중치를 조정하여 실제 데이터와의 차이를 줄일 필요가 있음.
분석 3.
이전 분석에서 제안된 개선점을 반영하여 코드를 수정하겠습니다. 개선점은 다음과 같습니다:
unclassified_rate와 unclassified_conservative_interaction) 간 다중공선성을 줄이기 위해 VIF(Variance Inflation Factor)를 계산하고, 필요 시 변수를 제외하거나 주성분 분석(PCA) 적용.sim_K의 범위가 관측 K보다 좁은 문제를 해결하기 위해 랜덤 노이즈 추가.K 값을 로그 변환하여 잔차 분포를 정규화.ballot_design_bias, scanner_error)를 제외하거나 LASSO를 통해 변수 선택.# 필요한 패키지 로드
packages <- c("readxl", "ggplot2", "lme4", "gridExtra", "dplyr")
new_packages <- packages[!(packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) install.packages(new_packages)
lapply(packages, library, character.only = TRUE)
library(car) # VIF 계산용
library(glmnet) # LASSO 회귀용
# 데이터 로드 (Excel 파일)
data <- read_excel("pe19res.xlsx")
# 변수 변환 및 스케일링 (개선: 0~1로 정규화)
set.seed(42)
data <- data %>%
mutate(
KC = M1 / H1,
KU = M2 / H2,
K = KU / KC,
log_K = log(K + 1), # K 값을 로그 변환 (0 이하 방지 위해 +1)
unclassified_rate_raw = U_all / vote_all,
unclassified_rate = scale(U_all / vote_all, center = FALSE, scale = max(U_all / vote_all)), # 0~1로 정규화
elderly_ratio = scale(over60, center = FALSE, scale = max(over60)), # 0~1로 정규화
ballot_design_bias = runif(n(), 0.5, 1.5),
scanner_error = runif(n(), 0.01, 0.05),
manipulation_factor = runif(n(), 0.5, 2.0),
conservative_tendency = H1 / (H1 + M1),
elderly_conservative_interaction = elderly_ratio * conservative_tendency,
unclassified_conservative_interaction = unclassified_rate * conservative_tendency,
manipulation_conservative_interaction = manipulation_factor * conservative_tendency
)
# 다중공선성 확인: VIF 계산
# 먼저 고정 효과 변수들만 포함한 선형 모델 적합
vif_model <- lm(log_K ~ poly(elderly_ratio, 2) + unclassified_rate +
ballot_design_bias + scanner_error + manipulation_factor +
elderly_conservative_interaction + unclassified_conservative_interaction +
manipulation_conservative_interaction, data = data)
vif_values <- vif(vif_model)
print("VIF Values:")
print(vif_values)
# VIF가 5 이상인 경우 해당 변수 제거 (다중공선성 문제 해결)
# 예: VIF가 높은 변수 제거 후 모델 재정의 (여기서는 예시로 unclassified_conservative_interaction 제거 가정)
data <- data %>%
mutate(unclassified_conservative_interaction = NULL)
# LASSO를 통한 변수 선택 (선택적)
X <- model.matrix(log_K ~ poly(elderly_ratio, 2) + unclassified_rate +
ballot_design_bias + scanner_error + manipulation_factor +
elderly_conservative_interaction + manipulation_conservative_interaction,
data = data)[, -1] # 절편 제외
Y <- data$log_K
lasso_model <- cv.glmnet(X, Y, alpha = 1) # LASSO 회귀
coef_lasso <- coef(lasso_model, s = "lambda.min")
print("LASSO Selected Coefficients:")
print(coef_lasso)
# LASSO로 선택된 변수만 포함 (유의미하지 않은 변수 제외 가정)
# 여기서는 예시로 ballot_design_bias와 scanner_error 제외
mixed_model <- lmer(log_K ~ poly(elderly_ratio, 2) + unclassified_rate +
manipulation_factor + elderly_conservative_interaction +
manipulation_conservative_interaction + (1 | region),
data = data)
# 이상치 식별 및 제거 (로그 변환된 잔차 사용)
residuals_scaled <- residuals(mixed_model, scaled = TRUE)
outlier_threshold <- 2.5
outliers <- which(abs(residuals_scaled) > outlier_threshold)
data_clean <- data[-outliers, ]
# 이상치 제거 후 모델 재적합
mixed_model_clean <- lmer(log_K ~ poly(elderly_ratio, 2) + unclassified_rate +
manipulation_factor + elderly_conservative_interaction +
manipulation_conservative_interaction + (1 | region),
data = data_clean)
# 모델 요약
cat("\n이상치 제거 후 다층 모델 분석 결과 (로그 변환 적용):\n")
print(summary(mixed_model_clean))
# 시뮬레이션 로직 개선 (변동성 추가)
set.seed(42)
data_clean <- data_clean %>%
mutate(
sim_alpha = (0.058 * elderly_ratio + 0.1 * unclassified_rate +
manipulation_factor + 0.1 * manipulation_conservative_interaction),
sim_beta = sim_alpha / 1.5,
sim_log_K = 0.6 + 1.5 * sim_alpha * unclassified_rate_raw - 0.1 * sim_beta +
rnorm(n(), 0, sd(residuals(mixed_model_clean))), # 잔차 기반 노이즈 추가
sim_K = exp(sim_log_K) - 1 # 로그 변환 역변환
)
# 시뮬레이션 결과 비교
cat("\n이상치 제거 후 관측 K vs 시뮬레이션 K (로그 변환 및 변동성 추가):\n")
print(summary(data_clean[, c("K", "sim_K")]))
# 시각화 업데이트 (로그 변환 반영)
p1 <- ggplot(data_clean, aes(x = elderly_ratio, y = log_K, color = conservative_tendency)) +
geom_point() +
labs(x = "60대 이상 비율 (0~1 정규화)", y = "log(K) 값", title = "고령층 비율 vs log(K) (이상치 제거)") +
scale_color_viridis_c(name = "보수 성향") +
theme_minimal()
p2 <- ggplot(data_clean, aes(x = log_K, y = sim_log_K, color = unclassified_rate_raw)) +
geom_point() +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(x = "관측 log(K)", y = "시뮬레이션 log(K)", title = "관측 log(K) vs 시뮬레이션 log(K) (이상치 제거)") +
scale_color_viridis_c(name = "미분류표 비율 (비표준화)", option = "plasma") +
theme_minimal()
p3 <- ggplot(data_clean, aes(x = log_K, y = log_K - sim_log_K, color = manipulation_factor)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(x = "관측 log(K)", y = "잔차 (log(K) - sim_log_K)", title = "잔차 분석 (이상치 제거)") +
scale_color_viridis_c(name = "조작 요인", option = "magma") +
theme_minimal()
gridExtra::grid.arrange(p1, p2, p3, ncol = 3)
다중공선성 해결:
unclassified_conservative_interaction 제거 가정).ballot_design_bias, scanner_error 제외).변수 스케일링 개선:
unclassified_rate와 elderly_ratio를 0~1로 정규화하여 가중치 영향을 균일하게 조정.scale(center = FALSE, scale = max)를 사용하여 값의 범위를 0~1로 변환.시뮬레이션 변동성 반영:
sim_log_K 계산 시 잔차의 표준편차를 기반으로 랜덤 노이즈(rnorm)를 추가하여 sim_K의 변동성을 확대.log_K를 사용하여 시뮬레이션 후 역변환(exp(sim_log_K) - 1).잔차 분포 정규화:
K 값을 로그 변환(log_K = log(K + 1))하여 잔차 분포를 정규화.변수 선택 최적화:
ballot_design_bias, scanner_error)를 제외하고 모델 간소화.elderly_ratio, unclassified_rate, manipulation_factor, 상호작용 항목)만 포함.sim_K의 범위가 관측 K와 더 유사해지고, 평균 차이가 줄어듦.REML Criterion at Convergence: -878.3
Scaled Residuals (잔차 분석)
Min 1Q Median 3Q Max
-2.5453 -0.5811 -0.0231 0.6047 2.8938
Groups Name Variance Std.Dev.
region (Intercept) 4.165e-05 0.006453
Residual 1.322e-03 0.036366
지역(region) 단위 변동성: 0.00004165 (표준편차 0.006453)
잔차 변동성: 0.001322 (표준편차 0.036366)
| 변수 | 추정값 (Estimate) | 표준오차 (Std. Error) | t 값 (t-value) | 해석 |
|---|---|---|---|---|
| (Intercept) | 0.51294 | 0.02419 | 21.206 | 기본 K 값의 로그 변환 후 평균값 |
| poly(elderly_ratio, 2)1 | 0.65604 | 0.10609 | 6.184 | 고령층 비율이 증가하면 K 값도 증가하는 경향 |
| poly(elderly_ratio, 2)2 | 0.15755 | 0.03967 | 3.972 | 고령층 비율이 더 높아지면 K 증가폭이 완만해짐 (비선형 관계 반영) |
| unclassified_rate | 0.12151 | 0.03927 | 3.094 | 미분류표 비율이 높을수록 K 값 증가 |
| elderly_conservative_interaction | -0.27636 | 0.05082 | -5.438 | 보수 성향이 높은 지역에서는 고령층 비율이 증가할수록 K 값이 감소 |
✅ 결론:
K sim_K
Min. :0.4562 Min. :0.6143
1st Qu.:0.5703 1st Qu.:0.7736
Median :0.6171 Median :0.8294
Mean :0.6371 Mean :0.8369
3rd Qu.:0.6801 3rd Qu.:0.8947
Max. :0.9984 Max. :1.1462
✅ 시뮬레이션과 실제 데이터 비교
결론:
1️⃣ 고령층 비율과 K 값 사이에는 비선형 관계가 있음
2️⃣ 미분류표 비율이 증가하면 K 값도 증가
3️⃣ 보수 성향이 강한 지역에서는 고령층 비율이 높을수록 K 값이 감소
4️⃣ 시뮬레이션이 실제 데이터와 유사한 패턴을 보이나, 약간 높은 값을 예측하는 경향이 있음
✅ 시뮬레이션 모델 보정
sim_K가 낮은 K 값을 과대평가하는 경향이 있음. sim_K 공식을 수정하여 보수 성향과 고령층 비율의 상호작용을 더욱 강조 가능. ✅ 랜덤 효과 조정
region)이 K 값 변동에 큰 영향을 미치지 않음.