【问题标题】:parse multiple XML files based on a vector and rbind in a dataframe基于向量解析多个 XML 文件并在数据帧中进行 rbind
【发布时间】:2016-12-09 23:55:42
【问题描述】:

在堆栈器的一些努力和帮助下,我已经能够解析网页并将其保存为数据框。我想对多个 xml 文件重复相同的操作并 rbind 列表。这是我尝试并成功完成的:

library(XML)    
xml.url <- "http://www.ebi.ac.uk/ena/data/view/ERS445758&display=xml"
doc <- xmlParse(xml.url)
x <- xmlToDataFrame(getNodeSet(doc,"//SAMPLE_ATTRIBUTE"))
x$UNITS <- NULL 
x_t <- t(x) 
x_t <- as.data.frame(x_t)
names(x_t) <- as.matrix(x_t[1, ])
x_t <- x_t[-1, ]
x_t[] <- lapply(x_t, function(x) type.convert(as.character(x)))

以上代码运行良好,现在当我尝试应用一个函数对多个 xml 文件执行相同操作时:

ERS_ID <- c("ERS445758","ERS445759", "ERS445760", "ERS445761", "ERS445762")

xml_url_test =    as.vector(sprintf("http://www.ebi.ac.uk/ena/data/view/ERS445758&display=xml",
              ERS_ID))

XML_parser <- function(XML_url){
doc <- xmlParse(XML_url)
x <- xmlToDataFrame(getNodeSet(doc,"//SAMPLE_ATTRIBUTE"))
x$UNITS <- NULL 
x_t <- t(x)
x_t <- as.data.frame(x_t)
names(x_t) <- as.matrix(x_t[1, ])
x_t <- x_t[-1, ]
x_t[] <- lapply(x_t, function(x) type.convert(as.character(x)))
return(x_t)
}

major_test <- sapply(xml_url_test, XML_parser)

它有效,但给了我一个长列表,该列表与我为单个 XML 文件生成的数据框格式不正确。 最后,我还想在具有来自 ERS_ID 向量的 ERS ​​编号的最终数据帧中添加一列 函数中的 x_t$ERSid &lt;- ERS_ID 之类的东西

有人可以指出我在函数中缺少什么以及完成任务的更好方法吗?

谢谢!

【问题讨论】:

    标签: r xml parsing


    【解决方案1】:

    您的主要问题是使用sapply 而不是lapply(),后者返回一个列表,而前者试图简化为向量或矩阵,这里是一个矩阵。

    major_test <- lapply(xml_url_test, XML_parser)
    

    当然,sapplylapply 的包装器,也可以返回一个列表:sapply(..., simplify=FALSE)

    major_test <- sapply(xml_url_test, XML_parser, simplify=FALSE)
    

    但是,出现了其他一些项目:

    1. 开始时,您没有使用 sprintf 的 %s 运算符将 ERS_ID 连接到 url 词干。所以现在,相同的网址正在重复。
    2. 最后,您没有将数据帧列表绑定到已编译的最终单个数据帧中。
    3. 在您定义的函数中添加新的 ERS ​​列,传入 ERS_ID 向量。在创建列时,还要删除带有 gsubERS 前缀。

    R 代码(已调整)

    XML_parser <- function(eid) {
      XML_url <- as.vector(sprintf("http://www.ebi.ac.uk/ena/data/view/%s&display=xml", eid))
      doc <- xmlParse(XML_url)
      x <- xmlToDataFrame(getNodeSet(doc,"//SAMPLE_ATTRIBUTE"))
      x$UNITS <- NULL 
      x_t <- t(x)
      x_t <- as.data.frame(x_t)
      names(x_t) <- as.matrix(x_t[1, ])
      x_t <- x_t[-1, ]
      x_t[] <- lapply(x_t, function(x) type.convert(as.character(x)))
      x_t$ERSid <- gsub("ERS", "", eid)         # ADD COL, REMOVE ERS
      x_t <- x_t[,c(ncol(x_t),2:ncol(x_t)-1)]   # MOVE NEW COL TO FIRST
      return(x_t)
    }
    
    major_test <- lapply(ERS_ID, XML_parser)
    # major_test <- sapply(ERS_ID, XML_parser, simplify=FALSE)
    
    # BIND DATA FRAMES TOGETHER
    finaldf <- do.call(rbind, major_test)
    # RESET ROW NAMES
    row.names(finaldf) <- seq(nrow(finaldf))
    

    【讨论】:

      【解决方案2】:

      使用xml2tidyverse 你可以这样做:

      require(xml2)
      require(purrr)
      require(tidyr)
      urls <- rep("http://www.ebi.ac.uk/ena/data/view/ERS445758&display=xml", 2)
      identifier <- LETTERS[seq_along(urls)] # Take a unique identifier per url here
      
      parse_attribute <- function(x){
        out <- data.frame(tag = xml_text(xml_find_all(x, "./TAG")),
                   value = xml_text(xml_find_all(x, "./VALUE")), stringsAsFactors = FALSE)
        spread(out, tag, value)
      }
      
      doc <- map(urls, read_xml)
      
      out <- doc %>% 
        map(xml_find_all, "//SAMPLE_ATTRIBUTE") %>% 
        set_names(identifier) %>%
        map_df(parse_attribute, .id="url")
      

      这会给你一个 2x36 的 data.frame。要解析列类型,我建议使用readr::type_convert(out)

      输出如下:

        url age body product     body site body-mass index                       chimera check collection date
      1   A  28       mucosa Sigmoid colon        16.95502 ChimeraSlayer; Usearch 4.1 database      2009-03-16
      2   B  28       mucosa Sigmoid colon        16.95502 ChimeraSlayer; Usearch 4.1 database      2009-03-16
        disease status ENA-BASE-COUNT ENA-CHECKLIST ENA-FIRST-PUBLIC ENA-LAST-UPDATE ENA-SPOT-COUNT
      1      remission         627051     ERC000015       2014-12-31      2016-10-21           1668
      2      remission         627051     ERC000015       2014-12-31      2016-10-21           1668
                environment (biome)       environment (feature) environment (material) experimental factor
      1 organism-associated habitat organism-associated habitat                  mucus          microbiome
      2 organism-associated habitat organism-associated habitat                  mucus          microbiome
        gastrointestinal tract disorder geographic location (country and/or sea,region) geographic location (latitude)
      1              Ulcerative Colitis                                           India                       72.82807
      2              Ulcerative Colitis                                           India                       72.82807
        geographic location (longitude) host subject id human gut environmental package investigation type
      1                        18.94084               1                       human-gut         metagenome
      2                        18.94084               1                       human-gut         metagenome
                                 medication multiplex identifiers pcr primers    phenotype project name
      1 ASA;Steroids;Probiotics;Antibiotics            TGATACGTCT    27F-338R pathological         BMRP
      2 ASA;Steroids;Probiotics;Antibiotics            TGATACGTCT    27F-338R pathological         BMRP
        sample collection device or method sequence quality check sequencing method sequencing template  sex target gene
      1                             biopsy               software    pyrosequencing                 DNA male    16S rRNA
      2                             biopsy               software    pyrosequencing                 DNA male    16S rRNA
        target subfragment
      1               V1V2
      2               V1V2
      

      【讨论】:

      • 您好,感谢您的回答!我仍然熟悉 tidyr 我尝试了你的代码,它有效!只是想知道如何将原始 ERS445758 id 添加为其相应的已解析元数据的列变量。基本上添加一列 ERS ​​并在其中添加 ERS445758。所有 ERS ​​id 的儿子等等
      • 这就是set_names 发挥作用的地方。您只需设置identifier 来表示您的urls 的相应标识符。列名由map_df(..., .id="myname") 定义。所以在上面的代码中,我将标识列命名为url,并以AB 为例:identifier &lt;- LETTERS[seq_along(urls)]
      【解决方案3】:

      purrr 在这里非常有用,因为您可以使用 map 或在嵌套元素中使用 at_depth 迭代 URL 向量或 XML 文件列表,并使用 *_df 表单简化结果和flatten

      library(tidyverse)
      library(xml2)
      
      # be kind, don't call this more times than you need to
      x <- c("ERS445758","ERS445759", "ERS445760", "ERS445761", "ERS445762") %>% 
          sprintf("http://www.ebi.ac.uk/ena/data/view/%s&display=xml", .) %>% 
          map(read_xml)    # read each URL into a list item
      
      df <- x %>% map(xml_find_all, '//SAMPLE_ATTRIBUTE') %>%     # for each item select nodes
          at_depth(2, as_list) %>%     # convert each (nested) attribute to list
          map_df(map_df, flatten)    # flatten items, collect pages to df, then all to one df
      
      df
      ## # A tibble: 175 × 3
      ##                       TAG                               VALUE UNITS
      ##                     <chr>                               <chr> <chr>
      ## 1      investigation type                          metagenome  <NA>
      ## 2            project name                                BMRP  <NA>
      ## 3     experimental factor                          microbiome  <NA>
      ## 4             target gene                            16S rRNA  <NA>
      ## 5      target subfragment                                V1V2  <NA>
      ## 6             pcr primers                            27F-338R  <NA>
      ## 7   multiplex identifiers                          TGATACGTCT  <NA>
      ## 8       sequencing method                      pyrosequencing  <NA>
      ## 9  sequence quality check                            software  <NA>
      ## 10          chimera check ChimeraSlayer; Usearch 4.1 database  <NA>
      ## # ... with 165 more rows
      

      【讨论】:

      • 哇,这是一个很棒的解决方案。我不知道as_list。这比我的解决方案优雅得多。
      【解决方案4】:

      您可以retrieve multiple IDs 使用逗号分隔的列表或范围(如 ERS445758-ERS445762)和单个 REST url,并避免对 ENA 进行多次查询。

      此代码将所有 5 个样本放入一个节点集中,然后使用 xpath 字符串中的前导点应用函数,使其相对于该节点。

      ERS_ID <- c("ERS445758","ERS445759", "ERS445760", "ERS445761", "ERS445762")
      url <- paste0( "http://www.ebi.ac.uk/ena/data/view/", paste(ERS_ID, collapse=","), "&display=xml")  
      doc <- xmlParse(url)
      samples <- getNodeSet( doc, "//SAMPLE")
      ## check the first node
      samples[[1]]
      ## get the sample attribute node set and apply xmlToDataFrame to that  
      x <- lapply( lapply(samples, getNodeSet,  ".//SAMPLE_ATTRIBUTE"), xmlToDataFrame)
      # labels for bind_rows
      names(x) <- sapply(samples, xpathSApply, ".//PRIMARY_ID", xmlValue)  
      library(dplyr)
      y <- bind_rows(x, .id="sample")
      
      z <- subset(y, TAG %in% c("age","sex","body site","body-mass index") , 1:3)
             sample             TAG         VALUE
      15  ERS445758             age            28
      16  ERS445758             sex          male
      17  ERS445758       body site Sigmoid colon
      19  ERS445758 body-mass index    16.9550173
      50  ERS445759             age            58
      51  ERS445759             sex          male
      ...
      
      library(tidyr)
      z %>% spread( TAG, VALUE)
           sample age     body site body-mass index    sex
      1 ERS445758  28 Sigmoid colon      16.9550173   male
      2 ERS445759  58 Sigmoid colon     23.22543185   male
      3 ERS445760  26 Sigmoid colon     20.76124567 female
      4 ERS445761  30 Sigmoid colon               0   male
      5 ERS445762  36 Sigmoid colon               0   male
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多