快速加载使用 `saveRDS()` 保存的 data.frame 行的子集
quickly load a subset of rows from data.frame saved with `saveRDS()`
对于通过保存大 data.frame
(或 data.table
)创建的大文件 (1GB),是否可以非常快速地从该文件加载一小部分行?
(为了清楚起见,额外说明:我的意思是和 mmap
一样快,即运行时间应该与提取的内存量大致成正比,但在总数据集的大小。"Skipping data" 基本上应该具有零成本。这可能非常简单,也可能不可能,或者介于两者之间,具体取决于序列化格式。)
我希望 R serialization 格式可以轻松地向前跳到文件的相关部分。
仅仅因为 gzip 需要从头开始解压缩所有内容,我假设压缩文件不可能做到这一点是否正确?
saveRDS(object, file = "", ascii = FALSE, version = NULL,
compress = TRUE, refhook = NULL)
但我希望二进制 (ascii=F
) 未压缩 (compress=F
) 可能允许这样的事情。在文件上使用 mmap
,然后快速跳到感兴趣的行和列?
我希望它已经完成,或者有另一种格式(相当 space 有效)允许这样做并且在 R 中得到很好的支持。
我使用过 gdbm
(来自 Python)之类的东西,甚至在 Rcpp 中为特定的数据结构实现了一个自定义系统,但我对这些都不满意。
发布这篇文章后,我对包 ff
(CRAN) 进行了一些工作,并对它印象深刻(尽管对 character
向量的支持不多)。
Am I right in assuming that this would be impossible with a compressed
file, simply because gzip requires to uncompress everything from the
beginning?
的确,为了简短的解释,让我们以一些虚拟方法为起点:
AAAAVVBABBBC
gzip 会做这样的事情:4A2VBA3BC
显然你不能在不阅读文件的情况下从文件中提取所有 A
,因为你无法猜测末尾是否有 A
。
对于另一个问题 "Loading part of a saved file" 我想不出解决方案。您可能可以使用 write.csv
和 read.csv
(或 data.table
包中的 fwrite
和 fread
)以及 skip
和 nrows
参数可能是另一种选择。
无论如何,在已读取的文件上使用任何函数都意味着在过滤之前将整个文件加载到内存中,这与读取文件然后从内存中提取子集没有更多的时间。
您可以在 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 系统 awk wich is able to parse millions of lines in a few milliseconds, or to use a windows compiled version of awk。
主要优点是 awk 能够根据正则表达式或某些其他条件过滤每行数据。
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þ
我们有文件的 header,列表的 header,然后是组成列表的每个向量,因为我们不知道字符向量将占用多少大小不能跳到任意数据,我们必须解析每个 header(文本数据给出它的长度之前的字节)。更糟糕的是,现在要获得相应的整数,我们必须转到整数向量 header,如果不解析每个字符 header 并对它们求和,就无法确定它。
所以在我看来,制作一些东西是可能的,但可能不会比阅读所有 object 快多少,而且保存格式会很脆弱(因为 R 已经有 3 种格式可以保存 objects).
与 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
对于通过保存大 data.frame
(或 data.table
)创建的大文件 (1GB),是否可以非常快速地从该文件加载一小部分行?
(为了清楚起见,额外说明:我的意思是和 mmap
一样快,即运行时间应该与提取的内存量大致成正比,但在总数据集的大小。"Skipping data" 基本上应该具有零成本。这可能非常简单,也可能不可能,或者介于两者之间,具体取决于序列化格式。)
我希望 R serialization 格式可以轻松地向前跳到文件的相关部分。
仅仅因为 gzip 需要从头开始解压缩所有内容,我假设压缩文件不可能做到这一点是否正确?
saveRDS(object, file = "", ascii = FALSE, version = NULL,
compress = TRUE, refhook = NULL)
但我希望二进制 (ascii=F
) 未压缩 (compress=F
) 可能允许这样的事情。在文件上使用 mmap
,然后快速跳到感兴趣的行和列?
我希望它已经完成,或者有另一种格式(相当 space 有效)允许这样做并且在 R 中得到很好的支持。
我使用过 gdbm
(来自 Python)之类的东西,甚至在 Rcpp 中为特定的数据结构实现了一个自定义系统,但我对这些都不满意。
发布这篇文章后,我对包 ff
(CRAN) 进行了一些工作,并对它印象深刻(尽管对 character
向量的支持不多)。
Am I right in assuming that this would be impossible with a compressed file, simply because gzip requires to uncompress everything from the beginning?
的确,为了简短的解释,让我们以一些虚拟方法为起点:
AAAAVVBABBBC
gzip 会做这样的事情:4A2VBA3BC
显然你不能在不阅读文件的情况下从文件中提取所有 A
,因为你无法猜测末尾是否有 A
。
对于另一个问题 "Loading part of a saved file" 我想不出解决方案。您可能可以使用 write.csv
和 read.csv
(或 data.table
包中的 fwrite
和 fread
)以及 skip
和 nrows
参数可能是另一种选择。
无论如何,在已读取的文件上使用任何函数都意味着在过滤之前将整个文件加载到内存中,这与读取文件然后从内存中提取子集没有更多的时间。
您可以在 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 系统 awk wich is able to parse millions of lines in a few milliseconds, or to use a windows compiled version of awk。
主要优点是 awk 能够根据正则表达式或某些其他条件过滤每行数据。
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þ
我们有文件的 header,列表的 header,然后是组成列表的每个向量,因为我们不知道字符向量将占用多少大小不能跳到任意数据,我们必须解析每个 header(文本数据给出它的长度之前的字节)。更糟糕的是,现在要获得相应的整数,我们必须转到整数向量 header,如果不解析每个字符 header 并对它们求和,就无法确定它。
所以在我看来,制作一些东西是可能的,但可能不会比阅读所有 object 快多少,而且保存格式会很脆弱(因为 R 已经有 3 种格式可以保存 objects).
与 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