我有很多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'
有没有办法加快这个过程?
答案 0 :(得分:3)
考虑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
...
答案 1 :(得分:2)
这是一个应该使用xml2包的解决方案。我构建了一个函数,它接受一个文件名,然后处理你上面提到的5个属性。 评论应该澄清脚本的运作。
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函数是一步解决方案。