---
title: "지도제작 대회"
subtitle: "권역별 당선인 의석수 변화"
description: |
국회의원 당선인 API를 통해 권역별 의석수 변화를 시각화한다.
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
---
# 데이터
공공데이터포털 [중앙선거관리위원회 당선인정보 API](https://www.data.go.kr/data/15000864/openapi.do)
를 통해 당선인 정보를 조회한다. (선거종류, 선거구명, 시도명, 구시군명, 기호, 정당, 성명, 성별, 연령, 경력, 득표수, 득표율 등을 조회)
## 국회의원 선거 코드
```{r}
#| eval: true
library(tidyverse)
library(rvest)
library(gt)
library(gtExtras)
library(rvest)
# iconv(names(krvote::code_election), from = "EUC-KR", to = "UTF-8")
code_election <- krvote::code_election
names(code_election) <- c("선거코드", "선거명", "선거구분")
legislator_code <- code_election |>
dplyr::filter(str_detect(선거명, "국회의원")) |>
pull(선거코드) |>
unique()
legislator_code
```
## 당선인 API
### 대통령 당선인
```{r}
library(httr)
library(jsonlite)
# httr의 GET 함수로 데이터를 추출한다
response <- GET("http://apis.data.go.kr/9760000/WinnerInfoInqireService2/getWinnerInfoInqire",
query = list(sgId = "20220309",
sgTypecode = "1",
sdName = "전국",
sggName = "대한민국",
pageNo = 1,
numOfRows = 10,
resultType = "json",
serviceKey = Sys.getenv('DATA_GO_DECODE_KEY')))
# 응답 상태와 내용을 확인한다
print(status_code(response))
response_list <- content(response, "text") |>
fromJSON()
response_tbl <- response_list$getWinnerInfoInqire$item
response_tbl
```
### 국회의원 당선인
```{r}
# httr의 GET 함수로 데이터를 추출한다
response <- GET("http://apis.data.go.kr/9760000/WinnerInfoInqireService2/getWinnerInfoInqire",
query = list(sgId = legislator_code[8],
sgTypecode = "2",
sdName = "서울특별시",
sggName = "종로구",
pageNo = 1,
numOfRows = 10,
resultType = "json",
serviceKey = Sys.getenv('DATA_GO_DECODE_KEY')))
# 응답 상태와 내용을 확인한다
print(status_code(response))
response_list <- content(response, "text") |>
fromJSON()
response_tbl <- response_list$getWinnerInfoInqire$item
response_tbl
```
## 국회의원 선거구
선거종류코드(`sgTypecode`): (0) 대표선거명, (1)대통령,(2)국회의원,(3)시도지사,(4)구시군장,(5)시도의원,(6)구시군의회의원,(7)국회의원비례대표,(8)광역의원비례대표,(9)기초의원비례대표,(10)교육의원,(11)교육감
### 스크립트
```{r}
# httr의 GET 함수로 데이터를 추출한다
response <- GET("http://apis.data.go.kr/9760000/CommonCodeService/getCommonSggCodeList",
query = list(sgId = legislator_code[7],
sgTypecode = "2",
numOfRows = "1000",
resultType = "json",
serviceKey = Sys.getenv('DATA_GO_DECODE_KEY')))
# 응답 상태와 내용을 확인한다
print(status_code(response))
response_list <- content(response, "text") |>
fromJSON()
response_tbl <- response_list$getCommonSggCodeList$item
response_tbl
```
### 함수
```{r}
legislator_raw <- code_election |>
dplyr::filter(str_detect(선거명, "국회의원"),
선거구분 != "0")
get_precinct <- function(legislator_cd = "20200415",
sgTypecode = "2") {
# httr의 GET 함수로 데이터를 추출한다
response <- GET("http://apis.data.go.kr/9760000/CommonCodeService/getCommonSggCodeList",
query = list(sgId = legislator_cd,
sgTypecode = sgTypecode,
numOfRows = "1000",
resultType = "json",
serviceKey = Sys.getenv('DATA_GO_DECODE_KEY')))
# 응답 상태와 내용을 확인한다
print(status_code(response))
response_list <- content(response, "text") |>
fromJSON()
response_tbl <- response_list$getCommonSggCodeList$item
return(response_tbl |> as_tibble())
}
get_precinct("20220309")
```
### 전체 선거구
```{r}
# 재보궐 제외
# legislator_tbl <- legislator_raw |>
# dplyr::filter(선거코드 %in% c("20040415", "20080409", "20120411",
# "20160413", "20200415"))
legislator_tbl <- legislator_raw |>
mutate(data = map2(선거코드, 선거구분, get_precinct))
precinct_tbl <- legislator_tbl |>
unnest(data)
precinct_tbl
```
## 국회의원 당선인
### 함수
```{r}
get_elected <- function(sgId = "20200415", sgTypecode = "2",
sdName = "서울특별시",sggName = "종로구") {
response <- GET("http://apis.data.go.kr/9760000/WinnerInfoInqireService2/getWinnerInfoInqire",
query = list(sgId = sgId,
sgTypecode = sgTypecode,
sdName = sdName,
sggName = sggName,
numOfRows = 100,
resultType = "json",
serviceKey = Sys.getenv('DATA_GO_DECODE_KEY')))
# 응답 상태와 내용을 확인한다
cat(status_code(response), ": ", sgId, sgTypecode, sdName, sggName, "\n")
response_list <- content(response, "text") |>
fromJSON()
response_tbl <- response_list$getWinnerInfoInqire$item
Sys.sleep(runif(1, 0,0.5))
return(response_tbl |> as_tibble())
}
get_elected()
```
### 전체 당선인
```{r}
#| eval: false
# sido_code <- tribble(
# ~"시도코드", ~"시도명",
# "1100", "서울특별시",
# "2600", "부산광역시",
# "2700", "대구광역시",
# "2800", "인천광역시",
# "2900", "광주광역시",
# "3000", "대전광역시",
# "3100", "울산광역시",
# "5100", "세종특별자치시",
# "4100", "경기도",
# "4200", "강원도",
# "4300", "충청북도",
# "4400", "충청남도",
# "4500", "전라북도",
# "4600", "전라남도",
# "4700", "경상북도",
# "4800", "경상남도",
# "4900", "제주특별자치도")
elected_raw <- legislator_tbl |>
dplyr::filter(선거코드 == "20200415",
선거구분 == "2") |>
unnest(data) |>
select(sgId, sgTypecode, sdName, sggName, wiwName) |>
mutate(data = pmap(list(sgId, sgTypecode, sdName, sggName), get_elected))
elected_tbl <- elected_raw |>
select(data) |>
unnest(data)
```
### 전체선거 당선인
```{r}
#| eval: false
get_elected_safely <- safely(get_elected, otherwise = "error")
total_elected_raw <- legislator_tbl |>
unnest(data) |>
select(sgId, sgTypecode, sdName, sggName, wiwName) |>
mutate(data = pmap(list(sgId, sgTypecode, sdName, sggName), get_elected_safely))
total_elected_tbl <- total_elected_raw |>
mutate(result = map(data, pluck, "result")) |>
select(sgId, sgTypecode, sdName, sggName, wiwName, result)
total_elected_tbl |>
write_rds("data/total_elected_tbl.rds")
```
### 후처리(오류)
```{r}
#| eval: false
total_elected_tbl <-
read_rds("data/total_elected_tbl.rds")
total_elected_true <- total_elected_tbl |>
mutate(tibble = map_lgl(result, is_tibble)) |>
dplyr::filter(tibble) |>
select(-tibble)
total_elected_false <- total_elected_raw |>
dplyr::filter(sgId == "20120411",
sdName == "인천광역시",
sggName %in% c("남동구을", "부평구갑", "서구강화군갑")) |>
mutate(data = pmap(list(sgId, sgTypecode, sdName, sggName), get_elected_safely)) |>
mutate(result = map(data, pluck, "result")) |>
select(sgId, sgTypecode, sdName, sggName, wiwName, result)
total_elected_all <- bind_rows(total_elected_true, total_elected_false)
total_elected_all |>
write_rds("data/total_elected_all.rds")
```
# 분석
## 국회의원 정수
```{r}
library(tidyverse)
library(gt)
code_election |>
dplyr::filter(str_detect(선거명, "국회의원"),
선거구분 == "0") |>
add_column(의원수 = c(299, 299, 300, 300, 300)) |>
gt() |>
gtExtras::gt_theme_538() |>
cols_align("center")
```
## 역대 정당별 의석수
```{r}
total_elected_all <-
read_rds("data/total_elected_all.rds")
elected_tbl <- total_elected_all |>
select(result) |>
unnest(result)
elected_tbl |>
count(name, birthday, sort=TRUE) |>
count(n) |>
mutate(비율 = nn/sum(nn))
```
```{r}
elected_tbl |>
dplyr::filter(sgId %in% c("20040415", "20080409", "20120411", "20160413", "20200415")) |>
count(sdName, sgId, sort = TRUE) |>
pivot_wider(names_from = sgId, values_from = n)
```
```{r}
# 재선 이상 -------------
elected_key <- elected_tbl |>
dplyr::filter(sgId %in% c("20040415", "20080409", "20120411", "20160413", "20200415")) |>
count(name, birthday, sort=TRUE) |>
dplyr::filter(n > 2) |>
mutate(name_birth = glue::glue("{name}_{birthday}"))
elected_tbl |>
dplyr::filter(sgId %in% c("20040415", "20080409", "20120411", "20160413", "20200415")) |>
mutate(name_birth = glue::glue("{name}_{birthday}")) |>
inner_join(elected_key) |>
ggplot(aes(x = sgId, y = name_birth, group = name_birth)) +
geom_point() +
geom_line()
```
```{r}
#| eval: true
elected_basetable <- elected_tbl |>
left_join(code_election |> dplyr::filter(선거구분 == 0), by = c("sgId" = "선거코드")) |>
mutate(현정당 = case_when(jdName %in% c("민주정의당", "민주자유당", "신한국당",
"한나라당", "새누리당", "자유한국당","새로운보수당",
"미래를향한전진4.0", "미래통합당", "국민의힘") ~ "국민의힘",
jdName %in% c("평화민주당", "통일민주당", "민주당", "새정치국민회의",
"열린우리당", "대통합민주신당", "통합민주당",
"민주통합당", "새정치민주연합", "더불어민주당") ~ "더불어민주당",
TRUE ~ "그외정당")) |>
dplyr::filter(sgId %in% c("20040415", "20080409", "20120411", "20160413", "20200415")) |>
mutate(선거명 = str_remove(선거명, "\\s+국회의원선거")) |>
mutate(sgId_name = str_glue("{선거명} \n{sgId}"))
election_total_gg <- elected_basetable |>
count(sgId_name, 선거명, 현정당, sort=TRUE) |>
ggplot(aes(x = sgId_name, y = n, fill = 현정당)) +
geom_col(width = 0.5) +
scale_fill_manual(values = c("더불어민주당" = "#0054A6", "국민의힘" = "#E2231A",
"그외정당" = "#A6A6A6")) +
labs(fill = NULL,
x ="",
y = "",
title = "역대 총선 민주당과 국민의힘 선거결과") +
theme_korean() +
theme(legend.position = "top",
legend.key.size = unit(0.3, "cm"),
axis.text.x = element_text(angle = 0)) +
coord_flip() +
geom_hline(yintercept = 150, linetype = 2, color = "gray30") +
geom_text(aes(label = n), size = 3, position = position_stack(vjust = 0.5))
# election_total_gg
#
# ragg::agg_jpeg("images/election_total_gg.jpeg",
# width = 10, height = 7, units = "in", res = 600)
# election_total_gg
# dev.off()
```
![](images/election_total_gg.jpeg)
## 의석당 득표수
```{r}
#| eval: false
elected_region_gg <- elected_basetable |>
mutate(권역 = case_when(
sdName %in% c("경기도", "서울특별시", "인천광역시") ~ "수도권",
sdName %in% c("전라남도", "전라북도", "광주광역시") ~ "전라권",
sdName %in% c("경상남도", "경상북도", "대구광역시", "부산광역시", "울산광역시") ~ "경상권",
sdName %in% "전국" ~ "전국",
sdName %in% c("강원도", "세종특별자치시", "제주도", "제주특별자치도", "대전광역시", "충청남도", "충청북도") ~ "충청제주강원")) |>
mutate(득표수 = parse_number(dugsu),
득표율 = parse_number(dugyul)) |>
group_by(sgId, 권역, 현정당) |>
summarise(득표수 = sum(득표수),
득표율 = sum(득표율),
의석수 = n(), .groups = 'drop') |>
pivot_wider(names_from = 현정당, values_from = c("득표수", "득표율", "의석수"), values_fill = 0) |>
mutate(의석수차 = 의석수_더불어민주당 - 의석수_국민의힘) |>
left_join(code_election |> dplyr::filter(선거구분 == 0), by = c("sgId" = "선거코드")) |>
mutate(선거명 = str_remove(선거명, "\\s+국회의원선거")) |>
mutate(sgId_name = str_glue("{선거명} \n{sgId}")) |>
select(sgId_name, sgId, 권역, 의석수차) |>
mutate(의석수차 = ifelse(권역 == "전국" & sgId == "20200415", -2, 의석수차)) |>
# 더불어 민주당/시민당 17, 미래통합당/한국당 19, 국민의당 3, 열린민주당 3
# https://ko.wikipedia.org/wiki/대한민국_제21대_국회의원_선거
mutate(권역 = factor(권역, levels = c("경상권", "전국", "충청제주강원", "전라권", "수도권"))) |>
ggplot(aes(x = sgId_name, y = 의석수차, color = 권역, group = 권역)) +
geom_point(size = 2, show.legend = TRUE) +
geom_line(aes(size = 권역)) +
coord_flip() +
theme_korean() +
labs(y = "의석수차(민주당 - 국민의힘)",
x = "",
caption = "자료: 중앙선거관리위원회 당선인정보 API",
title = "역대 권역별 국회의원 의석수 변화",
subtitle = "권역별 민주당계열 정당 의석수에서 국민의힘 계열정당 의석수 차이") +
geom_text_repel(aes(label = 의석수차), size = 5, show.legend = FALSE) +
scale_color_manual(values = c("전라권" = "#b8cafc", "경상권" = "#fcb8b8",
"전국" = "#A6A6A6", "충청제주강원" = "black",
"수도권" = "#021ffa")) +
theme(legend.position = "top") +
scale_size_manual(values = c(0.5, 0.5, 0.5, 0.5, 1))
# elected_region_gg
#
# ragg::agg_jpeg("images/elected_region_gg.jpeg",
# width = 10, height = 7, units = "in", res = 600)
# elected_region_gg
# dev.off()
```
![](images/elected_region_gg.jpeg)
## 연령
```{r}
elected_basetable |>
mutate(age = parse_number(age)) |>
group_by(sgId, 선거명, 현정당) |>
summarise(평균나이 = mean(age, na.rm = TRUE),
최고연령 = max(age, na.rm = TRUE),
최저연령 = min(age, na.rm = TRUE)) |>
select(-최고연령, -최저연령) |>
pivot_wider(names_from = 현정당, values_from = 평균나이)
elected_basetable |>
mutate(age = parse_number(age)) |>
dplyr::filter(현정당 != "그외정당") |>
ggplot(aes(x = sgId, y = age, fill = gender)) +
geom_boxplot(width=0.3) +
coord_flip() +
facet_wrap(~현정당) +
theme(legend.position = "top")
```
## 국회의원 추적
```{r}
library(ggrepel)
elected_basetable |>
mutate(득표수 = parse_number(dugsu),
득표율 = parse_number(dugyul)) |>
dplyr::filter(득표수 != 0) |>
dplyr::filter(현정당 == "더불어민주당",
sdName %in% c("서울특별시", "경기도", "인천광역시")) |>
ggplot(aes(x = 득표수, y = 득표율, color = sgId)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
geom_text_repel(aes(label = name), size = 2, position = position_jitter(width = 0.1, height = 0.1)) +
theme(legend.position = "top") +
facet_wrap(vars(현정당, sgId), nrow = 1, scales = "free")
```