我正在尝试将大型XML文件解析为R数据帧。 XML的结构不均匀,并不总是包含所有元素,有时每个节点包含多个重复元素。
XML是:
<root>
<members>
<member>
<id>1</id>
<educations>
<education>
<institution>Sydney University</institution>
<program>Masters of Science</program>
<start-date>2010</start-date>
<end-date>2015</end-date>
<description></description>
</education>
<education>
<institution>UTS</institution>
<program>Bachelor of Science</program>
<start-date>2004</start-date>
<end-date>2008</end-date>
</education>
</educations>
</member>
<member>
<id>2</id>
</member>
<member>
<id>3</id>
<educations>
<education>
<is-current>true</is-current>
<institution>Monash Univeristy</institution>
<start-date>2010</start-date>
</education>
</educations>
</member>
</members>
</root>
所需的输出表将为每个成员及其教育块提供重复的ID。因此ID 1每个教育期间有2行,而ID 3只有1行。
使用xmlToList()会创建过多的列,我找不到复制每个子节点ID的方法。
答案 0 :(得分:0)
这是一个公认的笨拙的解决方案,可能有更优雅的tidiverse-esque解决方案。 但是,这似乎可以完成这项工作。
library(XML)
library(plyr)
names_use <- c("institution", "program", "start-date", "end-date","description")
list_xml <- xmlToList(test)
df_use <- ldply(list_xml$member, function(x){
if(is.null(x$educations)){
df_edu <- data.frame(x$id,t(rep(NA,5)))
names(df_edu) <- c("id",names_use)
return(df_edu)
}
df_res <- ldply(x$educations, function(edu_tmp){
df_edu <- as.data.frame(t(unlist(edu_tmp)),
stringsAsFactors = F)
for(i_names in names_use){
if(!i_names %in% names(df_edu)){
df_edu[,i_names] <- NA
}
}
return(df_edu)
})
df_res$id <- x$id
return(df_res[,c("id",names_use)])
})
df_use <- df_use[,c("id",names_use)]
df_use
id institution program start-date end-date description
1 1 Sydney University Masters of Science 2010 2015 NA
2 1 UTS Bachelor of Science 2004 2008 NA
3 2 <NA> <NA> <NA> <NA> NA
4 3 Monash Univeristy <NA> 2010 <NA> NA
答案 1 :(得分:0)
另一种方法:
library(xml2)
library(tidyverse)
我喜欢整洁的列名,所以我们将添加辅助函数:
mgca <- function(tbl) {
x <- colnames(tbl)
x <- tolower(x)
x <- gsub("[[:punct:][:space:]]+", "_", x)
x <- gsub("_+", "_", x)
x <- gsub("(^_|_$)", "", x)
x <- make.unique(x, sep = "_")
colnames(tbl) <- x
tbl
}
doc <- read_xml("so.xml")
这里的想法是首先迭代每个<member>
,然后为其提取<id>
。
进入<member>
后,看看我们是否有孩子。如果没有,只需在数据框中返回<id>
即可。如果我们这样做,那么进一步迭代每个<education>
节点,识别出现的子节点,只将它们拉出来并为每个节点创建一个数据框,包括<id>
,最后将它们全部整合成一个清理列名称并获得更好的列类型后的最终数据框架:
xml_find_all(doc, ".//member") %>%
map_df(~{
id <- (xml_find_first(.x, ".//id") %>% xml_text()) %||% NA_character_
edus <- xml_find_all(.x, ".//educations/education")
if (length(edus) > 0) {
map_df(edus, ~{
kid <- .x
nodes <- xml_children(kid) %>% xml_name()
map(nodes, ~xml_find_first(kid, sprintf(".//%s", .x)) %>%
xml_text()) %>%
set_names(nodes) %>%
append(list(id = id)) %>%
flatten_df()
})
} else {
data_frame(id = id)
}
}) %>%
mgca() %>%
type_convert()
## # A tibble: 4 x 7
## institution program start_date end_date description id is_current
## <chr> <chr> <int> <int> <chr> <int> <chr>
## 1 Sydney University Masters of Science 2010 2015 <NA> 1 <NA>
## 2 UTS Bachelor of Science 2004 2008 <NA> 1 <NA>
## 3 <NA> <NA> NA NA <NA> 2 <NA>
## 4 Monash Univeristy <NA> 2010 NA <NA> 3 true
由于type_convert()
无法读懂头脑,因此您可能需要将is_current
转换为自己的逻辑向量。