如何将嵌套 XML 导入 R 数据框

How to import nested XML into R data frame

我希望将以下 XML 文档导入数据框: http://opensource.adobe.com/Spry/data/donuts.xml

应该创建了 3 个数据框:

  1. 项目 -(字段 = ID 类型名称 PPU)
  2. Batters -(字段 = BatterID、BatterName、ItemID - Items 数据框的键)
  3. Toppings -(字段 = ToppingID、ToppingName、ItemID - Items 数据框的键)

(数据不需要是 3NF - 即每个击球手可以针对列出的每个项目重复)

使用 XML2 包,到目前为止,我使用以下代码导入 XML 并将其转换为嵌套列表:

library(xml2)
xmlobj <- read_xml("http://opensource.adobe.com/Spry/data/donuts.xml")
ls1 <- as_list(xmlobj) #Converts XML to a nested list

我现在正在寻求将列表解析/展平为如上所述的 3 个数据帧。

如何最好地实现这一点?是通过一系列循环 (lapply/map),将对象传递到向量中,然后加载数据框吗?还是我应该避免完全使用 XML2 / Lists 并使用 XML 包并使用 XPath 类型语法实现此目的?

我尝试了以下方法并可以提取单个项目的 Item 属性和元素,但是当我尝试 lapply 该函数时它崩溃了:

#Function for pulling out item attributes from list
ItemDF <- function(myItem){

  #Gather Item data into DF including attributes
  itemFrame <- data_frame(
                id = attr(myItem$item,'id'),
                type = attr(myItem$item,'type'),
                name = unlist(myItem$item$name),
                ppu = unlist(myItem$item$ppu)
              )

  return(itemFrame)
}

#Single instance
df1 <- ItemDF(ls1$items[1])
df1

#Lapply across all items throws an error
lapply(ls1$items,ItemDF)

(注意这个数据集是一个概念证明,所以我正在寻找一种方法,然后我可以适应我希望处理的其他 XML 文件)。

library(xml2)
library( tidyverse )

xmlobj <- read_xml("http://opensource.adobe.com/Spry/data/donuts.xml")

df_items <- data.frame( 
  id   = xml_find_all( xmlobj, ".//item" ) %>% xml_attr( "id" ),
  type = xml_find_all( xmlobj, ".//item" ) %>%  xml_attr( "type" ),
  name = xml_find_all( xmlobj, ".//item/name" ) %>% xml_text(),
  ppu  = xml_find_all( xmlobj, ".//item/ppu" ) %>% xml_text(),
  stringsAsFactors = FALSE )

#     id   type       name  ppu
# 1 0001  donut       Cake 0.55
# 2 0002  donut     Raised 0.55
# 3 0003  donut Buttermilk 0.55
# 4 0004    bar        Bar 0.75
# 5 0005  twist      Twist 0.65
# 6 0006 filled     Filled 0.75

df_batters <- xml_find_all( xmlobj, ".//item" )  %>% 
  map_df(~{
    set_names(
      xml_find_all(.x, ".//batters/batter") %>% xml_attr( "id" ),
      xml_find_all(.x, ".//batters/batter") %>% xml_text()
    ) %>% 
      as.list() %>%  
      flatten_df() %>%
      mutate(itemID = xml_attr(.x, "id" ) )
  }) %>%
  type_convert() %>% 
  gather( batter, batterID, -itemID, na.rm = TRUE) %>%
  select( batterID, batter, itemID )

# # A tibble: 10 x 3
#    batterID batter       itemID
#  *    <int> <chr>        <chr> 
#  1     1001 Regular      0001  
#  2     1001 Regular      0002  
#  3     1001 Regular      0003  
#  4     1001 Regular      0004  
#  5     1001 Regular      0005  
#  6     1001 Regular      0006  
#  7     1002 Chocolate    0001  
#  8     1002 Chocolate    0003  
#  9     1003 Blueberry    0001  
# 10     1003 Devil's Food 0001  

df_toppings <- xml_find_all( xmlobj, ".//item" )  %>% 
  map_df(~{
    set_names(
      xml_find_all(.x, ".//topping") %>% xml_attr( "id" ),
      xml_find_all(.x, ".//topping") %>% xml_text()
    ) %>% 
      as.list() %>%  
      flatten_df() %>%
      mutate(itemID = xml_attr(.x, "id" ) )
  }) %>%
  type_convert() %>% 
  gather( topping, toppingID, -itemID, na.rm = TRUE) %>%
  select( toppingID, topping, itemID )

# # A tibble: 20 x 3
#    toppingID topping                  itemID
# *      <int> <chr>                    <chr> 
#  1      5001 None                     0001  
#  2      5001 None                     0002  
#  3      5002 Glazed                   0001  
#  4      5002 Glazed                   0002  
#  5      5002 Glazed                   0005  
#  6      5002 Glazed                   0006  
#  7      5005 Sugar                    0001  
#  8      5005 Sugar                    0002  
#  9      5005 Sugar                    0005  
# 10      5007 Powdered Sugar           0001  
# 11      5007 Powdered Sugar           0006  
# 12      5006 Chocolate with Sprinkles 0001  
# 13      5003 Chocolate                0001  
# 14      5003 Chocolate                0002  
# 15      5003 Chocolate                0004  
# 16      5003 Chocolate                0006  
# 17      5004 Maple                    0001  
# 18      5004 Maple                    0002  
# 19      5004 Maple                    0004  
# 20      5004 Maple                    0006  

项目数据部分的关键(仅限击球手)我的 2 美分:

df_batters <- xml_find_all(xmlobj, ".//item") %>% 
  map_df(~{
    bind_cols(
      itemID = xml_attr(.x, "id"),
      batterID = xml_find_all(.x, ".//batters/batter") %>% xml_attr("id"),
      batter = xml_find_all(.x, ".//batters/batter") %>% xml_text()
    )}) %>% type_convert()

#   itemID batterID batter      
#    <chr>     <dbl> <chr>       
#  1 0001       1001 Regular     
#  2 0001       1002 Chocolate   
#  3 0001       1003 Blueberry   
#  4 0001       1003 Devil's Food
#  5 0002       1001 Regular     
#  6 0003       1001 Regular     
#  7 0003       1002 Chocolate   
#  8 0004       1001 Regular     
#  9 0005       1001 Regular     
# 10 0006       1001 Regular 

以防其他人遇到我的问题。上面的例子工作得很好。但是,我在工作中必须处理的 xml 文件在使用上面的代码时没有产生任何结果。我必须首先在管道的开头引入根节点才能使其工作,即:

xmlobj %>%
  xml_root() %>%
  xml_find_all(".//item") %>%
  map(.......) %>% .....