簡體   English   中英

將不均勻的XML解析為R data.frame

[英]Parsing uneven XML into R data.frame

我正在嘗試將大型XML文件解析為R數據幀。 XML的結構參差不齊,並不總是包含所有元素,有時每個節點包含1個以上的重復元素。

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的方法。

這是一個公認的笨拙的解決方案,可能還有更優雅的tidiverse式解決方案。 但是,這似乎可以解決問題。

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

另一種方法:

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轉換為邏輯向量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM