1 데이터

중앙선거관리위원회 웹사이트에서 시도 데이터를 받아보자.

library(tidyverse)
library(rvest)
library(httr)


nec_20_url <- "http://info.nec.go.kr/electioninfo/electionInfo_report.xhtml?electionId=0020220309&requestURI=%2FWEB-INF%2Fjsp%2Felectioninfo%2F0020220309%2Fvc%2Fvccp09.jsp&topMenuId=VC&secondMenuId=VCCP09&menuId=VCCP09&statementId=VCCP09_%231&electionCode=1&cityCode=0&sggCityCode=0&townCode=-1&sggTownCode=0&x=94&y=25"

nec_20_html <- read_html(nec_20_url)

Sys.setlocale("LC_ALL", "C")
[1] "C"
nec_20_raw <- nec_20_html %>% 
  html_elements(css = '.table01') %>% 
  html_table(fill = TRUE) %>% 
  .[[1]]

Sys.setlocale("LC_ALL", "Korean")
[1] "LC_COLLATE=Korean_Korea.949;LC_CTYPE=Korean_Korea.949;LC_MONETARY=Korean_Korea.949;LC_NUMERIC=C;LC_TIME=Korean_Korea.949"
nec_20_tbl <- nec_20_raw %>% 
  janitor::clean_names(ascii = FALSE) %>% 
  set_names(c("시도명", "선거인수", "투표수", "이재명", "윤석열", "심상정", "오준호", "허경영", "이백윤", "옥은호", "김경재", "조원진", "김재연", "이경희", "김민찬", "계", "무효투표수", "기권수", "개표율")) %>% 
  slice(2:n()) %>% 
  filter(시도명 != "") %>% 
  pivot_longer(이재명:김민찬, names_to = "후보", values_to = "득표수") %>% 
  mutate(득표수 = parse_number(득표수)) %>%
  select(시도명, 후보, 득표수) %>% 
  filter(시도명 != "합계") 

nec_20_tbl
# A tibble: 204 x 3
   시도명     후보    득표수
   <chr>      <chr>    <dbl>
 1 서울특별시 이재명 2944981
 2 서울특별시 윤석열 3255747
 3 서울특별시 심상정  180324
 4 서울특별시 오준호    3829
 5 서울특별시 허경영   36540
 6 서울특별시 이백윤    1571
 7 서울특별시 옥은호     844
 8 서울특별시 김경재    1791
 9 서울특별시 조원진    4657
10 서울특별시 김재연    5615
# ... with 194 more rows

2 데이터 전처리

nec_20_viz <- nec_20_tbl %>% 
  group_by(시도명) %>% 
  mutate(득표율 = 득표수 / sum(득표수) ) %>% 
  ungroup() %>% 
  filter(후보 %in% c("이재명", "윤석열")) %>% 
  ## 시각화를 위한 차이
  select(-득표수) %>% 
  pivot_wider(names_from = 후보, values_from = 득표율) %>% 
  mutate(차이 = 이재명 - 윤석열)

nec_20_viz
# A tibble: 17 x 4
   시도명         이재명 윤석열    차이
   <chr>           <dbl>  <dbl>   <dbl>
 1 서울특별시      0.457  0.506 -0.0483
 2 부산광역시      0.382  0.583 -0.201 
 3 대구광역시      0.216  0.751 -0.535 
 4 인천광역시      0.489  0.471  0.0186
 5 광주광역시      0.848  0.127  0.721 
 6 대전광역시      0.464  0.496 -0.0311
 7 울산광역시      0.408  0.544 -0.136 
 8 세종특별자치시  0.519  0.441  0.0777
 9 경기도          0.509  0.456  0.0532
10 강원도          0.417  0.542 -0.125 
11 충청북도        0.451  0.507 -0.0555
12 충청남도        0.450  0.511 -0.0612
13 전라북도        0.830  0.144  0.686 
14 전라남도        0.861  0.114  0.747 
15 경상북도        0.238  0.728 -0.490 
16 경상남도        0.374  0.582 -0.209 
17 제주특별자치도  0.526  0.427  0.0990

3 시각화

3.1 지도

library(sf)
sido_tilemap <- st_read("data/tilemap_sido/sido_hex_map_raw.shp")
Reading layer `sido_hex_map_raw' from data source 
  `C:\docs\president\data\tilemap_sido\sido_hex_map_raw.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 17 features and 2 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 847131 ymin: 1576357 xmax: 1189641 ymax: 2045920
Projected CRS: PCS_ITRF2000_TM
sido_tilemap %>% 
  ggplot() +
    geom_sf(aes(geometry = geometry)) +
    theme_void()

3.2 지도 + 데이터

sido_tilemap_sf <- sido_tilemap %>% 
  left_join(nec_20_viz, by = c("CTP_KOR_NM" = "시도명")) %>% 
  left_join(nesdc::sido_code, by = c("CTP_KOR_NM" = "시도명") ) %>% 
  mutate(label = glue::glue("{시도}\n{scales::percent(차이, accuracy =0.1)}"))

sido_tilemap_sf
Simple feature collection with 17 features and 8 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 847131 ymin: 1576357 xmax: 1189641 ymax: 2045920
Projected CRS: PCS_ITRF2000_TM
First 10 features:
   CTPRVN_CD     CTP_KOR_NM    이재명    윤석열        차이 시도코드 시도
1         29     광주광역시 0.8482088 0.1272337  0.72097515     2900 광주
2         46       전라남도 0.8610084 0.1144599  0.74654849     4600 전남
3         48       경상남도 0.3738236 0.5824603 -0.20863664     4800 경남
4         26     부산광역시 0.3815725 0.5825543 -0.20098175     2600 부산
5         45       전라북도 0.8298084 0.1442845  0.68552389     4500 전북
6         30     대전광역시 0.4644912 0.4955783 -0.03108711     3000 대전
7         27     대구광역시 0.2160845 0.7514300 -0.53534556     2700 대구
8         44       충청남도 0.4496495 0.5108424 -0.06119290     4400 충남
9         36 세종특별자치시 0.5191547 0.4414744  0.07768029     5100 세종
10        43       충청북도 0.4512462 0.5067476 -0.05550139     4300 충북
                         geometry        label
1  POLYGON ((864318.2 1740704,...  광주\n72.1%
2  POLYGON ((945649 1740704, 9...  전남\n74.7%
3  POLYGON ((1026980 1740704, ... 경남\n-20.9%
4  POLYGON ((1108310 1740704, ... 부산\n-20.1%
5  POLYGON ((904983.6 1811138,...  전북\n68.6%
6  POLYGON ((986314.3 1811138,...  대전\n-3.1%
7  POLYGON ((1067645 1811138, ... 대구\n-53.5%
8  POLYGON ((864318.2 1881573,...  충남\n-6.1%
9  POLYGON ((945649 1881573, 9...   세종\n7.8%
10 POLYGON ((1026980 1881573, ...  충북\n-5.6%

3.3 시각화

sido_tilemap_g <- sido_tilemap_sf %>% 
  ggplot() +
    geom_sf(aes(geometry = geometry, fill = 차이)) +
    theme_void(base_family = "NanumBarunpen") +
    geom_sf_text(aes(geometry = geometry, label = label),
                 size = 7,
                 fun.geometry = function(x) st_centroid(x)) +
    scale_fill_gradientn( colours = c("red", "white", "blue"), labels = scales::percent) +
    labs(
      title = "제20대 대통령선거 시도별 득표율 차이",
      subtitle = "득표율 차이: |이재명(47.83%) - 윤석열(48.56%)| =  0.73%",
      fill = "득표율 차이(%)",
      caption = "데이터출처: 중앙선거관리위원회 선거통계시스템"
    ) +
    theme(
      text = element_text(family = "NanumBarunpen"),
      plot.tag.position = c(0.85, .97),
      legend.position = "right",
      legend.title=element_text(size=15), 
      legend.key.size = unit(1.5, 'cm'),
      legend.text=element_text(size=13),
      plot.title=element_text(size=25, face="bold", family = "NanumBarunpen"),
      plot.subtitle=element_text(face="bold", size=17, colour="grey10", family = "NanumBarunpen"))   

sido_tilemap_g

ragg::agg_png("fig/sido_tilemap_g.png", width = 297, height = 210, units = "mm", res = 600, scaling = 0.85)
sido_tilemap_g
dev.off()
png 
  2 
 

데이터 과학자 이광춘 저작

kwangchun.lee.7@gmail.com