xpath和r - 创建一个密钥表

时间:2014-12-02 18:23:49

标签: xml r xpath apply

我是新的xml包用于r和新用于xpath。我有一个非常大的xml文件,我正在解析。我使用循环编写了一些代码但是花费的时间太长,所以我使用xpath编写更高效的代码。 xml看起来像这样:

...
<person personId="1">
<personNames>
<personName nameId="1000">
<first>Joe<last>
<last>Jones<last>
</personName>
<personName nameId="1001">
<first>Joseph><first>
<last>Jones<last>
</personName>
<personName nameId="1002"
<first>The One and only Joe<first>
</personName>
</personNames>
</person>
...

有些人有一个名字,有些人有更多。有些人有名字和姓氏,有些只是名字或姓氏。所以,我需要小心。

我能够使用xpath:

高效地创建名字和姓氏的数据框
library(XML)
doc<-xmlTreeParse("People.xml",useInternalNodes = TRUE)
top<-xmlRoot(doc)
First<-as.character(xpathApply(top,"//person/personNames/personName/first", xmlValue))
name_id<-as.integer(xpathApply(top,"//person/personNames/personName[first]/@nameId"))
FirstNames<-data.frame(TMS_name_id=name_id,first=First)
Last<-as.character(xpathApply(top,"//person/personNames/personName/last", xmlValue))
name_id<-as.integer(xpathApply(top,"//person/personNames/personName[last]/@nameId"))
LastNames<-data.frame(name_id=name_id,last=Last)
Names<-merge(x=FirstNames,y=LastNames,by="name_id",all=TRUE)

我的姓名数据框看起来不错。它具有nameId,名字和每个人的姓氏。如果缺少名字或姓氏,则为空。它在几分钟内生成(610K行!)。真棒。

问题是将这些名称与父personId相关联。我假设我需要遍历数据框中的名称,并获取具有正确nameId属性的personId,但我无法执行此操作。例如,以下代码给出了一个null结果:

xpathSApply(top,"//person/personNames/personName[@nameId="1000"]/@personId")

我期待的结果是1.在personId的数据框中添加列的最有效方法是什么?

鉴于上面的示例,我想要一个如下所示的数据框:

nameId  first                  last                  personId
1000    Joe                    Jones                 1
1001    Joseph                 Jones                 1
1002    The one and only Joe   <NA>                  1

2 个答案:

答案 0 :(得分:2)

由于名字和姓氏都没有平衡,看起来你需要更加谨慎地匹配它们,然后一次性提取它们。

这里有一些有效的测试数据

library(XML)
dd<-xmlInternalTreeParse('<people><person personId="1">
<personNames>
<personName nameId="1000"><first>Joe</first><last>Jones</last></personName>
<personName nameId="1001"><first>Joseph</first><last>Jones</last></personName>
<personName nameId="1002"><first>The One and only Joe</first></personName>
</personNames>
</person></people>')

然后我将包含plyr以使事情更容易崩溃,并创建一个辅助函数来用NA

替换缺失的值
library(plyr)
getXmlValue<-function(node, select) {
    x<-node[select]
      if(length(x)==1) {
        xmlValue(x[[1]])
    } else {
        NA
    }
}

然后我可以做

rbind.fill(xpathApply(dd, "//person", function(x) {
    pn <- xpathApply(x, "./personNames/personName", function(x) {
        data.frame(
            nameId=xmlGetAttr(x, "nameId"), 
            first=getXmlValue(x, "first"), 
            last=getXmlValue(x,"last"))
    })
    cbind(personID=xmlGetAttr(x, "personId"), rbind.fill(pn))
}))

获取

  personID nameId                first  last
1        1   1000                  Joe Jones
2        1   1001               Joseph Jones
3        1   1002 The One and only Joe  <NA>

答案 1 :(得分:0)

以下是有点复杂的;它的灵感来自于创建许多单行data.frames然后将它们组合在一起的成本。我不知道这是否更有效(获得反馈会很有意思......)。

在第一遍中,我记录事件发生时的“几何”

geom <- xpathSApply(dd, "//person|//personName|//first|//last", xmlName)

并在第二遍中提取我感兴趣的名字

## hack: implement XMLAttributeValue method for xmlValue
xmlValue.XMLAttributeValue <- as.character
nms <- xpathSApply(dd, 
    "//person/@personId|//personName/@nameId|//first|//last", xmlValue)

然后我弄清楚如何将我发现的名称放入矩形网格中的正确单元格

cols <- c(nameId="personName", first="first", last="last")
pidx = geom == "person"
ridx = cumsum(geom == "personName")
cidx <- match(geom, cols, 0)

## fill matrix with leaf nodes
m <- matrix(character(), max(ridx), max(cidx), 
            dimnames=list(NULL, names(cols)))
m[cbind(ridx, cidx)] <- nms[!pidx]

## 'expand' parent elements and bind to matrix
times <- diff(c(ridx[pidx], max(ridx)))
m <- cbind(personId=rep(nms[pidx], times), m)

带有最终结果

> m
     personId nameId first                  last   
[1,] "1"      "1000" "Joe"                  "Jones"
[2,] "1"      "1001" "Joseph"               "Jones"
[3,] "1"      "1002" "The One and only Joe" NA