양당과 소선구제

양당과 소선구제에 대해서 살펴보자.

저자
소속

1 데이터셋

코드
library(tidyverse)

## 당선인 데이터
total_elected_all <- 
  read_rds("data/total_elected_all.rds")

chosun_tbl <- total_elected_all |> 
  filter(sgId == "20200415") |> 
  select(result) |> 
  unnest(result)

2 분석

2.1 제21대 총선

코드
chosun_tbl |>
  filter(sgTypecode == "2") |> 
  group_by(jdName) |> 
  mutate(across(dugsu:dugyul, as.numeric)) |> 
  mutate(투표수 = dugsu/(dugyul/100)) |>
  select(sgId, sggName, jdName, dugsu, dugyul, 투표수) |> 
  summarise(당선자수 = n(),
            총득표수 = sum(dugsu),
            총투표수 = sum(투표수)) |> 
  mutate(총득표율 = 총득표수 / sum(총투표수),
         당선자비율 = 당선자수 / sum(당선자수)) |> 
  relocate(당선자비율, .after = 당선자수)
#> # A tibble: 4 × 6
#>   jdName       당선자수 당선자비율 총득표수  총투표수 총득표율
#>   <chr>           <int>      <dbl>    <dbl>     <dbl>    <dbl>
#> 1 더불어민주당      163    0.644   10528543 18489574.  0.366  
#> 2 무소속              5    0.0198    228367   542409.  0.00794
#> 3 미래통합당         84    0.332    5360550  9568298.  0.186  
#> 4 정의당              1    0.00395    56516   143514.  0.00197

2.2 총선 함수

코드
calc_pcnt <- function(sgId_val = "20200415") {
  
  chongsun_tbl <- total_elected_all |> 
    filter(sgId == sgId_val,
           sgTypecode == "2") |>
    select(result) |> 
    unnest(result)
  
  chongsun_tbl |>
    group_by(jdName) |>
    mutate(across(dugsu:dugyul, as.numeric)) |>
    mutate(투표수 = dugsu/(dugyul/100)) |>
    summarise(당선자수 = n(),
              총득표수 = sum(dugsu, na.rm = TRUE),
              총투표수 = sum(투표수, na.rm = TRUE)) |> 
    mutate(총득표율 = 총득표수 / sum(총투표수, na.rm = TRUE),
           당선자비율 = 당선자수 / sum(당선자수, na.rm = TRUE)) |> 
    relocate(당선자비율, .after = 당선자수)
}

calc_pcnt("20160413")
#> # A tibble: 5 × 6
#>   jdName       당선자수 당선자비율 총득표수  총투표수 총득표율
#>   <chr>           <int>      <dbl>    <dbl>     <dbl>    <dbl>
#> 1 국민의당           25    0.0988   1181715  2359827.  0.0492 
#> 2 더불어민주당      110    0.435    5059991 10656043.  0.211  
#> 3 무소속             11    0.0435    521726  1080669.  0.0217 
#> 4 새누리당          105    0.415    4864607  9654014.  0.203  
#> 5 정의당              2    0.00791   132940   254308.  0.00554

2.3 전체결과

코드
two_party_tbl <- total_elected_all |> 
  count(sgId, name = "당선인수") |> 
  filter(당선인수 > 200) |> 
  mutate(data = map(sgId, calc_pcnt)) |> 
  unnest(data) |> 
  mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
                                        "미래통합당", "국민의힘") ~ "국민의힘",
                            jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
                                        "민주통합당", "더불어민주당") ~ "민주당",
                            TRUE ~ "그외정당"))  |> 
  mutate(양당 = ifelse(정당 %in% c("국민의힘", "민주당"), "양당", "그외정당")) |> 
  group_by(sgId, 양당) |> 
  summarise(당선인수 = sum(당선자수),
            총투표수 = sum(총투표수),
            총득표수 = sum(총득표수)) |>  
  mutate(당선비율 = 당선인수 / sum(당선인수),
         총투표율 = 총투표수 / sum(총투표수),
         총득표율 = 총득표수 / sum(총투표수)) |> 
  ungroup()

two_party_tbl
#> # A tibble: 10 × 8
#>    sgId     양당     당선인수  총투표수 총득표수 당선비율 총투표율 총득표율
#>    <chr>    <chr>       <int>     <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#>  1 20040415 그외정당       14  1020766.   515345   0.0576   0.0478  0.0242 
#>  2 20040415 양당          229 20312140. 10185569   0.942    0.952   0.477  
#>  3 20080409 그외정당       48  3510747.  1731383   0.196    0.204   0.101  
#>  4 20080409 양당          197 13703486.  7397139   0.804    0.796   0.430  
#>  5 20120411 그외정당       13  1150327.   528422   0.0528   0.0534  0.0245 
#>  6 20120411 양당          233 20397842. 10912333   0.947    0.947   0.506  
#>  7 20160413 그외정당       38  3694803.  1836381   0.150    0.154   0.0765 
#>  8 20160413 양당          215 20310057.  9924598   0.850    0.846   0.413  
#>  9 20200415 그외정당        6   685923.   284883   0.0237   0.0239  0.00991
#> 10 20200415 양당          247 28057872. 15889093   0.976    0.976   0.553
코드
minju_power_tbl <- total_elected_all |> 
  count(sgId, name = "당선인수") |> 
  filter(당선인수 > 200) |> 
  mutate(data = map(sgId, calc_pcnt)) |> 
  unnest(data) |> 
  mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
                                        "미래통합당", "국민의힘") ~ "국민의힘",
                            jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
                                        "민주통합당", "더불어민주당") ~ "민주당",
                            TRUE ~ "그외정당"))  |> 
  group_by(sgId, 정당) |> 
  summarise(당선인수 = sum(당선자수),
            총투표수 = sum(총투표수),
            총득표수 = sum(총득표수)) |>  
  mutate(당선비율 = 당선인수 / sum(당선인수),
         총투표율 = 총투표수 / sum(총투표수),
         총득표율 = 총득표수 / sum(총투표수)) |> 
  ungroup()

minju_power_tbl
#> # A tibble: 15 × 8
#>    sgId     정당     당선인수  총투표수 총득표수 당선비율 총투표율 총득표율
#>    <chr>    <chr>       <int>     <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#>  1 20040415 국민의힘      100  9060542.  4711716   0.412    0.425   0.221  
#>  2 20040415 그외정당       14  1020766.   515345   0.0576   0.0478  0.0242 
#>  3 20040415 민주당        129 11251598.  5473853   0.531    0.527   0.257  
#>  4 20080409 국민의힘      131  9395402.  5116931   0.535    0.546   0.297  
#>  5 20080409 그외정당       48  3510747.  1731383   0.196    0.204   0.101  
#>  6 20080409 민주당         66  4308084.  2280208   0.269    0.250   0.132  
#>  7 20120411 국민의힘      127 11170801.  5996207   0.516    0.518   0.278  
#>  8 20120411 그외정당       13  1150327.   528422   0.0528   0.0534  0.0245 
#>  9 20120411 민주당        106  9227042.  4916126   0.431    0.428   0.228  
#> 10 20160413 국민의힘      105  9654014.  4864607   0.415    0.402   0.203  
#> 11 20160413 그외정당       38  3694803.  1836381   0.150    0.154   0.0765 
#> 12 20160413 민주당        110 10656043.  5059991   0.435    0.444   0.211  
#> 13 20200415 국민의힘       84  9568298.  5360550   0.332    0.333   0.186  
#> 14 20200415 그외정당        6   685923.   284883   0.0237   0.0239  0.00991
#> 15 20200415 민주당        163 18489574. 10528543   0.644    0.643   0.366

3 시각화

3.1 통합

코드
library(ggrepel)

sgId_tbl <- tribble(~"sgId", ~"sgName",
"20040415", "제17대",
"20080409", "제18대",
"20120411", "제19대",
"20160413", "제20대",
"20200415", "제21대")

two_party_tbl |> 
  left_join(sgId_tbl) |> 
  filter(양당 == "양당") |>
  ggplot(aes(x = 총득표율, y = 당선비율 , color = 양당, group = 양당 )) +
    geom_line() +
    geom_point() +
    scale_x_continuous(labels = scales::percent, limits = c(0,1)) +
    scale_y_continuous(labels = scales::percent, limits = c(0,1)) + 
    coord_fixed(ratio = 1) +
    geom_text_repel(aes(label = sgName), nudge_x = 0.03, nudge_y = 0.00, force = 1.5, color = "gray35") +
    theme_korean() +
    theme(legend.position = "none") +
    labs(title = "양당 총득표율과 당선비율",
         caption = "출처: 중앙선거관리위원회 당선인 API") +
    scale_color_manual(values = c("black"))

3.2 민주 vs 국힘

코드
minju_power_tbl <- total_elected_all |> 
  count(sgId, name = "당선인수") |> 
  filter(당선인수 > 200) |> 
  mutate(data = map(sgId, calc_pcnt)) |> 
  unnest(data) |> 
  mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
                                        "미래통합당", "국민의힘") ~ "국민의힘",
                            jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
                                        "민주통합당", "더불어민주당") ~ "민주당",
                            TRUE ~ "그외정당"))  |> 
  group_by(sgId, 정당) |> 
  summarise(당선인수 = sum(당선자수),
            총투표수 = sum(총투표수),
            총득표수 = sum(총득표수)) |>  
  mutate(당선비율 = 당선인수 / sum(당선인수),
         총투표율 = 총투표수 / sum(총투표수),
         총득표율 = 총득표수 / sum(총투표수)) |> 
  ungroup()

minju_power_tbl
#> # A tibble: 15 × 8
#>    sgId     정당     당선인수  총투표수 총득표수 당선비율 총투표율 총득표율
#>    <chr>    <chr>       <int>     <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#>  1 20040415 국민의힘      100  9060542.  4711716   0.412    0.425   0.221  
#>  2 20040415 그외정당       14  1020766.   515345   0.0576   0.0478  0.0242 
#>  3 20040415 민주당        129 11251598.  5473853   0.531    0.527   0.257  
#>  4 20080409 국민의힘      131  9395402.  5116931   0.535    0.546   0.297  
#>  5 20080409 그외정당       48  3510747.  1731383   0.196    0.204   0.101  
#>  6 20080409 민주당         66  4308084.  2280208   0.269    0.250   0.132  
#>  7 20120411 국민의힘      127 11170801.  5996207   0.516    0.518   0.278  
#>  8 20120411 그외정당       13  1150327.   528422   0.0528   0.0534  0.0245 
#>  9 20120411 민주당        106  9227042.  4916126   0.431    0.428   0.228  
#> 10 20160413 국민의힘      105  9654014.  4864607   0.415    0.402   0.203  
#> 11 20160413 그외정당       38  3694803.  1836381   0.150    0.154   0.0765 
#> 12 20160413 민주당        110 10656043.  5059991   0.435    0.444   0.211  
#> 13 20200415 국민의힘       84  9568298.  5360550   0.332    0.333   0.186  
#> 14 20200415 그외정당        6   685923.   284883   0.0237   0.0239  0.00991
#> 15 20200415 민주당        163 18489574. 10528543   0.644    0.643   0.366


minju_power_tbl |> 
  left_join(sgId_tbl) |> 
  # filter(정당 != "그외정당") |>
  mutate(정당 = factor(정당, levels = c("민주당", "국민의힘", "그외정당"))) |> 
  ggplot(aes(x = 총득표율, y = 당선비율 , color = 정당, group = 정당 )) +
    geom_line() +
    geom_point() +
    scale_x_continuous(labels = scales::percent, limits = c(0,1)) +
    scale_y_continuous(labels = scales::percent, limits = c(0,1)) + 
    coord_fixed(ratio = 1) +
    geom_text_repel(aes(label = sgName), nudge_x = 0.03, nudge_y = 0.00, force = 1.5, color = "gray35") +
    theme_korean() +
    theme(legend.position = "top") +
    labs(title = "양당 총득표율과 당선비율",
         caption = "출처: 중앙선거관리위원회 당선인 API") +
    scale_color_manual(values = c("국민의힘" = "red", "민주당" = "blue", "그외정당" = "black")) +
    geom_abline(slope = 1, intercept = 0, linetype = 2, color = "gray30")

3.3 막대그래프

3.3.1 양당 당선자

코드
single_member_gg <- two_party_tbl |> 
  select(sgId, 양당, 당선인수) |> 
  pivot_wider(names_from = 양당, values_from = 당선인수) |> 
  mutate(선거인수 = 양당 + 그외정당) |> 
  select(-그외정당) |> 
  pivot_longer(양당:선거인수, names_to = "구분", values_to = "정수") |>
  mutate(구분 = ifelse(구분 == "양당", "민주+국힘", "선거정수")) |> 
  mutate(구분 = factor(구분, levels = c("민주+국힘", "선거정수"))) |>
  left_join(sgId_tbl) |>
  ggplot(aes(x =  sgName, y = 정수, fill = 구분, width = ifelse(구분 == "민주+국힘", 0.3, 0.5))) +
    geom_col(data = . %>% filter(구분 == "선거정수"), alpha = 0.9) +
    geom_col(data = . %>% filter(구분 == "민주+국힘"), alpha = 0.9) +
    scale_fill_manual(values = c("선거정수" = "gray77", "민주+국힘" = "darkblue")) +
    theme_korean() +
    theme(legend.position = "top") +
    labs(x = "",
         y = "선거정수 및 양당 당선자수",
         title = "역대 국회의원 선거 선거정수와 양당 당선자수") +
    geom_text(data = two_party_tbl |> filter(양당 == "양당") |> 
                    left_join(sgId_tbl) |> 
                    mutate(구분 = "총득표수"),
                    aes(x = sgName, y = 당선인수, 
                        label = scales::percent(당선비율, accuracy = 0.1)), 
              vjust = -0.5, size = 6, family = "NanumSquare", fontface = "bold") +
    scale_y_continuous(limits = c(0, 260))

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

3.3.2 투표자 반영

코드
total_cast_tbl <- two_party_tbl |> 
  group_by(sgId) |> 
  summarise(총투표수 = sum(총투표수))

single_cast_gg <- two_party_tbl |>
  filter(양당 == "양당") |> 
  select(sgId, 총득표수) |> 
  left_join(total_cast_tbl) |> 
  pivot_longer(총득표수:총투표수, names_to = "구분", values_to = "사람수") |>
  left_join(sgId_tbl) |>
  ggplot(aes(x =  sgName, y = 사람수, fill = 구분, width = ifelse(구분 == "총득표수", 0.3, 0.5))) +
    geom_col(data = . %>% filter(구분 == "총투표수"), alpha = 0.9) +
    geom_col(data = . %>% filter(구분 == "총득표수"), alpha = 0.9) +
    scale_fill_manual(values = c("총투표수" = "gray77", "총득표수" = "darkblue")) +
    theme_korean() +
    theme(legend.position = "top") +
    labs(x = "",
         y = "투표수 및 당선자 득표수",
         title = "역대 국회의원 선거 총투표수와 양당 당선 득표수") +
    scale_y_continuous(labels = \(x) paste0( scales::comma(x / 10000), "만")) +
    geom_text(data = two_party_tbl |> filter(양당 == "양당") |> 
                    left_join(sgId_tbl) |> 
                    mutate(구분 = "총득표수") |> 
                    mutate(양당득표율 = 총득표수 / 총투표수),
                    aes(x = sgName, y = 총득표수, 
                        label = scales::percent(양당득표율, accuracy = 0.1)), 
              vjust = -0.5, size = 6, family = "NanumSquare", fontface = "bold") 

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

4 지역별

4.1 데이터

코드
chongsun_tbl <- total_elected_all |> 
  filter(sgId == "20200415",
         sgTypecode == "2") |> 
  select(result) |> 
  unnest(result)

sido_tbl <- chongsun_tbl |>
  group_by(sdName, jdName) |>
  mutate(across(dugsu:dugyul, as.numeric)) |>
  mutate(투표수 = dugsu/(dugyul/100)) |>
  summarise(당선자수 = n(),
            총득표수 = sum(dugsu, na.rm = TRUE),
            총투표수 = sum(투표수, na.rm = TRUE)) |> 
  mutate(총득표율 = 총득표수 / sum(총투표수, na.rm = TRUE),
         당선자비율 = 당선자수 / sum(당선자수, na.rm = TRUE)) |> 
  relocate(당선자비율, .after = 당선자수) |> 
  mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
                                        "미래통합당", "국민의힘") ~ "국민의힘",
                            jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
                                        "민주통합당", "더불어민주당") ~ "민주당",
                            TRUE ~ "그외정당"))  |> 
  mutate(양당 = ifelse(정당 %in% c("국민의힘", "민주당"), "양당", "그외정당")) |> 
  group_by(sdName, 양당) |> 
  summarise(당선인수 = sum(당선자수),
            총투표수 = sum(총투표수),
            총득표수 = sum(총득표수)) |>  
  mutate(당선비율 = 당선인수 / sum(당선인수),
         총투표율 = 총투표수 / sum(총투표수),
         총득표율 = 총득표수 / sum(총투표수)) |> 
  ungroup()

sido_cast_tbl <- chongsun_tbl |> 
  mutate(across(dugsu:dugyul, as.numeric)) |> 
  mutate(투표수 = dugsu/(dugyul/100)) |>
  group_by(sdName) |> 
  summarise(총투표수 = sum(투표수))

4.2 시각화

코드
single_sido_gg <- sido_tbl |> 
  filter(양당 == "양당") |>
  select(sdName, 총득표수) |> 
  left_join(sido_cast_tbl) |> 
  pivot_longer(총득표수:총투표수, names_to = "구분", values_to = "사람수") |> 
  ggplot(aes(x = fct_reorder(sdName, 사람수), y = 사람수, fill = 구분, 
             width = ifelse(구분 == "총득표수", 0.3, 0.6))) +
    geom_col(data = . %>% filter(구분 == "총투표수")) +
    geom_col(data = . %>% filter(구분 == "총득표수")) +
    scale_fill_manual(values = c("총투표수" = "gray77", "총득표수" = "darkblue")) +
    theme_korean() +
    theme(legend.position = "top") +
    labs(x = "",
         y = "투표수 및 당선자 득표수",
         title = "제21대 국회의원 선거 시도별 투표수와 양당 당선 득표수") +
    coord_flip() +
    geom_text(data = sido_tbl |> filter(양당 == "양당") |> 
                mutate(득표율 = 총득표수 / 총투표수) |> 
                mutate(색상 = ifelse(득표율 > 0.572, "red", "blue")) |>                 
                mutate(구분 = ""),
                    aes(x = sdName, y = 총득표수, 
                        label = scales::percent(총득표율, accuracy = 0.1), 
                        color = 색상), 
              hjust = -0.2, size = 4.5, family = "NanumSquare", fontface = "bold") +
    scale_color_identity() +
    scale_y_continuous(labels = \(x) paste0( scales::comma(x / 10000), "만"))   

single_sido_gg

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

5 추세

코드
library(ggridges)

single_trend_gg <- sgId_tbl |> 
  left_join(total_elected_all) |> 
  select(result) |> 
  unnest(result) |> 
  mutate(across(dugsu:dugyul, as.numeric)) |> 
  mutate(투표수 = dugsu/(dugyul/100)) |>
  rename(득표율 = dugyul) |> 
  mutate(득표율 = 득표율 / 100) |> 
  mutate(정당 = case_when(jdName %in% c("한나라당", "새누리당", "자유한국당",
                                        "미래통합당", "국민의힘") ~ "국민의힘",
                            jdName %in% c("새정치국민회의", "열린우리당", "통합민주당",
                                        "민주통합당", "더불어민주당") ~ "민주당",
                            TRUE ~ "그외정당"))  |> 
  filter(정당 != "그외정당") |>
  mutate(sgId = fct_rev(sgId))  |> 
  # filter(득표율 > 0.2) |>
  left_join(sgId_tbl) |> 
  ggplot(aes(x = 득표율, y = sgName, fill = paste(sgName, 정당))) +
    # geom_density_ridges(stat='binline', scale=0.9, alpha=0.8) + 
    geom_density_ridges( alpha = .8, color = "white", from = 0.2, to = 0.9, scale = 0.8) +
    scale_y_discrete(expand = c(0, 0)) +
    scale_x_continuous(labels = scales::percent, expand = c(0, 0)) +  
    coord_cartesian(clip = "off") +
    theme_ridges(grid = FALSE) +
    scale_fill_cyclical(
      values = c("#ff0000", "#0000ff"),
      labels = c(`제17대 국민의힘` = "국민의힘", `제17대 민주당` = "민주당"),
      name = "정당", guide = "legend"
    ) +
    theme_korean() +
    theme(legend.position = "top") +
    geom_vline(xintercept = 0.5, color = "gray30", size = 0.5, linetype = "dashed") +
    labs(
      x = "당선자 득표율 (%)",
      y = "국회의원 선거",
      title = "민주당과 국민의힘 당선자 득표율 분포 변화",
      caption = "자료출처: 중앙선거관리위원회 선거통계시스템 당선자 API"
    )  

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