【问题标题】:Merge many XML files into one data frame in R在R中将许多XML文件合并到一个数据框中
【发布时间】:2021-03-07 01:47:18
【问题描述】:

我有许多 XML 文件(大约 100,000 个),它们都如下所示。每个文件有大约 100 个点节点。为了说明,我只展示了其中的五个。

<?xml version="1.0" encoding="UTF-8"?>
-<car id="id1">
<point time="1272686841" lon="-122.40648" lat="37.79778" status="E" unit="id1"/>
<point time="1272686781" lon="-122.40544" lat="37.79714" status="M" unit="id1"/>
<point time="1272686722" lon="-122.40714" lat="37.79774" status="M" unit="id1"/>
<point time="1272686661" lon="-122.40704" lat="37.7976" status="M" unit="id1"/>
<point time="1272686619" lon="-122.40616" lat="37.79698" status="E" unit="id1"/>
</car>

我想将所有这些 XML 文件合并到 R 中的一个大数据框(大约 100,000x100=10,000,000 行),其中包含五列(时间、经度、纬度、单位、状态)。所有文件都有相同的五个变量,但它们的顺序可能不同。

以下是我的代码。我首先创建了五个向量来保存这五个变量。然后我去每个文件,一个一个地读取条目。

setwd("C:\\Users\\MyName\\Desktop\\XMLTest")
all.files <- list.files()
n <- 2000000
all.lon <- rep(NA, n)
all.lat <- rep(NA, n)
all.time <- rep(NA, n)
all.status <- rep(NA, n)
all.unit <- rep(NA, n)
i <- 1
for (cur.file in all.files) {
  if (tolower(file_ext(cur.file)) == "xml") {
    xmlfile <- xmlTreeParse(cur.file)
    xmltop <- xmlRoot(xmlfile)
    for (j in 1:length(xmltop)) {
      cur.node <- xmltop[[j]]
      cur.lon <- as.numeric(xmlGetAttr(cur.node, "lon"))
      cur.lat <- as.numeric(xmlGetAttr(cur.node, "lat"))
      cur.time <- as.numeric(xmlGetAttr(cur.node, "time"))
      cur.unit <- xmlGetAttr(cur.node, "unit")
      cur.status <- xmlGetAttr(cur.node, "status")
      all.lon[i] <- cur.lon
      all.lat[i] <- cur.lat
      all.time[i] <- cur.time
      all.status[i] <- cur.status
      all.unit[i] <- cur.unit
      i <- i + 1
    }
  }
}

我是 XML 新手,所以这是我现在能做的最好的事情。问题是它非常慢。一个原因是文件太多。另一个原因是 for 循环 for (j in 1:length(xmltop)) 来读取条目。我试过xmlToDataFrame,但它不起作用。

> xmlToDataFrame(cur.file)
Error in matrix(vals, length(nfields), byrow = TRUE) : 
  'data' must be of a vector type, was 'NULL'

有什么方法可以加快这个过程吗?

【问题讨论】:

  • 如果你所有的 XML 文件看起来都一样,你可以试试readLines,我认为它非常快,但会返回原始字符串。然后,您可以使用 lapply 的函数从字符串中提取相关值。
  • 使用 xml2 包,只需几条语句就可以处理整个文件。请参阅 xml2 包中的 xml_nodes 和 xml_attr 函数。这将消除内部循环以提高性能。
  • @Dave2e 我会看看xml2 包。谢谢。

标签: r xml


【解决方案1】:

考虑一个lapply() 解决方案,它可以加快文件迭代。而且由于所有数据都驻留在属性中,因此您可以在一次调用中使用 XML 的xPathSApply()

library(XML)

setwd("C:\\Users\\MyName\\Desktop\\XMLTest")
all.files <- list.files(pattern="\\.xml", path=getwd(), full.names=TRUE)

dfList <- lapply(all.files, function(x){
  xml <- xmlParse(x)
  pointAttribs <- xpathSApply(doc=xml, path="//point",  xmlAttrs)
  # TRANSPOSE XPATH LIST TO DF 
  df <- data.frame(t(pointAttribs))
  # CONVERT TO NUMERIC
  df[c('time', 'lon', 'lat')] <- sapply(df[c('time', 'lon', 'lat')], 
                                        function(x) as.numeric(as.character(x)))
  return(df)
})

df <- do.call(rbind, dfList)
df
#          time       lon      lat status unit
# 1  1272686841 -122.4065 37.79778      E  id1
# 2  1272686781 -122.4054 37.79714      M  id1
# 3  1272686722 -122.4071 37.79774      M  id1
# 4  1272686661 -122.4070 37.79760      M  id1
# 5  1272686619 -122.4062 37.79698      E  id1
...

【讨论】:

    【解决方案2】:

    这是一个使用 xml2 包的解决方案。我构建了一个函数,它接受一个文件名,然后解析你上面提到的 5 个属性。

    cmets 应该阐明脚本的工作原理。

    library(tools)
    library(xml2)
    #page<-read_xml('<?xml version="1.0" encoding="UTF-8"?>
    #               <car id="id1">
    #               <point time="1272686841" lon="-122.40648" lat="37.79778" status="E" unit="id1"/>
    #               <point time="1272686781" lon="-122.40544" lat="37.79714" status="M" unit="id1"/>
    #               <point time="1272686722" lon="-122.40714" lat="37.79774" status="M" unit="id1"/>
    #               <point time="1272686661" lon="-122.40704" lat="37.7976" status="M" unit="id1"/>
    #               <point lon="-122.40616" time="1272686619"  lat="37.79698" status="E" unit="id1"/>
    #               </car>')
    
    readfile<-function(nextfile) {
      #read files and extract the desired nodes
      page<-read_xml(nextfile)
      nodes<-xml_find_all(page, "point")
      #results<-xml_attrs(nodes)  #test list if all attrs are in the same order
      
      time<-xml_attr(nodes, "time")
      lon<-xml_attr(nodes, "lon")
      lat<-xml_attr(nodes, "lat")
      status<-xml_attr(nodes, "status")
      unit<-xml_attr(nodes, "unit")
      df<-data.frame(time, lon, lat, status, unit)
    }
    
    #get list of files and filter out xml files
    all.files <- list.files()
    all.files<- all.files[tolower(file_ext(all.files)) == "xml"]
    #call function, returns list of data frames then merge together
    results<-lapply(all.files, readfile)
    do.call(rbind, results)
    

    由于属性的顺序可能不一样,因此每个属性都是通过调用 xml_attr 来检索的。如果顺序一致,那么使用 xml_attrs 函数是一步到位的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-18
      • 2023-04-08
      • 1970-01-01
      • 2015-03-30
      • 1970-01-01
      • 2019-06-02
      • 2017-07-11
      相关资源
      最近更新 更多