XML во фрейм данных с отсутствующими узлами

Версии этого вопроса задавались раньше, как здесь и здесь. Однако я все еще не могу заставить его работать. Я пытаюсь преобразовать XML-документ во фрейм данных. Проблема в том, что некоторые из переменных не присутствуют для некоторых наблюдений, поэтому я получаю сообщение об ошибке, потому что существует другое количество строк. Мои данные выглядят так:

library("xml2")
library("dplyr")

example <- read_xml(
'
<particDesc>
<person role="participant" sameAs="#P484" xml:id="EDcon250_S1">
<age value="3">35-49</age>
<sex value="1">male</sex>
<occupation>waiter</occupation>
<langKnowledge>
<langKnown level="L1" tag="ita"/>
</langKnowledge>
</person>
<person role="participant" sameAs="#P485" xml:id="EDcon250_S7">
<age value="0">unknown</age>
<sex value="2">female</sex>
<occupation>waitress</occupation>
<langKnowledge>
<langKnown level="L1" tag="ger-AT"/>
</langKnowledge>
</person>
<person role="participant" sameAs="#P465" xml:id="EDcon250_S2">
<age value="2">25-34</age>
<sex value="2">female</sex>
<langKnowledge>
<langKnown level="L1" tag="ger-AT"/>
<langKnown level="L1" tag="eng-US"/>
</langKnowledge>
</person>
</particDesc>
')

Я использую пакет Wickham xml2 для чтения xml. Я бы предпочел использовать этот пакет, но был бы открыт для использования XML, если это лучший (или единственный) способ решения этой проблемы. В любом случае мой код следующий:

participants <- xml_find_all(example, './/person[@role = "participant"]')

extract_participants <- function(div){
id <- xml_attr(div, "id")
same_as <- xml_attr(div, "sameAs")
role <- xml_attr(div, "role")
age <- xml_find_all(div, ".//age") %>% xml_text()
sex <- xml_find_all(div, ".//sex") %>% xml_text()
occupation <- xml_find_all(div, ".//occupation") %>% xml_text()

data_frame(id, same_as,role, age, sex, occupation)
}


parts_ls <- lapply(participants, extract_participants)

participants_df <- do.call(rbind, parts_ls)

Проблема в этом конкретном случае касается переменной occupation (у третьего лица ее нет), но в моих фактических данных это также может быть одна из других переменных. Как я уже сказал, я вижу, что этот вопрос задавался раньше, но я мог заставить любое из предложений работать (вероятно, из-за того, что я не до конца понял решение). В конечном счете, я хотел бы, чтобы NA возвращались всякий раз, когда отсутствует конкретный узел (поэтому переменная occupation для третьего лица будет NA.

При редактировании: альтернативная версия XML

library("XML")
library("magrittr")

example2 <- xmlParse(
'
 <particDesc>
 <person role="participant" sameAs="#P484" xml:id="EDcon250_S1">
 <age value="3">35-49</age>
 <sex value="1">male</sex>
 <occupation>waiter</occupation>
 <langKnowledge>
 <langKnown level="L1" tag="ita"/>
 </langKnowledge>
 </person>
 <person role="participant" sameAs="#P485" xml:id="EDcon250_S7">
 <age value="0">unknown</age>
 <sex value="2">female</sex>
 <occupation>waitress</occupation>
 <langKnowledge>
 <langKnown level="L1" tag="ger-AT"/>
 </langKnowledge>
 </person>
 <person role="participant" sameAs="#P465" xml:id="EDcon250_S2">
 <age value="2">25-34</age>
 <sex value="2">female</sex>
 <langKnowledge>
 <langKnown level="L1" tag="ger-AT"/>
 <langKnown level="L1" tag="eng-US"/>
 </langKnowledge>
 </person>
 </particDesc>
')

example_root <- xmlRoot(example2) 

process <- function(x){
id <- xmlGetAttr(x, "id")
role <- xmlGetAttr(x, "role")
age <- getNodeSet(x, ".//age") %>% xmlSApply(xmlValue)
sex <- getNodeSet(x, ".//sex") %>% xmlSApply(xmlValue)
#occupation <- getNodeSet(x, ".//occupation") %>% xmlSApply(xmlValue)
data.frame(id = id,
           role = role,
           #occupation = occupation,
           age = age,
           sex = sex,
           stringsAsFactors = FALSE)
}


 ls <- xpathApply(example_root, "//person", process)
 df <- do.call(rbind, ls)

Просто раскомментируйте occupation, чтобы увидеть проблему.


person JoeF    schedule 09.06.2015    source источник


Ответы (1)


У меня есть над чем поработать, но я не уверен, что это идеальное решение (я думаю, что оно довольно длинное). В любом случае, вот что у меня есть на данный момент. Предложения по улучшению приветствуются.

library("XML")
library("magrittr")

example2 <- xmlParse(
'
<particDesc>
<person role="participant" sameAs="#P484" xml:id="EDcon250_S1">
<age value="3">35-49</age>
<sex value="1">male</sex>
<occupation>waiter</occupation>
<langKnowledge>
<langKnown level="L1" tag="ita"/>
</langKnowledge>
</person>
<person role="participant" sameAs="#P485" xml:id="EDcon250_S7">
<age value="0">unknown</age>
<sex value="2">female</sex>
<occupation>waitress</occupation>
<langKnowledge>
<langKnown level="L1" tag="ger-AT"/>
</langKnowledge>
</person>
<person role="participant" sameAs="#P465" xml:id="EDcon250_S2">
<age value="2">25-34</age>
<sex value="2">female</sex>
<langKnowledge>
<langKnown level="L1" tag="ger-AT"/>
<langKnown level="L1" tag="eng-US"/>
</langKnowledge>
</person>
</particDesc>
')

example_root <- xmlRoot(example2) 
person <- getNodeSet(example_root, "//person")

id <- lapply(person, xmlGetAttr, "id") %>% unlist()
role <- lapply(person, xmlGetAttr, "role") %>% unlist()
age <- lapply(person, xpathSApply, ".//age", xmlValue) %>% unlist()
sex <- lapply(person, xpathSApply, ".//sex", xmlValue) %>% unlist()
occupation <- lapply(person, xpathSApply, ".//occupation", xmlValue)
occupation[sapply(occupation, is.list)] <- NA 
occupation <- unlist(occupation)

df <- data.frame(
   id = id,
   role = role,
   age = age,
   sex = sex,
   occupation = occupation)

При редактировании: для завершения, вот соответствующая версия xml2 (в сокращении)

example <- read_xml(
'
 <particDesc>
 <person role="participant" sameAs="#P484" xml:id="EDcon250_S1">
 <age value="3">35-49</age>
 <sex value="1">male</sex>
 <occupation>waiter</occupation>
 <langKnowledge>
 <langKnown level="L1" tag="ita"/>
 </langKnowledge>
 </person>
 <person role="participant" sameAs="#P485" xml:id="EDcon250_S7">
 <age value="0">unknown</age>
 <sex value="2">female</sex>
 <occupation>waitress</occupation>
 <langKnowledge>
 <langKnown level="L1" tag="ger-AT"/>
 </langKnowledge>
 </person>
 <person role="participant" sameAs="#P465" xml:id="EDcon250_S2">
 <age value="2">25-34</age>
 <sex value="2">female</sex>
 <langKnowledge>
 <langKnown level="L1" tag="ger-AT"/>
 <langKnown level="L1" tag="eng-US"/>
 </langKnowledge>
 </person>
 </particDesc>
')

participants <- xml_find_all(example, './/person[@role = "participant"]')

id <- lapply(participants, xml_attr, "id")
occupation <- lapply(participants, xml_find_all, ".//occupation")
occupation <- lapply(occupation, xml_text)
occupation[!sapply(occupation, function(y) length(y == 0))] <- NA

occupation <- unlist(occupation)
id <- unlist(id)

data_frame(
  id = id, 
  occupation = occupation)
person JoeF    schedule 09.06.2015