리얼미터 수도권

리얼미터 수도권 역대 지지율을 살펴보자.

저자
소속

1 첫시도

1.1 PDF 다운로드 링크

코드
library(tidyverse)
library(rvest)

rm_url <- glue::glue('http://www.realmeter.net/category/pdf/page/1/')

rm_html <- read_html(rm_url)

## PDF 다운로드 링크 ---------------
rm_links_text <- rm_html |> 
  html_nodes("a") |> 
  html_text()

rm_links <- rm_html |> 
  html_nodes("a") |> 
  html_attr("href")

rm_pdf_raw <- tibble('text' = rm_links_text, 'link' = rm_links)

rm_pdf_tbl <- rm_pdf_raw |> 
  filter(str_detect(text, "주간통계표")) |> 
  mutate(text = str_extract(text, "\\d{2}년\\s+\\d{1,2}월\\s+\\d{1}주"))  |> 
  mutate(filename = str_extract(link, '[^/]+\\.pdf'))

1.2 PDF 다운로드

코드
fs::dir_create("data/realmeter/")

download.file(url = rm_pdf_tbl$link[1], mode='wb', 
              destfile = glue::glue("data/realmeter/{rm_pdf_tbl$filename[1]}"))

1.3 PDF 다운로드 함수

코드
link <- rm_pdf_tbl$link[2]
filename <- rm_pdf_tbl$filename[2]

download_pdfs <- function(link, filename) {
  download.file(url = link, mode='wb', 
              destfile = glue::glue("data/realmeter/{filename}"))
}

download_pdfs(link, filename)

1.4 전체 다운로드

코드

get_reports <- function(page="1"){
  
  rm_url <- glue::glue('http://www.realmeter.net/category/pdf/page/{page}/')

  rm_html <- read_html(rm_url)
  
  ## PDF 다운로드 링크 ---------------
  rm_links_text <- rm_html |> 
    html_nodes("a") |> 
    html_text()
  
  rm_links <- rm_html |> 
    html_nodes("a") |> 
    html_attr("href")
  
  rm_pdf_raw <- tibble('text' = rm_links_text, 'link' = rm_links)
  
  rm_pdf_tbl <- rm_pdf_raw |> 
    filter(str_detect(text, "주간통계표")) |> 
    # mutate(text = str_extract(text, "\\d{2}년\\s+\\d{1,2}월\\s+\\d{1}주"))  |> 
    mutate(filename = str_extract(link, '[^/]+\\.pdf')) |> 
    mutate(link = ifelse(str_detect(link, "^http"), link, 
                         glue::glue("http://realmeter.cafe24.com{link}")))
  
  return(rm_pdf_tbl)
}

get_reports(157)

pdf_pages <- 1:157

pdf_report_raw <- pdf_pages |> 
  enframe(value = "page")  |> 
  slice(156) |> 
  mutate(data = map(page, get_reports)) 

pdf_report_raw |> 
  unnest(data) |> 
  pull(link)

pdf_report_raw |> 
  unnest(data) |> 
  mutate(report = walk2(link, filename, download_pdfs))

2 재시도

2.1 다운로드 목록

코드
library(tidyverse)
library(rvest)

## 함수
get_reports <- function(page="1"){
  
  cat("\n----------------------------\n", 
      "     ", page, " 페이지",
      "\n----------------------------\n")
  
  rm_url <- glue::glue('http://www.realmeter.net/category/pdf/page/{page}/')

  rm_html <- read_html(rm_url)
  
  ## PDF 다운로드 링크 ---------------
  rm_links_text <- rm_html |> 
    html_nodes("a") |> 
    html_text()
  
  rm_links <- rm_html |> 
    html_nodes("a") |> 
    html_attr("href")
  
  rm_pdf_raw <- tibble('text' = rm_links_text, 'link' = rm_links)
  
  rm_pdf_tbl <- rm_pdf_raw |> 
    filter(str_detect(text, "주간통계표")) |> 
    # mutate(text = str_extract(text, "\\d{2}년\\s+\\d{1,2}월\\s+\\d{1}주"))  |> 
    mutate(filename = str_extract(link, '[^/]+\\.pdf')) |> 
    mutate(link = ifelse(str_detect(link, "^http"), link, 
                         glue::glue("http://realmeter.cafe24.com{link}")))
  
  return(rm_pdf_tbl)
}

## 다운로드 페이지

pdf_pages <- 1:158

pdf_report_raw <- pdf_pages |> 
  enframe(value = "page")  |> 
  mutate(data = map(page, get_reports))

pdf_report_tbl <- pdf_report_raw |> 
  select(data) |> 
  mutate(nrow = map_int(data, nrow)) |> 
  filter(nrow !=0) |> 
  unnest(data)

pdf_report_tbl |> 
  write_rds("data/realmeter/pdf_report_tbl.rds")

2.2 PDF 다운로드

코드
pdf_report_tbl <- 
  read_rds("data/realmeter/pdf_report_tbl.rds")


download_pdfs <- function(link, filename) {
  cat("\n----------------------------\n", 
      "     ", filename, ": 파일명",
      "\n----------------------------\n")
  download.file(url = link, mode='wb', 
              destfile = glue::glue("data/realmeter/{filename}"))
}

pdf_report_tbl |> 
  mutate(pdfs = walk2(link, filename, download_pdfs))

3 분석

3.1 윤석열 대통령 이후

코드
library(tidyverse)
library(fs)

pdf_report_tbl <- 
  read_rds("data/realmeter/pdf_report_tbl.rds")

local_path_file <- fs::dir_ls("data/realmeter/") |> 
  enframe(value = "path_file") |> 
  mutate(filename = path_file(name)) |> 
  select(path_file, filename)

# 날짜 변환 함수
convert_to_date <- function(d) {
  year <- as.numeric(sub("년.*", "", d)) + 2000  # 2000을 더해서 4자리 연도를 만듭니다.
  month <- as.numeric(sub(".* (\\d+)월.*", "\\1", d))
  week <- as.numeric(sub(".*월 (\\d+)주", "\\1", d))
  start_date <- ymd(paste(year, month, 1, sep = "-"))
  
  return(start_date + days(7 * (week - 1)))
}


yoon_pdf <- pdf_report_tbl |> 
  left_join(local_path_file) |> 
  select(text, path_file, filename) |> 
  mutate(date = str_extract(text, pattern = '(\\d+년 \\d+월 \\d+주)')) |> 
  mutate(date = convert_to_date(date)) |> 
  filter(date > as.Date("2022-05-01"),
         str_detect(filename, "주간통계표")) |> 
  select(date, path_file, filename) |> 
  arrange(date)

yoon_pdf

3.2 응답자 특성

코드
library(pdftools)
library(openai)

raw_text <- pdftools::pdf_text(pdf = "data/realmeter/주간통계표22년5월2주최종_y51w.pdf")

demo_text <- raw_text |> 
  enframe() |> 
  mutate(value = str_squish(value)) |> 
  # filter(str_detect(value, "^(?=.*보수)(?=.*중도)(?=.*진보)")) |> 
  filter(str_detect(value, "응답자\\s+특성")) |> 
  pull(value)

system_task <- glue::glue("당신은 통계 데이터 전문가입니다.
                          당신에게 텍스트 형식의 표로 주어졌을 때,
                          설명은 없이 텍스트를 표로 바꾸는 것입니다.",
                          "텍스트 형식의 표는 다음과 같습니다.",
                          "데이터의 상단에는 표의 칼럼명이 위치하고 첫번째 혹은 
                          두번째 칼럼은 다차원표의 범주(Level)에 대한 정보를 담고 있고
                          나머지 셀은 숫자로 구성됩니다.")
                          # "'만18세 이상 29세 이하'는 하나의 값입니다.")

gpt_response <- create_chat_completion(
            # model = "gpt-3.5-turbo",
            model = "gpt-4",
            messages = list(
              list(
                "role" = "system",
                "content" = system_task
              ),
              list(
                "role" = "user",
                "content" = demo_text
              )
            ),
            temperature = 0,
            top_p = 1,
            n = 1,
            openai_api_key = Sys.getenv("OPENAI_API_KEY"),
            openai_organization = NULL
          )


gpt_response |> 
  write_rds("data/realmeter_deom.rds")
코드
library(tidyverse)
library(openai)

gpt_response <- 
  read_rds("data/realmeter_deom.rds")

result_md <- gpt_response |> 
  pluck('choices') |> 
  pull('message.content')  |> 
  str_split("\n") %>%
  .[[1]]
  
result_md
#>  [1] "| 구 분 | 사례수(명) | 비율(%) | 가중값 사례수(명) | 가중값 비율(%) | 배율 |"
#>  [2] "|---|---|---|---|---|---|"                                                   
#>  [3] "| 전 체 | 2526 | 100.0 | 2526 | 100.0 | 1.00 |"                              
#>  [4] "| 남 자 | 1566 | 62.0 | 1252 | 49.6 | 0.80 |"                                
#>  [5] "| 여 자 | 960 | 38.0 | 1274 | 50.4 | 1.33 |"                                 
#>  [6] "| 만18세이상 | 300 | 11.9 | 437 | 17.3 | 1.46 |"                             
#>  [7] "| 2 9 세 이 하 | 292 | 11.6 | 382 | 15.1 | 1.31 |"                           
#>  [8] "| 4 0 대 | 404 | 16.0 | 467 | 18.5 | 1.16 |"                                 
#>  [9] "| 5 0 대 | 626 | 24.8 | 493 | 19.5 | 0.79 |"                                 
#> [10] "| 6 0 대 | 545 | 21.6 | 410 | 16.2 | 0.75 |"                                 
#> [11] "| 7 0 세 이 상 | 359 | 14.2 | 337 | 13.3 | 0.94 |"                           
#> [12] "| 서 울 | 619 | 24.5 | 478 | 18.9 | 0.77 |"                                  
#> [13] "| 인 천 / 경 기 | 768 | 30.4 | 798 | 31.6 | 1.04 |"                          
#> [14] "| 대전/세종/충청 | 231 | 9.1 | 268 | 10.6 | 1.16 |"                          
#> [15] "| 강 원 | 79 | 3.1 | 74 | 2.9 | 0.94 |"                                      
#> [16] "| 부산/울산/경남 | 315 | 12.5 | 381 | 15.1 | 1.21 |"                         
#> [17] "| 대 구 / 경 북 | 223 | 8.8 | 248 | 9.8 | 1.11 |"                            
#> [18] "| 광 주 / 전 라 | 257 | 10.2 | 248 | 9.8 | 0.96 |"                           
#> [19] "| 제 주 | 34 | 1.3 | 31 | 1.2 | 0.91 |"


markdown_to_df <- function(markdown_table) {
  
  # Remove rows with only dashes (assuming they're separator rows)
  markdown_table <- markdown_table[!str_detect(markdown_table, "^\\|[-]+\\|")]

  # Split each line into its components based on the pipe delimiter
  split_lines <- str_split(markdown_table, "\\|")
  
  # Extract headers and data
  headers <- str_trim(unlist(split_lines[1]))
  data <- do.call(rbind, split_lines[-1])
  
  # Convert data to dataframe and assign column names
  df <- as.data.frame(data, stringsAsFactors = FALSE)
  colnames(df) <- headers
  
  return(df)
}

demo_raw <- markdown_to_df(result_md)

deom_tbl <- demo_raw |> 
  janitor::clean_names(ascii= FALSE) |> 
  select(!starts_with("x")) |> 
  set_names(c("구분", "완료_사례", "완료_비율", "가중_사례", "가중_비율", "가중값")) |> 
  as_tibble() |> 
  mutate_all(str_squish) |> 
  mutate(구분 = str_remove_all(구분, "\\s+")) |> 
  mutate(구분 = case_when(str_detect(구분, "만18세이상") ~ "20대",
                          str_detect(구분, "29세이하") ~ "30대",
                          TRUE ~ 구분))

deom_tbl |> 
  gt::gt()
구분 완료_사례 완료_비율 가중_사례 가중_비율 가중값
전체 2526 100.0 2526 100.0 1.00
남자 1566 62.0 1252 49.6 0.80
여자 960 38.0 1274 50.4 1.33
20대 300 11.9 437 17.3 1.46
30대 292 11.6 382 15.1 1.31
40대 404 16.0 467 18.5 1.16
50대 626 24.8 493 19.5 0.79
60대 545 21.6 410 16.2 0.75
70세이상 359 14.2 337 13.3 0.94
서울 619 24.5 478 18.9 0.77
인천/경기 768 30.4 798 31.6 1.04
대전/세종/충청 231 9.1 268 10.6 1.16
강원 79 3.1 74 2.9 0.94
부산/울산/경남 315 12.5 381 15.1 1.21
대구/경북 223 8.8 248 9.8 1.11
광주/전라 257 10.2 248 9.8 0.96
제주 34 1.3 31 1.2 0.91