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 delimitersplit_lines<-str_split(markdown_table, "\\|")# Extract headers and dataheaders<-str_trim(unlist(split_lines[1]))data<-do.call(rbind, split_lines[-1])# Convert data to dataframe and assign column namesdf<-as.data.frame(data, stringsAsFactors =FALSE)colnames(df)<-headersreturn(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
소스 코드
---title: "지도제작 대회"subtitle: "리얼미터 수도권"description: | 리얼미터 수도권 역대 지지율을 살펴보자.author: - name: 이광춘 url: https://www.linkedin.com/in/kwangchunlee/ affiliation: 한국 R 사용자회 affiliation-url: https://github.com/bit2rtitle-block-banner: trueformat: html: theme: flatly code-fold: true code-overflow: wrap toc: true toc-depth: 3 toc-title: 목차 number-sections: true highlight-style: github self-contained: false default-image-extension: jpgfilters: - lightboxlightbox: autolink-citations: trueknitr: opts_chunk: eval: false message: false warning: false collapse: true comment: "#>" R.options: knitr.graphics.auto_pdf: trueeditor_options: chunk_output_type: console---# 첫시도## PDF 다운로드 링크```{r}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'))```## PDF 다운로드```{r}#| eval: falsefs::dir_create("data/realmeter/")download.file(url = rm_pdf_tbl$link[1], mode='wb', destfile = glue::glue("data/realmeter/{rm_pdf_tbl$filename[1]}"))```## PDF 다운로드 함수```{r}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)```## 전체 다운로드```{r}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:157pdf_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))```# 재시도## 다운로드 목록```{r}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:158pdf_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")```## PDF 다운로드```{r}#| eval: falsepdf_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))```# 분석## 윤석열 대통령 이후```{r}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```## 응답자 특성```{r}#| eval: falselibrary(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")``````{r}#| eval: truelibrary(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_mdmarkdown_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) <- headersreturn(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()```