2016-07-15 73 views
1

通过保存大的data.frame(或data.table)创建的大文件(1GB),可以非常快速地加载行的一小部分那个文件?快速加载从`saveRDS()`保存的data.frame中的行的子集`

额外的清晰度:我的意思是什么一样快,mmap,即运行时应该是大约成正比提取内存量,但不断在总数据集的大小“跳过数据”应该有本质零成本,根据序列化格式,这可能非常容易或不可能,或者介于两者之间。)

我希望R serialization格式可以很容易地跳过文件到文件的相关部分。

我是正确的假设,这将是不可能的压缩文件,仅仅是因为需要的gzip解压缩一切从头开始?

saveRDS(object, file = "", ascii = FALSE, version = NULL, 
     compress = TRUE, refhook = NULL) 

但我希望二进制(ascii=F)压缩(compress=F)可能会允许这样的事情。在文件上使用mmap,然后快速跳到感兴趣的行和列?

我希望它已经完成,或者存在另一种格式(相当节省空间),允许这和R.

我用的东西像gdbm很好的支持(在Python)甚至在Rcpp中为特定的数据结构实现了一个自定义系统,但是我对这些都不满意。

在发布之后,我使用了包ffCRAN),并且对它印象非常深刻(尽管对character矢量支持不多)。

回答

3

我说得对不对的假设,这将是不可能的压缩文件 ,只是因为需要的gzip解压缩一切从 开始?

事实上,对于一个简短的说明,让我们一些虚拟的方法为出发点:

AAAAVVBABBBC的gzip会做这样的事情:4A2VBA3BC

显然,你不能提取该文件中的所有A不读书它是否有一个A在年底或不是所有的,因为你不能猜测。

对于其他问题“已保存的文件的加载部分”我不能看到我的头顶上有一个解决方案。您可以使用write.csvread.csv(或fwritefread来自data.table包),其中skipnrows参数可以是替代参数。

通过一切手段,使用上已经阅读将意味着加载在内存中的整个文件过滤,这是不超过读取文件,然后从内存子集化更多的时间之前文件中的任何功能。

您可以在Rcpp中创建一些内容,利用流读取数据而不将它们加载到内存中,但是在决定它是否应该保留之前阅读和解析每个条目将不会提供真正的更好的吞吐量。

saveDRS将保存DATAS的序列化版本,例如:

> myvector <- c("1","2","3"). 
> serialize(myvector,NULL) 
[1] 58 0a 00 00 00 02 00 03 02 03 00 02 03 00 00 00 00 10 00 00 00 03 00 04 00 09 00 00 00 01 31 00 04 00 09 00 00 00 01 32 00 04 00 09 00 00 
[47] 00 01 33 

这当然是可解析的,但根据格式每字节意味着读取字节。

在另一方面,你可以为CSV(或write.table用于更复杂的数据)写入和沿线阅读的东西之前使用外部工具:

z <- tempfile() 
write.table(df, z, row.names = FALSE) 
shortdf <- read.table(text= system(command = paste0("awk 'NR > 5 && NR < 10 { print }'" ,z))) 

你需要用linux系统它能够在几毫秒内解析数百万行,或者显然使用windows编译版本

主要优点是能够在正则表达式或某些其他条件下过滤每行数据。

补用于data.frame的情况下,data.frame或多或少一个载体(简单的情况),该列表将依次所以如果我们有一个数据帧等被保存的列表:

> str(ex) 
'data.frame': 3 obs. of 2 variables: 
$ a: chr "one" "five" "Whatever" 
$ b: num 1 2 3 

它的序列是:

> serialize(ex,NULL) 
    [1] 58 0a 00 00 00 02 00 03 02 03 00 02 03 00 00 00 03 13 00 00 00 02 00 00 00 10 00 00 00 03 00 04 00 09 00 00 00 03 6f 6e 65 00 04 00 09 00 
[47] 00 00 04 66 69 76 65 00 04 00 09 00 00 00 08 57 68 61 74 65 76 65 72 00 00 00 0e 00 00 00 03 3f f0 00 00 00 00 00 00 40 00 00 00 00 00 00 
[93] 00 40 08 00 00 00 00 00 00 00 00 04 02 00 00 00 01 00 04 00 09 00 00 00 05 6e 61 6d 65 73 00 00 00 10 00 00 00 02 00 04 00 09 00 00 00 01 
[139] 61 00 04 00 09 00 00 00 01 62 00 00 04 02 00 00 00 01 00 04 00 09 00 00 00 09 72 6f 77 2e 6e 61 6d 65 73 00 00 00 0d 00 00 00 02 80 00 00 
[185] 00 ff ff ff fd 00 00 04 02 00 00 00 01 00 04 00 09 00 00 00 05 63 6c 61 73 73 00 00 00 10 00 00 00 01 00 04 00 09 00 00 00 0a 64 61 74 61 
[231] 2e 66 72 61 6d 65 00 00 00 fe 

翻译为ASCII一个想法:

X 
    one five Whatever?ð@@ names a b  row.names 
ÿÿÿý class 
data.frameþ 

我们有文件头,列表头,然后每个矢量组成列表,因为我们不知道字符向量需要多少大小,我们不能跳到任意数据,我们必须解析每个头(字节就在文本数据给出它的长度之前)。现在更糟糕的是,为了获得相应的整数,我们必须去整数向量头,如果不分析每个字符头并将它们相加,则无法确定整数向量头。

所以在我看来,制作一些东西是可能的,但可能不会比读取所有对象更快,并且会对保存格式变得脆弱(因为R已经有3种格式来保存对象)。

Some reference here

相同的视图,ASCII格式的序列化输出(更具可读性得到它是如何组织):

> write(rawToChar(serialize(ex,NULL,ascii=TRUE)),"") 
A 
2 
197123 
131840 
787 
2 
16 
3 
262153 
3 
one 
262153 
4 
five 
262153 
8 
Whatever 
14 
3 
1 
2 
3 
1026 
1 
262153 
5 
names 
16 
2 
262153 
1 
a 
262153 
1 
b 
1026 
1 
262153 
9 
row.names 
13 
2 
NA 
-3 
1026 
1 
262153 
5 
class 
16 
1 
262153 
10 
data.frame 
254 
+0

*“,但阅读并决定是否应保持之前解析每个条目否则不会给你一个真正的更好的吞吐量。“*没有必要读一切。 'fseek'可以在一段时间内跳过任意大小的数据。真正的问题是格式是否让我们知道我们希望忽略的子数据结构的确切大小(在磁盘上)。 –

+0

@AaronMcDaid不是,格式是连续的。读取data.frame或多或少读取一个列表,代码是[here](https://github.com/wch/r-source/blob/73e11b7c40d3630604855e8eee3d1f309e2c9a57/src/main/serialize.c#L1611-L1645)如果你想知道我的意思。简而言之,我的意思是说你不能真的'跳过'N行,因为你必须为每一行做多个fseeks。我会在答案中加入一些细节。 – Tensibai

+1

感谢您的澄清。我会再次读你的答案。实际上,我刚写完自己的代码来解决这个问题,将data.frame的列存储在一系列'bigmemory'对象中[(CRAN上的bigmemory)](https://cran.r-project.org /web/packages/bigmemory/index.html)。这允许任意查找任何行。我必须注意以特殊方式存储'人物'矢量,但现在它正在工作。 –