所以,我有大量的XML文件和很多报告。我在下面创建了数据示例,以大致显示xml的大小及其结构:
define measure Query_NewSales[isKit] = SUMX(Query_NewSales, IF(NOT (
ISBLANK (
LOOKUPVALUE(Query_Kits[KitName],Query_Kits[KitName],Query_NewSales[Item])
)), 1, 0))
measure Query_NewSales[AvgCPI] = SUMX(Query_NewSales, IF(Query_NewSales[isKit],
-- lookup to DAX_KitsPricedMissingRemoved
,-- lookup to DAXTable_NewPurchasesGroupedByItem
))
EVALUATE ADDCOLUMNS(Query_NewSales, "IsKit", Query_NewSales["IsKit"], ...)
我想将此数据转换为data.frame,但XML的结构并不简单。以前使用XML我创建了循环,每个报告将其子节点转换为data.frame,但是这里(在此数据中)子节点数大于30(并未将所有这些都放在示例中),并且结构不同(列表节点在XML中甚至可以出现2级深度)。
所以我几乎没有问题:
1)我确信循环报告不是处理此问题的最佳方法。我该如何处理这个问题?
2)我可以以某种方式提取一个报告的所有数据两行data.frame(递归可能)吗?
3)或者我可以为XML的每个列表对象自动创建单独的data.frames吗?
非常感谢任何帮助。
结果示例如下所示:
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]]
,但正如我之前提到的,子列表也可以在单独的表中。
答案 0 :(得分: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"
如果要将具有合适表示形式的字符串转换为数字,假设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&#34;的每个列表对象创建单独的data.frames。
D)我注意到在问号标签中,OP(似乎)需要更新的xml2包。
我直接使用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的代价。结果应该等于或优于其他公用事业和图书馆。
答案 1 :(得分:2)
因为你提到,我想转换这些数据,考虑XSLT,这是专门用于将复杂XML重构为各种最终用途结构的转换语言。在您的情况下,展平任何以XML格式保存节点的文本,然后可以使用xmlToDataFrame()
轻松导入。虽然下面使用xsltproc和.NET Xsl类,但支持XSLT 1.0的任何external processor或语言模块(例如,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 (使用Powershell xsl脚本调用.NET Xsl类,请参阅here)
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"))