재보궐: 강서구 위치

강서구청장 선거 위치

저자
소속

1 데이터셋

1.1 GRDP

서울시 자치구별 지역내총생산(2015년 기준) 통계

코드
library(tidyverse)
library(readxl)

grdp_raw <- read_excel("data/자치구별+지역내총생산(2015년+기준)_20231011134915.xlsx", skip = 0)

grdp_tbl <- grdp_raw |>
  janitor::clean_names(ascii = FALSE) |> 
  select(자치구 = 자치구별_2, contains("x2020")) |> 
  janitor::row_to_names(row_number = 1) |> 
  set_names(c("자치구", "GRDP", "GRDP_pcnt", "GRDP_2", "GRDP_pcnt2")) |> 
  select(자치구, GRDP) |> 
  mutate( GRDP = parse_number(GRDP)) |> 
  filter(자치구 != "소계") |> mutate(GRDP = GRDP / 1000000 )

grdp_tbl
#> # A tibble: 25 × 2
#>    자치구    GRDP
#>    <chr>    <dbl>
#>  1 종로구   33.9 
#>  2 중구     56.1 
#>  3 용산구   12.8 
#>  4 성동구   12.4 
#>  5 광진구    6.45
#>  6 동대문구  7.40
#>  7 중랑구    4.75
#>  8 성북구    6.19
#>  9 강북구    3.36
#> 10 도봉구    3.50
#> # ℹ 15 more rows

1.2 인구수

코드
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("은평구", "서대문구", "마포구", "종로구", "중구", "용산구", "동대문구", "성동구", "성북구", "중랑구", "광진구", "강북구", "도봉구", "노원구"), "강북", "강남")) |> 
  mutate(인구수 = 인구수 / 10000)

seoul_pop
#> # A tibble: 25 × 3
#>    구명     인구수 강북강남
#>    <chr>     <dbl> <chr>   
#>  1 종로구     14.0 강북    
#>  2 중구       12.1 강북    
#>  3 용산구     21.7 강북    
#>  4 성동구     27.9 강북    
#>  5 광진구     33.6 강북    
#>  6 동대문구   34.0 강북    
#>  7 중랑구     38.3 강북    
#>  8 성북구     42.8 강북    
#>  9 강북구     29.1 강북    
#> 10 도봉구     30.8 강북    
#> # ℹ 15 more rows

1.3 인구 GRDP

코드
pop_grdp <- seoul_pop |> 
  left_join(grdp_tbl, by = c("구명" = "자치구"))

pop_grdp
#> # A tibble: 25 × 4
#>    구명     인구수 강북강남  GRDP
#>    <chr>     <dbl> <chr>    <dbl>
#>  1 종로구     14.0 강북     33.9 
#>  2 중구       12.1 강북     56.1 
#>  3 용산구     21.7 강북     12.8 
#>  4 성동구     27.9 강북     12.4 
#>  5 광진구     33.6 강북      6.45
#>  6 동대문구   34.0 강북      7.40
#>  7 중랑구     38.3 강북      4.75
#>  8 성북구     42.8 강북      6.19
#>  9 강북구     29.1 강북      3.36
#> 10 도봉구     30.8 강북      3.50
#> # ℹ 15 more rows

2 분석

2.1 인구수와 GRDP

코드
library(ggrepel)

pop_grdp |> 
  ggplot(aes(x = 인구수, y = GRDP)) +
    geom_point() +
    theme_minimal(base_family="MaruBrui") +
    theme(legend.position = "none") +
    labs(title = "서울시 자치구 인구수와 지역내총생산",
         x = "인구수(만명)",
         y = "지역내총생산(조원)") +
    scale_x_continuous(labels = scales::unit_format(unit = " 만명", sep = "")) +
    scale_y_continuous(labels = scales::unit_format(unit = " 조원", sep = "")) +
    geom_text_repel(aes(label = 구명),
                    size = 4.5, min.segment.length = 0, force = 0.5, max.overlaps=Inf,
                    color = "black")

3 선거결과

3.1 2021 재보궐 선거

코드
by_election_2021_vote <- krvote::by_election_2021 |> 
  filter(시도명 == "서울특별시",
         구시군 == "강서구")  |> 
  filter(후보 != "후보_계",
         읍면동명 != "합계",
         구분 != "계") |> 
  select(읍면동명, 구분, 후보,  득표수) |> 
  mutate(정당 = case_when(
    str_detect(후보, "국민의힘") ~ "국민의힘",
    str_detect(후보, "더불어민주당") ~ "민주당",
    TRUE ~ "기타")) |> 
  group_by(정당) |> 
  summarise(득표수 = sum(득표수)) |> 
  pivot_wider(names_from = 정당, values_from = 득표수) |> 
  mutate(선거 = "2021 서울시장") 

by_election_2021_cast <- krvote::by_election_2021 |> 
  filter(시도명 == "서울특별시",
         구시군 == "강서구")  |> 
  select(구시군, 읍면동명, 선거인수, 투표수) |> 
  filter(읍면동명 == "합계") |> 
  distinct_all() |> 
  mutate(투표율 = 투표수 / 선거인수) |> 
  select(선거인수, 투표수)

by_election_2021 <- bind_cols(by_election_2021_cast, by_election_2021_vote) |> 
  select(선거, everything())

by_election_2021
#> # A tibble: 1 × 6
#>   선거          선거인수 투표수 국민의힘  기타 민주당
#>   <chr>            <dbl>  <dbl>    <dbl> <dbl>  <dbl>
#> 1 2021 서울시장   505314 284823   152517  9824 120310

3.2 2022 대통령선거

코드
# 투표율
president_2022_cast <- krvote::election_20220309$득표율 |> 
  filter(시도명 == "서울특별시",
         구시군명 == "강서구")  |>
  summarise(선거인수 = sum(선거인수),
            투표수 = sum(투표수))

president_2022_vote <- krvote::election_20220309$득표율 |> 
  filter(시도명 == "서울특별시",
         구시군명 == "강서구") |>
  pivot_longer(이재명:김민찬, names_to = "후보", values_to = "득표수") |>
  mutate(정당 = case_when(
    str_detect(후보, "윤석열") ~ "국민의힘",
    str_detect(후보, "이재명") ~ "민주당",
    TRUE ~ "기타")) |> 
  group_by(정당) |> 
  summarise(득표수 = sum(득표수)) |> 
  pivot_wider(names_from = 정당, values_from = 득표수) |> 
  mutate(선거 = "2022 대통령선거")   
   

president_2022 <- bind_cols(president_2022_cast, president_2022_vote) |> 
  select(선거, everything())

president_2022
#> # A tibble: 1 × 6
#>   선거            선거인수 투표수 국민의힘  기타 민주당
#>   <chr>              <dbl>  <dbl>    <dbl> <dbl>  <dbl>
#> 1 2022 대통령선거   504185 390247   181510 14866 190000

3.3 2020 국회의원선거

코드
chongsun_2020 <- krvote::general_2020 |> 
  filter(시도 == "서울",
         str_detect(선거구, "^강서")) |> 
  unnest(data) |> 
  mutate(구분 = ifelse(stringi::stri_enc_isutf8(구분), 구분, 
                     iconv(구분, from = "euc-kr", to ="utf-8") )) |> 
  filter(!구분 %in% c("계", "기권수", "무표투표수")) |>
  mutate(후보 = case_when(
    str_detect(구분, "더불어민주당") ~ "민주당",
    str_detect(구분, "미래통합당") ~ "국민의힘",
    str_detect(구분, "선거인수") ~ "선거인수",
    str_detect(구분, "투표수") ~ "투표수",
    TRUE ~ "기타")) |> 
  group_by(후보) |> 
  summarise(사람수 = sum(사람수)) |> 
  pivot_wider(names_from = 후보, values_from = 사람수) |> 
  mutate(선거 = "2020 국회의원") |> 
  select(선거, 선거인수, 투표수, 국민의힘, 기타, 민주당)

chongsun_2020
#> # A tibble: 1 × 6
#>   선거          선거인수 투표수 국민의힘  기타 민주당
#>   <chr>            <dbl>  <dbl>    <dbl> <dbl>  <dbl>
#> 1 2020 국회의원   511124 343759   133155 12084 194596

3.4 2023년 보궐선거

코드
library(readxl)

by_election_2023_raw <- read_excel("data/[2023년_하반기_재·보궐선거]_개표진행상황.xlsx",
                                   skip = 5)

by_election_2023 <- by_election_2023_raw |>
  set_names(c("선거구명", "구시군명", "선거인수", "투표수", "더불어민주당\n진교훈", 
"국민의힘\n김태우", "정의당\n권수정", "x8", "진보당\n권혜인", 
"녹색당\n김유리", "자유통일당\n고영일", "계", 
"무효투표수", "기권수", "개표율")) |> 
  filter(!is.na(구시군명)) |> 
  pivot_longer(cols = contains("\n"), names_to = "후보",
              values_to = "사람수") |> 
  select(구시군명, 선거인수, 투표수, 후보, 사람수) |> 
  mutate(사람수 = parse_number(사람수)) |> 
  mutate(후보 = case_when(
    str_detect(후보, "더불어민주당") ~ "민주당",
    str_detect(후보, "국민의힘") ~ "국민의힘",
    TRUE ~ "기타")) |> 
  group_by(선거인수, 투표수, 후보) |> 
  summarise(사람수 = sum(사람수)) |> 
  ungroup() |> 
  pivot_wider(names_from = 후보, values_from = 사람수) |> 
  mutate(선거 = "2023 재보궐") |> 
  select(선거, 선거인수, 투표수, 국민의힘, 기타, 민주당) |> 
  mutate(선거인수 = parse_number(선거인수),
         투표수 = parse_number(투표수)) 

3.5 2022년 지방선거

코드
local_2022_vote <- krvote::local_sido_20220601 |> 
  filter(선거구명 == "서울특별시") |> 
  unnest(data) |> 
  filter(구시군명 == "강서구") |> 
  pivot_longer(더불어민주당_송영길:무소속_김광종) |> 
  mutate(정당 = case_when(
    str_detect(name, "더불어민주당") ~ "민주당",
    str_detect(name, "국민의힘") ~ "국민의힘",
    TRUE ~ "기타")) |> 
  group_by(정당) |>
  summarise(사람수 = sum(value)) |> 
  pivot_wider(names_from = 정당, values_from = 사람수) |> 
  mutate(선거 = "2022 서울시장")     

local_2022_cast <- krvote::local_sido_20220601 |> 
  filter(선거구명 == "서울특별시") |> 
  unnest(data) |> 
  filter(구시군명 == "강서구") |> 
  summarise(선거인수 = sum(선거인수), 투표수 = sum(투표수))

local_2023 <- bind_cols(local_2022_cast, local_2022_vote) |> 
  select(선거, everything())

local_2023
#> # A tibble: 1 × 6
#>   선거          선거인수 투표수 국민의힘  기타 민주당
#>   <chr>            <dbl>  <dbl>    <dbl> <dbl>  <dbl>
#> 1 2022 서울시장   504606 260947   145128  4664 108938

3.6 데이터 결합

코드
gs_tbl <- bind_rows(by_election_2021, president_2022, chongsun_2020, by_election_2023, local_2023) |> 
  mutate(선거 = factor(선거, levels = c("2020 국회의원", "2021 서울시장",
                                        "2022 대통령선거", "2022 서울시장", "2023 재보궐"))) |> 
  arrange(선거)

gs_tbl
#> # A tibble: 5 × 6
#>   선거            선거인수 투표수 국민의힘  기타 민주당
#>   <fct>              <dbl>  <dbl>    <dbl> <dbl>  <dbl>
#> 1 2020 국회의원     511124 343759   133155 12084 194596
#> 2 2021 서울시장     505314 284823   152517  9824 120310
#> 3 2022 대통령선거   504185 390247   181510 14866 190000
#> 4 2022 서울시장     504606 260947   145128  4664 108938
#> 5 2023 재보궐       500603 243664    95492  9950 137066

4 시각화

4.1 투표율

코드
gs_tbl |>
  select(선거, 선거인수, 투표수) |> 
  pivot_longer(-선거) |> 
  mutate(name = factor(name, levels = c("투표수", "선거인수"))) |>
  ggplot(aes(x = 선거, y = value, fill = name, 
             width = ifelse(name == "투표수", 0.2, 0.4))) +
    geom_col(stat = "identity") +
    theme_bw(base_family = "NanumGothic") +
    theme(legend.position = "top",
          axis.text.y = element_text(size = rel(1.5), colour = "gray35", family = "NanumBarunpen", face="bold"),
          axis.text.x = element_text(size = rel(1.0), colour = "black", family = "NanumBarunpen", face="bold",
                                     angle = 0, vjust = 0.5, hjust=0.5),
          strip.background=element_rect(fill="gray95"),
          plot.title=element_text(size=18, face="bold", family = "NanumBarunpen"),
          plot.subtitle=element_text(face="bold", size=13, colour="grey10", family = "NanumBarunpen")) +
    labs(x        = "",
         y        = "",
         title    = "강서구 선거인수와 투표수",
         caption  = "자료 출처: 중앙선거관리위원회 선거통계 시스템",
         fill     = "") +
    scale_y_continuous(labels = scales::comma, limits = c(0, 530000)) +
    scale_fill_manual(values = c("darkblue", "gray77")) +
    coord_flip() +
    geom_text(aes(label = scales::comma(value)), 
              hjust = -0.1, size = 2.5, 
              family = "NanumBarunpen") 

4.2 득표수

코드
gs_tbl |>
  select(-선거인수, -투표수) |>   
  pivot_longer(-선거) |>
  filter(name != "기타") |>
  mutate(value = ifelse(name == "민주당", value, -value)) |> 
  ggplot(aes(x = 선거, y = value, fill = name)) +
    geom_bar(stat = "identity", width = 0.5) +
    geom_hline(yintercept = 0, linetype = "dashed", color = "gray10") + # 0을 기준으로 빨간색 점선 추가
    labs(
      title = "강서구 주요선거 득표수",
      x = "",
      y = "득표수",
      fill = "정당"
    ) +
    scale_y_continuous(labels = function(x) scales::comma(abs(x))) +
    coord_flip() +
    scale_fill_manual(values = c("red", "darkblue"))  +
    theme_bw(base_family = "NanumGothic") +
    theme(legend.position = "top",
          axis.text.y = element_text(size = rel(1.5), colour = "gray35", family = "NanumBarunpen", face="bold"),
          axis.text.x = element_text(size = rel(1.0), colour = "black", family = "NanumBarunpen", face="bold",
                                     angle = 0, vjust = 0.5, hjust=0.5),
          strip.background=element_rect(fill="gray95"),
          plot.title=element_text(size=18, face="bold", family = "NanumBarunpen"),
          plot.subtitle=element_text(face="bold", size=13, colour="grey10", family = "NanumBarunpen"))   

4.3 득표차

코드
gs_tbl |>
  mutate(표차이 = 민주당 - 국민의힘) |> 
  select(-선거인수, -투표수, - 기타, -국민의힘, -민주당) |> 
  ggplot(aes(x = 선거, y = 표차이, fill = ifelse(표차이>0, "민주당", "국민의힘"))) +
    geom_bar(stat = "identity", width = 0.5) +
    geom_hline(yintercept = 0, linetype = "dashed", color = "gray10") + # 0을 기준으로 빨간색 점선 추가
    labs(
      title = "강서구 주요선거 득표차",
      x = "",
      y = "양당 득표차이",
      fill = "정당"
    ) +
    scale_y_continuous(labels = function(x) scales::comma(abs(x)),
                       limits = c(-70000, 70000)) +
    coord_flip() +
    scale_fill_manual(values = c("red", "darkblue"))  +
    theme_bw(base_family = "NanumGothic") +
    theme(legend.position = "top",
          axis.text.y = element_text(size = rel(1.5), colour = "gray35", family = "NanumBarunpen", face="bold"),
          axis.text.x = element_text(size = rel(1.0), colour = "black", family = "NanumBarunpen", face="bold",
                                     angle = 0, vjust = 0.5, hjust=0.5),
          strip.background=element_rect(fill="gray95"),
          plot.title=element_text(size=18, face="bold", family = "NanumBarunpen"),
          plot.subtitle=element_text(face="bold", size=13, colour="grey10", family = "NanumBarunpen"))