将不均匀的XML解析为R data.frame

时间:2017-10-25 04:18:00

标签: r xml

我正在尝试将大型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的方法。

2 个答案:

答案 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转换为自己的逻辑向量。