【问题标题】:Read binary data into R efficiently有效地将二进制数据读入 R
【发布时间】:2017-04-15 20:08:11
【问题描述】:

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

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,这需要的时间太长。

有没有更快的方法来做到这一点?

【问题讨论】:

    标签: r dataframe


    【解决方案1】:

    你可以 pipeawk

    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")
    

    使用与 OP 的数据集类似的数据集,

    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 
    

    【讨论】:

    • 如何动态更改 fread("awk '{gsub(/./,\"&,\", $1);print $1}' yourfile.txt") 命令中的输入文件如果我想在循环中写入“your_file_1.txt”,然后在下一次迭代中写入“your_file_2.txt”???
    • @heinheo 不会是来自`AnandaMahto 的帖子帮助中的sprintf
    • @heinheo,如果您有需要读入的文件列表,您可以使用for 循环或lapply 与我共享的Fawk 函数。我没有看到挂断....
    • @heinheo 它适用于我创建的几个文件。 lst <- lapply(list.files(pattern='binary\\d+.txt'), Fawk); sapply(lst, dim) # [,1] [,2] [1,] 800 800 [2,] 70001 70001
    • 真的不知道该接受哪个答案,因为两者都很好
    【解决方案2】:

    我建议从“readr”包中探索read_fwf。你可以这样做:

    library(readr)
    len <- nchar(readLines("yourfile.txt", n = 1))
    read_fwf("yourfile.txt", fwf_widths(rep(1, len)))
    

    或者,您可以尝试the "iotools" package,这可能会更快:

    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 
    

    就此而言,使用 base 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 
    

    结果是matrix,因此您可能需要添加几秒钟才能转换为data.framedata.table,如果这正是您想要的。

    【讨论】:

    • fread 是否也有解决方案?它比 readr 包中的解决方案快一点,我在安装它时遇到问题.....
    • @heinheo,你能试试我分享的“iotools”替代方案吗?根据我的经验,它非常快。
    • 您知道如何将我的文件动态粘贴到您的 Fawk 函数中,而无需重新定义它吗?我可以将文件名作为参数传递吗?
    • @heinheo,我已经通过提供“infile”参数做到了这一点。所以,如果你的文件是“someotherfile.txt”,你应该可以运行Fawk("someotherfile.txt")。你问的是这个吗?
    • 真的不知道该接受哪个答案,因为两者都很好
    【解决方案3】:

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

    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
    猜你喜欢
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 2018-08-16
    • 2018-09-07
    • 2021-07-28
    • 2014-07-17
    • 1970-01-01
    相关资源
    最近更新 更多