서울 재보궐 이후

강서구청장 이후 서울특별시 판세를 가늠해보자.

저자
소속

1 인구수

행정안전부 주민등록 인구 및 세대현황에서 서울특별시 구별 인구수를 강서구 중심으로 살펴본자.

코드
library(tidyverse)
library(rvest)
library(gt)
library(gtExtras)

pop_raw <- read_csv("data/202308_202308_주민등록인구및세대현황_월간.csv",
                   locale = locale('ko',encoding='euc-kr'))
seoul_pop <- pop_raw |> 
  select(구명 = 행정구역, 인구수 = `2023년08월_총인구수`) |> 
  mutate(구명 = str_extract(구명, "\\w+구")) |> 
  drop_na() |> 
  mutate(강북강남 = ifelse(구명 %in% c("은평구", "서대문구", "마포구", "종로구", "중구", "용산구", "동대문구", "성동구", "성북구", "중랑구", "광진구", "강북구", "도봉구", "노원구"), "강북", "강남"))

 
seoul_pop_gt <- seoul_pop |>
  arrange(desc(인구수)) |> 
  gt() |> 
    gt_theme_538() |> 
    cols_align("center") |> 
    fmt_integer(columns = 인구수) |> 
    grand_summary_rows(
      columns = 인구수,
      fns = list(
        합계 ~ sum(.)
      ),
      fmt = ~ fmt_integer(., use_seps = TRUE)
    ) |>
    tab_header(
      title = md("**서울특별시 구별 인구수**"),
      subtitle = md("2023년 8월 기준")
    ) |>
    data_color(
      column = 강북강남,
      method = "factor",
      palette = c("red", "blue"),
      alpha = 0.8
    )

seoul_pop_gt
서울특별시 구별 인구수
2023년 8월 기준
구명 인구수 강북강남
송파구 656,176 강남
강서구 565,195 강남
강남구 540,730 강남
노원구 499,908 강북
관악구 485,994 강남
은평구 465,977 강북
강동구 458,400 강남
양천구 437,665 강남
성북구 428,122 강북
서초구 404,616 강남
구로구 394,363 강남
중랑구 383,072 강북
동작구 380,478 강남
영등포구 375,526 강남
마포구 364,573 강북
동대문구 340,333 강북
광진구 336,446 강북
도봉구 307,968 강북
서대문구 307,672 강북
강북구 290,583 강북
성동구 278,750 강북
금천구 228,858 강남
용산구 216,547 강북
종로구 140,032 강북
중구 121,482 강북
합계 9,409,466
코드

# gtsave(seoul_pop_gt, "images/seoul_pop_gt.png")

1.1 시각화

코드
# draw dot plot with ggplot2 
library(ggrepel)
extrafont::loadfonts(device = "win")

comma_man <- function(x) {
  ifelse(x >= 10000, 
         paste0(format(x / 10000, big.mark = ","), "만"),
         as.character(x))
}

seoul_pop_gg <- seoul_pop |> 
  mutate(기준 = 1) |> 
  mutate(강서구여부 = ifelse(구명 == "강서구", "강서구", "강서구아님")) |>
  ggplot(aes(y=인구수)) +
    geom_vline(xintercept = 1, color = "gray90") +
    geom_point(aes(x=기준, color=강서구여부), size=2) +
    geom_text_repel(aes(x = 기준, y = 인구수, label = 구명), 
                        size = 3, family = "MaruBuri",
                        nudge_y = 0.001 ) +
    scale_color_manual(values = c("black", "gray70")) +
    scale_y_continuous(labels = comma_man) +
    labs(title = "서울특별시 구별 인구수",
         subtitle = "2023년 8월 기준",
         x = NULL,
         y = NULL,
         caption = "자료출처: 행정안전부 주민등록 인구 및 세대현황") +
    coord_flip() +
    # coord_fixed(ratio = 0.1) +  # Set the aspect ratio
    theme_korean() +
    theme(legend.position = "none",
          panel.grid.major = element_blank(), 
          panel.grid.minor = element_blank(),
          plot.margin = unit(c(1.5, 1, 11, 1), "cm"),
          axis.ticks.y = element_blank(),  # Hide y-axis ticks
          axis.text.y = element_blank())   # Hide y-axis tick labels 

seoul_pop_gg

ragg::agg_jpeg("images/seoul_pop_gg.jpeg",
               width = 10, height = 7, units = "in", res = 600)
seoul_pop_gg
dev.off()

2 선거결과

2.1 데이터셋

코드
library(krvote)

대선_2022 <- krvote::election_20220309$득표율 |> 
  filter(시도명 == "서울특별시") |> 
  group_by(구시군명) |> 
  summarise(민주당 = sum(이재명),
            국민의힘 = sum(윤석열)) |> 
  ungroup() |> 
  mutate(대선2022 = ifelse(민주당 > 국민의힘, "민주당", "국민의힘"))  |> 
  select(구시군명, 대선2022)

지선_2022 <- krvote::local_sido_20220601 |> 
  filter(str_detect(선거구명, "서울")) |> 
  unnest(data) |> 
  group_by(구시군명) |> 
  summarise(민주당 = sum(더불어민주당_송영길),
            국민의힘 = sum(국민의힘_오세훈)) |> 
  ungroup() |> 
  mutate(지선2022 = ifelse(민주당 > 국민의힘, "민주당", "국민의힘")) |> 
  select(구시군명, 지선2022)

구청장_2022 <- krvote::local_sgg_20220601 |> 
  filter(str_detect(시도명, "서울")) |> 
  mutate(득표 = parse_number(득표)) |> 
  group_by(구시군명 = 선거구_구시군, 정당) |> 
  summarise(득표 = sum(득표)) |> 
  ungroup() |>
  pivot_wider(names_from = 정당, values_from = 득표, values_fill = 0) |> 
  mutate(구청장2022 = ifelse(더불어민주당 > 국민의힘, "민주당", "국민의힘")) |>
  select(구시군명, 구청장2022)


총선_2020 <- krvote::general_2020 |> 
  filter(str_detect(시도, "서울")) |> 
  unnest(data) |> 
  mutate(구시군명 = str_remove(선거구, "[갑|을|병]$")) |> 
  mutate(정당 = case_when(str_detect(구분, "더불어민주당") ~ "민주당",
                          str_detect(구분, "미래통합당") ~ "국민의힘",
                          TRUE ~ "기타")) |> 
  group_by(구시군명, 정당) |>
  summarise(사람수 = sum(사람수)) |> 
  ungroup() |> 
  pivot_wider(names_from = 정당, values_from = 사람수, values_fill = 0) |>
  mutate(총선2020 = ifelse(민주당 > 국민의힘, "민주당", "국민의힘")) |> 
  select(구시군명, 총선2020)  |> 
  mutate(구시군명 = str_remove(구시군명, "중구성동구갑_|중구성동구을_"))

재보궐2021 <- krvote::by_election_2021 |> 
  filter(시도명 == "서울특별시") |> 
  group_by(구시군, 후보) |> 
  summarise(득표수 = sum(득표수)) |> 
  ungroup() |> 
  mutate(정당 = case_when(str_detect(후보, "더불어민주당") ~ "민주당",
                          str_detect(후보, "국민의힘") ~ "국민의힘",
                          TRUE ~ "기타")) |> 
  group_by(구시군명=구시군, 정당) |>
  summarise(득표수 = sum(득표수)) |> 
  ungroup() |> 
  pivot_wider(names_from = 정당, values_from = 득표수, values_fill = 0) |>
  mutate(재보궐2021 = ifelse(민주당 > 국민의힘, "민주당", "국민의힘")) |> 
  select(구시군명, 재보궐2021)

history_raw <- left_join(총선_2020, 재보궐2021)  |> 
  left_join(대선_2022) |> 
  left_join(지선_2022) |> 
  left_join(구청장_2022)

2.2 시각화

“강서구”와 가장 비슷한 선거 결과를 보인 구를 챗GPT로 찾아보자.

코드
history_cor <- history_raw |> 
  pivot_longer(cols = -구시군명, names_to = "선거", values_to = "정당") |>  
  mutate(선거 = factor(선거, levels = c("총선2020", "재보궐2021", "대선2022", "지선2022", "구청장2022"))) |>
  mutate(구시군명 = factor(구시군명, levels = c("송파구", "강서구", "강남구", "노원구", "관악구", 
                                      "은평구", "강동구", "양천구", "성북구", "서초구", 
                                      "구로구", "중랑구", "동작구", "영등포구", "마포구", 
                                      "동대문구", "광진구", "도봉구", "서대문구", "강북구", 
                                      "성동구", "금천구", "용산구", "종로구", "중구")) |> fct_rev()) |> 
  pivot_wider(names_from = 구시군명, values_from = 정당) 
  # mutate(across(-선거, \(x) factor(x, levels = c("민주당", "국민의힘"))))
  # clipr::write_clip(breaks = ",")    

2.2.1 구청장 확대

코드
# jaccard <- function(a, b) {
#     intersection = length(intersect(a, b))
#     union = length(a) + length(b) - intersection
#     return (intersection/union)
# }

# jaccard(hisotry_cor$강서구, hisotry_cor$도봉구)
history_cor_tbl <- history_cor |> select(-선거) |> 
  mutate(across(everything(), \(x) factor(x, levels = c("민주당", "국민의힘"))))
  # mutate(across(everything(), \(x) factor(x, levels = c("민주당", "국민의힘"))))

## Goodman and Kruskal's lambda
library(DescTools)

history_res <- sapply(history_cor_tbl, \(X) sapply(history_cor_tbl, \(Y) Lambda(table(X, Y))))

강서구동일구 <- history_res |> 
  as_tibble() |>
  mutate(구시군명 = rownames(history_res)) |>
  pivot_longer(-구시군명, names_to = "구시군", values_to = "상관계수") |> 
  filter(구시군명 == "강서구") |> 
  filter(상관계수 == 1) |> 
  pull(구시군) 

강서구동일구
#> [1] "강서구"   "구로구"   "도봉구"   "서대문구"
코드
history_raw |> 
  pivot_longer(cols = -구시군명, names_to = "선거", values_to = "정당") |>  
  mutate(선거 = factor(선거, levels = c("총선2020", "재보궐2021", "대선2022", "지선2022",
                                    "구청장2022"))) |>
  mutate(구시군명 = factor(구시군명, levels = c("송파구", "강서구", "강남구", "노원구", "관악구", 
                                      "은평구", "강동구", "양천구", "성북구", "서초구", 
                                      "구로구", "중랑구", "동작구", "영등포구", "마포구", 
                                      "동대문구", "광진구", "도봉구", "서대문구", "강북구", 
                                      "성동구", "금천구", "용산구", "종로구", "중구")) |> fct_rev()) |>
  mutate(강서구 = ifelse(구시군명 %in% c('강서구', '강북구', '관악구', '구로구', '금천구', '노원구', '도봉구'), "강서구", "비강서구")) |> 
  arrange(강서구) |> 
  # filter(강서구 == "강서구") |> 
  filter(구시군명 %in% dput(강서구동일구)) |> 
  ggplot(aes(x = 선거, y = 구시군명, color = 정당, group = 구시군명)) +
    geom_line(color = "gray50") +
    geom_point(size = 3) +
    scale_color_manual(values = c("민주당" = "blue", "국민의힘" = "red")) +
    labs(x = "",
         y = "",
         title = "강서구와 동일한 선거 결과를 보인 구",
         caption = "자료출처: 중앙선거관리위원회 선거통계시스템") +
    theme(legend.position = "top")
#> c("강서구", "구로구", "도봉구", "서대문구")

2.2.2 3대 선거로 한정

코드
history_big_tbl <- history_cor |> 
  filter(선거 != "구청장2022") |>
  select(-선거) |> 
  mutate(across(everything(), \(x) factor(x, levels = c("민주당", "국민의힘"))))
  # mutate(across(everything(), \(x) factor(x, levels = c("민주당", "국민의힘"))))

## Goodman and Kruskal's lambda

history_big <- sapply(history_big_tbl, \(X) sapply(history_big_tbl, \(Y) Lambda(table(X, Y))))

강서구빅3 <- history_big |> 
  as_tibble() |>
  mutate(구시군명 = rownames(history_res)) |>
  pivot_longer(-구시군명, names_to = "구시군", values_to = "상관계수") |> 
  filter(구시군명 == "강서구") |> 
  filter(상관계수 == 1) |> 
  pull(구시군) 

강서구빅3
#>  [1] "강북구"   "강서구"   "관악구"   "구로구"   "금천구"   "노원구"  
#>  [7] "도봉구"   "서대문구" "성북구"   "은평구"   "중랑구"
코드
history_gg <- history_raw |> 
  pivot_longer(cols = -구시군명, names_to = "선거", values_to = "정당") |>  
  mutate(선거 = factor(선거, levels = c("총선2020", "재보궐2021", "대선2022", "지선2022",
                                    "구청장2022"))) |>
  mutate(구시군명 = factor(구시군명, levels = c("강서구", "구로구", "도봉구", "서대문구", 
                                        "강북구", "관악구", "금천구", "노원구", "성북구", 
                                        "은평구", "중랑구" )) |> fct_rev()) |>
  mutate(강서구 = ifelse(구시군명 %in% dput(강서구동일구), "강서구", "비강서구")) |> 
  filter(구시군명 %in% dput(강서구빅3)) |> 
  arrange(강서구) |> 
  ggplot(aes(x = 선거, y = 구시군명, color = 정당, group = 구시군명)) +
    geom_line(color = "gray50") +
    geom_point(size = 3) +
    scale_color_manual(values = c("민주당" = "blue", "국민의힘" = "red")) +
    labs(x = "",
         y = "",
         title = "강서구와 동일한 선거 결과를 보인 구",
         caption = "자료출처: 중앙선거관리위원회 선거통계시스템") +
    theme(legend.position = "top")
#> c("강서구", "구로구", "도봉구", "서대문구")
#> c("강북구", "강서구", "관악구", "구로구", "금천구", 
#> "노원구", "도봉구", "서대문구", "성북구", "은평구", 
#> "중랑구")

history_gg

코드

ragg::agg_jpeg("images/history_gg.jpeg",
               width = 10, height = 7, units = "in", res = 600)
history_gg
dev.off()
#> png 
#>   2