---
title: "지도제작 대회"
subtitle: "양당과 소선구제"
description: |
양당과 소선구제에 대해서 살펴보자.
author:
- name: 이광춘
url: https://www.linkedin.com/in/kwangchunlee/
affiliation: 한국 R 사용자회
affiliation-url: https://github.com/bit2r
title-block-banner: true
format:
html:
theme: flatly
code-fold: true
code-overflow: wrap
toc: true
toc-depth: 3
toc-title: 목차
number-sections: true
highlight-style: github
self-contained: false
default-image-extension: jpg
filters:
- lightbox
lightbox: auto
link-citations: true
knitr:
opts_chunk:
eval: true
message: false
warning: false
collapse: true
comment: "#>"
R.options:
knitr.graphics.auto_pdf: true
editor_options:
chunk_output_type: console
---
# 데이터셋
```{r}
library(tidyverse)
## 당선인 데이터
total_elected_all <-
read_rds("data/total_elected_all.rds")
chosun_tbl <- total_elected_all |>
filter(sgId == "20200415") |>
select(result) |>
unnest(result)
```
# 분석
## 제21대 총선
```{r}
chosun_tbl |>
filter(sgTypecode == "2") |>
group_by(jdName) |>
mutate(across(dugsu:dugyul, as.numeric)) |>
mutate(투표수 = dugsu/(dugyul/100)) |>
select(sgId, sggName, jdName, dugsu, dugyul, 투표수) |>
summarise(당선자수 = n(),
총득표수 = sum(dugsu),
총투표수 = sum(투표수)) |>
mutate(총득표율 = 총득표수 / sum(총투표수),
당선자비율 = 당선자수 / sum(당선자수)) |>
relocate(당선자비율, .after = 당선자수)
```
## 총선 함수
```{r}
calc_pcnt <- function(sgId_val = "20200415") {
chongsun_tbl <- total_elected_all |>
filter(sgId == sgId_val,
sgTypecode == "2") |>
select(result) |>
unnest(result)
chongsun_tbl |>
group_by(jdName) |>
mutate(across(dugsu:dugyul, as.numeric)) |>
mutate(투표수 = dugsu/(dugyul/100)) |>
summarise(당선자수 = n(),
총득표수 = sum(dugsu, na.rm = TRUE),
총투표수 = sum(투표수, na.rm = TRUE)) |>
mutate(총득표율 = 총득표수 / sum(총투표수, na.rm = TRUE),
당선자비율 = 당선자수 / sum(당선자수, na.rm = TRUE)) |>
relocate(당선자비율, .after = 당선자수)
}
calc_pcnt("20160413")
```
## 전체결과
```{r}
two_party_tbl <- total_elected_all |>
count(sgId, name = "당선인수") |>
filter(당선인수 > 200) |>
mutate(data = map(sgId, calc_pcnt)) |>
unnest(data) |>
mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
"미래통합당", "국민의힘") ~ "국민의힘",
jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
"민주통합당", "더불어민주당") ~ "민주당",
TRUE ~ "그외정당")) |>
mutate(양당 = ifelse(정당 %in% c("국민의힘", "민주당"), "양당", "그외정당")) |>
group_by(sgId, 양당) |>
summarise(당선인수 = sum(당선자수),
총투표수 = sum(총투표수),
총득표수 = sum(총득표수)) |>
mutate(당선비율 = 당선인수 / sum(당선인수),
총투표율 = 총투표수 / sum(총투표수),
총득표율 = 총득표수 / sum(총투표수)) |>
ungroup()
two_party_tbl
```
```{r}
minju_power_tbl <- total_elected_all |>
count(sgId, name = "당선인수") |>
filter(당선인수 > 200) |>
mutate(data = map(sgId, calc_pcnt)) |>
unnest(data) |>
mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
"미래통합당", "국민의힘") ~ "국민의힘",
jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
"민주통합당", "더불어민주당") ~ "민주당",
TRUE ~ "그외정당")) |>
group_by(sgId, 정당) |>
summarise(당선인수 = sum(당선자수),
총투표수 = sum(총투표수),
총득표수 = sum(총득표수)) |>
mutate(당선비율 = 당선인수 / sum(당선인수),
총투표율 = 총투표수 / sum(총투표수),
총득표율 = 총득표수 / sum(총투표수)) |>
ungroup()
minju_power_tbl
```
# 시각화
## 통합
```{r}
library(ggrepel)
sgId_tbl <- tribble(~"sgId", ~"sgName",
"20040415", "제17대",
"20080409", "제18대",
"20120411", "제19대",
"20160413", "제20대",
"20200415", "제21대")
two_party_tbl |>
left_join(sgId_tbl) |>
filter(양당 == "양당") |>
ggplot(aes(x = 총득표율, y = 당선비율 , color = 양당, group = 양당 )) +
geom_line() +
geom_point() +
scale_x_continuous(labels = scales::percent, limits = c(0,1)) +
scale_y_continuous(labels = scales::percent, limits = c(0,1)) +
coord_fixed(ratio = 1) +
geom_text_repel(aes(label = sgName), nudge_x = 0.03, nudge_y = 0.00, force = 1.5, color = "gray35") +
theme_korean() +
theme(legend.position = "none") +
labs(title = "양당 총득표율과 당선비율",
caption = "출처: 중앙선거관리위원회 당선인 API") +
scale_color_manual(values = c("black"))
```
## 민주 vs 국힘
```{r}
minju_power_tbl <- total_elected_all |>
count(sgId, name = "당선인수") |>
filter(당선인수 > 200) |>
mutate(data = map(sgId, calc_pcnt)) |>
unnest(data) |>
mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
"미래통합당", "국민의힘") ~ "국민의힘",
jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
"민주통합당", "더불어민주당") ~ "민주당",
TRUE ~ "그외정당")) |>
group_by(sgId, 정당) |>
summarise(당선인수 = sum(당선자수),
총투표수 = sum(총투표수),
총득표수 = sum(총득표수)) |>
mutate(당선비율 = 당선인수 / sum(당선인수),
총투표율 = 총투표수 / sum(총투표수),
총득표율 = 총득표수 / sum(총투표수)) |>
ungroup()
minju_power_tbl
minju_power_tbl |>
left_join(sgId_tbl) |>
# filter(정당 != "그외정당") |>
mutate(정당 = factor(정당, levels = c("민주당", "국민의힘", "그외정당"))) |>
ggplot(aes(x = 총득표율, y = 당선비율 , color = 정당, group = 정당 )) +
geom_line() +
geom_point() +
scale_x_continuous(labels = scales::percent, limits = c(0,1)) +
scale_y_continuous(labels = scales::percent, limits = c(0,1)) +
coord_fixed(ratio = 1) +
geom_text_repel(aes(label = sgName), nudge_x = 0.03, nudge_y = 0.00, force = 1.5, color = "gray35") +
theme_korean() +
theme(legend.position = "top") +
labs(title = "양당 총득표율과 당선비율",
caption = "출처: 중앙선거관리위원회 당선인 API") +
scale_color_manual(values = c("국민의힘" = "red", "민주당" = "blue", "그외정당" = "black")) +
geom_abline(slope = 1, intercept = 0, linetype = 2, color = "gray30")
```
## 막대그래프
### 양당 당선자
```{r}
#| eval: false
single_member_gg <- two_party_tbl |>
select(sgId, 양당, 당선인수) |>
pivot_wider(names_from = 양당, values_from = 당선인수) |>
mutate(선거인수 = 양당 + 그외정당) |>
select(-그외정당) |>
pivot_longer(양당:선거인수, names_to = "구분", values_to = "정수") |>
mutate(구분 = ifelse(구분 == "양당", "민주+국힘", "선거정수")) |>
mutate(구분 = factor(구분, levels = c("민주+국힘", "선거정수"))) |>
left_join(sgId_tbl) |>
ggplot(aes(x = sgName, y = 정수, fill = 구분, width = ifelse(구분 == "민주+국힘", 0.3, 0.5))) +
geom_col(data = . %>% filter(구분 == "선거정수"), alpha = 0.9) +
geom_col(data = . %>% filter(구분 == "민주+국힘"), alpha = 0.9) +
scale_fill_manual(values = c("선거정수" = "gray77", "민주+국힘" = "darkblue")) +
theme_korean() +
theme(legend.position = "top") +
labs(x = "",
y = "선거정수 및 양당 당선자수",
title = "역대 국회의원 선거 선거정수와 양당 당선자수") +
geom_text(data = two_party_tbl |> filter(양당 == "양당") |>
left_join(sgId_tbl) |>
mutate(구분 = "총득표수"),
aes(x = sgName, y = 당선인수,
label = scales::percent(당선비율, accuracy = 0.1)),
vjust = -0.5, size = 6, family = "NanumSquare", fontface = "bold") +
scale_y_continuous(limits = c(0, 260))
ragg::agg_jpeg("images/single_member_gg.jpeg",
width = 10, height = 7, units = "in", res = 600)
single_member_gg
dev.off()
```
![](images/single_member_gg.jpeg)
### 투표자 반영
```{r}
#| eval: false
total_cast_tbl <- two_party_tbl |>
group_by(sgId) |>
summarise(총투표수 = sum(총투표수))
single_cast_gg <- two_party_tbl |>
filter(양당 == "양당") |>
select(sgId, 총득표수) |>
left_join(total_cast_tbl) |>
pivot_longer(총득표수:총투표수, names_to = "구분", values_to = "사람수") |>
left_join(sgId_tbl) |>
ggplot(aes(x = sgName, y = 사람수, fill = 구분, width = ifelse(구분 == "총득표수", 0.3, 0.5))) +
geom_col(data = . %>% filter(구분 == "총투표수"), alpha = 0.9) +
geom_col(data = . %>% filter(구분 == "총득표수"), alpha = 0.9) +
scale_fill_manual(values = c("총투표수" = "gray77", "총득표수" = "darkblue")) +
theme_korean() +
theme(legend.position = "top") +
labs(x = "",
y = "투표수 및 당선자 득표수",
title = "역대 국회의원 선거 총투표수와 양당 당선 득표수") +
scale_y_continuous(labels = \(x) paste0( scales::comma(x / 10000), "만")) +
geom_text(data = two_party_tbl |> filter(양당 == "양당") |>
left_join(sgId_tbl) |>
mutate(구분 = "총득표수") |>
mutate(양당득표율 = 총득표수 / 총투표수),
aes(x = sgName, y = 총득표수,
label = scales::percent(양당득표율, accuracy = 0.1)),
vjust = -0.5, size = 6, family = "NanumSquare", fontface = "bold")
ragg::agg_jpeg("images/single_cast_gg.jpeg",
width = 10, height = 7, units = "in", res = 600)
single_cast_gg
dev.off()
```
![](images/single_cast_gg.jpeg)
# 지역별
## 데이터
```{r}
chongsun_tbl <- total_elected_all |>
filter(sgId == "20200415",
sgTypecode == "2") |>
select(result) |>
unnest(result)
sido_tbl <- chongsun_tbl |>
group_by(sdName, jdName) |>
mutate(across(dugsu:dugyul, as.numeric)) |>
mutate(투표수 = dugsu/(dugyul/100)) |>
summarise(당선자수 = n(),
총득표수 = sum(dugsu, na.rm = TRUE),
총투표수 = sum(투표수, na.rm = TRUE)) |>
mutate(총득표율 = 총득표수 / sum(총투표수, na.rm = TRUE),
당선자비율 = 당선자수 / sum(당선자수, na.rm = TRUE)) |>
relocate(당선자비율, .after = 당선자수) |>
mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
"미래통합당", "국민의힘") ~ "국민의힘",
jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
"민주통합당", "더불어민주당") ~ "민주당",
TRUE ~ "그외정당")) |>
mutate(양당 = ifelse(정당 %in% c("국민의힘", "민주당"), "양당", "그외정당")) |>
group_by(sdName, 양당) |>
summarise(당선인수 = sum(당선자수),
총투표수 = sum(총투표수),
총득표수 = sum(총득표수)) |>
mutate(당선비율 = 당선인수 / sum(당선인수),
총투표율 = 총투표수 / sum(총투표수),
총득표율 = 총득표수 / sum(총투표수)) |>
ungroup()
sido_cast_tbl <- chongsun_tbl |>
mutate(across(dugsu:dugyul, as.numeric)) |>
mutate(투표수 = dugsu/(dugyul/100)) |>
group_by(sdName) |>
summarise(총투표수 = sum(투표수))
```
## 시각화
```{r}
#| eval: false
single_sido_gg <- sido_tbl |>
filter(양당 == "양당") |>
select(sdName, 총득표수) |>
left_join(sido_cast_tbl) |>
pivot_longer(총득표수:총투표수, names_to = "구분", values_to = "사람수") |>
ggplot(aes(x = fct_reorder(sdName, 사람수), y = 사람수, fill = 구분,
width = ifelse(구분 == "총득표수", 0.3, 0.6))) +
geom_col(data = . %>% filter(구분 == "총투표수")) +
geom_col(data = . %>% filter(구분 == "총득표수")) +
scale_fill_manual(values = c("총투표수" = "gray77", "총득표수" = "darkblue")) +
theme_korean() +
theme(legend.position = "top") +
labs(x = "",
y = "투표수 및 당선자 득표수",
title = "제21대 국회의원 선거 시도별 투표수와 양당 당선 득표수") +
coord_flip() +
geom_text(data = sido_tbl |> filter(양당 == "양당") |>
mutate(득표율 = 총득표수 / 총투표수) |>
mutate(색상 = ifelse(득표율 > 0.572, "red", "blue")) |>
mutate(구분 = ""),
aes(x = sdName, y = 총득표수,
label = scales::percent(총득표율, accuracy = 0.1),
color = 색상),
hjust = -0.2, size = 4.5, family = "NanumSquare", fontface = "bold") +
scale_color_identity() +
scale_y_continuous(labels = \(x) paste0( scales::comma(x / 10000), "만"))
single_sido_gg
ragg::agg_jpeg("images/single_sido_gg.jpeg",
width = 10, height = 7, units = "in", res = 600)
single_sido_gg
dev.off()
```
![](images/single_sido_gg.jpeg)
# 추세
```{r}
#| eval: false
library(ggridges)
single_trend_gg <- sgId_tbl |>
left_join(total_elected_all) |>
select(result) |>
unnest(result) |>
mutate(across(dugsu:dugyul, as.numeric)) |>
mutate(투표수 = dugsu/(dugyul/100)) |>
rename(득표율 = dugyul) |>
mutate(득표율 = 득표율 / 100) |>
mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
"미래통합당", "국민의힘") ~ "국민의힘",
jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
"민주통합당", "더불어민주당") ~ "민주당",
TRUE ~ "그외정당")) |>
filter(정당 != "그외정당") |>
mutate(sgId = fct_rev(sgId)) |>
# filter(득표율 > 0.2) |>
left_join(sgId_tbl) |>
ggplot(aes(x = 득표율, y = sgName, fill = paste(sgName, 정당))) +
# geom_density_ridges(stat='binline', scale=0.9, alpha=0.8) +
geom_density_ridges( alpha = .8, color = "white", from = 0.2, to = 0.9, scale = 0.8) +
scale_y_discrete(expand = c(0, 0)) +
scale_x_continuous(labels = scales::percent, expand = c(0, 0)) +
coord_cartesian(clip = "off") +
theme_ridges(grid = FALSE) +
scale_fill_cyclical(
values = c("#ff0000", "#0000ff"),
labels = c(`제17대 국민의힘` = "국민의힘", `제17대 민주당` = "민주당"),
name = "정당", guide = "legend"
) +
theme_korean() +
theme(legend.position = "top") +
geom_vline(xintercept = 0.5, color = "gray30", size = 0.5, linetype = "dashed") +
labs(
x = "당선자 득표율 (%)",
y = "국회의원 선거",
title = "민주당과 국민의힘 당선자 득표율 분포 변화",
caption = "자료출처: 중앙선거관리위원회 선거통계시스템 당선자 API"
)
ragg::agg_jpeg("images/single_trends_gg.jpeg",
width = 10, height = 7, units = "in", res = 600)
single_trend_gg
dev.off()
```
![](images/single_trends_gg.jpeg)
```{r}