首页 > 解决方案 > 将 cellosaurus.xml 文件转换为 R 中的 data.frame

问题描述

我有一个 XML 文件,但我无法获得良好的 data.frame 格式。我很接近,但它还没有完全到那里。

cellosaurus.xml<cell-line-list>通过删除之前和之后的所有内容和</cell-line-list>标记对该文件进行了轻微修改

这是我到目前为止写的乱七八糟的代码:

require(XML)
require(xml2)
require(rvest)
require(dplyr)
require(xmltools)
require(stringi)
require(gtools)
setwd("~/Documents/Cancer_Cell_Lines/Cellosaurus")

file <- "cellosaurus.xml"
cellosaurus <- file %>% xml2::read_xml()
nodeset <- cellosaurus %>% xml_children()

terminal_xpaths <- nodeset[1] %>% xml_get_paths() %>% unlist() %>% unique()
terminal_nodesets <- lapply(terminal_xpaths[1], xml2::xml_find_all, x = cellosaurus)
df_list <- terminal_nodesets %>% purrr::map(xml_dig_df)
df <- lapply(df_list[[1]], function(x) as.data.frame(x))
table <- do.call("smartbind", df) 

问题1:有重复的列名混淆了。例如,在文件中有许多路径最终位于一个名为 cv.term 的节点上,例如

"/cell-line-list/cell-line/disease-list/cv-term" 
"/cell-line-list/cell-line/species-list/cv-term" 
"/cell-line-list/cell-line/derived-from/cv-term" 

但在表中我得到名为cv.term,的列cv.term.1cv.term.2但由于缺少数据,内容混淆了。有没有办法来解决这个问题。

问题 2:文件很大,需要很长时间才能运行(我只能测试完整文件的一小部分),我无法弄清楚如何正确拆分 xml,除了通过拆分成尽可能多的文件,有大约 109,000 个节点。然后我很难将这么多文件合并到我的代码中供 R 阅读。

任何帮助表示赞赏。

标签: rxmldataframe

解决方案


要使用关系数据库术语,请考虑数据规范化。具体来说,只要 XML 中的大多数节点实际上都是一对多的列表,您可以将每个列表提取为单独的长数据帧,并通过唯一的 id(例如cell_line节点编号)合并在一起。

幸运的是,有一个很棒的提取工具,称为XSLT,这是一种特殊用途的声明性语言(与 SQL 相同的类型),旨在将 XML 转换为各种最终使用需求,例如提取可以更简单地解析为数据帧和然后将所有项目合并在一起。XSLT 的美妙之处还在于与 R 无关,并且可以移植到其他应用程序层(Java、PHP、Python)或专用XSLT 处理器

有关最终解决方案的路线图,请参见下面的流程。下面的所有 XSLT 脚本都从每个单元行节点的特定部分进行解析,并将 XML 展平为一个子级别:

R

library(xml2)
library(xslt)    # INSTALL PACKAGE BEFORE HAND
library(dplyr)   # ONLY FOR bind_rows

# PARSE XML AND XSLT
doc <- read_xml('Cellosaurus.xml')
scripts <- list.files(path='/path/to/xslt/scripts', pattern='.xsl')

xpaths <- c('//accession', '//cell-line', '//hla_gene', '//marker', 
            '//name', '//species_list', '//url')

proc_xml_parse <- function(x, s) {
  style <- read_xml(s, package = "xslt")

  # TRANSFORM INPUT INTO OUTPUT
  new_xml <- xslt::xml_xslt(doc, style)

  # INNER DF LIST BUILD
  df_list <- lapply(xml_find_all(new_xml, x), function(x) { 
    vals <- xml_children(x)
    setNames(data.frame(t(xml_text(vals)), stringsAsFactors = FALSE), xml_name(vals))
  })

  bind_rows(df_list)
}

# OUTER DF LIST BUILD    
df_list <- Map(proc_xml_parse, xpaths, scripts)

# CHAIN MERGE
final_df <- Reduce(function(x,y) merge(x, y, by="cell_num", all=TRUE), df_list)

XSLT 脚本

将每个保存为单独的 .xsl 或 .xslt 文件(特殊的 .xml 文件),以便在上面的 R 中加载。通过复制 XML 中其他列表节点的模式来添加更多 XSLT 脚本,如下所示并不能全部捕获。

细胞系列表

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:copy>
            <cell_num>
                <xsl:value-of select="count(preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

加入名单

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:apply-templates select="accession-list"/>
    </xsl:template>

    <xsl:template match="accession-list">
        <xsl:apply-templates select="accession"/>
    </xsl:template>

    <xsl:template match="accession">
        <xsl:copy>
            <cell_num>
                <xsl:value-of select="count(ancestor::cell-line[1]/preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <accession_value><xsl:value-of select="."/></accession_value>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

名单

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:apply-templates select="name-list"/>
    </xsl:template>

    <xsl:template match="name-list">
        <xsl:apply-templates select="name"/>
    </xsl:template>

    <xsl:template match="name">
        <xsl:copy>
            <cell_num>
                <xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <name_value><xsl:value-of select="."/></name_value>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

网页列表

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:apply-templates select="web-page-list"/>
    </xsl:template>

    <xsl:template match="web-page-list">
        <xsl:apply-templates select="url"/>
    </xsl:template>

    <xsl:template match="url">
        <xsl:copy>
            <cell_num>
                <xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <url_value><xsl:value-of select="."/></url_value>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

HLA 列表

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:apply-templates select="hla-lists/hla-list"/>
    </xsl:template>

    <xsl:template match="hla-list">
        <xsl:apply-templates select="hla-gene"/>
    </xsl:template>

    <xsl:template match="hla-gene">
        <hla_gene>
            <cell_num>
                <xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <hla_value><xsl:value-of select="."/></hla_value>
        </hla_gene>
    </xsl:template>

</xsl:stylesheet>

特别清单

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:apply-templates select="species-list/cv-term"/>
    </xsl:template>

    <xsl:template match="cv-term">
        <species_list>
            <cell_num>
                <xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <species_value><xsl:value-of select="."/></species_value>
        </species_list>
    </xsl:template>

</xsl:stylesheet>

标记列表

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="Cellosaurus">
        <xsl:copy>
            <xsl:apply-templates select="cell-line-list/cell-line"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="cell-line">
        <xsl:apply-templates select="str-list"/>
    </xsl:template>

    <xsl:template match="str-list">
        <xsl:apply-templates select="marker-list"/>
    </xsl:template>

    <xsl:template match="marker-list">
        <xsl:apply-templates select="marker"/>
    </xsl:template>

    <xsl:template match="marker">
        <xsl:copy>
            <cell_num>
                <xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
            </cell_num>
            <xsl:for-each select="@*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <xsl:copy-of select="marker-data-list/marker-data/alleles"/>
        </xsl:copy>
    </xsl:template>        
</xsl:stylesheet>

输出

在链合并之后,每个唯一行的值都重复,类似于长数据帧的 SQL 连接(多对多)。请注意:如果您不希望合并输出下方有一个命名的数据框列表:

数据输出


推荐阅读