Я написал функцию для разбиения (больших) гетерогенных XML-файлов в кадры данных, где split выполняется с помощью выражения xpath. По гетерогенному я имею в виду, что интересующие предметы попадают в набор разных «столбчатых» структур. Однако для больших ish XML-файлов, скажем, 50K элементов и 5 типов, код кажется более «вялым», чем я ожидал.Быстрое разделение XML-документа на data.frames по выражению xpath
Вопрос в том, существует ли существующая функциональность для этого, которую я пропустил, а если нет, есть ли очевидный способ повысить скорость кода ниже?
Вот минимальный пример такого рода структуры XML я рассматриваю:
xmldoc <- xml2::read_xml(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resp>
<respMeta>
<status>200</status>
<!-- ... -->
</respMeta>
<content>
<list>
<item>
<Type>Type1</Type>
<ColA>Foo</ColA>
<ColB>Bar</ColB>
</item>
<item>
<Type>Type2</Type>
<ColC>Baz</ColC>
</item>
<item>
<Type>Type3</Type>
<ColA>Lorem</ColA>
<ColB>Ipsum</ColB>
<ColC>Dolor</ColC>
</item>
</list>
<!-- ... many many more entries here -->
</content>
</resp>')
Цель состоит в том, чтобы преобразовать это в N кадров данных, где N является количество уникальных значений в //item/Type
(который неизвестен во время разбора).
Вот моя реализация:
#' Split XML Document into Dataframes by Xpath Expression
#'
#' @param xml An (xml2) xml document object.
#'
#' @param xpath the path to the values to split by. \code{xml_text} is used
#' to get the value.
#'
#' @importFrom xml2 xml_text xml_find_all xml_find_first xml_children xml_name
#' @importFrom stats setNames
#' @importFrom dplyr bind_cols
#' @importFrom magrittr %>%
#'
#' @return list of data frames (tbl_df)
#'
#' @export
xml_to_dfs <- function(xml, xpath)
{
u <- xml_find_all(xml, xpath) %>% xml_text %>% unique %>% sort
select <- paste0(xpath, "[. ='", u, "']/..") %>% setNames(u)
paths <-
lapply(select, . %>% xml_find_first(x = xml) %>% xml_children %>% xml_name)
queries <- Map(paste, select, paths, MoreArgs = list(sep = "/"))
columns <-
lapply(queries, . %>% lapply(. %>% xml_find_all(x = xml) %>% xml_text))
Map(setNames, columns, paths) %>% lapply(bind_cols)
}
В результате минимального, например, только один ряд в каждом кадре, тогда:
xml_to_dfs(xmldoc, "//item/Type")
$Type1
# A tibble: 1 × 3
Type ColA ColB
<chr> <chr> <chr>
1 Type1 Foo Bar
$Type2
# A tibble: 1 × 2
Type ColC
<chr> <chr>
1 Type2 Baz
$Type3
# A tibble: 1 × 4
Type ColA ColB ColC
<chr> <chr> <chr> <chr>
1 Type3 Lorem Ipsum Dolor
Для меня скорость с вашим решением и мое решение почти точно такой же, ~ 20secs. Ваше решение короче, поэтому +1 для этого; но также потребуется некоторая пост-обработка, так как результатом должен быть список dfs, причем только столбцы, соответствующие каждому типу (это должно быть достаточно быстро, хотя). – Stefan