在R中,我想使用XML包解析XML文件。实际文件来自Eurostats REST服务。您将在问题末尾找到指向实际数据的链接。该文件的相关结构如下:
doc <- xmlParse( # needed to run example
'<?xml version="1.0" ?>
<Series>
<Obs>
<ObsDimension value="2009"/>
<ObsValue value="NaN"/>
<Attributes>
<Value id="OBS_STATUS" value="na"/>
</Attributes>
</Obs>
<Obs>
<ObsDimension value="2006"/>
<ObsValue value="NaN"/>
<Attributes>
<Value id="OBS_STATUS" value="na"/>
</Attributes>
</Obs>
<Obs>
<ObsDimension value="2009"/>
<ObsValue value="43.75"/>
</Obs>
<Obs>
<ObsDimension value="2006"/>
<ObsValue value="NaN"/>
<Attributes>
<Value id="OBS_STATUS" value="na"/>
<Value id="OBS_FLAG" value="e"/>
</Attributes>
</Obs>
</Series>
') # needed to run example
因此每个Obs节点都有一个Dimension和一个Value。此外,还有两个可选属性,由id属性OBS_STATUS或OBS_FLAG标识。我想解析这个结构,以便在属性不存在时使用NA
。结果应如下所示:
dimension value status flag
1 2009 NaN na <NA>
2 2006 NaN na <NA>
3 2009 43.75 <NA> <NA>
4 2006 NaN na e
我准备了以下代码,显然失败了,因为列的长度不一样。
library(XML)
data.frame(dimension = xpathSApply(doc,"//ObsDimension",xmlGetAttr,"value"),
value = xpathSApply(doc,"//ObsValue",xmlGetAttr,"value"),
status = xpathSApply(doc,
"//Attributes/Value[@id='OBS_STATUS']",
xmlGetAttr,"value"),
flag = xpathSApply(doc,
"//Attributes/Value[@id='OBS_FLAG']",
xmlGetAttr,"value"))
如果指定的节点不存在,是否有一种定义可选值的好方法?任何帮助将不胜感激。
收到@MrFlick回复后,附录添加。 我实际需要解析的数据可以使用以下代码加载:
library(XML)
library(RCurl)
file <- "http://ec.europa.eu/eurostat/SDMX/diss-web/rest/data/cdh_e_fos/..PC.FOS1.BE/?startperiod=2005&endPeriod=2013"
content <- getURL(file, httpheader = list('User-Agent' = 'R-Agent'))
root <- xmlRoot(xmlInternalTreeParse(content, useInternalNodes = TRUE))
答案 0 :(得分:2)
这是一种可能的策略。有一个很好的xmlToDataFrame
函数,但是你的数据格式不正确。我认为将数据转换为更合适的格式然后使用该函数是最容易的。这是一个这样的转变
trn<-newXMLDoc()
addChildren(trn, newXMLNode("data"))
for(x in getNodeSet(doc, "//Obs")) {
row<-newXMLNode("row")
for( z in getNodeSet(x, ".//*[not(*)]")) {
li <- newXMLNode(xmlGetAttr(z, "id", xmlName(z)))
addChildren(li, newXMLTextNode(xmlGetAttr(z, "value",NA)))
addChildren(row, li)
}
addChildren(xmlRoot(trn), row)
}
我们创建一个新的XML文档,最终看起来像
<?xml version="1.0"?>
<data>
<row>
<ObsDimension>2009</ObsDimension>
<ObsValue>NaN</ObsValue>
<OBS_STATUS>na</OBS_STATUS>
</row>
<row>
<ObsDimension>2006</ObsDimension>
<ObsValue>NaN</ObsValue>
<OBS_STATUS>na</OBS_STATUS>
</row>
<row>
<ObsDimension>2009</ObsDimension>
<ObsValue>43.75</ObsValue>
</row>
<row>
<ObsDimension>2006</ObsDimension>
<ObsValue>NaN</ObsValue>
<OBS_STATUS>na</OBS_STATUS>
<OBS_FLAG>e</OBS_FLAG>
</row>
</data>
我们可以致电
xmlToDataFrame(trn)
获取
ObsDimension ObsValue OBS_STATUS OBS_FLAG
1 2009 NaN na <NA>
2 2006 NaN na <NA>
3 2009 43.75 <NA> <NA>
4 2006 NaN na e
是的我使用了一些丑陋的for循环,但这确实是为了确保我们为每个Obs
节点创建一个值。这确实是数据的主要单位,所以当抓取带有xpath的节点时,你不能跳过它。您可以直接在循环中构建data.frame,但我更愿意让xmlToDataFrame
处理每个节点可能具有可能不同数量的元素的事实。
如果确实需要在Node不存在时指定默认值,则可以创建一个函数similr xmlGetAttr
,但也可以检查一个节点。这是一个辅助函数。
xmlGetNodeAttr <- function(n, xp, attr, default=NA) {
ns<-getNodeSet(n, xp)
if(length(ns)<1) {
return(default)
} else {
sapply(ns, xmlGetAttr, attr, default)
}
}
我们可以使用
将其应用于您的数据do.call(rbind, lapply(xmlChildren(xmlRoot(doc)), function(x) {
data.frame(
dimension=xmlGetNodeAttr(x, "./ObsDimension","value",NA),
value=xmlGetNodeAttr(x, "./ObsValue","value",NA),
status=xmlGetNodeAttr(x, "./Attributes/Value[@id='OBS_STATUS']","value",NA),
flag=xmlGetNodeAttr(x, "./Attributes/Value[@id='OBS_FLAG']","value",NA)
)
}))
产生相同的结果。在这里,我们仍然必须单独遍历Obs
个节点,因为无法使用xpath强制匹配每个Obs
。