1

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

为了清楚起见,额外说明:我的意思是mmap,运行时间应该与提取的内存量大致成正比,但总数据集的大小是恒定的。“跳过数据”应该基本上为零成本。这可能非常容易,或不可能,或介于两者之间,取决于序列化格式。)

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

我是否正确地假设使用压缩文件这是不可能的,仅仅是因为 gzip 需要从一开始就解压缩所有内容?

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

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

我希望它已经完成,或者有另一种格式(合理的空间效率)允许这样做并且在 R 中得到很好的支持。

我使用过gdbm(来自 Python)之类的东西,甚至在 Rcpp 中为特定的数据结构实现了一个自定义系统,但我对这些都不满意。

发布后,我对这个包ffCRANcharacter )做了一些工作,对它印象深刻(虽然对向量的支持不多)。

4

1 回答 1

3

我是否正确地假设使用压缩文件这是不可能的,仅仅是因为 gzip 需要从一开始就解压缩所有内容?

事实上,为了简短的解释,让我们以一些虚拟方法作为起点:

AAAAVVBABBBCgzip 会做类似的事情: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 种格式来保存对象)。

这里有一些参考

与 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
于 2016-07-15T15:51:04.953 回答