2016-11-25 33 views
0

我編寫了一個將大型異構XML文件拆分爲數據框的功能,其中拆分由xpath表達式完成。異構我的意思是說,感興趣的項目屬於一組不同的「列」結構。但是,對於大小爲50K的項目和5種類型的XML文件,代碼似乎比我預期的更爲「低迷」。通過xpath表達式將XML文檔快速拆分爲data.frames

問題是:是否有現有的功能來做到這一點,我錯過了,如果沒有,是否有一種明顯的方式來提高下面的代碼速度?

這裏是我正在考慮的一種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>') 

的目標是將其轉換爲ň數據幀,其中ñ是唯一值的數量//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 

回答

1

什麼是這樣的:

require(xml2) 
require(purrr) 

oc <- read_xml("path-to-xml-file") 
xml_find_all(doc, ".//item") %>% 
    map(xml_children) %>% 
    map(~as_tibble(t(set_names(xml_text(.), xml_name(.))))) #or map_df 

我甚至會在map_df的最後一行給你:

# A tibble: 3 × 4 
    Type ColA ColB ColC 
    <chr> <chr> <chr> <chr> 
1 Type1 Foo Bar <NA> 
2 Type2 <NA> <NA> Baz 
3 Type3 Lorem Ipsum Dolor 

P.S:這也涉及到:https://github.com/hadley/purrr/issues/255

+0

對我來說,速度的解決方案,我的解決方案几乎是完全一樣的,〜20secs。你的解決方案更短,因此+1;但也需要一些後期處理,因爲結果應該是dfs列表,只有與每種類型相關的列(儘管這應該足夠快)。 – Stefan