![](/img/trans.png)
[英]JSON to XSLT : default XML node if JSON element does NOT exist
[英]XML to dataframe How to get a default value if node does not exist
在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))
这是一种可能的策略。 有一个不错的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的情况下指定默认值,则可以为xmlGetAttr
创建一个函数similr,但同时还要检查一个节点。 这是一个辅助功能。
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
匹配。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.