PDF: как преобразовать списки из одного столбца в фрейм с несколькими столбцами? - списки людей в подгруппах внутри групп в несколько столбцов - PullRequest
1 голос
/ 21 февраля 2020

У меня около 15 PDF-файлов, содержащих списки людей . Эти PDF-файлы имеют ширину только одного столбца, поэтому это чистый список. Но в некотором роде эти списки являются вложенными (подгруппы внутри подгрупп внутри групп ...). Там нет числовых данных, кроме первого номера каждого человека в списке (что очень важно для моего анализа), и аналогичной информации о заказе.

Мне нужно вытащить из PDF этот список и преобразовать их в обычный фрейм данных.

Вот пример структуры одного PDF:

TERRITORY ONE
1. GROUP ONE
1. Name Surname
2. Name Surname
3. Name Surname
4. Name Surname
2. GROUP TWO
1. Name Surname
2. Name Surname
3. Name Surname
4. Name Surname
TERRITORY TWO
(...)

Это первый PDF: http://bocyl.jcyl.es/boletines/1983/04/02/pdf/BOCYL-D-02041983-1.pdf


!!! Я обнаружил, что эти документы также хранятся на веб-странице, поэтому в формате HTML: http://bocyl.jcyl.es/html/1983/04/02/html/BOCYL-D-02041983-1.do Возможно, легче взять содержимое из их вместо этого из PDF?


Это следует, как вы можете себе представить (территория два, три, четыре ..., с последующими подгруппами один, два, три, четыре, ... et c .). В последнем PDF-файле получается около 600 строк на файл PDF и более.

Мне нужно создать фрейм данных, соответствующий следующей структуре примера:

   PERSON    |    TERRITORY  |  GROUP  | POSITION IN LIST
Name Surname | TERRITORY ONE | GROUP 1 |         1
(...)
Name Surname | TERRITORY ONE | GROUP 2 |         4
(...)
Name Surname | TERRITORY TWO | GROUP 1 |         3

Одна строка должна быть одним человеком

POSITION IN LIST должен относиться к порядку, в котором человек Name Surname появился в данном году (каждый PDF-файл за год), в его TERRITORY, в его GROUP.

Считайте это чем-то вроде ранжирования, в котором важен порядок личности. Очень немногие люди из PDF1 (год 1) снова появятся в PDF2 (год 2), а затем в PDF3 (год 3) и др. c. Итак, одна из целей всего этого - узнать, сколько и кто повторяет год за годом в этом списке.

А также, для анализа важно знать положение этого человека, который повторяет в каждый год, чтобы нарисовать эволюцию этого человека или узнать, исчезнет ли этот человек после X года, et c.

PS: простите мой Engli sh, это не мой родной язык: (

Ответы [ 2 ]

1 голос
/ 22 февраля 2020

Вот более полный ответ, основанный на очистке веб-страницы, а не PDF, и все еще только с использованием одного источника. Таким образом, еще не протестировано с более чем одной веб-страницей для очистки. Если у вас есть дополнительные веб-страницы с исходными данными, добавьте их в вектор в верхней части кода ниже.

Я могу оставить это в качестве упражнения для вас @pbstckvrflw!

Это было много работы, но, к счастью, мне понравилось делать это и изучать что-то новое.

Однако пожалуйста, обратите внимание , что задачи такого масштаба обычно не подходят для SO вопросов, и лучше попробовать трудно сделать свою собственную попытку решения, а затем задать очень конкретные c вопросы о проблемах, которые вы обнаружите.

Я надеюсь, что вы можете внимательно прочитать код, который я написал, и попытаться понять, что происходит в каждом из них. шаг. Главное, что вам может понадобиться узнать о map и о том, как он может применять функцию к каждому элементу в списке. Я широко использовал map, потому что мы работаем с вложенными списками. Есть также несколько хороших регулярных выражений.

Это далеко от идеального кода и могут быть ошибки или неэффективность. Было бы лучше, если бы некоторые из них были разложены на повторяемые функции. И он генерирует несколько промежуточных объектов, что немного грязно, но так оно и есть. Частично код для ясности разбит на блоки, отчасти потому, что у меня больше нет времени интегрировать блоки в более плавный рабочий процесс без чрезмерного риска поломки.

library(rvest)
library(stringr)
library(purrr)
library(dplyr)
library(tibble)
library(conflicted)
conflict_prefer("pluck", "purrr")

# you should add any further URLs to this vector
urls <- c("http://bocyl.jcyl.es/html/1983/04/02/html/BOCYL-D-02041983-1.do")

# scrape text from the relevant part of the webpage
# (assume that any additional URLs have the same structure)
text <- urls %>%
  map(., ~ {xml2::read_html(.) %>%
      rvest::html_nodes("#presentDocumentos p:not([class])") %>% 
      html_text})

# extract a manageable name from the URL and use it to name each text
names(text) <- urls %>% 
  str_extract_all(., pattern = "(?<=/)BOCYL.*(?=\\.do$)")

# do any manual fixes for errors in source data
text1 <- text %>% 
  map(., ~
  str_replace_all(., "PARTIDO COMUNISTA DE ESPAÑA PARTIDO COMUNISTA DE CASTILLA- LEON", "2. PARTIDO COMUNISTA DE ESPAÑA PARTIDO COMUNISTA DE CASTILLA- LEON"))

text2 <- text1 %>%
  map(., ~ 
        str_replace_all(., "(\\.)*(\\s)*$", "") %>% 
        str_replace_all(., "(\\s)+", " ") %>% 
        str_replace_all(., "^Suplente.*", "") %>% 
        str_c(., collapse = ";") %>% 
        str_split(., pattern = "JUNTA ELECTORAL DE ") %>% 
        map(., ~ tail(., -1) %>% 
              str_split(.,
                        pattern = ";(?=\\d{1,2}\\.\\s([:upper:]|\\s){2,})") %>% 
              set_names(str_to_title(map(., 1))) %>% 
              map(., ~ tail(., -1))
        )
  )


text3 <- text2 %>% 
  map(., ~
        map(., ~ 
              map(., ~ str_split(., pattern = ";(?=\\d{1,2}\\.\\s)") %>% 
                    set_names(map(., 1) %>% 
                                str_extract(., pattern = "(?<=\\d{1,2}\\.\\s)[:upper:].*")) %>% 
                    map(., ~ tail(., -1) %>% 
                          enframe(., name = "list_position", value = "person_name") %>% 
                          mutate_at(
                            vars("person_name"),
                            ~ str_extract_all(.,
                                              pattern = "(?<=\\d{1,2}\\.\\s)[:alpha:]+.*"))))))

text4 <- text3 %>% 
  map(., ~
        map(., ~ 
              map(., ~
                    map_df(., c, .id = "political_group"))))

text5 <- text4 %>% 
  map(., ~
        map(., ~ 
              map_df(., c, .id = "territory")))

# EXAMPLE
# To look at just the data frame produced from the first web page supplied
# (with columns rearranged as desired):
data_frame1 <- text5 %>% 
  pluck(1, 1) %>% 
  select(person_name, everything())
data_frame1

Я вставил последний код репозиторий GitHub, который я сделал . Если вы удовлетворены этим ответом на свой вопрос, отметьте его как принятый ответ.

0 голосов
/ 21 февраля 2020

Это не совсем полный ответ, но он поможет вам в этом. Go пройдитесь по нему шаг за шагом и посмотрите, что происходит с каждой строкой. Вы сможете использовать purrr::map, чтобы применить шаги к списку файлов PDF и c. Я просто взял текст первого / единственного pdf здесь (text[[1]]), чтобы все было относительно просто.

Я хотел бы немного привести в порядок и упростить код и добавить больше комментарии, но хотелось бы ответить, как только я буду готов.

К сожалению, из-за проблем с чтением в PDF и созданием фрагмента текста dput я не могу предоставить репрезентацию, но вы должны быть в состоянии изменить следующее, чтобы вы начали:

library(here)
library(pdftools)
library(purrr)
library(stringr)

# download.file(
#   url = "http://bocyl.jcyl.es/boletines/1983/04/02/pdf/BOCYL-D-02041983-1.pdf",
#   destfile = here("pdfs", "BOCYL-D-02041983-1.pdf"),
#   mode = "wb"
# )

pdfs <- list.files(path = here("pdfs"), pattern = "pdf$")
text <- map(pdfs, ~ pdftools::pdf_text(here("pdfs", .)))

# be better to combine these into a single piped function (mappable)
# we need to combine the text into a single block before editing and splitting
text1_combined <- str_c(text[[1]], collapse = "")
text1_split <- str_split(text1_combined, "JUNTA")
# remove header text
text1_split <- text1_split[[1]] %>% tail(-1)
# repair text lost from str_split
text1_list <- map(text1_split, ~ paste0("JUNTA", .))

# extract territory name and use it to name each sub-list
text1_list <- text1_list %>% set_names(., nm = str_extract(., "^([:upper:]|\\s)+(?=\r)"))
text1_trimmed <- map(text1_list, ~ str_replace(., "^([:upper:]|\\s)+\r\n", ""))

text1_trim_tidy <- map(text1_trimmed, ~ str_replace_all(., "\r\n(?=[:digit:])", ","))
text1_trim_tidy <- map(text1_trim_tidy, ~ str_replace_all(., "\r\n", " "))
text1_trim_tidy <- map(text1_trim_tidy, ~ str_replace_all(., "\\s+$", ""))

text1_by_party <- map(text1_trim_tidy, ~ str_split(., ",(?=[:digit:]+\\.\\s[:upper:]{2,})"))


# clear up intermediate objects
# rm(text1_combined)
# rm(text1_split)
# rm(text1_list)
# rm(text1_trimmed)
# rm(text1_trim_tidy)

Надеюсь, я отредактирую это или добавлю другой ответ, как только приведу в порядок код. Я сделал репозиторий GitHub здесь для дальнейшего использования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...