data_clean/02_국회의원_제22대_20240410.R

################################################################# ---
##             엑셀파일 유권자 투개표 : 국회의원               ##
##                    개발자: 이광춘                           ##
##                최종수정일: 2024-04-17                       ##
################################################################# ---


# 제21대 국회의원 -------------
library(tidyverse)
library(readxl)
library(testthat)
library(here)
library(httr)
library(rvest)

##---------------------------------------------------------------- --
##                    제22대 국회의원                           --
##---------------------------------------------------------------- --
# 1. 데이터 -------------
## 1.1. 스크립트 --------

# 설정된 URL
url <- "http://info.nec.go.kr/electioninfo/electionInfo_report.xhtml"

# POST 요청을 위한 바디 데이터
body <- list(
  electionId = "0020240410",
  requestURI = "/electioninfo/0020240410/vc/vccp09.jsp",
  topMenuId = "VC",
  secondMenuId = "VCCP09",
  menuId = "VCCP09",
  statementId = "VCCP09_#2",
  electionCode = "2",
  cityCode = "1100",
  sggCityCode = "2110101",
  townCode = "-1",
  sggTownCode = "0",
  x = "52",
  y = "20"
)

# POST 요청 실행
response <- POST(url, body = body, encode = "form")

# 응답으로 받은 HTML 확인
content_data <- content(response, as = "text", encoding = "UTF-8")

# HTML을 파싱하여 원하는 데이터 추출
html_data <- read_html(content_data)

# 예시: 특정 HTML 요소 선택 및 데이터 추출 (태그 이름, 클래스 등에 따라 변경 필요)
extracted_tbl <- html_data %>%
  html_nodes("table") %>%
  html_table() %>%
  .[[1]]

v_contents <- extracted_tbl |>
  janitor::clean_names(ascii = FALSE) |>
  slice(3:n()) |>
  filter(구시군명 != "")

v_varnames <- extracted_tbl |>
  janitor::clean_names(ascii = FALSE) |>
  slice(2) |>
  pivot_longer(cols = everything()) |>
  mutate(value = if_else(value == "", name, value)) |>
  pull(value) |> dput()

v_tbl <- v_contents |>
  set_names(v_varnames)


## 1.2. 함수 --------

get_votes <- function(cityCode = "1100", sggCityCode = "2110101") {

  # 설정된 URL
  url <- "http://info.nec.go.kr/electioninfo/electionInfo_report.xhtml"

  # POST 요청을 위한 바디 데이터
  body <- list(
    electionId = "0020240410",
    requestURI = "/electioninfo/0020240410/vc/vccp09.jsp",
    topMenuId = "VC",
    secondMenuId = "VCCP09",
    menuId = "VCCP09",
    statementId = "VCCP09_#2",
    electionCode = "2",
    cityCode = cityCode,
    sggCityCode = sggCityCode,
    townCode = "-1",
    sggTownCode = "0",
    x = "52",
    y = "20"
  )

  # POST 요청 실행
  response <- POST(url, body = body, encode = "form")

  # 응답으로 받은 HTML 확인
  content_data <- content(response, as = "text", encoding = "UTF-8")

  # HTML을 파싱하여 원하는 데이터 추출
  html_data <- read_html(content_data)

  # 예시: 특정 HTML 요소 선택 및 데이터 추출 (태그 이름, 클래스 등에 따라 변경 필요)
  extracted_tbl <- html_data %>%
    html_nodes("table") %>%
    html_table() %>%
    .[[1]]

  v_contents <- extracted_tbl |>
    janitor::clean_names(ascii = FALSE) |>
    slice(3:n()) |>
    filter(구시군명 != "")

  v_varnames <- extracted_tbl |>
    janitor::clean_names(ascii = FALSE) |>
    slice(2) |>
    pivot_longer(cols = everything()) |>
    mutate(value = if_else(value == "", name, value)) |>
    pull(value) |> dput()

  v_tbl <- v_contents |>
    set_names(v_varnames)

  return(v_tbl)
}

get_votes("1100", "2110402")

# 2. 데이터 저장 -------------
## 2.1. 시도 시군구 코드 --------
city_codes <- tribble(
  ~cityCode,  ~cityName,
  "1100", "서울특별시",
  "2600", "부산광역시",
  "2700", "대구광역시",
  "2800", "인천광역시",
  "2900", "광주광역시",
  "3000", "대전광역시",
  "3100", "울산광역시",
  "5100", "세종특별자치시",
  "4100", "경기도",
  "5200", "강원특별자치도",
  "4300", "충청북도",
  "4400", "충청남도",
  "5300", "전북특별자치도",
  "4600", "전라남도",
  "4700", "경상북도",
  "4800", "경상남도",
  "4900", "제주특별자치도"
)

print(city_codes)


## 2.2. 시군구 코드 --------
# 설정된 URL
url <- "http://info.nec.go.kr/electioninfo/electionInfo_report.xhtml"

# POST 요청을 위한 바디 데이터
body <- list(
  electionId = "0020240410",
  requestURI = "/electioninfo/0020240410/vc/vccp09.jsp",
  topMenuId = "VC",
  secondMenuId = "VCCP09",
  menuId = "VCCP09",
  statementId = "VCCP09_#2",
  electionCode = "2",
  cityCode = "1100",
  sggCityCode = "2110101",
  townCode = "-1",
  sggTownCode = "0",
  x = "52",
  y = "20"
)

# POST 요청 실행
response <- POST(url, body = body, encode = "form")

# 응답으로 받은 HTML 확인
content_data <- content(response, as = "text", encoding = "UTF-8")

# HTML을 파싱하여 원하는 데이터 추출
html_data <- read_html(content_data)

sgg_name <- html_data |>
  html_nodes(xpath = '//*[@id="sggCityCode"]/option') |>
  html_text()

sgg_code <- html_data |>
  html_nodes(xpath = '//*[@id="sggCityCode"]/option') |>
  html_attr("value")


 <- tibble(sgg_name, sgg_code) |>
  filter(str_detect(sgg_name, "^[가-힣]"))


## 2.3. 선거구 함수 -------------------------------------------------------------

get_sgg_code <- function(cityCode = "1100") {
  url <- "http://info.nec.go.kr/electioninfo/electionInfo_report.xhtml"

  # POST 요청을 위한 바디 데이터
  body <- list(
    electionId = "0020240410",
    requestURI = "/electioninfo/0020240410/vc/vccp09.jsp",
    topMenuId = "VC",
    secondMenuId = "VCCP09",
    menuId = "VCCP09",
    statementId = "VCCP09_#2",
    electionCode = "2",
    cityCode = cityCode,
    sggCityCode = cityCode,
    townCode = "-1",
    sggTownCode = "0",
    x = "52",
    y = "20"
  )

  # POST 요청 실행
  response <- POST(url, body = body, encode = "form")

  # 응답으로 받은 HTML 확인
  content_data <- content(response, as = "text", encoding = "UTF-8")

  # HTML을 파싱하여 원하는 데이터 추출
  html_data <- read_html(content_data)

  sggCityName <- html_data |>
    html_nodes(xpath = '//*[@id="sggCityCode"]/option') |>
    html_text()

  sggCityCode <- html_data |>
    html_nodes(xpath = '//*[@id="sggCityCode"]/option') |>
    html_attr("value")

  sgg_tbl <- tibble(sggCityCode, sggCityName) |>
    filter(str_detect(sggCityName, "^[가-힣]"))

  return(sgg_tbl)
}

get_sgg_code()

## 2.4. 시도 선거구 코드 ---------------------------------------------------

city_sgg_raw <- city_codes |>
  mutate(data = map(cityCode, get_sgg_code))

city_sgg_tbl  <- city_sgg_raw |>
  unnest(data)

city_sgg_tbl |>
  write_csv("data/제22대_총선_NEC_선거구코드.csv")

# 3. 투개표 ------------------------------------------------------------------

city_sgg_tbl <-
  read_csv("data/제22대_총선_NEC_선거구코드.csv")

# 3.1. 투개표 원데이터 -----------------------------------------------------------

get_votes_verbose <- function(cityCode = "1100", sggCityCode = "2110101") {

  cat("=============================\n")
  cat("시도 코드: ", cityCode, "\n")
  cat("선거구 코드: ", sggCityCode, "\n")
  cat("=============================\n")

  # 설정된 URL
  url <- "http://info.nec.go.kr/electioninfo/electionInfo_report.xhtml"

  # POST 요청을 위한 바디 데이터
  body <- list(
    electionId = "0020240410",
    requestURI = "/electioninfo/0020240410/vc/vccp09.jsp",
    topMenuId = "VC",
    secondMenuId = "VCCP09",
    menuId = "VCCP09",
    statementId = "VCCP09_#2",
    electionCode = "2",
    cityCode = cityCode,
    sggCityCode = sggCityCode,
    townCode = "-1",
    sggTownCode = "0",
    x = "52",
    y = "20"
  )

  # POST 요청 실행
  response <- POST(url, body = body, encode = "form")

  # 응답으로 받은 HTML 확인
  content_data <- content(response, as = "text", encoding = "UTF-8")

  # HTML을 파싱하여 원하는 데이터 추출
  html_data <- read_html(content_data)

  # 예시: 특정 HTML 요소 선택 및 데이터 추출 (태그 이름, 클래스 등에 따라 변경 필요)
  extracted_tbl <- html_data %>%
    html_nodes("table") %>%
    html_table() %>%
    .[[1]]

  if(cityCode == "5100") {

    v_contents <- extracted_tbl |>
      janitor::clean_names(ascii = FALSE) |>
      slice(3:n()) |>
      filter(시도명 != "")

    v_varnames <- extracted_tbl |>
      janitor::clean_names(ascii = FALSE) |>
      slice(2) |>
      pivot_longer(cols = everything()) |>
      mutate(value = if_else(value == "", name, value)) |>
      pull(value) |> dput()

  } else {
    v_contents <- extracted_tbl |>
      janitor::clean_names(ascii = FALSE) |>
      slice(3:n()) |>
      filter(구시군명 != "")

    v_varnames <- extracted_tbl |>
      janitor::clean_names(ascii = FALSE) |>
      slice(2) |>
      pivot_longer(cols = everything()) |>
      mutate(value = if_else(value == "", name, value)) |>
      pull(value) |> dput()
  }



  v_tbl <- v_contents |>
    set_names(v_varnames)

  return(v_tbl)
}

votes_raw <- city_sgg_tbl |>
  mutate(data = map2(cityCode, sggCityCode,
                     safely(get_votes_verbose, otherwise = NA_character_)))

votes_raw |>
  write_rds("data/제22대_총선_NEC_투개표.rds")


# 4. 후처리 -----------------------------------------------------------------
## 4.1. 스크립트 ------------------------------------------------------------
votes_raw <-
  read_rds("data/제22대_총선_NEC_투개표.rds")

precinct <- votes_raw |>
  mutate(result = map(data, "result")) |>
  slice(1) |>
  pull(result) %>%
  .[[1]]


common_tbl <- precinct |>
  select( which(names(precinct) %in% c("시도명", "구시군명", "선거인수", "투표수", "계",
                                       "무효투표수", "기권자수", "개표율")))
party_candidate <- precinct |>
  select((which(names(precinct) == "투표수") + 2):which(names(precinct) == "계") -1) |>
  names() |> dput()

precint_raw <- precinct |>
  pivot_longer(cols = party_candidate,
                names_to = "정당_후보", values_to = "득표수")

precinct_tbl <- precint_raw |>
  select(which(names(precint_raw) %in% c("시도명", "구시군명", "선거인수", "투표수", "계",
                                      "무효투표수", "기권자수", "개표율", "정당_후보", "득표수")))

precinct_tbl

## 4.2. 함수 ------------------------------------------------------------

clean_nec_raw <- function(precinct) {

  common_tbl <- precinct |>
    select( which(names(precinct) %in% c("시도명", "구시군명", "선거인수", "투표수", "계",
                                         "무효투표수", "기권자수", "개표율")))

  party_candidate <- precinct |>
    select((which(names(precinct) == "투표수") + 2):which(names(precinct) == "계") -1) |>
    names() |> dput()

  precint_raw <- precinct |>
    pivot_longer(cols = party_candidate,
                 names_to = "정당_후보", values_to = "득표수")

  precinct_tbl <- precint_raw |>
    select(which(names(precint_raw) %in% c("시도명", "구시군명", "선거인수", "투표수", "계",
                                           "무효투표수", "기권자수", "개표율", "정당_후보", "득표수")))

  precinct_tbl
}

vote_tbl <- votes_raw |>
  mutate(result = map(data, "result")) |>
  mutate(clean_data = map(result, clean_nec_raw)) |>
  select(cityName, sggCityName, clean_data) |>
  unnest(clean_data)

precinct_type <- vote_tbl |>
  group_by(cityName, sggCityName) |>
  summarise(구시군명 = paste0(구시군명, collapse = ", ")) |>
  ungroup() |>
  mutate(선거구구분 = if_else( str_detect(구시군명, "소계"), "복합", "단일"))

general_2024 <- vote_tbl |>
  left_join(precinct_type |> select(-구시군명), by = c("cityName", "sggCityName")) |>
  mutate(구분자 = case_when(선거구구분 == "복합" & 구시군명 == "소계" ~ "추출",
                             선거구구분 == "단일" ~ "추출",
                            TRUE ~ "제거")) |>
  filter(구분자 == "추출") |>
  # 세종
  mutate(구시군명 = if_else(is.na(구시군명), 시도명, 구시군명)) |>
  select(-c(선거구구분, 구분자, 시도명)) |>
  relocate(정당_후보, .before = 선거인수) |>
  mutate(across(선거인수:득표수, parse_number)) |>
  # 정당, 후보
  mutate(정당 = case_when(str_detect(정당_후보, "국민의힘") ~ "국민의힘",
                          str_detect(정당_후보, "더불어민주당") ~ "더불어민주당",
                          str_detect(정당_후보, "무소속") ~ "무소속",
                          str_detect(정당_후보, "개혁신당") ~ "개혁신당",
                          str_detect(정당_후보, "새로운미래") ~ "새로운미래",
                          str_detect(정당_후보, "진보당") ~ "진보당",
                          str_detect(정당_후보, "녹색정의당") ~ "녹색정의당",
                          str_detect(정당_후보, "자유통일당") ~ "자유통일당",
                          str_detect(정당_후보, "한국국민당") ~ "한국국민당",
                          str_detect(정당_후보, "내일로미래로") ~ "내일로미래로",
                          str_detect(정당_후보, "소나무당") ~ "소나무당",
                          str_detect(정당_후보, "우리공화당") ~ "우리공화당",
                          str_detect(정당_후보, "자유민주당") ~ "자유민주당",
                          str_detect(정당_후보, "한국농어민당") ~ "한국농어민당",
                          str_detect(정당_후보, "가락특권폐지당") ~ "가락특권폐지당",
                          str_detect(정당_후보, "기독당") ~ "기독당",
                          str_detect(정당_후보, "기후민생당") ~ "기후민생당",
                          str_detect(정당_후보, "노동당") ~ "노동당",
                          str_detect(정당_후보, "대한국민당") ~ "대한국민당",
                          str_detect(정당_후보, "민중민주당") ~ "민중민주당",
                          str_detect(정당_후보, "새진보연합") ~ "새진보연합")) |>
  mutate(후보 = str_remove(정당_후보, 정당)) |>
  select(-정당_후보) |>
  relocate(c(정당, 후보), .before = 선거인수) |>
  rename(시도명 = cityName, 선거구 = sggCityName)
  # mutate(후보 = str_sub(정당_후보, -3, end = -1)) |>
  # mutate(정당 = str_remove(정당_후보, 후보)) |>

general_2024 |>
  filter(str_detect(선거구, "분당")) |>
  select(시도명, 선거구, 구시군명, 정당, 후보)

### 데이터 정합성 확인
test_that("국회선거 2024 성남시분당구을 후보득표검증", {

  congress_check_df <-  general_2024 %>%
    filter(시도명 == "경기도" & 선거구 == "성남시분당구을") |>
    select(후보, 득표수) |>
    pivot_wider(names_from = 후보, values_from = 득표수)

  expect_that( congress_check_df$`김병욱`, equals(66196))
  expect_that( congress_check_df$`김은혜`, equals(69259))
})

# 5. 내보내기  ----------------------------------------------------------------

general_2024 <- krvote::clean_varnames(general_2024)

# general_2024 <- general_2024 %>%
#   mutate(data = map(data, krvote::clean_varnames))

usethis::use_data(general_2024, overwrite = TRUE)
ai-carpentry/krvote documentation built on April 22, 2024, 1 a.m.