是否有更快的重写来处理丢失的 XML 属性?
Is there a faster rewrite to deal with missing XML attributes?
我正在处理巨大的 XML 文件,例如75GB 或更多,我的意思是任何小的开销都会变成几分钟甚至数小时的处理速度。
我的代码的核心在解析 XML 块时执行以下操作。假设我有一大块 3 行。请注意,我只关心 a
、b
和 c
属性,但可能存在缺少属性的项目元素,例如
xmlvec <- c('<item a="1" c="2" x="very long whatever" />',
'<item b="3" c="4" x="very long whatever" />',
'<item a="5" b="6" c="7" x="very long whatever" />')
我定义了一个映射,包括要查找的属性以及将它们重命名为什么,就是这样,我想阅读的那些:
mapping <- c("a", "b", "c")
# this doesn't matter here
#names(mapping) <- c("aa", "bb", "cc")
如果我执行以下操作,我会得到缺失值 and/or NA 列名称,因为缺失属性会影响行的绑定,请注意自第一个 item 元素以来缺失的 b
列没有:
df <- as.data.frame(do.call(
rbind,
lapply(xml_children(read_xml(paste("<xml>", paste(xmlvec, collapse=""), "</xml>"))),
function(x) {
xml_attrs(x)[mapping]
}
)
), stringsAsFactors = FALSE)
df
a NA c
1 1 <NA> 2
2 <NA> 3 4
3 5 6 7
由于属性 b
在这个小块的第一行中丢失,所以我得到一个 NA
列,以后我无法将其与任何列名匹配。由于任何块的第一行都是任意的并且可以有任何缺失的属性,我需要在读取 每个属性 时强制执行模式,这样封闭的数据框就不会被破坏,但这非常昂贵的性能方面:
df <- as.data.frame(do.call(
rbind,
lapply(xml_children(read_xml(paste("<xml>", paste(xmlvec, collapse=""), "</xml>"))),
function(x) {
y <- xml_attrs(x)[mapping]
if (any(is.na(names(y)))) {
y <- y[-which(is.na(names(y)))]
}
y[setdiff(mapping, names(y))] <- NA
y[order(factor(names(y), levels=mapping))]
}
)
), stringsAsFactors = FALSE)
df
a b c
1 1 <NA> 2
2 <NA> 3 4
3 5 6 7
看到现在强制执行了列架构和顺序,但由于这是在每行的基础上完成的,因此在性能上付出了非常高的代价。有没有更好的方法?
此解决方案适用于提供的示例。如果您的 XML 嵌套元素更加复杂,您可能需要一个替代方案。
基本思路是将xmlvec
的每一个向量转化为一个data.frame,然后用data.table::rbindlist()
将data.frame绑定在一起。如果您使用 fill = T
和 use.names = T
,rbindlist
也会负责填充缺失的数据
library(XML)
library(data.table)
library(magrittr)
xmlvec <- c('<item a="1" c="2" x="very long whatever" />',
'<item b="3" c="4" x="very long whatever" />',
'<item a="5" b="6" c="7" x="very long whatever" />')
l <- lapply( xmlvec, function(x) {
XML::xmlToList( x ) %>%
t() %>%
as.data.frame()
})
dt <- data.table::rbindlist(l, use.names = T, fill = T)
dt
# a c x b
# 1: 1 2 very long whatever <NA>
# 2: <NA> 4 very long whatever 3
# 3: 5 7 very long whatever 6
## Now you can subset the columns of interest if you so wish
mapping <- c("a","b","c")
dt[, ..mapping]
# a b c
# 1: 1 <NA> 2
# 2: <NA> 3 4
# 3: 5 6 7
如果您更喜欢 tidyverse 方法,我认为 dplyr::bind_rows()
可能会提供与 rbindlist
.
类似的功能
要在绑定前删除 select 列,您可以在 lapply
中删除它们
l <- lapply( xmlvec, function(x) {
XML::xmlToList( x ) %>%
t() %>%
as.data.frame() %>%
dplyr::select( intersect(names(.), mapping ) )
})
dt <- data.table::rbindlist(l, use.names = T, fill = T)
dt
# a c b
# 1: 1 2 <NA>
# 2: <NA> 4 3
# 3: 5 7 6
一种完全不同但可能最有效*的方法是使用 Rcpp
和 rapidxmlr
编写您自己的 XML 解析器。
这将适用于单个 .xml 文档(根据您所有 判断您正在使用的文档)。
所以我们可以把你的小例子做成一个单独的xml文档
xml <- '<?xml version="1.0" encoding="UTF-8"?>
<items>
<item a="1" c="2" x="very long whatever" />
<item b="3" c="4" x="very long whatever" />
<item a="5" b="6" c="7" x="very long whatever" />
</items>'
writeLines(xml, "~/Desktop/xml_test/xml.xml")
然后我们可以使用rapidxmlr库通过Rcpp写一些C++代码。
我也在使用开发库 gpx
因为我想要 this node_size() 功能。 (我也是这个 gpx 库的作者)。
对于这个您知道向量 a
、b
和 c
的简单示例,我们可以简单地在顶部定义它们并在属性存在时填充它们。
library(rapidxmlr)
library(Rcpp)
# devtools::install_github("dcooley/gpx")
library(gpx)
cppFunction(
depends = c("rapidxmlr", "gpx")
, include = c(
'#include "rapidxml.hpp"'
, '#include "gpx/utils.hpp"'
, '#include <fstream>'
)
, code = "
Rcpp::DataFrame df_from_xml( std::string xml_file ) {
// parse document
rapidxml::xml_document<> doc;
std::ifstream theFile( xml_file );
std::vector< char > buffer(
(std::istreambuf_iterator<char>(theFile)),
std::istreambuf_iterator<char>()
);
buffer.push_back('\0');
doc.parse<0>( &buffer[0] );
// get root node
rapidxml::xml_node<> *root_node = doc.first_node(\"items\");
size_t n = gpx::utils::node_size( root_node, \"item\");
// initalise vectors to store the results
Rcpp::StringVector a( n, Rcpp::StringVector::get_na());
Rcpp::StringVector b( n, Rcpp::StringVector::get_na());
Rcpp::StringVector c( n, Rcpp::StringVector::get_na());
R_xlen_t counter = 0;
for(
rapidxml::xml_node<> *item_node = root_node->first_node(\"item\");
item_node;
item_node = item_node -> next_sibling()
) {
// get attributes of the node
if( item_node -> first_attribute(\"a\") ) {
a[ counter ] = item_node -> first_attribute(\"a\") -> value();
}
if( item_node -> first_attribute(\"b\") ) {
b[ counter ] = item_node -> first_attribute(\"b\") -> value();
}
if( item_node -> first_attribute(\"c\") ) {
c[ counter ] = item_node -> first_attribute(\"c\") -> value();
}
counter++;
}
return Rcpp::DataFrame::create(
_[\"a\"] = a,
_[\"b\"] = b,
_[\"c\"] = c
);
}
"
)
f <- normalizePath("~/Desktop/xml_test/xml.xml")
df_from_xml( f )
# a b c
# 1 1 <NA> 2
# 2 <NA> 3 4
# 3 5 6 7
*代码速度方面的效率,而不是学习如何编写 Rcpp / C++ 代码。
我正在处理巨大的 XML 文件,例如75GB 或更多,我的意思是任何小的开销都会变成几分钟甚至数小时的处理速度。
我的代码的核心在解析 XML 块时执行以下操作。假设我有一大块 3 行。请注意,我只关心 a
、b
和 c
属性,但可能存在缺少属性的项目元素,例如
xmlvec <- c('<item a="1" c="2" x="very long whatever" />',
'<item b="3" c="4" x="very long whatever" />',
'<item a="5" b="6" c="7" x="very long whatever" />')
我定义了一个映射,包括要查找的属性以及将它们重命名为什么,就是这样,我想阅读的那些:
mapping <- c("a", "b", "c")
# this doesn't matter here
#names(mapping) <- c("aa", "bb", "cc")
如果我执行以下操作,我会得到缺失值 and/or NA 列名称,因为缺失属性会影响行的绑定,请注意自第一个 item 元素以来缺失的 b
列没有:
df <- as.data.frame(do.call(
rbind,
lapply(xml_children(read_xml(paste("<xml>", paste(xmlvec, collapse=""), "</xml>"))),
function(x) {
xml_attrs(x)[mapping]
}
)
), stringsAsFactors = FALSE)
df
a NA c
1 1 <NA> 2
2 <NA> 3 4
3 5 6 7
由于属性 b
在这个小块的第一行中丢失,所以我得到一个 NA
列,以后我无法将其与任何列名匹配。由于任何块的第一行都是任意的并且可以有任何缺失的属性,我需要在读取 每个属性 时强制执行模式,这样封闭的数据框就不会被破坏,但这非常昂贵的性能方面:
df <- as.data.frame(do.call(
rbind,
lapply(xml_children(read_xml(paste("<xml>", paste(xmlvec, collapse=""), "</xml>"))),
function(x) {
y <- xml_attrs(x)[mapping]
if (any(is.na(names(y)))) {
y <- y[-which(is.na(names(y)))]
}
y[setdiff(mapping, names(y))] <- NA
y[order(factor(names(y), levels=mapping))]
}
)
), stringsAsFactors = FALSE)
df
a b c
1 1 <NA> 2
2 <NA> 3 4
3 5 6 7
看到现在强制执行了列架构和顺序,但由于这是在每行的基础上完成的,因此在性能上付出了非常高的代价。有没有更好的方法?
此解决方案适用于提供的示例。如果您的 XML 嵌套元素更加复杂,您可能需要一个替代方案。
基本思路是将xmlvec
的每一个向量转化为一个data.frame,然后用data.table::rbindlist()
将data.frame绑定在一起。如果您使用 fill = T
和 use.names = T
rbindlist
也会负责填充缺失的数据
library(XML)
library(data.table)
library(magrittr)
xmlvec <- c('<item a="1" c="2" x="very long whatever" />',
'<item b="3" c="4" x="very long whatever" />',
'<item a="5" b="6" c="7" x="very long whatever" />')
l <- lapply( xmlvec, function(x) {
XML::xmlToList( x ) %>%
t() %>%
as.data.frame()
})
dt <- data.table::rbindlist(l, use.names = T, fill = T)
dt
# a c x b
# 1: 1 2 very long whatever <NA>
# 2: <NA> 4 very long whatever 3
# 3: 5 7 very long whatever 6
## Now you can subset the columns of interest if you so wish
mapping <- c("a","b","c")
dt[, ..mapping]
# a b c
# 1: 1 <NA> 2
# 2: <NA> 3 4
# 3: 5 6 7
如果您更喜欢 tidyverse 方法,我认为 dplyr::bind_rows()
可能会提供与 rbindlist
.
要在绑定前删除 select 列,您可以在 lapply
l <- lapply( xmlvec, function(x) {
XML::xmlToList( x ) %>%
t() %>%
as.data.frame() %>%
dplyr::select( intersect(names(.), mapping ) )
})
dt <- data.table::rbindlist(l, use.names = T, fill = T)
dt
# a c b
# 1: 1 2 <NA>
# 2: <NA> 4 3
# 3: 5 7 6
一种完全不同但可能最有效*的方法是使用 Rcpp
和 rapidxmlr
编写您自己的 XML 解析器。
这将适用于单个 .xml 文档(根据您所有
所以我们可以把你的小例子做成一个单独的xml文档
xml <- '<?xml version="1.0" encoding="UTF-8"?>
<items>
<item a="1" c="2" x="very long whatever" />
<item b="3" c="4" x="very long whatever" />
<item a="5" b="6" c="7" x="very long whatever" />
</items>'
writeLines(xml, "~/Desktop/xml_test/xml.xml")
然后我们可以使用rapidxmlr库通过Rcpp写一些C++代码。
我也在使用开发库 gpx
因为我想要 this node_size() 功能。 (我也是这个 gpx 库的作者)。
对于这个您知道向量 a
、b
和 c
的简单示例,我们可以简单地在顶部定义它们并在属性存在时填充它们。
library(rapidxmlr)
library(Rcpp)
# devtools::install_github("dcooley/gpx")
library(gpx)
cppFunction(
depends = c("rapidxmlr", "gpx")
, include = c(
'#include "rapidxml.hpp"'
, '#include "gpx/utils.hpp"'
, '#include <fstream>'
)
, code = "
Rcpp::DataFrame df_from_xml( std::string xml_file ) {
// parse document
rapidxml::xml_document<> doc;
std::ifstream theFile( xml_file );
std::vector< char > buffer(
(std::istreambuf_iterator<char>(theFile)),
std::istreambuf_iterator<char>()
);
buffer.push_back('\0');
doc.parse<0>( &buffer[0] );
// get root node
rapidxml::xml_node<> *root_node = doc.first_node(\"items\");
size_t n = gpx::utils::node_size( root_node, \"item\");
// initalise vectors to store the results
Rcpp::StringVector a( n, Rcpp::StringVector::get_na());
Rcpp::StringVector b( n, Rcpp::StringVector::get_na());
Rcpp::StringVector c( n, Rcpp::StringVector::get_na());
R_xlen_t counter = 0;
for(
rapidxml::xml_node<> *item_node = root_node->first_node(\"item\");
item_node;
item_node = item_node -> next_sibling()
) {
// get attributes of the node
if( item_node -> first_attribute(\"a\") ) {
a[ counter ] = item_node -> first_attribute(\"a\") -> value();
}
if( item_node -> first_attribute(\"b\") ) {
b[ counter ] = item_node -> first_attribute(\"b\") -> value();
}
if( item_node -> first_attribute(\"c\") ) {
c[ counter ] = item_node -> first_attribute(\"c\") -> value();
}
counter++;
}
return Rcpp::DataFrame::create(
_[\"a\"] = a,
_[\"b\"] = b,
_[\"c\"] = c
);
}
"
)
f <- normalizePath("~/Desktop/xml_test/xml.xml")
df_from_xml( f )
# a b c
# 1 1 <NA> 2
# 2 <NA> 3 4
# 3 5 6 7
*代码速度方面的效率,而不是学习如何编写 Rcpp / C++ 代码。