[英]Parsing large and complicated XML file to data.frame
因此,我有一個包含大量報告的大型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]]
我想將此數據轉換為data.frame,但是XML的結構並不簡單。 以前使用XML時,我創建了一個循環,每個報告都將其子節點轉換為data.frame,但是在此數據中,子節點數大於30(示例中未全部包含),並且結構有所不同(列表節點在XML中甚至可以出現2個層次)。
所以我有幾個問題:
1)我確信,循環報表不是解決此問題的最佳方法。 我應該如何解決這個問題?
2)我可以以某種方式(遞歸地)提取一個報表的所有數據兩行一行的data.frame嗎?
3)還是可以為XML的每個列表對象自動創建單獨的data.frame?
任何幫助將非常感激。
結果示例如下所示:
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
,但正如我之前提到的,子列表也可以位於單獨的表中。
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"
如果要轉換具有適當表示形式的數字字符串,請假定df
是上面的數據框:
data.frame(t(lapply(df, function(x)
ifelse(is.na(y<-suppressWarnings(as.numeric(x))), x, y))))
沒有數字表示的字符串將不會轉換。
動機
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
(您可能想添加更多代碼來命名數據框列)
時間是指我的平均筆記本電腦。
說明
解決方案的核心在於XPATH .//descendant::*[not(*)]
。 .//descendant::
提取當前上下文的所有后代(“ Record
節點); 添加[not(*)]
進一步使布局平坦。 這允許線性化樹結構,使其更適合於數據科學建模。
*
的靈活性是以計算為代價的。 但是,計算負擔並不在於R(這是一種解釋性語言),而是以高效的外部C庫libxml2為代價。 結果應等於或優於其他實用程序和庫。
正如您提到的, 我想轉換此數據 ,請考慮使用XSLT ,它是一種專用的轉換語言,旨在將復雜的XML重組為各種最終用途結構。 並且在您的情況下,將XML中的所有文本保存節點展平,然后可以使用xmlToDataFrame()
輕松導入。 雖然下面使用xsltproc和.NET Xsl類,但是支持XSLT 1.0的任何外部處理器或語言模塊(例如Python,Java,C#,VB,PHP)都可以工作:
XSLT (另存為.xsl文件)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/R">
<xsl:copy>
<xsl:apply-templates select="Report"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Report">
<xsl:copy>
<xsl:apply-templates select="descendant::*[string-length(text())>0]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{concat(local-name(), position())}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
XML輸出(帶有編號后綴,以避免重復的列錯誤)
<?xml version="1.0"?>
<R>
<Report>
<Date1>2017-05-26T00:00:00</Date1>
<Grade2>2</Grade2>
<Code3>R</Code3>
<Description4>local</Description4>
<Score5>xxx</Score5>
<Date6>2017-04-30T00:00:00</Date6>
<Grade7>2</Grade7>
<Code8>R</Code8>
<Score9>xyx</Score9>
<FirstName10>MM</FirstName10>
<DateOfInquiry11>2017-03-19</DateOfInquiry11>
<Reason12>cc</Reason12>
<DateOfInquiry13>2016-10-14</DateOfInquiry13>
<Reason14>er</Reason14>
<NumberOfInquiries15>2</NumberOfInquiries15>
</Report>
</R>
R Mac / Linux腳本(調用xsltproc,Unix機器上的可用軟件包)
library(XML)
setwd("/path/to/working/folder")
# COMMAND LINE CALL (INSTALL xsltproc IN TERMINAL)
system(paste("cd", getwd(), " && xsltproc -o Output.xml XSLTScript.xsl Input.xml"))
# PARSE AND LOAD TO DF
doc <- xmlParse('Output.xml')
df <- xmlToDataFrame(nodes = getNodeSet(doc, "//Report"))
str(df)
# 'data.frame': 6 obs. of 15 variables:
# $ Date1 : chr "2017-05-26T00:00:00" "2017-05-26T00:00:00" "2017-05-26T00:00:00" "2017-05-26T00:00:00" ...
# $ Grade2 : chr "2" "2" "2" "2" ...
# $ Code3 : chr "R" "R" "R" "R" ...
# $ Description4 : chr "local" "local" "local" "local" ...
# $ Score5 : chr "xxx" "xxx" "xxx" "xxx" ...
# $ Date6 : chr "2017-04-30T00:00:00" "2017-04-30T00:00:00" "2017-04-30T00:00:00" "2017-04-30T00:00:00" ...
# $ Grade7 : chr "2" "2" "2" "2" ...
# $ Code8 : chr "R" "R" "R" "R" ...
# $ Score9 : chr "xyx" "xyx" "xyx" "xyx" ...
# $ FirstName10 : chr "MM" "MM" "MM" "MM" ...
# $ DateOfInquiry11 : chr "2017-03-19" "2017-03-19" "2017-03-19" "2017-03-19" ...
# $ Reason12 : chr "cc" "cc" "cc" "cc" ...
# $ DateOfInquiry13 : chr "2016-10-14" "2016-10-14" "2016-10-14" "2016-10-14" ...
# $ Reason14 : chr "er" "er" "er" "er" ...
# $ NumberOfInquiries15: chr "2" "2" "2" "2" ...
R Windows (使用調用.NET Xsl類的Powershell xsl腳本,請參見此處 )
library(XML)
# COMMAND LINE CALL (NO INSTALLS NEEDED)
system(paste0('Powershell.exe -File',
' "C:\\Path\\To\\PowerShell\\Script.ps1"',
' "C:\\Path\\To\\Input.xml"',
' "C:\\Path\\To\\XSLT\\Script.xsl"',
' "C:\\Path\\To\\Output.xml"'))
# PARSE AND LOAD TO DF
doc <- xmlParse('Output.xml')
df <- xmlToDataFrame(nodes = getNodeSet(doc, "//Report"))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.