【问题标题】:Alternative to for loop with "dynamic" variables with R使用 R 替代带有“动态”变量的 for 循环
【发布时间】:2017-02-18 23:31:33
【问题描述】:

我是 StackOverflow 的新手,尽管我玩 R 已经有一段时间了。我正在努力解决一个我无法在网站上找到任何答案的问题。如果我的任务不够准确,请纠正我。

我有两个 3d 数组,在这个简化的例子中是 256x256x200。第一个是一个字段,第二个由索引组成,范围从 1 到 8。我想根据索引的值和计数计算每个垂直级别的平均值,即 200 个级别的字段的平均值对于每个索引(从 1 到 8)。仅当有足够的索引计数(即循环内的 if 条件)时才应执行此操作。我的输出必须是 8x200 的矩阵。

对于示例,我创建了两个随机数组。下面是我正在使用的基本代码:

nz=200
lev=1:nz
indices=8
var0=array(rnorm(256*256*nz),dim=c(256,256,nz))
#octo=array(sample(1:indices),dim=c(256,256,nz)) 
octo=array(sample(1:indices,size=256*256*nz,replace=T),dim=c(256,256,nz))
counts=apply(octo,3,function(x) table(factor(x,levels=1:indices)))
#thr=0.1
thr=0.125
np=length(var0[,1,1])*length(var0[1,,1])
profile=array(NA,dim=c(nz,indices))


t0=proc.time()
for (i in 1:indices)
{
    for (z in 1:length(lev)) 
    {
       if (counts[i,z]/np>thr) 
       {v0=var0[,,z];  profile[z,i]=counts[i,z]/np*mean(v0[octo[,,z]==i],na.rm=T)} 
    }
}
print(proc.time()-t0)

user  system elapsed 
5.169   0.001   5.170 

我尝试使用 apply 系列函数,但我无法以合理有效的方式将其写下来,因为我需要每次计算都考虑一个改变其级别的“动态”变量(即 octocounts 变量)。我的真实案例是通过更大的矩阵制作的,这应该在几十个领域完成,因此时间非常重要。 您知道任何更快的替代方案吗? 非常感谢您的帮助!

编辑:我更正了 octo 的原始定义并调整了阈值 thr。这样,if 条件就有意义了,因为它并不总是得到尊重。

【问题讨论】:

  • 你考虑过融化数据吗?如果你真的有动态值,也许一个整洁的结构会让它更干净。
  • 另外,计数不只是一个 8192 重复的 8x200 矩阵吗? octo 的定义/计数的定义是否正确?
  • @shape 是……我也觉得很奇怪……
  • @shape 感谢您的 cmets!是的,你完全正确,octo 定义不明确,但它的定义只是一个测试用例,可以用作快速示例。

标签: r for-loop apply


【解决方案1】:

这是一个避免循环和/或应用语句的data.table reshape 解决方案:

nz=200
lev=1:nz
indices=8
var0=array(rnorm(256*256*nz),dim=c(256,256,nz))
octo=array(sample(1:indices),dim=c(256,256,nz))
counts=apply(octo,3,function(x) table(factor(x,levels=1:indices)))
thr=0.1
np=length(var0[,1,1])*length(var0[1,,1])
profile=array(NA,dim=c(nz,indices))


# From here load data.table to do the manipulation
# reshape2 to convert back into a matrix at the end
library(data.table)
library(reshape2)

# Take the data long and convert to data.table
var01 <- setDT(melt(var0))
octo1 <- setDT(melt(octo))

# Join the data to get corresponding data
# EDIT, it currently works, but I think that's because all data is defined
# adding nomatch in case of missing data
octo1 <- octo1[var01, on = c('Var1','Var2','Var3'), nomatch = NA] 

# Make our calculation grouping by the vertical dimension and the value
profile <- octo1[,if(.N/np > thr) .N / np * mean(i.value, na.rm = TRUE) else NA, by = .(value,Var3)]

# Recast to matrix
profile <- acast(profile, value ~ Var3, mean, value.var = 'V1')

【讨论】:

  • @PaoloDavini 是的,这是一个似乎更容易以长格式可视化的计算。对应张量内的查找变得复杂。
  • 你确定这能正常工作吗?如果我运行这段代码,我得到的只是包含 1:8 的列
  • @parksw3 已修复,最后的 acast 应该有 value 列,它正在计算不同列的平均值
  • 在这种情况下,acast 中的平均值实际上并没有做任何事情,因为每个索引和级别(分别为 value 和 var3)应该只有一个组合,但 acast 需要一个函数多对一
【解决方案2】:

我认为我通过 sapply 找到了一个很好的解决方案,包括 thr

f1<-function()
{   
for (i in 1:indices)
{
for (z in 1:length(lev)) {if (counts[i,z]/np>thr) {v0=var0[,,z]; profile[z,i]=counts[i,z]/np*mean(v0[octo[,,z]==i],na.rm=T) } }
}
return(profile)
}

f2<-function()
{
profile=sapply(lev, function(i) {
            v0=var0[,,i];
            mV=sapply(1:indices, function(j) {mean(v0[octo[,,i] == j], na.rm = TRUE)})
            counts[,i]/np*mV
    })

profile[counts/np <= thr]=NA
profile<-matrix(profile, nz, indices, byrow = TRUE)
return(profile)
}

f3<-function()
{
profile=sapply(lev, function(i) {
            v0=var0[,,i];
            mV=sapply(1:indices, function(j) {if (counts[j,i]/np>thr) {mean(v0[octo[,,i] == j], na.rm = TRUE)} else {NA}})
            counts[,i]/np*mV
    })

profile<-matrix(profile, nz, indices, byrow = TRUE)
return(profile)
}

其实 f1() 是原版,f2() 是@parksw3 的版本,f3() 是我稍微改进的版本。

benchmark(f1(),f2(),f3(),replications=10)

test   replications elapsed relative user.self sys.self user.child  sys.child
1 f1()           10  27.382    1.411    27.375        0          0         0
2 f2()           10  35.195    1.814    35.186        0          0         0
3 f3()           10  19.403    1.000    19.392        0          0         0

这样,它总是比标准循环快。 data.table 可能更快,但它需要完全更改我目前无法执行的数据结构。希望这会有所帮助!

【讨论】:

  • 嗯,这不是说 sapply 比原来的方法慢吗? f2 似乎在我的机器上运行得更快。此外,您处理 NA 的方法仍然执行不必要的计算。我还没弄明白……
  • 当 if 条件总是满足时,f2 比 f1 快... f3 使用 sapply 但避免了平均计算,总是比 f1 快,并且当 if 条件满足时它会“收敛”到 f2总是很满意...
【解决方案3】:

这在我的机器上似乎更快:

profile2 <- sapply(lev, function(i){
    v0 <- var0[,,i]
    mV <- sapply(1:indices, function(j){
        mean(v0[octo[,,i] == j], na.rm = TRUE)
    })
    counts[,i]/np*mV
})
profile2[counts/np > thr] <- NA
profile2<- t(profile2)

all.equal(profile, profile2)
## TRUE

我尝试将它们与microbenchmark 包进行比较,但需要相当长的时间...这是我与rbenchmark 包进行的快速比较

f1 <- function(){
    for (i in 1:indices){
        for (z in 1:length(lev)) {
            if (counts[i,z]/np>thr){
                v0=var0[,,z];  profile[z,i]=counts[i,z]/np*mean(v0[octo[,,z]==i],na.rm=T)
            } 
        }
    }
}

f2 <- function(){
    prof <- sapply(lev, function(i){
        v0 <- var0[,,i]
        mV <- sapply(1:indices, function(j){
            mean(v0[octo[,,i] == j], na.rm = TRUE)
        })
        counts[,i]/np*mV
    })
    profile2[counts/np > thr] <- NA
    profile2<- t(profile2)
}

library(rbenchmark)
benchmark(f1(), f2(), replications = 10)

我将两个代码都放入一个函数并进行了测试。结果如下:

##   test replications elapsed relative user.self sys.self
## 1 f1()           10   89.03    1.342     85.15     1.72
## 2 f2()           10   66.34    1.000     61.50     0.75

【讨论】:

  • 这听起来很棒,非常感谢。我不习惯 sapply 的嵌套,但它是一个很好的选择。乍一看,我的机器大约需要运行一半。我会尽快做一些基准测试。唯一遗憾的是,实际上它也在为未达到 thr 的“无用”情况进行计算。
  • @PaoloDavini 是的...我只是想...让我看看我能不能解决它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-18
  • 2021-03-23
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
相关资源
最近更新 更多