使用列表进行模拟

问题描述 投票:0回答:3

在学习 R 的过程中,我给自己设定了一个小挑战。问题是,给定 500 个均值为 20 的正态分布数字样本,对于从 6 到 10 的标准差,我会得到多少个低于 20 的数字。为了了解更多信息,我决定为每个 sd 获取 4 个样本。所以到最后我应该:

sd6samp1:...

sd6samp2:...

....

sd10samp4:...

我第一个有效的方法是:

 ddss<-c(6:10) # sd's
 sam<-c(1:4) # 4 samples for each
 k=0  # counter in 0
 for (i in ddss) {   # for each sd
   for (j in sam) {  # for each sample
     nam <- paste("sam",i,".",j, sep="") # building a name
     n <- assign(nam,rnorm(500, 20, i))  # the great assign function
     k <- k+sum(n<=0)
   }
   print(assign(paste("ds",i,sep=""), k)) # ohh assign you're great
   k=0 # reset counter
 }

在寻找如何使用循环“i”创建变量名称时,发现“分配”可以完成工作,但它也说:

请注意,如果您计划进行一些模拟, 许多专家会说你应该使用列表。

所以我认为学习列表会很好......

同时我还发现了一个很好的其他选择...... dds <- c(6:10)

for (i in ddss) {
   print(paste('prob. x<=0), with sd=',i))
   print(pnorm(0,mean=20,sd=i)*500)
}

这可以回答这个问题,但是列表还有待完成……而且还有很多 R 知识有待学习。主要思想不是知道负数的概率或数量...而是学习 R,特别是一些循环。

所以,我一直在尝试使用提到的列表

我最接近的方法是:

ddss<-c(6:10) # sd's to be calculated.
sam<-c(1:4) # 4 samples for each sd
liss<-list()  # initializing the list
for (i in ddss) {   # for each sd
   liss[[i]] <- list()
   for (j in sam) {  # for each sample
      liss[[i]][[j]] <- rnorm(500, 20, i)
      print(paste('ds',i,'samp',j,'=',sum(liss[[i]][[j]]<0)))
   }
}

通过这个我获得了信息,但我想知道两个问题(1 和 2)以及其他一些问题(3 和 4):

  1. 我得到一个包含 10 个元素的列表,其中 6 个为空元素,然后是 4 个带有子列表的元素。我似乎不知道如何使用列表(sd)的元素 1:4 和 6:9 名称(非常 sd)。

  2. 即使我尝试过,我也无法通过“for”循环来命名列表元素。对这些问题的任何见解都会很棒。

  3. 因为在模拟的背景下。您认为哪个更好:嵌套列表(带有子列表的列表)或简单(更长)列表?

  4. 我想知道“应用”功能在这里是否有任何帮助,我尝试做一些事情,例如:

vbv<-matrix(c(6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9))
lsl<-apply(vbv, 2, function(x) rnorm(500,20,x))

但看起来我还没有接近......

r random simulation nested-lists
3个回答
4
投票

问题出在你的索引中:你正在从 ddss 运行索引器 i,它从 6 运行到 10。因此,在你的外循环的第一个任务中,你的第一个语句实际上是说:

liss[[6]]<-list()
,这意味着第一个5 个为 NULL。

因此,如果您坚持使用循环,这就是您应该做的(检查

?seq_along
):

ddss<-c(6:10) # sd's to be calculated.
sam<-c(1:4) # 4 samples for each sd
liss<-list()  # initializing the list
for (i in seq_along(ddss)) {   # now, i runs from 1 to 5
   liss[[i]] <- list()
   for (j in sam) {  # for each sample
      liss[[i]][[j]] <- rnorm(500, 20, i)
      print(paste('ds',ddss[i],'samp',j,'=',sum(liss[[i]][[j]]<0)))
   }
   names(liss[[i]])<-as.character(sam)#this should solve your naming issue (1/2)
}
names(liss)<-as.character(ddss)#this should solve your naming issue (2/2)

请注意,一如既往,最好将变量命名为比 i 或 j 更有用的名称:如果您将其命名为 curds,也许您不会立即将其用作列表中的索引器?

现在,如果您确实希望改进(但想坚持列表),那么您确实想使用应用样式函数:

liss<-lapply(ddss, function(curds){ #apply the inline function to each ds and store results in a list
  return(lapply(sam, function(cursam){ #apply inline function to each sam and store results in a list
    rv<-rnorm(500, 20, curds)
    cat('ds',curds,'samp',cursam,'=',sum(rv<0), "\n") #maybe better for your purposes.
    return(rv)
  }))
}) 

最后,对于您的情况,没有太多理由实际使用列表(您甚至不需要保留每个 ds/sam 的采样数据):您可以将所有内容存储为三维数组,但由于您指定了它作为学习练习(嘿,也许数组可能是你的下一个练习:-)),我就这样吧。


3
投票

lapply()
在这里很有帮助,我们可以只应用 SD 的一组值。它有助于围绕
rnorm()
函数编写自定义包装器,以便我们可以为
rnorm()
的各个参数传递不同的值,并以良好的方式处理 k 重复项(示例中的 k = 4)还。该包装纸如下
foo()

foo <- function(sd, n, mean, reps = 1) {
    rands <- rnorm(n * reps, mean = mean, sd = sd)
    if(reps > 1)
        rands <- matrix(rands, ncol = reps)
    rands
}

我们在

lapply()
调用中使用它,如下所示:

sims <- lapply(6:10, FUN = foo, mean = 20, n = 500, reps = 4)

这给出了:

R> str(sims)
List of 5
 $ : num [1:500, 1:4] 30.3 22 15.6 20 19.4 ...
 $ : num [1:500, 1:4] 20.9 21.7 17.7 35 30 ...
 $ : num [1:500, 1:4] 17.88 26.48 5.19 19.25 15.59 ...
 $ : num [1:500, 1:4] 27.41 12.72 9.38 35.09 11.08 ...
 $ : num [1:500, 1:4] 16.2 11.6 20.5 35.4 27.3 ...

然后我们可以计算观测值的数量 < 20 per SD

names(sims) <- paste("SD", 6:10, sep = "")
out <- lapply(sims, function(x) colSums(x < 20))

这给出了:

R> out
$SD6
[1] 218 251 253 227

$SD7
[1] 250 242 233 232

$SD8
[1] 258 241 246 274

$SD9
[1] 252 245 249 258

$SD10
[1] 253 259 241 242

@Joris 建议我展示如何访问列表的元素。例如,如果您想要 SD = 20 的模拟结果,我们可以这样做

out[[4]]
因为 20 是我们应用的 SD 向量中的第四个值,或者,因为我命名了输出列表的元素
 out
,我们可以使用
out[["SD10"]]
来了解模拟结果。

回答有关循环等的一些具体问题,

  • 要将名称添加到列表中,请使用
    names()
    ,例如
    names(mylist)
    <- c("foo","bar")
    . You'd be better off in your loop calling
    names()` 每次循环迭代一次,以便在单个镜头中设置名称 - 您可能不想在进行过程中填写名称,因为那样效率很低。
  • 我认为无论您使用嵌套列表还是使用包含矩阵的列表(按照我的示例),都没有太大区别。要更改
    foo()
    返回一个列表,以便
    lapply()
    的输出是列表的列表,我们可以这样做:

代码:

bar <- function(sd, n, mean, reps = 1) {
    rands <- rnorm(n * reps, mean = mean, sd = sd)
    if(reps > 1)
        rands <- split(rands, rep(seq_len(reps), each = n))
    rands
}
sims2 <- lapply(6:10, FUN = bar, mean = 20, n = 500, reps = 4)
names(sims2) <- paste("SD", 6:10, sep = "")
out2 <- lapply(sims2, function(x) sapply(x, function(y) sum(y < 20)))

给出与之前相同的输出。


3
投票

我将使用

plyr
包提出另一个解决方案,我认为它是为此类练习量身定制的。

library(plyr)

# generate a data frame of parameters, repeating some as required
parameters  = data.frame(mean = 20, sd = rep(6:10, each = 4))

# generate sample data for each combination of parameters
sample_data = mdply(df, rnorm, n = 500)

# generate answer by counting number of observations less than 20
answer = data.frame(
    parameters, 
    obs_less_20 = rowSums(sample_data[,-c(1, 2),] < 20)
)

head(answer)

mean sd obs_less_20
1   20  6         247
2   20  6         250
3   20  6         242
4   20  6         259
5   20  7         240
6   20  7         237
© www.soinside.com 2019 - 2024. All rights reserved.