Как преобразовать файл Markdown в R DataFrame - PullRequest
0 голосов
/ 23 января 2020

У меня есть следующий файл markdown (md3.md), и я хочу преобразовать его в R dataframe, используя преобразование XML в качестве промежуточного шага. Затем можно использовать XML R package для преобразования файла XML в кадр данных.

# level_1
## level_11
- ind1
- ind2
## level_12
- ind3
# level_2
## level_21
### level_211
- ind4

Для преобразования файла MD в XML Я использовал:

library(commonmark)
library(xml2)

md <- readLines("md3.md")
xml <- markdown_xml(md)
write(xml, "md3.xml")

, но Полученный файл слишком сложен, и я не знаю, как преобразовать его в фрейм данных. Я попытался использовать R XML package аналогично следующему, но я не уверен, как правильно преобразовать express файл XML:

library(XML)
library(dplyr)

xml_doc <- readLines("md3.xml")

myXML <- xmlParse(xml_doc)
myData <- xmlToDataFrame(myXML, stringsAsFactors = FALSE,) %>%
  mutate_all(~type.convert(., as.is = T))

Возможный желаемый вывод для этого Фрейм данных может быть следующим (я использую уровни дерева L_1, L_2, L_3, indicators в качестве полей для использования реляционной базы данных):

L_1 <- c('level_1', 'level_1', 'level_1', 'level_2')
L_2 <- c('level_11', 'level_11', 'level_12', 'level_21')
L_3 <- c('', '', '', 'level_211')
indicators <- c('ind1', 'ind2', 'ind3', 'ind4')

df <- data.frame(L_1, L_2, L_3, indicators)
df
#>       L_1      L_2       L_3 indicators
#> 1 level_1 level_11                 ind1
#> 2 level_1 level_11                 ind2
#> 3 level_1 level_12                 ind3
#> 4 level_2 level_21 level_211       ind4

1 Ответ

1 голос
/ 24 января 2020

Непонятно, как именно вы хотели бы иметь структуру с несколькими уровнями вложенности и элементами с разными длинами, представленными в виде прямоугольного фрейма данных angular. Я предполагаю, что один из способов сделать это состоит в том, чтобы иметь одну строку с типом и содержимым каждого элемента, например:

library(xml2)

xml_doc  <- readLines("md3.xml")
myXML    <- xml2::read_xml(xml_doc)
elements <- unlist(xml2::as_list(myXML)$`document`); 
data.frame(type = names(elements), contents = as.character(elements))
#>                        type  contents
#> 1              heading.text   level_1
#> 2              heading.text  level_11
#> 3  list.item.paragraph.text      ind1
#> 4  list.item.paragraph.text      ind2
#> 5              heading.text  level_12
#> 6  list.item.paragraph.text      ind3
#> 7              heading.text   level_2
#> 8              heading.text  level_21
#> 9              heading.text level_211
#> 10 list.item.paragraph.text      ind4

Существуют различные способы сохранить структуру вложенности, но все они немного произвольны. и искусственный, если у вас нет определенной цели c. Я с радостью помогу в достижении этой цели, если вы хотите расширить свой вопрос.


РЕДАКТИРОВАТЬ

С желаемым результатом, указанным в ОП, можно извлечь данные, которые нам нужны для поддержки вложенной структуры. Сначала нам нужно извлечь атрибут «level», а также любое содержимое из xml. Мы можем сделать это с помощью рекурсивной функции:

list_miner <- function(x)
{
  if(!is.null(attr(x, "level"))) return(c(level = attr(x, "level"), x[[1]]))
  if(class(x) == "list") return(lapply(x, list_miner))
  else return(c( x))
}

Мы применяем функцию следующим образом:

xml_doc  <- readLines("md3.xml")
myXML    <- xml2::read_xml(xml_doc)
xlist    <- xml2::as_list(myXML)
elements <- unlist(lapply(xlist, list_miner))
df       <- data.frame(type = names(elements), contents = as.character(elements))

Теперь df содержит всю необходимую нам информацию:

#>                                 type  contents
#> 1             document.heading.level         1
#> 2                   document.heading   level_1
#> 3             document.heading.level         2
#> 4                   document.heading  level_11
#> 5  document.list.item.paragraph.text      ind1
#> 6  document.list.item.paragraph.text      ind2
#> 7             document.heading.level         2
#> 8                   document.heading  level_12
#> 9  document.list.item.paragraph.text      ind3
#> 10            document.heading.level         1
#> 11                  document.heading   level_2
#> 12            document.heading.level         2
#> 13                  document.heading  level_21
#> 14            document.heading.level         3
#> 15                  document.heading level_211
#> 16 document.list.item.paragraph.text      ind4

Преобразование его в правильный формат требует много трудностей, но вот как это может быть достигнуто:

df %>% 
mutate(level1 = cumsum(1 * (type == "document.heading.level" & contents == "1"))) %>% 
group_by(level1) %>% 
mutate(level1text = contents[type == "document.heading"][1]) %>% 
filter(level1 == 0 | seq_along(type) > 2) %>%
mutate(level2 = cumsum(1 * (type == "document.heading.level" & contents == "2"))) %>%
group_by(level1, level2) %>%
mutate(level2text = contents[type == "document.heading"][1]) %>% 
filter(level2 == 0 | seq_along(type) > 2) %>%
mutate(level3 = cumsum(1 * (type == "document.heading.level" & contents == "3"))) %>%
group_by(level1, level2, level3) %>%
mutate(level3text = contents[type == "document.heading"][1]) %>% 
filter(level3 == 0 | seq_along(type) > 2) %>%
ungroup() %>%
select(header_level_1 = level1text, header_level_2 = level2text,
          header_level_3 = level3text, text = contents)

Что дает:

#> # A tibble: 4 x 4
#>   header_level_1 header_level_2 header_level_3 text 
#>   <fct>          <fct>          <fct>          <fct>
#> 1 level_1        level_11       <NA>           ind1 
#> 2 level_1        level_11       <NA>           ind2 
#> 3 level_1        level_12       <NA>           ind3 
#> 4 level_2        level_21       level_211      ind4 
...