从使用`saveRDS()`

时间:2016-07-15 15:20:06

标签: r rdata

使用保存大data.frame(或data.table)创建的大文件(1GB)是否可以非常快速地从该文件加载一小部分行?

Extra for clear :我指的是与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向量的支持不多)。

1 个答案:

答案 0 :(得分:3)

  

我是否正确地假设压缩无法做到这一点   文件,只是因为gzip需要从中解压缩所有内容   开始?

事实上,对于一个简短的解释,让我们采用一些虚拟方法作为起点:

AAAAVVBABBBC gzip会执行以下操作:4A2VBA3BC

显然,如果没有全部阅读,你就无法从文件中提取所有A,因为你无法猜到最后是否有A

对于另一个问题“加载已保存文件的一部分”,我无法在头脑中看到解决方案。您可以使用write.csvread.csv参数fwritefread(或来自data.table包的skipnrows)可能是另一种选择。

无论如何,在已经读取的文件上使用任何函数都意味着在过滤之前将整个文件加载到内存中,这不再是读取文件然后从内存中进行子集化的时间。

你可以在Rcpp中制作一些东西,利用流来读取数据而不将它们加载到内存中,但是在决定是否应该保存之前读取和解析每个条目都不会给你带来真正更好的吞吐量。

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

> 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