在 R 中选择二元向量中的位置(我怎样才能固定?)

Selecting positions in a binary vector (how can I fasten up?) in R

我有一个如下所示的数据框:

SNP1 01010101000000100000010010001010011001010101
SNP2 01010010101000100000000000000001100001001000
SNP3 01010101000000000000000000000100011111111111

...但实际上包含约 800 万行,每个二进制向量的长度均为 1000。

我需要 select 这些二进制向量中的特定位置(跨所有行)。我发现这样做的肮脏方法是删除行名称,将每个数字转换为一列,然后创建一个包含我感兴趣的位置的对象。

以下内容适用于示例数据,但对我的真实数据来说效率不高(现在 运行 很长一段时间了)。有什么办法可以让它更快吗?

library(data.table)
library(stringr)
setwd("test/")
DATADIR="datadir/"
OUTPUTDIR="outputdir/"
dir.create(OUTPUTDIR, showWarnings = FALSE)

baseline<-read.table(paste0(DATADIR,"input.file"), colClasses = "character")
  # Pass BP name to row name (so that I can split the binary vector into multiple columns)
  row.names(baseline) <- baseline$V1
  baseline$V1 <- NULL

  # split cells containing the binary vectors into multiple columns - thank you @Onyambu for this!
  baseline_new <-  read.table(text = gsub('(.)','\1 ',baseline$V2),fill=TRUE)

  # select columns of interest
  columns_to_keep <- c(1, 4, 8, 10)
  baseline_new_ss <- baseline_new[, columns_to_keep]

  # create new object containing a column with the original row names, then recreate binary vector based on subsetted binary positions. 
  baseline_final <- as.data.frame(row.names(baseline))
  baseline_final$V2 <- as.character(interaction(baseline_new_ss,sep=""))

输出(select仅位置 1、4、8 和 10)应如下所示:

SNP1 0110
SNP2 0100
SNP3 0110

我相信有一种不那么复杂的方法可以做到这一点。

谢谢!!

你可以试试这个:

at <- function(binary_strings, positions)
{
  charvec <- character(length(binary_strings))
  for(i in seq_along(positions))
  {
    charvec <- paste0(charvec, substr(binary_strings, positions[i], positions[i]))
  }
  return(charvec)
}

现在你可以做

at(baseline$`whatever your binary column is called`, c(1, 4, 8, 10))
#> [1] "0110" "0100" "0110"

所以用管道你可以做到

library(magrittr)

baseline$`whatever your binary column is called` %<>% at(c(1, 4, 8, 10))

print(baseline)
#>      whatever your binary column is called
#> SNP1                                  0110
#> SNP2                                  0100
#> SNP3                                  0110

我使用相当慢的 Windows PC 在 800 万行上以 7 秒的速度对此进行了基准测试。

您可以将 strsplit、select 元素与 mapplypaste 一起使用回到数据框中。虽然不知道这有多快,但它很简洁:)

`rownames<-`(data.frame(values=
                          mapply(function(x) Reduce(paste0, x[c(1, 4, 8, 10)]), 
                                 sapply(dat$V2, strsplit, ""))),
         dat$V1)
#      values
# SNP1   0110
# SNP2   0100
# SNP3   0110

也许有一个 data.table 解决方案不会在内部制作副本 -> fast.


数据:

"SNP1 01010101000000100000010010001010011001010101
SNP2 01010010101000100000000000000001100001001000
SNP3 01010101000000000000000000000100011111111111"->tx
dat <- data.table::fread(text=tx, header=F)

另一种选择是使用 stringi:

时间码:

nr <- 1e6
nc <- 1e3
l <- rep(paste(rep(1L, nc), collapse=""), nr)
writeLines(l, "test.txt")

cols <- c(1,4,8,10)

library(stringi)
library(iotools)    
microbenchmark::microbenchmark(times=1L,
    stringi=lapply(cols, function(n) stri_sub(l, n, n)),
    iotools=input.file("test.txt", formatter=dstrfw, 
        col_types=rep("character", nc), widths=rep(1L, nc))[, cols]
)

时间:

Unit: seconds
    expr       min        lq      mean    median        uq       max neval
 stringi  1.329223  1.329223  1.329223  1.329223  1.329223  1.329223     1
 iotools 76.250773 76.250773 76.250773 76.250773 76.250773 76.250773     1