将大而复杂的XML文件解析为data.frame

min*_*nem 1 xml performance r xml-parsing xml2

所以,我有大量的XML文件,包含大量报告.我在下面创建了数据示例,以大致显示xml的大小及其结构:

x <- "<Report><Agreements><AgreementList /></Agreements><CIP><RecordList><Record><Date>2017-05-26T00:00:00</Date><Grade>2</Grade><ReasonsList><Reason><Code>R</Code><Description>local</Description></Reason></ReasonsList><Score>xxx</Score></Record><Record><Date>2017-04-30T00:00:00</Date><Grade>2</Grade><ReasonsList><Reason><Code>R</Code><Description/></Reason></ReasonsList><Score>xyx</Score></Record></RecordList></CIP><Individual><Contact><Email/></Contact><General><FirstName>MM</FirstName></General></Individual><Inquiries><InquiryList><Inquiry><DateOfInquiry>2017-03-19</DateOfInquiry><Reason>cc</Reason></Inquiry><Inquiry><DateOfInquiry>2016-10-14</DateOfInquiry><Reason>er</Reason></Inquiry></InquiryList><Summary><NumberOfInquiries>2</NumberOfInquiries></Summary></Inquiries></Report>"

x <- paste(rep(x, 1.5e+5), collapse = "")
x <- paste0("<R>", x, "</R>")
require(XML)
p <- xmlParse(x)
p <- xmlRoot(p)
p[[1]]
Run Code Online (Sandbox Code Playgroud)

我想将这些数据转换为data.frame,但XML的结构并不简单.以前使用XML我创建了循环,每个报告将其子节点转换为data.frame,但是这里(在此数据中)子节点数大于30(并未将所有这些都放在示例中),并且结构不同(列表节点在XML中甚至可以出现2级深度).

所以我几乎没有问题:

1)我确信循环报告不是处理此问题的最佳方法.我该如何处理这个问题?

2)我可以以某种方式提取一个报告的所有数据两行一行data.frame(递归可能)?

3)或者我可以为XML的每个列表对象自动创建单独的data.frames吗?

任何帮助将非常感激.

更新:

结果示例可能如下所示:

Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   1 obs. of  17 variables:
 $ Record.1.Date                : chr "2017-05-26T00:00:00"
 $ Record.1.Grade               : num 2
 $ Record.1.Reason.1.Code       : chr "R"
 $ Record.1.Reason.1.Description: chr "local"
 $ Record.1.Score               : chr "xxx"
 $ Record.2.Date                : chr "2017-05-26T00:00:00"
 $ Record.2.Grade               : num 2
 $ Record.2.Reason.1.Code       : chr "R"
 $ Record.2.Reason.1.Description: chr "NA"
 $ Record.2.Score               : chr "xyx"
 $ Email.1                      : chr "NA"
 $ FirstName                    : chr "MM"
 $ Inquiry.1.DateOfInquiry      : POSIXct, format: "2017-03-19"
 $ Inquiry.1.Reason             : chr "cc"
 $ Inquiry.2.DateOfInquiry      : POSIXct, format: "2016-10-14"
 $ Inquiry.2.Reason             : chr "er"
 $ NumberOfInquiries            : num 2
Run Code Online (Sandbox Code Playgroud)

,但正如我之前提到的,子列表也可以在单独的表中.

ant*_*nio 7

L=xmlToList(x)
str(data.frame(t(unlist(L)), stringsAsFactors=FALSE))
# 'data.frame': 1 obs. of  15 variables:
#  $ CIP.RecordList.Record.Date                          : chr "2017-05-26T00:00:00"
#  $ CIP.RecordList.Record.Grade                         : chr "2"
#  $ CIP.RecordList.Record.ReasonsList.Reason.Code       : chr "R"
#  $ CIP.RecordList.Record.ReasonsList.Reason.Description: chr "local"
#  $ CIP.RecordList.Record.Score                         : chr "xxx"
#  $ CIP.RecordList.Record.Date.1                        : chr "2017-04-30T00:00:00"
#  $ CIP.RecordList.Record.Grade.1                       : chr "2"
#  $ CIP.RecordList.Record.ReasonsList.Reason.Code.1     : chr "R"
#  $ CIP.RecordList.Record.Score.1                       : chr "xyx"
#  $ Individual.General.FirstName                        : chr "MM"
#  $ Inquiries.InquiryList.Inquiry.DateOfInquiry         : chr "2017-03-19"
#  $ Inquiries.InquiryList.Inquiry.Reason                : chr "cc"
#  $ Inquiries.InquiryList.Inquiry.DateOfInquiry.1       : chr "2016-10-14"
#  $ Inquiries.InquiryList.Inquiry.Reason.1              : chr "er"
#  $ Inquiries.Summary.NumberOfInquiries                 : chr "2"
Run Code Online (Sandbox Code Playgroud)

如果要将具有合适表示形式的字符串转换为数字,假设这df是上面的数据框:

data.frame(t(lapply(df, function(x) 
               ifelse(is.na(y<-suppressWarnings(as.numeric(x))), x, y))))
Run Code Online (Sandbox Code Playgroud)

没有数字表示的字符串将不会被转换.

更新

动机

A)在一些评论中,OP增加了对执行速度的进一步请求,这对于一次性任务(例如数据导入)通常不是问题.上面的解决方案基于递归,正如问题中明确要求的那样.当然,遍历节点会增加很多开销.

B)最近的一个答案提出了一种基于外部工具集合的复杂方法.当然可能有不同的实用程序来管理XML文件,但是恕我直言,许多XPATH工作可以在R本身中舒适有效地完成.

C)OP想知道是否可以"为XML的每个列表对象创建单独的data.frames".

D)我注意到在问号标签中,OP(似乎)需要更新的xml2包.

我直接从R使用XPATH解决了上述问题.

XPATH方法

下面我在一个单独的数据框中提取Record节点.也可以对其他(子)节点使用相同的方法.

library(xml2)
xx=read_xml(x)                                                                              
xx=(xml_find_all(xx, "//Record"))
system.time(
    xx <- xml_find_all(xx, ".//descendant::*[not(*)]"))
#  user  system elapsed 
# 38.00    0.36   38.35 
system.time(xx <- xml_text(xx))
#  user  system elapsed 
# 68.39    0.05   68.53 
head(data.frame(t(matrix(xx, 5))))
#                    X1 X2 X3    X4  X5
# 1 2017-05-26T00:00:00  2  R local xxx
# 2 2017-04-30T00:00:00  2  R       xyx
# 3 2017-05-26T00:00:00  2  R local xxx
# 4 2017-04-30T00:00:00  2  R       xyx
# 5 2017-05-26T00:00:00  2  R local xxx
# 6 2017-04-30T00:00:00  2  R       xyx
Run Code Online (Sandbox Code Playgroud)

(您可能希望在名称数据框列中添加更多代码)

时间是指我的普通笔记本电脑.

说明

解决方案的核心在于XPATH .//descendant::*[not(*)]. .//descendant::提取当前上下文的所有后代(Record节点); 添加 [not(*)]进一步平整布局.这允许线性化树结构,使其更适合于数据科学建模.

灵活性* 在计算方面是有代价的.然而,计算负担并不依赖于R,这是一种解释型语言,但需要高效的外部C库libxml2.结果应该等于或优于其他实用程序和库.