개요
이 문서는 샘플 데이터를 난수로 생성하여 지방선거 득표 예측 절차를 시연합니다.
실제 예측에서는 공식 자료(NEC 등)로 대체하세요.
데이터 생성
- 동별 유권자 수(2,000~6,000), 투표율(45%~75%)을 임의 생성
- 각 동에서 정당별 선호 비중을 임의로 생성 후 정규화
코드
library(tidyverse)
library(gt)
# 후보/정당 정의
cands <- c("김민주" = "민주당", "이국힘" = "국민의힘", "정개혁" = "개혁신당")
n_precinct <- 25
base <- tibble(
동 = paste0("동", sprintf("%02d", 1:n_precinct)),
유권자수 = sample(2000:6000, n_precinct, replace = TRUE),
투표율 = runif(n_precinct, 0.45, 0.75)
)
# 동별 선호 비중(난수) -> 정규화
shares <-
base |>
mutate(
민주_raw = rexp(n_precinct, rate = 1.2),
국힘_raw = rexp(n_precinct, rate = 1.1),
개혁_raw = rexp(n_precinct, rate = 1.6)
) |>
pivot_longer(ends_with("_raw"), names_to = "키", values_to = "raw") |>
mutate(정당 = recode(키,
"민주_raw" = "민주당",
"국힘_raw" = "국민의힘",
"개혁_raw" = "개혁신당"
)) |>
group_by(동) |>
mutate(비중 = raw / sum(raw)) |>
ungroup() |>
select(동, 정당, 비중)
# 득표수 계산
votes <-
shares |>
left_join(base, by = "동") |>
mutate(총투표수 = round(유권자수 * 투표율),
득표수 = round(총투표수 * 비중)) |>
select(동, 정당, 득표수, 총투표수)
# 전체 요약(정당별 득표율)
summary_party <-
votes |>
group_by(정당) |>
summarise(득표수 = sum(득표수), .groups = "drop") |>
mutate(득표율 = 득표수 / sum(득표수)) |>
arrange(desc(득표율))
summary_party
# A tibble: 3 × 3
정당 득표수 득표율
<chr> <dbl> <dbl>
1 국민의힘 24466 0.394
2 민주당 20143 0.325
3 개혁신당 17426 0.281
그래프: 정당별 예측 득표율(난수 시뮬레이션)
코드
library(scales)
library(ggplot2)
ggplot(summary_party, aes(x = reorder(정당, 득표율), y = 득표율)) +
geom_col() +
geom_text(aes(label = percent(득표율, accuracy = 0.1)), vjust = -0.5, size = 4) +
scale_y_continuous(labels = percent, limits = c(0, max(summary_party$득표율) * 1.25)) +
labs(title = "정당별 예측 득표율 (샘플 난수 데이터)",
x = NULL, y = "득표율") +
theme_minimal(base_size = 14)
해석(샘플): 난수 기반 시뮬레이션 결과로 특정 정당이 상대적으로 높은 득표율을 보일 수 있습니다. 이는 **데이터 생성 과정의 가정(선호 비중·투표율 분포)**에 좌우되며, 실제 분석에서는 표본 설계·가중치·검증을 통해 편향을 줄여야 합니다.
gt 표: 동별 1·2위 및 격차
아래 표는 동별 1위·2위 정당, 득표율 격차를 요약합니다.
코드
# 동별 득표율 계산
by_precinct <- votes |>
group_by(동) |>
mutate(득표율 = 득표수 / sum(득표수)) |>
arrange(desc(득표율), .by_group = TRUE) |>
mutate(ranking = row_number()) |>
ungroup()
top2 <- by_precinct |>
dplyr::filter(ranking <= 2) |>
select(동, 정당, 득표율, 총투표수, ranking)
wide <- top2 |>
pivot_wider(
names_from = ranking,
values_from = c(정당, 득표율),
names_sep = "_"
) |>
mutate(격차 = 득표율_1 - 득표율_2) |>
arrange(desc(격차))
wide |>
transmute(
동,
`1위 정당` = 정당_1,
`1위 득표율` = 득표율_1,
`2위 정당` = 정당_2,
`2위 득표율` = 득표율_2,
`격차(%)` = 격차,
`총투표수` = 총투표수
) |>
gt() |>
tab_header(
title = md("**동별 상위 득표 요약 (샘플 난수 데이터)**"),
subtitle = md("1·2위 정당과 격차, 총투표수")
) |>
fmt_percent(columns = c(`1위 득표율`, `2위 득표율`, `격차(%)`), decimals = 1) |>
fmt_number(columns = `총투표수`, decimals = 0, use_seps = TRUE) |>
cols_align(align = "center", columns = everything()) |>
tab_options(table.font.size = px(14))
| 1·2위 정당과 격차, 총투표수 |
| 동10 |
국민의힘 |
73.0% |
민주당 |
13.9% |
59.1% |
3,974 |
| 동25 |
민주당 |
70.0% |
국민의힘 |
17.2% |
52.9% |
2,343 |
| 동09 |
국민의힘 |
71.1% |
개혁신당 |
18.3% |
52.8% |
2,866 |
| 동14 |
국민의힘 |
68.6% |
개혁신당 |
18.7% |
49.9% |
1,605 |
| 동01 |
국민의힘 |
67.2% |
민주당 |
18.7% |
48.6% |
2,762 |
| 동18 |
국민의힘 |
71.8% |
민주당 |
24.6% |
47.2% |
4,191 |
| 동19 |
개혁신당 |
68.3% |
국민의힘 |
22.9% |
45.4% |
1,653 |
| 동24 |
개혁신당 |
60.2% |
국민의힘 |
22.1% |
38.1% |
3,166 |
| 동03 |
민주당 |
58.5% |
개혁신당 |
21.2% |
37.3% |
1,326 |
| 동21 |
민주당 |
66.4% |
국민의힘 |
32.2% |
34.2% |
2,208 |
| 동16 |
국민의힘 |
58.4% |
민주당 |
26.1% |
32.3% |
1,292 |
| 동11 |
개혁신당 |
57.3% |
민주당 |
26.9% |
30.4% |
4,040 |
| 동07 |
국민의힘 |
57.1% |
민주당 |
28.7% |
28.4% |
3,474 |
| 동22 |
개혁신당 |
58.0% |
민주당 |
31.9% |
26.2% |
1,802 |
| 동17 |
민주당 |
55.9% |
개혁신당 |
35.9% |
20.0% |
1,640 |
| 동05 |
개혁신당 |
50.2% |
국민의힘 |
32.2% |
18.0% |
2,001 |
| 동04 |
개혁신당 |
50.0% |
민주당 |
32.3% |
17.8% |
2,989 |
| 동20 |
민주당 |
41.9% |
개혁신당 |
31.7% |
10.1% |
2,307 |
| 동08 |
민주당 |
40.8% |
국민의힘 |
31.2% |
9.7% |
2,682 |
| 동23 |
개혁신당 |
39.8% |
민주당 |
30.6% |
9.1% |
1,237 |
| 동13 |
민주당 |
51.6% |
개혁신당 |
43.3% |
8.3% |
2,804 |
| 동12 |
개혁신당 |
43.4% |
국민의힘 |
35.4% |
8.1% |
2,434 |
| 동02 |
민주당 |
52.7% |
국민의힘 |
47.0% |
5.7% |
1,244 |
| 동06 |
국민의힘 |
47.2% |
민주당 |
43.1% |
4.2% |
3,677 |
| 동15 |
국민의힘 |
47.3% |
민주당 |
46.6% |
0.7% |
2,317 |
메모
- 본 문서는 샘플 파이프라인으로, 실제 예측에는 *공식 데이터 소스, 가중치 설계, 모델링(예: 다층 로지스틱/베이지안), 검증(홀드아웃/부트스트랩)*을 추가하세요.
- 그래프와 표는
set.seed()로 재현됩니다(시드 변경 시 결과가 달라짐).