xpath и r — создать таблицу ключей

Я новичок в пакете 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)

Фрейм данных My Names выглядит хорошо. Он имеет nameId, имя и фамилию каждого человека. Если имя или фамилия отсутствуют, это значение равно null. Он сгенерирован за несколько минут (610 тысяч строк!). Потрясающий.

Проблема заключается в том, чтобы связать эти имена с родительским personId. Я предполагаю, что мне нужно перебрать имена в моем фрейме данных и получить идентификатор человека с правильным атрибутом nameId, но я не могу этого сделать. Например, следующий код дает мне нулевой результат:

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

person user2980491    schedule 02.12.2014    source источник
comment
Не могли бы вы включить окончательный результат, который вы хотите получить для своего примера входных данных? Я не уверен в точной форме результата, который вы ищете.   -  person MrFlick    schedule 02.12.2014
comment
Правка сделана. Спасибо за уделенное время.   -  person user2980491    schedule 02.12.2014


Ответы (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>
person MrFlick    schedule 02.12.2014

Следующее немного запутано; это было вдохновлено затратами на создание множества однострочных фреймов данных и их последующее связывание вместе. Я не знаю, более эффективно это или нет (было бы интересно получить отзывы...).

При первом проходе я записываю «геометрию» событий по мере их возникновения.

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   
person Martin Morgan    schedule 02.12.2014