从 R 中的大型 XML 文件中提取数据的最有效方法

问题描述

我有一些大的(约 10 GB 并且每周都在增长),我需要将它们从 XML 转换为 R 中的数据帧以进行分析。 XML 的结构如下(多条记录,每条记录多几个字段元素):

      Year Month Group SubGroup V1 V2
    1 2020   Jan     A        a 20  1
    2 2020   Feb     A        a 70  1
    3 2020   Jan     A        b 20  2
    4 2020   Feb     A        b 20  2
    5 2020   Jan     B        a  0  0
    6 2020   Feb     B        a 10  1
    7 2020   Jan     B        b 20  2
    8 2020   Feb     B        b 80  8

我一直在努力寻找最有效的方法提取数据并将它们转换为 data.frame,但是一个主要的挑战是文件非常大,而且 XML 和 XML2 都遇到了问题,需要几个小时处理。我目前的策略是使用 <recordGroup> <records> <record> <recordId>123442</recordId> <reportingCountry>PT</reportingCountry> <date>2020-02-20</date> <field> <fieldName>Gender</fieldName> <fieldValue>F</fieldValue> </field> <field> <fieldName>Age</fieldName> <fieldValue>57</fieldValue> </field> <field> <fieldName>ClinicalSymptoms</fieldName> <fieldValue>COUGH</fieldValue> <fieldValue>FEVER</fieldValue> <fieldValue>O</fieldValue> <fieldValue>RUNOS</fieldValue> <fieldValue>SBREATH</fieldValue> </field> </record> </records> </recordGroup> 使用下面的代码,但这似乎效率更低。

xmlEventParse

我已经尝试过 XML2(内存问题)、XML(内存问题以及标准 DOM 解析),并且还打算尝试使用 XMLSchema,但没有设法让它工作。如果文件被拆分,XML 和 XML2 都可以工作。

希望能得到有关提高效率的任何指导,因为我正在处理的文件每周都在变大。我在 Linux 机器上使用 R。

解决方法

当内存是一个挑战时,请考虑硬盘。具体来说,考虑在 write.csv 运行中通过 xmlEventParse 使用迭代追加调用构建提取的已解析 XML 数据的大型 CSV 版本:

# INITIALIZE EMPTY CSV WITH EMPTY ROW
csv <- file.path("C:","Path","To","Large.csv")
fileConn <- file(csv); writeLines(paste0("id,tag,text"),fileConn); close(fileConn)

i <- 0
doc <- file.path("C:","Large.xml")
output <- xmlEventParse(doc,list(startElement=function(name,attrs){
                          if(name == "recordId") {i <<- i + 1}
                          tagName <<- name
                        },text=function(x) {
                          if(nchar(trimws(x)) > 0) {
                            write.table(data.frame(id=i,tag=tagName,text=x),file=csv,append=TRUE,sep=",",row.names=FALSE,col.names=FALSE)
                          }
                        }),useTagName=FALSE,addContext=FALSE)

输出

显然,正确的行/列迁移需要进一步的数据整理。但您现在可以使用许多工具或通过块读取大型 CSV。

id,text
1,"recordId","123442"
1,"reportingCountry","PT"
1,"date","2020-02-20"
1,"fieldName","Gender"
1,"fieldValue","F"
1,"Age"
1,"57"
1,"ClinicalSymptoms"
1,"COUGH"
1,"FEVER"
1,"O"
1,"RUNOS"
1,"SBREATH"
,

最后,我发现最快的方法如下:

  1. 使用 XML2 将 XML 文件拆分成更小的块。我正在处理的服务器上有超过 100GB 的 RAM,因此可以使用 foreach 与 6 个工作人员并行执行此过程,但里程数因可用 RAM 的数量而异。
  2. 拆分文件的函数会返回一个包含拆分文件位置的 data.frame。
  3. foreach 循环中处理较小的 XML 文件 - 这次可以使用所有内核,因此我使用了 12 个工人。处理使用 XML2,因为我发现这是最快的方式。最初提取的数据是长格式,但我随后在循环中转换为宽格式。
  4. 循环将不同的数据帧绑定并输出到一个大数据帧中。最后一步是使用 fwrite 保存 csv 文件。这似乎是最有效的方式。

通过这种方法,我可以在 6.5 分钟内处理一个 2.6GB 的 XML 文件。

我最终会添加代码,但它非常具体,所以需要概括一下。