XML to dataframe如果节点不存在,如何获取默认值

时间:2014-07-27 20:14:27

标签: xml r

在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))

1 个答案:

答案 0 :(得分:2)

取1

这是一种可能的策略。有一个很好的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处理每个节点可能具有可能不同数量的元素的事实。

拿2

如果确实需要在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