高效地将二进制数据读入R

4

我是一名有用的助手,可以为您翻译以下内容:

从一个文本文件中读取二进制数据,结构如下:

0101010100101010101010101010
1010101001010101010101010111
1111101010101010100101010101

该文件有800行,每行长度相等(但不同文件长度可能不同,因此硬编码无意义)。我希望输入数据以数据框的形式存储,其中每一行都是一行数据,每两个数字存储在不同的列中,例如:
col1 col2 col3 col4
0      1    0    1

目前我是这样做的

as.matrix(read.table(text=gsub("", ' ', readLines("input"))))->g

然而,由于每行大约有70,000个0/1,那样做太慢了。

有没有更快的方法?

3个回答

7
您可以使用awk进行pipe操作。
read.table(pipe("awk '{gsub(/./,\"& \", $1);print $1}' yourfile.txt"))
#   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18 V19 V20 V21
#1  0  1  0  1  0  1  0  1  0   0   1   0   1   0   1   0   1   0   1   0   1
#2  1  0  1  0  1  0  1  0  0   1   0   1   0   1   0   1   0   1   0   1   0
#3  1  1  1  1  1  0  1  0  1   0   1   0   1   0   1   0   1   0   0   1   0
#  V22 V23 V24 V25 V26 V27 V28
#1   0   1   0   1   0   1   0
#2   1   0   1   0   1   1   1
#3   1   0   1   0   1   0   1

或者

read.table(pipe("awk '{gsub(\"\",\" \", $1);print $1}' yourfile.txt"))

fread也可以与awk组合使用。

library(data.table)
fread("awk '{gsub(/./,\"&,\", $1);print $1}' yourfile.txt")

使用与原始数据集类似的数据集,

library(stringi)
write.table(stri_rand_strings(800,70000, '[0-1]'), file='binary1.txt',
         row.names=FALSE, quote=FALSE, col.names=FALSE)

system.time(fread("awk '{gsub(/./,\"&,\", $1);print $1}' binary1.txt"))
#  user  system elapsed 
#16.444   0.108  16.542 

如果我想在循环中先写入“your_file_1.txt”,然后在下一次迭代中写入“your_file_2.txt”,那么我该如何动态更改fread(“awk '{gsub(/./,"&,", $1);print $1}' yourfile.txt”)命令中的输入文件? - heinheo
@heinheo,难道不是AnandaMahto帖子中的sprintf能够帮助吗? - akrun
@heinheo,如果你有需要读取的文件列表,你可以使用for循环或者lapply结合我分享的Fawk函数。我不明白为什么会卡住。 - A5C1D2H2I1M1N2O1R2T1
它对我创建的一些文件有效。`lst <- lapply(list.files(pattern='binary\d+.txt'), Fawk); sapply(lst, dim)

[,1] [,2]

[1,] 800 800 [2,] 70001 70001`
- akrun
真的不知道该接受哪个答案,因为两个都非常好。 - heinheo

6
我建议您探索“readr”包中的read_fwf。您可以执行以下操作:
library(readr)
len <- nchar(readLines("yourfile.txt", n = 1))
read_fwf("yourfile.txt", fwf_widths(rep(1, len)))

另外,您可以尝试使用“iotools”包,这可能会更快:

library(iotools)
len <- nchar(readLines("yourfile.txt", n = 1))
input.file("yourfile.txt", formatter = dstrfw, 
            col_types = rep("integer", len), widths = rep(1, len))

这是一个小型的POC(概念验证)示例:
a <- tempfile()

writeLines("0101010100101010101010101010
1010101001010101010101010111
1111101010101010100101010101", a)

len <- nchar(readLines(a, n = 1))

library(readr)
read_fwf(a, fwf_widths(rep(1, len)))
# Source: local data frame [3 x 28]
# 
#   X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28
# 1  0  1  0  1  0  1  0  1  0   0   1   0   1   0   1   0   1   0   1   0   1   0   1   0   1   0   1   0
# 2  1  0  1  0  1  0  1  0  0   1   0   1   0   1   0   1   0   1   0   1   0   1   0   1   0   1   1   1
# 3  1  1  1  1  1  0  1  0  1   0   1   0   1   0   1   0   1   0   0   1   0   1   0   1   0   1   0   1

您的数据维度似乎使得read_fwf无法处理。我进行了一个小测试,比较了“iotools”方法和awk+fread

这是样例数据:

## Creates a file named "somefile.txt"
set.seed(1)
A <- replicate(10, sample(0:1, 70000, TRUE), FALSE)
A <- sapply(A, paste, collapse = "")
writeLines(rep(A, 800/length(A)), "somefile.txt")

以下是功能和结果。我编写了这些函数,让您可以尝试在实际数据上使用它们,以查看哪个效果最佳。
显然,目前来看,似乎readr已经被排除在外了 :-)
Freadr <- function(infile = "somefile.txt") {
  len <- nchar(readLines(infile, n = 1))
  read_fwf(infile, fwf_widths(rep(1, len)))
}
system.time(temp1 <- Freadr())
# |===============================================================| 100%   53 MB
#    user  system elapsed 
# 466.740   0.384 466.506 

Fiotools <- function(infile = "somefile.txt") {
  len <- nchar(readLines(infile, n = 1))
  input.file(infile, formatter = dstrfw, 
             col_types = rep("integer", len), widths = rep(1, len))
}
system.time(temp2 <- Fiotools())
#    user  system elapsed 
#   7.248   0.016   7.273 

Fawk <- function(infile = "somefile.txt") {
  cmd <- sprintf("awk '{gsub(/./,\"&,\", $1);print $1}' %s", infile)
  fread(cmd)
}
system.time(temp3 <- Fawk())
#    user  system elapsed 
#  12.948   0.156  13.109 

那么,使用基础的R语言也不算太糟糕:
fun4 <- function(infile = "somefile.txt") {
  do.call(rbind, lapply(strsplit(readLines(infile), "", TRUE), as.numeric))
}
system.time(fun4())
#    user  system elapsed 
#   9.056   0.260   9.304 

这里的结果是一个矩阵,因此如果您确实需要将其转换为data.framedata.table,则可能需要增加几秒钟的时间。


也许使用fread有解决方案吗?它比readr软件包的解决方案快一些,但我在安装它时遇到了问题..... - heinheo
@heinheo,你可以试试我分享的“iotools”替代方案。根据我的经验,它非常快。 - A5C1D2H2I1M1N2O1R2T1
你知道我怎样才能在不重新定义的情况下动态地将文件粘贴到你的Fawk函数中吗?我可以将文件名作为参数传递吗? - heinheo
@heinheo,我已经通过提供“infile”参数来完成了这个任务。因此,如果你的文件名是“someotherfile.txt”,你只需要运行Fawk("someotherfile.txt")就可以了。这是你想要的吗? - A5C1D2H2I1M1N2O1R2T1
真的不知道该接受哪个答案,因为两个都非常好。 - heinheo

2

从后续问题、数据结构和原始解决方案来看,您似乎需要一个矩阵(因为所有列都是相同类型),而不是在问题正文中指定的数据框架(会导致下游问题!)。数据似乎不太大,所以读入并拆分成单个字母。

lns = strsplit(readLines("somefile.txt"), "")

然后取消列表,将字符串匹配为整数,并将其重新塑造为矩阵。
v = match(unlist(lns), c("0", "1")) - 1L
m = matrix(v, nrow=length(lns), byrow=TRUE)

作为一个函数:
input2matrix <- function(fname) {
    lns = strsplit(readLines("somefile.txt"), "")
    v = match(unlist(lns), c("0", "1")) - 1L
    matrix(v, nrow=length(lns), byrow=TRUE)
}

这个例子是800 x 70000行,大约需要5秒钟。与其他答案相比,它也比所有其他解决方案更快(我无法轻松安装iotools,抱怨C级缺失符号Rspace),并且不会对操作系统和操作系统工具的可用性(以及对R!的知识)做出假设。


这似乎比我的基本R方法具有更好的性能优势。+1 - A5C1D2H2I1M1N2O1R2T1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接