对于R和(更重要的是)矢量化仍然很陌生,我不知道如何加快下面的代码。
for-loop通过对每个种子应用随机概率,计算在具有不同种子产生植物密度的几个路段上落在道路上的种子数量。由于我的真实数据帧有~200k行,种子数高达300k/段,在我当前的机器上使用下面的示例需要几个小时。
#Example data.frame
df <- data.frame(Density=c(0,0,0,3,0,120,300,120,0,0))
#Example SeedRain vector
SeedRainDists <- c(7.72,-43.11,16.80,-9.04,1.22,0.70,16.48,75.06,42.64,-5.50)
#Calculating the number of seeds from plant densities
df$Seeds <- df$Density * 500
#Applying a probability of reaching the road for every seed
df$SeedsOnRoad <- apply(as.matrix(df$Seeds),1,function(x){
SeedsOut <- 0
if(x>0){
#Summing up the number of seeds reaching a certain distance
for(i in 1:x){
SeedsOut <- SeedsOut +
ifelse(sample(SeedRainDists,1,replace=T)>40,1,0)
}
}
return(SeedsOut)
})
如果有人能给我一个提示,关于如何用向量化代替循环,或者如何在第一时间更好地组织数据以提高性能,我将非常感激!
编辑: Roland的回答表明我可能过分简化了这个问题。在for循环中,我从另一个作者记录的距离分布中提取一个随机值(这就是为什么我不能在这里提供数据)。添加了一个示例向量,其中包含SeedRain距离的可能值。
这应该做同样的模拟:
df$SeedsOnRoad2 <- sapply(df$Seeds,function(x){
rbinom(1,x,0.6)
})
# Density Seeds SeedsOnRoad SeedsOnRoad2
#1 0 0 0 0
#2 0 0 0 0
#3 0 0 0 0
#4 3 1500 892 877
#5 0 0 0 0
#6 120 60000 36048 36158
#7 300 150000 90031 89875
#8 120 60000 35985 35773
#9 0 0 0 0
#10 0 0 0 0
一种选择是为每一行df
生成所有Seeds
的sample()
。
在基于循环的代码之前使用set.seed(1)
,我得到:
> df
Density Seeds SeedsOnRoad
1 0 0 0
2 0 0 0
3 0 0 0
4 3 1500 289
5 0 0 0
6 120 60000 12044
7 300 150000 29984
8 120 60000 12079
9 0 0 0
10 0 0 0
如果我这样做,我会在很短的时间内得到相同的答案:
set.seed(1)
tmp <- sapply(df$Seeds,
function(x) sum(sample(SeedRainDists, x, replace = TRUE) > 40)))
> tmp
[1] 0 0 0 289 0 12044 29984 12079 0 0
比较:df <- transform(df, GavSeedsOnRoad = tmp)
df
> df
Density Seeds SeedsOnRoad GavSeedsOnRoad
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
4 3 1500 289 289
5 0 0 0 0
6 120 60000 12044 12044
7 300 150000 29984 29984
8 120 60000 12079 12079
9 0 0 0 0
10 0 0 0 0
这里需要注意的是:
- 尽量避免在循环中重复调用函数,如果函数是矢量化的,或者可以通过一次调用生成整个最终结果。在这里,您为
df
的每一行调用sample()
Seeds
次,每次调用返回来自SeedRainDists
的单个样本。在这里,我做一个单独的sample()
调用,要求Seeds
的样本大小,对于df
的每一行-因此我调用sample
10次,你的代码调用它271500次。 即使你必须在循环中反复调用一个函数,也要从循环中删除任何可以在循环完成后对整个结果进行矢量化的内容。这里的一个例子是
SeedsOut
的累积,它大量调用+()
。最好是将每个
SeedsOut
收集到一个向量中,然后在循环之外收集该向量的sum()
。例如SeedsOut <- numeric(length = x) for(i in seq_len(x)) { SeedsOut[i] <- ifelse(sample(SeedRainDists,1,replace=TRUE)>40,1,0) } sum(SeedOut)
请注意,R将逻辑视为数字
0
s或1
s,用于任何数学函数。因此sum(ifelse(sample(SeedRainDists, 100, replace=TRUE)>40,1,0))
和
sum(sample(SeedRainDists, 100, replace=TRUE)>40)
如果在相同的
set.seed()
下运行,结果将相同。
可能有一种更花哨的采样方法,需要更少地调用sample()
(并且有sample(SeedRainDists, sum(Seeds), replace = TRUE) > 40
,但随后您需要注意为df
的每一行选择该向量的正确元素-并不困难,只是一个轻微的麻烦),但是我所展示的可能足够有效?