我无法将文件加载到RAM中(假设用户可能需要具有100亿条记录的第一个十亿个文件)
这是我的解决方案,但我认为必须有一个更快的方法?
谢谢
# specified by the user
infile <- "/some/big/file.txt"
outfile <- "/some/smaller/file.txt"
num_lines <- 1000
# my attempt
incon <- file( infile , "r")
outcon <- file( outfile , "w")
for ( i in seq( num_lines ) ){
line <- readLines( incon , 1 )
writeLines( line , outcon )
}
close( incon )
close( outcon )
Run Code Online (Sandbox Code Playgroud)
你可以用ff::read.table.ffdf它.它将数据存储在硬盘上,不使用任何RAM.
library(ff)
infile <- read.table.ffdf(file = "/some/big/file.txt")
Run Code Online (Sandbox Code Playgroud)
基本上,您可以使用上述功能,方法base::read.table与产生的对象将存储在硬盘上的区别相同.
您还可以使用nrow参数并加载特定的行数.如果您想阅读,文档就在这里.一旦,您已经读取了该文件,那么您可以对所需的特定行进行分组,甚至可以将它们转换为data.frames适合RAM的行.
还有一个write.table.ffdf函数允许您编写一个ffdf对象(由此产生read.table.ffdf),这将使该过程更容易.
作为如何使用read.table.ffdf(或read.delim.ffdf几乎相同)的示例,请参阅以下内容:
#writting a file on my current directory
#note that there is no standard number of columns
sink(file='test.txt')
cat('foo , foo, foo\n')
cat('foo, foo\n')
cat('bar bar , bar\n')
sink()
#read it with read.delim.ffdf or read.table.ffdf
read.delim.ffdf(file='test.txt', sep='\n', header=F)
Run Code Online (Sandbox Code Playgroud)
输出:
ffdf (all open) dim=c(3,1), dimorder=c(1,2) row.names=NULL
ffdf virtual mapping
PhysicalName VirtualVmode PhysicalVmode AsIs VirtualIsMatrix PhysicalIsMatrix PhysicalElementNo PhysicalFirstCol PhysicalLastCol PhysicalIsOpen
V1 V1 integer integer FALSE FALSE FALSE 1 1 1 TRUE
ffdf data
V1
1 foo , foo, foo
2 foo, foo
3 bar bar , bar
Run Code Online (Sandbox Code Playgroud)
如果您使用的是txt文件,那么这是一个通用解决方案,因为每一行都将以\n字符结束.
我喜欢管道,因为我们可以使用其他工具.方便的是,R中的(真正优秀的)连接接口支持它:
## scratch file
filename <- "foo.txt"
## create a file, no header or rownames for simplicity
write.table(1:50, file=filename, col.names=FALSE, row.names=FALSE)
## sed command: print from first address to second, here 4 to 7
## the -n suppresses output unless selected
cmd <- paste0("sed -n -e '4,7p' ", filename)
##print(cmd) # to debug if needed
## we use the cmd inside pipe() as if it was file access so
## all other options to read.csv (or read.table) are available too
val <- read.csv(pipe(cmd), header=FALSE, col.names="selectedRows")
print(val, row.names=FALSE)
## clean up
unlink(filename)
Run Code Online (Sandbox Code Playgroud)
如果我们运行它,我们按预期得到第四到第七行:
edd@max:/tmp$ r piper.R
selectedRows
4
5
6
7
edd@max:/tmp$
Run Code Online (Sandbox Code Playgroud)
请注意,我们的使用sed除了假设之外没有对文件结构做任何假设
如果您假设具有不同记录分隔符的二进制文件,我们可以建议不同的解
另请注意,您可以控制传递给pipe()函数的命令.因此,如果您想要行1000004到1000007,则使用情况完全相同:您只需给出第一行和最后一行(每个段,可以有几个).而不是read.csv()你readLines()可以同样好用.
最后,sed随处可用,如果内存服务,也是Rtools的一部分.也可以使用Perl或许多其他工具获得基本过滤功能.
C++解决方案
为此编写一些c ++代码并不困难:
#include <fstream>
#include <R.h>
#include <Rdefines.h>
extern "C" {
// [[Rcpp::export]]
SEXP dump_n_lines(SEXP rin, SEXP rout, SEXP rn) {
// no checks on types and size
std::ifstream strin(CHAR(STRING_ELT(rin, 0)));
std::ofstream strout(CHAR(STRING_ELT(rout, 0)));
int N = INTEGER(rn)[0];
int n = 0;
while (strin && n < N) {
char c = strin.get();
if (c == '\n') ++n;
strout.put(c);
}
strin.close();
strout.close();
return R_NilValue;
}
}
Run Code Online (Sandbox Code Playgroud)
保存为yourfile.cpp,您可以这样做
Rcpp::sourceCpp('yourfile.cpp')
Run Code Online (Sandbox Code Playgroud)
从RStudio你不必加载任何东西.在控制台中,您将不得不加载Rcpp.您可能必须在Windows中安装Rtools.
更高效的R代码
通过读取更大的块而不是单行,您的代码也将加速:
dump_n_lines2 <- function(infile, outfile, num_lines, block_size = 1E6) {
incon <- file( infile , "r")
outcon <- file( outfile , "w")
remain <- num_lines
while (remain > 0) {
size <- min(remain, block_size)
lines <- readLines(incon , n = size)
writeLines(lines , outcon)
# check for eof:
if (length(lines) < size) break
remain <- remain - size
}
close( incon )
close( outcon )
}
Run Code Online (Sandbox Code Playgroud)
基准
lines <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo
imperdiet nunc, vel ultricies felis tincidunt sit amet. Aliquam id nulla eu mi
luctus vestibulum ac at leo. Integer ultrices, mi sit amet laoreet dignissim,
orci ligula laoreet diam, id elementum lorem enim in metus. Quisque orci neque,
vulputate ultrices ornare ac, interdum nec nunc. Suspendisse iaculis varius
dapibus. Donec eget placerat est, ac iaculis ipsum. Pellentesque rhoncus
maximus ipsum in hendrerit. Donec finibus posuere libero, vitae semper neque
faucibus at. Proin sagittis lacus ut augue sagittis pulvinar. Nulla fermentum
interdum orci, sed imperdiet nibh. Aliquam tincidunt turpis sit amet elementum
porttitor. Aliquam lectus dui, dapibus ut consectetur id, mollis quis magna.
Donec dapibus ac magna id bibendum."
lines <- rep(lines, 1E6)
writeLines(lines, con = "big.txt")
infile <- "big.txt"
outfile <- "small.txt"
num_lines <- 1E6L
library(microbenchmark)
microbenchmark(
solution0(infile, outfile, num_lines),
dump_n_lines2(infile, outfile, num_lines),
dump_n_lines(infile, outfile, num_lines)
)
Run Code Online (Sandbox Code Playgroud)
结果(解决方案0是OP的原始解决方案):
Unit: seconds
expr min lq mean median uq max neval cld
solution0(infile, outfile, num_lines) 11.523184 12.394079 12.635808 12.600581 12.904857 13.792251 100 c
dump_n_lines2(infile, outfile, num_lines) 6.745558 7.666935 7.926873 7.849393 8.297805 9.178277 100 b
dump_n_lines(infile, outfile, num_lines) 1.852281 2.411066 2.776543 2.844098 2.965970 4.081520 100 a
Run Code Online (Sandbox Code Playgroud)
可以通过一次读取大块数据来加速c ++解决方案.但是,这将使代码更加复杂.除非这是我必须定期做的事情,否则我可能会坚持使用纯R解决方案.
备注:当您的数据是表格式时,您可以使用我的LaF包从数据集中读取任意行和列,而无需将所有数据读入内存.