제8회 지방선거

서울시장

Author

데이터 과학자 이광춘

Published

October 3, 2022

득표율

Code
library(tidyverse)
library(geofacet)

extrafont::loadfonts()

## 데이터 -----------------------------

seoul_tbl <- read_rds("data/seoul_song_tbl.rds")

## 전체 현황 -----------------------------

overall_g <- seoul_tbl %>% 
  mutate(후보 = factor(후보, levels = c("민주당", "국민의힘", "기타"))) %>% 
  group_by(시점, 후보) %>% 
  summarise(득표 = sum(득표)) %>% 
  mutate(득표율 = 득표 / sum(득표)) %>% 
  ggplot(aes(x = 시점, y = 득표율, fill = 후보)) +
    geom_col(width =0.5) +
    facet_wrap( ~ 후보) +
    geom_text(aes(label = scales::percent(득표율, 0.1)), vjust = -1, size = 5) +
    scale_fill_manual(values = c("blue", "red", "gray50")) +
    scale_y_continuous(labels = scales::percent) +
    labs(title = "서울특별시 득표율 (2021 ~ 2022)",
         subtitle = "재보궐, 대선, 지선",
         x = "") +
    theme_bw(base_family = "Maruburi") +
    theme(legend.position = "none") +
    expand_limits(y = 0.8)

overall_g

ggsave( glue::glue("fig/서울시_전체.png") , 
        overall_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600) 
 
## 구청별 현황 -----------------------------
gu_g <- seoul_tbl %>% 
  mutate(후보 = factor(후보, levels = c("민주당", "국민의힘", "기타"))) %>% 
  group_by(시점, 구시군, 후보) %>% 
  summarise(득표 = sum(득표)) %>% 
  mutate(득표율 = 득표 / sum(득표)) %>% 
  ggplot(aes(x = 시점, y = 득표율, fill = 후보)) +
    geom_bar(width =0.5, position = "dodge", stat='identity') +
    facet_wrap( ~ 구시군) +
    geom_text(aes(label = scales::percent(득표율, 0.1)), 
              position=position_dodge(width=0.9),
              vjust = -0.5, size = 3) +
    scale_fill_manual(values = c("blue", "red", "gray50")) +
    scale_y_continuous(labels = scales::percent) +
    labs(title = "서울특별시 구별 득표율 (2021 ~ 2022)",
         subtitle = "재보궐, 대선, 지선",
         x = "") +
    theme_bw(base_family = "Maruburi") +
    theme(legend.position = "none",
          axis.text.x = element_text(size = 10, angle = 0),
          axis.text.y = element_text(size = 9)) +
    expand_limits(y = 0.8)

gu_g

ggsave( glue::glue("fig/서울시_구별.png") , 
        gu_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600) 

Figure 1: 최근 서울특별시 득표율

지도

Code
# 00. Packages -----------------------------------------------

library(tidyverse)
library(readxl)

# 01. 데이터 -------------------------------------------------

seoul_tbl  <- read_rds("data/seoul_song_tbl.rds")

# 02. 지도 ---------------------

library(geofacet)

# https://github.com/hafen/geofacet/issues/366
seoul_grid <- tribble(~"name",~"row",~"col",~"code",
                      "강북구",1,5,"강북구",
                      "도봉구",1,6,"도봉구",
                      "은평구",2,3,"은평구",
                      "종로구",2,4,"종로구",
                      "성북구",2,5,"성북구",
                      "노원구",2,6,"노원구",
                      "중랑구",2,7,"중랑구",
                      "강서구",3,1,"강서구",
                      "양천구",3,2,"양천구",
                      "마포구",3,3,"마포구",
                      "서대문구",3,4,"서대문구",
                      "중구",3,5,"중구",
                      "동대문구",3,6,"동대문구",
                      "광진구",3,7,"광진구",
                      "강동구",3,8,"강동구",
                      "구로구",4,2,"구로구",
                      "영등포구",4,3,"영등포구",
                      "동작구",4,4,"동작구",
                      "용산구",4,5,"용산구",
                      "성동구",4,6,"성동구",
                      "송파구",4,7,"송파구",
                      "금천구",5,3,"금천구",
                      "관악구",5,4,"관악구",
                      "서초구",5,5,"서초구",
                      "강남구",5,6,"강남구")

## 01. 구별 득표율 ---------------------

draw_geofacet <- function(election = "2022_대선") {
  
  type_year <- str_split(election, "_", simplify = TRUE)
  
  seoul_tbl %>% 
    mutate(후보 = factor(후보, levels = c("민주당", "국민의힘", "기타"))) %>%   
    filter(시점 == election) %>% 
    group_by(시점, 구시군, 후보) %>% 
    summarise(득표 = sum(득표)) %>% 
    mutate(득표율 = 득표 / sum(득표)) %>%   
    rename(name = 구시군)  %>% 
    ggplot(aes(후보, 득표율, fill = 후보)) +
      geom_col(width = 0.5) +
      coord_flip() +
      facet_geo(~ name, grid = seoul_grid) +
      geom_text(aes(label = scales::percent(득표율, 0.1)), hjust = -0.1, size = 3) +
      scale_fill_manual(values = c("blue", "red", "gray50")) +
      scale_y_continuous(labels = scales::percent) +
      labs(title = "서울특별시 구별 득표율 (2022)",
           subtitle = glue::glue("{type_year[1]}년 {type_year[2]}"),
           x = "") +
      theme_bw(base_family = "Maruburi") +
      theme(legend.position = "none") +
      expand_limits(y = 0.8)
}

지선_2021_g <- draw_geofacet("2021_지선")
지선_2022_g <- draw_geofacet("2022_지선")
대선_2022_g <- draw_geofacet("2022_대선")

ggsave( glue::glue("fig/서울시_지선_2021_g.png") , 
        지선_2021_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

ggsave( glue::glue("fig/서울시_지선_2022_g.png") , 
        지선_2022_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

ggsave( glue::glue("fig/서울시_대선_2022_g.png") , 
        대선_2022_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

## 02. 추세 득표율 ---------------------

seoul_geofacet_trend_g <- seoul_tbl %>% 
  mutate(후보 = factor(후보, levels = c("민주당", "국민의힘", "기타"))) %>%   
  group_by(시점, 구시군, 후보) %>% 
  summarise(득표 = sum(득표)) %>% 
  mutate(득표율 = 득표 / sum(득표)) %>%   
  ungroup() %>% 
  rename(name = 구시군)  %>% 
  ggplot(aes(x = 시점, y = 득표율, group= 후보, color = 후보)) +
    geom_line() +
    geom_point() +
    facet_wrap(~ 구시군) +
    facet_geo(~ name, grid = seoul_grid) +
    geom_text(aes(label = scales::percent(득표율, 0.1)), vjust = -0.3, size = 3) +
    scale_color_manual(values = c("blue", "red", "gray50")) +
    scale_y_continuous(labels = scales::percent) +
    theme_bw(base_family = "Maruburi") +
    theme(legend.position = "none") +
    expand_limits(y = 0.8) +
  labs(title = "서울특별시 득표율 (2021 ~ 2022)",
       subtitle = "재보궐, 대선, 지선",
       x = "")

ggsave( glue::glue("fig/서울시_geofacet_g.png") , 
        seoul_geofacet_trend_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

구별

Figure 2: 서울특별시 재보궐, 대선, 지선 구별 득표율

전체 추세

여론조사

정당지지율

Code
# 00. Packages -----------------------------------------------

library(tidyverse)
library(readxl)

# 01. 데이터 -------------------------------------------------

seoul_tbl  <- read_rds("data/seoul_song_tbl.rds")

# 02. 정당지지율 -------------------------------------------------

party_raw <- read_excel("z:/선거데이터/02_여론조사/제8회_지선_여론조사.xlsx", 
                        sheet = "정당지지도", skip = 3)

party_tbl <- party_raw %>% 
  rename(등록번호 = `...1`) %>% 
  slice(1:10) %>% 
  janitor::clean_names(ascii = FALSE) %>% 
  mutate(조사일자 = str_extract(조사기간, "\\d{2}\\.\\d{1,2}\\.\\d{1,2}")) %>% 
  select(등록번호, 조사일자, 더불어민주당, 국민의힘, 정의당, 기타정당, 응답_유보층) %>% 
  pivot_longer(cols = 더불어민주당:응답_유보층, 
               names_to = "정당",
               values_to = "지지율") %>% 
  mutate(지지율 = parse_number(지지율) / 100) %>% 
  mutate(조사일자 = lubridate::ymd(조사일자)) %>% 
  mutate(정당 = factor(정당, levels = c("국민의힘", "더불어민주당", "정의당", "기타정당", "응답_유보층")))
  
party_mean_tbl <- party_tbl %>% 
  group_by(조사일자, 정당) %>% 
  summarise(지지율 = mean(지지율)) %>% 
  ungroup() 

local_seoul_party_g <- party_tbl %>% 
  ggplot(aes(x = 조사일자, y = 지지율, color = 정당)) +
    geom_point() +
    geom_line(data = party_mean_tbl, aes(x = 조사일자, y = 지지율)) +
    scale_color_manual(values = c("red", "blue", "yellow", "gray70", "gray30")) +
    scale_y_continuous(labels = scales::percent) +
    scale_x_date(date_labels = "%m월%d일") +
    theme_bw(base_family = "MaruBuri") +
    labs(title = "제8회 지방선거 여론조사",
         subtitle = "후보자등록 신청 마감일('22. 5. 14.) 이후 여론조사결과")

local_seoul_party_g

ggsave( glue::glue("fig/서울시_지선_정당지지율.png") , 
        local_seoul_party_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

후보지지율

Code
# 3. 서울시장 -------------------------------------------------
## 3.1. 여론조사 결과 -----------------------------------------
seoul_raw <- read_excel("z:/선거데이터/02_여론조사/제8회_지선_여론조사.xlsx", 
                        sheet = "서울(광)", skip = 3)

seoul_interview <- seoul_raw %>% 
  slice(1:8) %>% 
  rename(등록번호 = `...1`) %>% 
  janitor::clean_names(ascii = FALSE) %>% 
  mutate(조사일자 = str_extract(조사기간, "\\d{2}\\.\\d{1,2}\\.\\d{1,2}")) %>% 
  mutate(구분 = "전화인터뷰")
  
seoul_ars <- seoul_raw %>% 
  slice(82:(82+12)) %>% 
  rename(등록번호 = `...1`) %>% 
  janitor::clean_names(ascii = FALSE) %>% 
  mutate(조사일자 = str_extract(조사기간, "\\d{2}\\.\\d{1,2}\\.\\d{1,2}")) %>% 
  mutate(구분 = "ARS")

seoul_tbl <- bind_rows(seoul_interview, seoul_ars) %>% 
  select(등록번호, 조사일자, 구분, 더불어민주당_송영길, 
         국민의힘_오세훈, 정의당_권수정, 기본소득당_신지혜, 
         무소속_김광종, 기타후보, 응답_유보층) %>% 
  pivot_longer(cols = 더불어민주당_송영길:응답_유보층,
               names_to = "정당",
               values_to = "지지율")  %>% 
  mutate(조사일자 = lubridate::ymd(조사일자)) %>% 
  mutate(지지율 = parse_number(지지율) / 100) %>% 
  mutate(정당 = factor(정당, levels = c("국민의힘_오세훈", 
                                    "더불어민주당_송영길",
                                    "정의당_권수정",
                                    "기본소득당_신지혜", 
                                    "무소속_김광종",
                                    "기타후보", 
                                    "응답_유보층" )))

## 3.2. 여론조사 평균 -----------------------------------------
seoul_mean_tbl <- seoul_tbl %>%
  group_by(조사일자, 정당) %>%
  summarise(지지율 = mean(지지율, na.rm = TRUE)) %>%
  ungroup()

## 3.3. 최종 결과 -----------------------------------------
seoul_actual <- tibble(등록번호 = "10000",
       조사일자 = as.Date("2022-06-01"), 
       구분     = "실제선거",
       정당     = "더불어민주당_송영길",
       지지율   = 0.3923
       ) %>% 
  add_row(등록번호 = "10000",
          조사일자 = as.Date("2022-06-01"), 
          구분     = "실제선거",
          정당     = "국민의힘_오세훈",
          지지율   = 0.5905) %>% 
  add_row(등록번호 = "10000",
          조사일자 = as.Date("2022-06-01"), 
          구분     = "실제선거",
          정당     = "정의당_권수정",
          지지율   = 0.0121)   

## 3.4. 연결선 -----------------------------------------
seoul_interpolation <- seoul_mean_tbl %>% 
  filter(조사일자 == max(조사일자)) %>% 
  bind_rows(seoul_actual %>% 
              select(조사일자, 정당, 지지율))

seoul_polls_g <- seoul_tbl %>% 
  ggplot(aes( x= 조사일자, y = 지지율, color = 정당)) +
  geom_point() +
  geom_line(data = seoul_mean_tbl) +
  scale_color_manual(values = c("red", "blue", "yellow", "gray30", "gray40", "gray50", "gray60")) +
  scale_y_continuous(labels = scales::percent) +
  scale_x_date(date_labels = "%m월%d일") +
  theme_bw(base_family = "MaruBuri") +
  labs(title = "제8회 지방선거 여론조사 - 서울시장",
       subtitle = "후보자등록 신청 마감일('22. 5. 14.) 이후 여론조사결과") +
  geom_point(data = seoul_actual, size = 5) +
  geom_line(data = seoul_interpolation, linetype =2) +
  geom_text(data = seoul_actual, aes(label = scales::percent(지지율) ), vjust= -1) +
  theme(panel.background = element_rect(fill = "gray90"))  

seoul_polls_g  

ggsave( glue::glue("fig/서울시_지선_여조_후보지지율.png") , 
        seoul_polls_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

## 여론조사 유형별로 구분 ------------------

seoul_polls_type_g <- seoul_tbl %>% 
  ggplot(aes( x= 조사일자, y = 지지율, color = 정당)) +
  geom_point() +
  geom_smooth(se = FALSE, method = "lm") +
  scale_color_manual(values = c("red", "blue", "yellow", "gray30", "gray40", "gray50", "gray60")) +
  scale_y_continuous(labels = scales::percent) +
  scale_x_date(date_labels = "%m월%d일") +
  theme_bw(base_family = "MaruBuri") +
  labs(title = "제8회 지방선거 조사방법별 여론조사 - 서울시장",
       subtitle = "후보자등록 신청 마감일('22. 5. 14.) 이후 여론조사결과") +
  facet_wrap( ~ 구분) +
  theme(panel.background = element_rect(fill = "gray90"))    

seoul_polls_type_g

ggsave( glue::glue("fig/서울시_지선_여조유형_후보지지율.png") , 
        seoul_polls_type_g,
        device = ragg::agg_png, 
        width = 297, height = 210, units = "mm", res = 600)

Figure 3: 여론조사 및 깜깜이 기간 이후 최종 후보 득표율