在 R 中有效地更改 matrix/array 中的单个元素

Efficiently change individual elements in matrix/array in R

我是 运行 R 中的一个模拟,我正在努力提高它的效率。

一些背景知识:这是一个抽象模拟,用于测试突变对种群的影响。种群有 N 个个体,每个个体有 M 个字母的基因型,每个字母可以是二十种氨基酸之一(我表示为 0:19)。

最(计算上)最昂贵的任务之一涉及采用具有 M 行和 N 列的矩阵“mat”,该矩阵最初以全零矩阵开始,

mat <- matrix(rep(0,M*N),nrow=M)

然后改变(突变)每个个体基因型中的至少一个字母。我至少说的原因是,理想情况下,我想设置一个突变率 (mutrate),如果我在我的整体模拟函数中设置为 2,它将导致每个个体在矩阵中发生 2 次突变。

我发现了两种计算成本相当高的方法。正如您在下面看到的,只有第二种方法合并了突变率参数 mutrate(我无法轻易想到如何将其合并到第一种方法中)。

   #method 1
   for(i in 1:N){
   position <- floor(runif(N, min=0, max=M))
   letter <- floor(runif(N, min=0, max=19))
   mat[position[i],i] = letter[i]}
   #method 2, somewhat faster and incorporates mutation rate
mat <- apply(mat,2,function(x) (x+sample(c(rep(0,M-mutrate),sample(0:19,size=mutrate))%%20))))

第二种方法包含模数,因为正如我提到的,基因型值必须介于 0 和 19 之间。

为了清楚起见,还有一些附加说明:

非常感谢任何帮助!

先回答你最后一个问题;您可以使用 mat[row,column] 访问矩阵中的单个单元格,或通过顺序单元格 ID 访问多个分散的单元格。单元格 1,1 是第一个单元格,后面是 2,13,1 等:

mat <- matrix(rep(0, 5*5), nrow=5)
mat[c(1,3,5,7,9)] = c(1,2,3,4,5)

mat
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    0    0    0    0
[2,]    0    4    0    0    0
[3,]    2    0    0    0    0
[4,]    0    5    0    0    0
[5,]    3    0    0    0    0

访问/覆盖单个单元格也很快。我能想到的执行任务的最快方法是首先为我们想要的值创建向量。所有列索引的向量(每列的次数为 mutrate),行索引的向量(随机),以及这些 column/row 组合的新值向量(随机)。

cols = rep(seq_len(N), mutrate)
rows = sample(M, N*mutrate, replace = T)
values = sample(genotypes, N*mutrate, replace = T) - 1 # -1 offset since genotypes are 0-indexed

for(i in seq_len(N*mutrate)) {
  mat[rows[i],cols[i]] = values[i]
}

我们还可以计算 cell-IDs 而不是 for-loop 来更新矩阵,这样我们就可以一次性更新所有矩阵单元格:

cols = rep(seq_len(N), mutrate)
rows = sample(M, N*mutrate, replace = T)
cellid = rows + (cols-1)*M
  
values = sample(genotypes, N*mutrate, replace = T) - 1 # -1 offset since genotypes are 0-indexed
  
mat[cellid] = values

尝试使用 6000x10000 矩阵对多种方法进行基准测试,显示每种方法的速度:

N = 6000  # individuals
M = 10000 # genotype length

genotypes = 20
mutrate = 2

method1 <- function() {
  
  mat <- matrix(rep(0,M*N),nrow=M)
  
  for(i in 1:(N*mutrate)){
    position <- sample(M, 1)
    letter <- sample(genotypes, 1) - 1
    mat[position,i%%N] = letter
  }
  
  return(mat)
  
}

method2 <- function() {
  
  mat <- matrix(rep(0,M*N),nrow=M)
  mat <- apply(mat,2,function(x) (x+sample(c(rep(0,M-mutrate),sample(0:19,size=mutrate))%%20)))
  
}

method3 <- function() {
  
  mat <- matrix(rep(0,M*N),nrow=M)
  
  cols = rep(seq_len(N), mutrate)
  rows = sample(M, N*mutrate, replace = T)
  values = sample(genotypes, N*mutrate, replace = T) - 1 # -1 offset since genotypes are 0-indexed
  
  for(i in seq_len(N*mutrate)) {
    mat[rows[i],cols[i]] = values[i]
  }
  
  return(mat)
  
}

method4 <- function() {
  
  mat <- matrix(rep(0,M*N),nrow=M)
  
  cols = rep(seq_len(N), mutrate)
  rows = sample(M, N*mutrate, replace = T)
  cellid = rows + (cols-1)*M
  
  values = sample(genotypes, N*mutrate, replace = T) - 1 # -1 offset since genotypes are 0-indexed
  
  mat[cellid] = values
  
  return(mat)
  
}

benchmark <- function(func, times=10) {
  begin <- as.numeric(Sys.time())
  for(i in seq_len(times))
    retval <- eval(parse(text=func))
  end <- as.numeric(Sys.time())
  cat(func, 'took', (end-begin)/times, 'seconds\n')
  return(retval)
}

ret1 <- benchmark('method1()')
ret2 <- benchmark('method2()')
ret3 <- benchmark('method3()')
ret4 <- benchmark('method4()')
我已经修改了您的第一个方法以加快速度并执行 mutrate。

method1() took 0.8936087 seconds
method2() took 8.767686 seconds
method3() took 0.7008878 seconds
method4() took 0.6548331 seconds