---
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)
elected_tbl <-
read_rds("data/total_elected_all.rds")
seoul_tbl <- elected_tbl |>
filter(sgId %in% c("20160413", "20200415", "20120411", "20080409", "20040415")) |>
filter(str_detect(sdName, "서울")) |>
select(result) |>
unnest(result) |>
mutate(정당명 = case_when(
jdName %in% c("한나라당", "새누리당", "미래통합당") ~ "국민의힘",
jdName %in% c("더불어민주당", "열린우리당", "통합민주당", "민주통합당") ~ "민주당", TRUE ~ "기타")) |>
count(sgId, 정당명) |>
pivot_wider(names_from = 정당명, values_from = n, values_fill = 0) |>
mutate(합계 = 국민의힘 + 민주당 + 기타)
```
## 경기도
```{r}
gg_tbl <- elected_tbl |>
filter(sgId %in% c("20160413", "20200415", "20120411", "20080409", "20040415")) |>
filter(str_detect(sdName, "경기")) |>
select(result) |>
unnest(result) |>
mutate(정당명 = case_when(
jdName %in% c("한나라당", "새누리당", "미래통합당") ~ "국민의힘",
jdName %in% c("더불어민주당", "열린우리당", "통합민주당", "민주통합당") ~ "민주당", TRUE ~ "기타")) |>
count(sgId, 정당명) |>
pivot_wider(names_from = 정당명, values_from = n, values_fill = 0) |>
mutate(합계 = 국민의힘 + 민주당 + 기타)
```
# 분석
## 서울특별시
```{r}
library(gt)
library(gtExtras)
seoul_tbl |>
gt(rowname_col = "sgId") |>
gt_theme_538() |>
cols_align("center") |>
cols_label(
sgId = "선거명"
) |>
## 표 전체 합계 -------------------------------------
grand_summary_rows(
columns = c(국민의힘, 민주당, 기타, 합계),
fns = list(label = "총계", fn = "sum"),
fmt = ~ fmt_integer(.),
side = "top"
) |>
tab_header(
title = md("서울특별시 국회의원 역대 당선자 현황"),
subtitle = md("중앙선거관리위원회 선거통계")
) |>
tab_spanner(
label = "정당명",
columns = c(
국민의힘, 민주당, 기타
)
) |>
tab_style(
style = cell_text(align = "center"),
locations = cells_stub_grand_summary()
)
```
## 경기도
```{r}
gg_tbl |>
gt(rowname_col = "sgId") |>
gt_theme_538() |>
cols_align("center") |>
cols_label(
sgId = "선거명"
) |>
## 표 전체 합계 -------------------------------------
grand_summary_rows(
columns = c(국민의힘, 민주당, 기타, 합계),
fns = list(label = "총계", fn = "sum"),
fmt = ~ fmt_integer(.),
side = "top"
) |>
tab_header(
title = md("경기도 국회의원 역대 당선자 현황"),
subtitle = md("중앙선거관리위원회 선거통계")
) |>
tab_spanner(
label = "정당명",
columns = c(
국민의힘, 민주당, 기타
)
) |>
tab_style(
style = cell_text(align = "center"),
locations = cells_stub_grand_summary()
)
```
# 지도
## 서울특별시
```{r}
library(sf)
precinct <- st_read("data/2020_21_elec_253.json") |>
st_set_crs(4326)
seoul_precinct <- precinct |>
filter(SGG_1 == "서울")
st_geometry(seoul_precinct) |> plot()
```
```{r}
seoul_party_tbl <- elected_tbl |>
filter(sgId %in% c("20200415")) |>
filter(str_detect(sdName, "서울")) |>
select(result) |>
unnest(result) |>
mutate(정당명 = case_when(
jdName %in% c("한나라당", "새누리당", "미래통합당") ~ "국민의힘",
jdName %in% c("더불어민주당", "열린우리당", "통합민주당", "민주통합당") ~ "민주당", TRUE ~ "기타")) |>
select(sggName, wiwName, giho, jdName, name, dugsu, dugyul, 정당명)
seoul_precinct_party <- seoul_precinct |>
mutate(sggName = str_remove(SGG_2, "서울특별시 ")) |>
left_join(seoul_party_tbl)
```
```{r}
seoul_precinct_party |>
mutate(정당명 = case_when(
jdName %in% c("한나라당", "새누리당", "미래통합당") ~ "국민의힘",
jdName %in% c("더불어민주당", "열린우리당", "통합민주당", "민주통합당") ~ "민주당",
jdName %in% c("정의당") ~ "정의당",
TRUE ~ "기타")) |>
ggplot() +
geom_sf(aes(geometry = geometry, fill = 정당명), alpha =0.2) +
scale_fill_manual(values = c("민주당" = "blue",
"국민의힘" = "red",
"정의당" = "yellow")) +
theme_void() +
theme(legend.position = "top") +
labs(fill = "정당명") +
ggrepel::geom_text_repel(
aes(label = sggName, geometry = geometry,
color = 정당명), stat = "sf_coordinates",
min.segment.length = 1, size = 4, max.overlaps = Inf,
show.legend = FALSE, fontface="bold"
) +
scale_color_manual(values = c("민주당" = "blue",
"국민의힘" = "red",
"정의당" = "black"))
```
## 경기도
```{r}
gg_precinct <- precinct |>
filter(SGG_1 == "경기")
st_geometry(gg_precinct) |> plot()
```
```{r}
gg_party_tbl <- elected_tbl |>
filter(sgId %in% c("20200415")) |>
filter(str_detect(sdName, "경기")) |>
select(result) |>
unnest(result) |>
mutate(정당명 = case_when(
jdName %in% c("한나라당", "새누리당", "미래통합당") ~ "국민의힘",
jdName %in% c("더불어민주당", "열린우리당", "통합민주당", "민주통합당") ~ "민주당", TRUE ~ "기타")) |>
select(sggName, wiwName, giho, jdName, name, dugsu, dugyul, 정당명)
gg_precinct_party <- gg_precinct |>
mutate(sggName = str_remove(SGG_2, "경기도 ")) |>
left_join(gg_party_tbl)
```
```{r}
sf_use_s2(FALSE)
gg_precinct_party |>
mutate(정당명 = case_when(
jdName %in% c("한나라당", "새누리당", "미래통합당") ~ "국민의힘",
jdName %in% c("더불어민주당", "열린우리당", "통합민주당", "민주통합당") ~ "민주당",
jdName %in% c("정의당") ~ "정의당",
TRUE ~ "기타")) |>
ggplot() +
geom_sf(aes(geometry = geometry, fill = 정당명), alpha =0.2) +
scale_fill_manual(values = c("민주당" = "blue",
"국민의힘" = "red",
"정의당" = "yellow")) +
theme_void() +
theme(legend.position = "top") +
labs(fill = "정당명") +
ggrepel::geom_text_repel(
aes(label = sggName, geometry = geometry,
color = 정당명), stat = "sf_coordinates",
min.segment.length = 1, size = 3, max.overlaps = Inf,
show.legend = FALSE, fontface="bold"
) +
scale_color_manual(values = c("민주당" = "blue",
"국민의힘" = "red",
"정의당" = "black"))
```
# 득표차
```{r}
#| eval: false
seoul_krvote <- krvote::general_2020 |>
filter(시도 == "서울") |>
unnest(data) |>
mutate(구분 = ifelse(stringi::stri_enc_isutf8(구분), 구분,
iconv(구분, from = "euc-kr", to = "utf-8")))
seoul_krvote_tbl <- seoul_krvote |>
group_by(선거구, 구분) |>
summarise(사람수 = sum(사람수)) |>
ungroup() |>
mutate(정당 = case_when(
str_detect(구분, "김성식") ~ "국민의힘",
str_detect(구분, "더불어민주당") ~ "민주당",
str_detect(구분, "미래통합당") ~ "국민의힘",
str_detect(구분, "선거인수") ~ "선거인수",
str_detect(구분, "투표수") ~ "투표수",
구분 == "계" ~ "계",
TRUE ~ "기타")) |>
filter(정당 %in% c("민주당", "국민의힘", "계")) |>
select(선거구, 정당, 사람수) |>
pivot_wider(names_from = 정당, values_from = 사람수) |>
mutate(민주_득표율 = 민주당 / 계,
국민_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국민_득표율) |>
mutate(선거구 = ifelse(str_detect(선거구, "_"),
str_extract(선거구, "(.*?)_") |> str_remove("_"), 선거구))
seoul_krvote_tbl |>
write_rds("data/seoul_krvote_tbl.rds")
```
```{r}
seoul_krvote_tbl <-
read_rds("data/seoul_krvote_tbl.rds")
seoul_diff_sf <- seoul_precinct_party |>
left_join(seoul_krvote_tbl, by = c("sggName" = "선거구"))
seoul_diff_sf |>
ggplot() +
geom_sf(aes(geometry = geometry, fill = 득표차, color = 정당명), lwd = 1) +
scale_fill_gradient2(high = "blue", mid = "gray100", low = "red") +
theme_void() +
theme(legend.position = "top") +
labs(fill = "득표차") +
ggrepel::geom_text_repel(
aes(label = str_glue("{sggName}\n{scales::percent(득표차, accuracy=0.1)}"), geometry = geometry),
stat = "sf_coordinates",
min.segment.length = 0, size = 4, max.overlaps = Inf,
show.legend = FALSE, fontface="bold", color = "black"
) +
scale_color_manual(values = c("민주당" = "blue",
"국민의힘" = "red"))
```
# 강서구
## 선거구
```{r}
library(rvest)
gs_precinct_raw <- read_html("https://ko.wikipedia.org/wiki/서울특별시의_선거구") |>
html_elements("table") |>
html_table() %>%
.[[1]] |>
janitor::clean_names(ascii = FALSE) |>
filter(str_detect(선거구명, "강서구"))
gs_precinct <- gs_precinct_raw |>
mutate(동명 = str_split(관할_구역, ",")) |>
select(-관할_구역) |>
unnest(동명) |>
mutate(동명 = str_remove(동명, "강서구") |> str_squish()) |>
mutate(선거구명 = str_remove(선거구명, "\\s+")) |>
select(-번호)
gs_precinct
```
```{r}
seoul_diff_sf |>
filter( str_detect(sggName, "^강서")) |>
ggplot() +
geom_sf(aes(geometry = geometry, fill = 득표차, color = 정당명), lwd = 1) +
scale_fill_gradient2(high = "blue", mid = "gray100", low = "red") +
theme_void() +
theme(legend.position = "top") +
labs(fill = "득표차") +
geom_text(
aes(label = str_glue("{sggName}\n{scales::percent(득표차, accuracy=0.1)}"), geometry = geometry),
stat = "sf_coordinates",
show.legend = FALSE, fontface="bold", color = "black"
) +
scale_color_manual(values = c("민주당" = "blue",
"국민의힘" = "red"))
```
## 행정동 지도
```{r}
library(sf)
korea_sf <- st_read("data/HangJeongDong_ver20230701.geojson")
gs_sf <- korea_sf |>
filter(sidonm == "서울특별시",
sggnm == "강서구") |>
mutate(dongnm = str_remove(adm_nm, "서울특별시 강서구 ")) |>
select(sidonm, sggnm, dongnm, geometry) |>
left_join(gs_precinct, by = c("dongnm" = "동명"))
gs_precinct_sf <- gs_sf |>
group_by(선거구명) |>
summarise(geometry = st_union(geometry))
gs_precinct_centroid <- st_centroid(gs_precinct_sf)
ggplot() +
geom_sf(data = gs_sf, aes(fill = dongnm), alpha = 0.3, show.legend = FALSE) +
geom_sf(data = gs_precinct_sf, color = "gray30",
alpha = 0.0, lwd = 1, show.legend = FALSE) +
theme_void() +
geom_sf(data = gs_precinct_centroid, color = "black") +
ggrepel::geom_text_repel(data = gs_precinct_centroid,
aes(label = 선거구명, geometry = geometry), stat = "sf_coordinates",
min.segment.length = 1, size = 4, max.overlaps = Inf)
```
## 재보궐 선거
```{r}
library(ggrepel)
gs_raw <- readxl::read_excel("data/[2023년_하반기_재·보궐선거]_개표단위별_개표결과.xlsx",
sheet = "Sheet1", skip =4)
gs_tbl <- gs_raw |>
set_names(c("읍면동명", "투표구명", "선거인수", "투표수", "민주당",
"국민의힘", "정의당", "x11", "진보당",
"녹색당", "자유통일당", "계",
"무표투표수", "기권수")) |>
select(-x11) |>
mutate(읍면동명 = str_replace(읍면동명, "제(\\d+)", "\\1")) |>
filter(투표구명 == "소계") |>
mutate(across(선거인수:기권수, parse_number)) |>
mutate(투표율 = 투표수 / 선거인수) |>
left_join(gs_precinct, by = c("읍면동명"="동명"))
gs_tbl |>
group_by(선거구명, 읍면동명) |>
summarise(선거인수 = sum(선거인수),
투표수 = sum(투표수)) |>
mutate(투표율 = 투표수 / 선거인수) |>
ggplot(aes(x = fct_reorder(읍면동명, 투표율), y = 투표율, fill = 선거구명)) +
geom_col( width = 0.5 ) +
facet_wrap(vars(선거구명), scales = "free") +
labs(x = "") +
theme_minimal() +
scale_y_continuous(labels = scales::percent) +
theme(legend.position = "none")
gs_tbl |>
mutate(민주_득표율 = 민주당 / 계,
국힘_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국힘_득표율) |>
ggplot(aes(x = fct_reorder(읍면동명, 득표차), y = 득표차, fill = 선거구명)) +
geom_col( width = 0.5 ) +
facet_wrap(vars(선거구명), scales = "free") +
labs(x = "") +
theme_minimal() +
scale_y_continuous(labels = scales::percent) +
theme(legend.position = "none")
```
## 투표율과 득표차
```{r}
gs_tbl |>
mutate(민주_득표율 = 민주당 / 계,
국힘_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국힘_득표율) |>
mutate(투표율 = 투표수 / 선거인수) |>
select(선거구명, 선거인수, 읍면동명, 득표차, 투표율) |>
ggplot(aes(x = 투표율, y = 득표차, color = 선거구명)) +
geom_point(aes(size = 선거인수)) +
facet_wrap(vars(선거구명)) +
geom_text_repel(aes(label = 읍면동명)) +
theme_minimal() +
theme(legend.position = "top") +
scale_x_continuous(labels = scales::percent) +
scale_y_continuous(labels = scales::percent) +
labs(x = "투표율", y = "득표차",
title = "강서구 재보궐선거 투표율과 득표차",
subtitle = "득표차 = 민주당 후보 득표율 - 국힘 후보 득표율 ") +
guides(color = FALSE) +
scale_size_continuous(labels = scales::comma)
```
# 2020 선거
```{r}
## 강서갑
gs_gap_raw <- readxl::read_excel("data/2020_강서구_개표현황_읍면동별.xlsx",
sheet = "강서구갑", skip =4)
gs_gap <- gs_gap_raw |>
set_names(c("선거구명", "읍면동명", "구분", "선거인수", "투표수", "민주당",
"국민의힘", "x11", "민중당", "국가혁명당", "무소속", "계",
"무표투표수", "기권수")) |>
select(-x11) |>
fill(선거구명, .direction = "down") |>
mutate(읍면동명 = str_replace(읍면동명, "제(\\d+)", "\\1")) |>
filter(구분 == "계") |>
mutate(across(선거인수:기권수, parse_number)) |>
mutate(투표율 = 투표수 / 선거인수)
## 강서을
gs_eul_raw <- readxl::read_excel("data/2020_강서구_개표현황_읍면동별.xlsx",
sheet = "강서구을", skip =4)
gs_eul <- gs_eul_raw |>
set_names(c("선거구명", "읍면동명", "구분", "선거인수", "투표수", "민주당",
"국민의힘", "x11", "우리공화당", "국가혁명당", "무소속", "계",
"무표투표수", "기권수")) |>
select(-x11) |>
fill(선거구명, .direction = "down") |>
mutate(읍면동명 = str_replace(읍면동명, "제(\\d+)", "\\1")) |>
filter(구분 == "계") |>
mutate(across(선거인수:기권수, parse_number)) |>
mutate(투표율 = 투표수 / 선거인수)
## 강서병
gs_byung_raw <- readxl::read_excel("data/2020_강서구_개표현황_읍면동별.xlsx",
sheet = "강서구병", skip =4)
gs_byung <- gs_byung_raw |>
set_names(c("선거구명", "읍면동명", "구분", "선거인수", "투표수", "민주당",
"국민의힘", "x11", "민중당", "국가혁명당", "무소속", "계",
"무표투표수", "기권수")) |>
select(-x11) |>
fill(선거구명, .direction = "down") |>
mutate(읍면동명 = str_replace(읍면동명, "제(\\d+)", "\\1")) |>
filter(구분 == "계") |>
mutate(across(선거인수:기권수, parse_number)) |>
mutate(투표율 = 투표수 / 선거인수)
## 강서구
gs_old <- bind_rows(gs_gap, gs_eul) |>
bind_rows(gs_byung)
```
## 시각화
```{r}
gs_old |>
group_by(선거구명, 읍면동명) |>
summarise(선거인수 = sum(선거인수),
투표수 = sum(투표수)) |>
mutate(투표율 = 투표수 / 선거인수) |>
ggplot(aes(x = fct_reorder(읍면동명, 투표율), y = 투표율, fill = 선거구명)) +
geom_col( width = 0.5 ) +
facet_wrap(vars(선거구명), scales = "free") +
labs(x = "") +
theme_minimal() +
scale_y_continuous(labels = scales::percent) +
theme(legend.position = "none")
gs_old |>
mutate(민주_득표율 = 민주당 / 계,
국힘_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국힘_득표율) |>
ggplot(aes(x = fct_reorder(읍면동명, 득표차), y = 득표차, fill = 선거구명)) +
geom_col( width = 0.5 ) +
facet_wrap(vars(선거구명), scales = "free") +
labs(x = "") +
theme_minimal() +
scale_y_continuous(labels = scales::percent) +
theme(legend.position = "none")
```
## 투표율과 득표차
```{r}
gs_old |>
mutate(민주_득표율 = 민주당 / 계,
국힘_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국힘_득표율) |>
mutate(투표율 = 투표수 / 선거인수) |>
select(선거구명, 선거인수, 읍면동명, 득표차, 투표율) |>
ggplot(aes(x = 투표율, y = 득표차, color = 선거구명)) +
geom_point(aes(size = 선거인수)) +
facet_wrap(vars(선거구명)) +
geom_text_repel(aes(label = 읍면동명)) +
theme_minimal() +
theme(legend.position = "top") +
scale_x_continuous(labels = scales::percent) +
scale_y_continuous(labels = scales::percent) +
labs(x = "투표율", y = "득표차",
title = "강서구 제21대 총선 투표율과 득표차",
subtitle = "득표차 = 민주당 후보 득표율 - 국힘 후보 득표율 ") +
guides(color = FALSE, size = guide_legend(nrow = 1)) +
scale_size_continuous(labels = scales::comma)
```
# 비교
```{r}
gs_by_election <- gs_tbl |>
mutate(민주_득표율 = 민주당 / 계,
국힘_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국힘_득표율) |>
select(선거구명, 읍면동명, 선거인수, 투표수, 투표율, 민주_득표율, 국힘_득표율, 득표차) |>
mutate(선거 = "보궐선거")
gs_2020 <- gs_old |>
mutate(민주_득표율 = 민주당 / 계,
국힘_득표율 = 국민의힘 / 계) |>
mutate(득표차 = 민주_득표율 - 국힘_득표율) |>
select(선거구명, 읍면동명, 선거인수, 투표수, 투표율, 민주_득표율, 국힘_득표율, 득표차) |>
mutate(선거 = "21대총선")
gs_total <- bind_rows(gs_2020, gs_by_election)
```
## 투표율
```{r}
gs_total |>
select(읍면동명, 선거, 투표율) |>
pivot_wider(names_from = 선거, values_from = 투표율) |>
ggplot(aes(x=`21대총선`, y = 보궐선거)) +
geom_point() +
geom_smooth(method = lm, se = FALSE) +
geom_text_repel(aes(label = 읍면동명))
```
## 득표차
```{r}
gs_total |>
select(읍면동명, 선거, 득표차) |>
pivot_wider(names_from = 선거, values_from = 득표차) |>
ggplot(aes(x=`21대총선`, y = 보궐선거)) +
geom_point() +
geom_smooth(method = lm, se = FALSE) +
geom_text_repel(aes(label = 읍면동명))
```
## 정당지지율
### 민주당
```{r}
gs_total |>
select(선거구명, 읍면동명, 선거, 민주_득표율) |>
pivot_wider(names_from = 선거, values_from = 민주_득표율) |>
ggplot(aes(x=`21대총선`, y = 보궐선거)) +
geom_point() +
geom_smooth(method = lm, se = FALSE) +
geom_text_repel(aes(label = 읍면동명)) +
facet_wrap(~선거구명, scales = "free")
```
### 국민의힘
```{r}
gs_total |>
select(선거구명, 읍면동명, 선거, 국힘_득표율) |>
pivot_wider(names_from = 선거, values_from = 국힘_득표율) |>
ggplot(aes(x=`21대총선`, y = 보궐선거)) +
geom_point() +
geom_smooth(method = lm, se = FALSE) +
geom_text_repel(aes(label = 읍면동명)) +
facet_wrap(~선거구명, scales = "free")
```
### 모두
```{r}
gs_total |>
select(선거구명, 읍면동명, 선거, 민주_득표율, 국힘_득표율) |>
pivot_longer(민주_득표율:국힘_득표율, names_to = "정당", values_to = "득표율") |>
pivot_wider(names_from = 선거, values_from = 득표율) |>
ggplot(aes(x=`21대총선`, y = 보궐선거, color = 정당)) +
geom_point() +
geom_smooth(method = lm, se = FALSE) +
geom_text_repel(aes(label = 읍면동명)) +
facet_wrap(~선거구명, scales = "free")
```
# 시각화
## 강서구갑
```{r}
gs_total |>
filter(선거구명 == "강서구갑") |>
select(선거구명, 읍면동명, 선거, 민주_득표율, 국힘_득표율) |>
pivot_longer(민주_득표율:국힘_득표율, names_to = "정당", values_to = "득표율") |>
ggplot(aes(x = 선거, y = 득표율, group = 정당, color = 정당)) +
geom_point() +
geom_line() +
facet_wrap(vars(읍면동명), scales = "fixed")
```
## 강서구을
```{r}
gs_total |>
filter(선거구명 == "강서구을") |>
select(선거구명, 읍면동명, 선거, 민주_득표율, 국힘_득표율) |>
pivot_longer(민주_득표율:국힘_득표율, names_to = "정당", values_to = "득표율") |>
ggplot(aes(x = 선거, y = 득표율, group = 정당, color = 정당)) +
geom_point() +
geom_line() +
facet_wrap(vars(읍면동명), scales = "fixed")
```
## 강서구병
```{r}
gs_total |>
filter(선거구명 == "강서구병") |>
select(선거구명, 읍면동명, 선거, 민주_득표율, 국힘_득표율) |>
pivot_longer(민주_득표율:국힘_득표율, names_to = "정당", values_to = "득표율") |>
ggplot(aes(x = 선거, y = 득표율, group = 정당, color = 정당)) +
geom_point() +
geom_line() +
facet_wrap(vars(읍면동명), scales = "fixed")
```