如何在R中为蒙特卡罗模拟循环创建更高效的代码

4
这个练习的目的是创建营养素摄入值的人口分布。早期的数据中有重复的测量数据,已被删除,所以数据框中的每一行都是一个独特的人。
我有这段代码,在少量数据框行测试时运行得非常好。但对于全部 7135 行,速度非常慢。我尝试计时,但在我的机器上经过的时间达到了15小时,我不得不强制退出。system.time 的结果为 Timing stopped at: 55625.08 2985.39 58673.87。
我希望能够得到任何关于如何加快模拟速度的建议。
Male.MC <-c()
for (j in 1:100)            {
for (i in 1:nrow(Male.Distrib))  {
    u2        <- Male.Distrib$stddev_u2[i] * rnorm(1, mean = 0, sd = 1)
    mc_bca    <- Male.Distrib$FixedEff[i] + u2
    temp      <- Lambda.Value*mc_bca+1
    ginv_a    <- temp^(1/Lambda.Value)
    d2ginv_a  <- max(0,(1-Lambda.Value)*temp^(1/Lambda.Value-2))
    mc_amount <- ginv_a + d2ginv_a * Male.Resid.Var / 2
z <- data.frame(
     RespondentID = Male.Distrib$RespondentID[i], 
     Subgroup     = Male.Distrib$Subgroup[i], 
     mc_amount    = mc_amount,
     IndvWeight   = Male.Distrib$INDWTS[i]/100
     )

Male.MC <- as.data.frame(rbind(Male.MC,z))
    }
}

针对我的数据集中的7135个观察值,每个观察值都会创建100个模拟营养价值,然后将其反向转换为原始测量水平(该模拟使用来自非线性混合效应模型在BoxCox转换营养价值上的结果)。
我不想使用for循环,因为我读到它们在R中效率低下,但我不了解基于apply的选项足以用作替代。 R正在独立机器上运行,通常这将是运行Windows 7变体的标准Dell式台式机,如果这影响更改代码的建议。
更新:为了进行测试, Lambda.Value = 0.4,Male.Resid.Var = 12.1029420429778,而且Male.Distrib$stddev_u2是所有观察值上的恒定值。
str(Male.Distrib)是:
'data.frame':   7135 obs. of  14 variables:
 $ RndmEff     : num  1.34 -5.86 -3.65 2.7 3.53 ...
 $ RespondentID: num  9966 9967 9970 9972 9974 ...
 $ Subgroup    : Ord.factor w/ 6 levels "3"<"4"<"5"<"6"<..: 4 3 2 4 1 4 2 5 1 2 ...
 $ RespondentID: int  9966 9967 9970 9972 9974 9976 9978 9979 9982 9993 ...
 $ Replicates  : num  41067 2322 17434 21723 375 ...
 $ IntakeAmt   : num  33.45 2.53 9.58 43.34 55.66 ...
 $ RACE        : int  2 3 2 2 3 2 2 2 2 1 ...
 $ INDWTS      : num  41067 2322 17434 21723 375 ...
 $ TOTWTS      : num  1.21e+08 1.21e+08 1.21e+08 1.21e+08 1.21e+08 ...
 $ GRPWTS      : num  41657878 22715139 10520535 41657878 10791729 ...
 $ NUMSUBJECTS : int  1466 1100 1424 1466 1061 1466 1424 1252 1061 1424 ...
 $ TOTSUBJECTS : int  7135 7135 7135 7135 7135 7135 7135 7135 7135 7135 ...
 $ FixedEff    : num  6.09 6.76 7.08 6.09 6.18 ...
 $ stddev_u2   : num  2.65 2.65 2.65 2.65 2.65 ...

head(Male.Distrib) is

    RndmEff RespondentID Subgroup RespondentID Replicates IntakeAmt RACE INDWTS    TOTWTS   GRPWTS NUMSUBJECTS TOTSUBJECTS  FixedEff stddev_u2
1  1.343753         9966        6         9966      41067 33.449808    2  41067 120622201 41657878        1466        7135  6.089918  2.645938
2 -5.856516         9967        5         9967       2322  2.533528    3   2322 120622201 22715139        1100        7135  6.755664  2.645938
3 -3.648339         9970        4         9970      17434  9.575439    2  17434 120622201 10520535        1424        7135  7.079757  2.645938
4  2.697533         9972        6         9972      21723 43.340180    2  21723 120622201 41657878        1466        7135  6.089918  2.645938
5  3.531878         9974        3         9974        375 55.660607    3    375 120622201 10791729        1061        7135  6.176319  2.645938
6  6.627767         9976        6         9976      48889 91.480049    2  48889 120622201 41657878        1466        7135  6.089918  2.645938

更新2:导致NaN结果的函数行是:
d2ginv_a  <- max(0,(1-Lambda.Value)*temp^(1/Lambda.Value-2))

感谢大家的帮助和评论,也感谢回复的速度。

更新:@Ben Bolker是正确的,负数temp值导致了NaN问题。我在测试时忽略了这一点(注释掉函数,只返回temp值,并将结果数据框命名为Test)。以下代码重现了NaN问题:

> min(Test)
[1] -2.103819
> min(Test)^(1/Lambda.Value)
[1] NaN

但是把该值作为一个数值输入后,进行相同的计算可以得出结果,所以在手动计算时我忽略了这一点:

> -2.103819^(1/Lambda.Value) 
[1] -6.419792

我现在有可用的代码,使用了向量化技术,速度非常快。以防其他人遇到同样的问题,我在下面发布可用的代码。我已经添加了一个最小值来防止计算中出现<0的问题。感谢所有帮助过我的人,还有咖啡。我尝试将rnorm结果放入数据框中,但这真的会减慢速度。使用这种方式创建它们,然后使用cbind非常快。 Male.Distrib是我完整的数据框,包含7135个观测值,但这段代码应该可以在我之前发布的缩减版本上运行(未经测试)。

Min_bca <- ((.5*min(Male.AddSugar$IntakeAmt))^Lambda.Value-1)/Lambda.Value
Test <- Male.Distrib[rep(seq.int(1,nrow(Male.Distrib)), 100), 1:ncol(Male.Distrib)]
RnormOutput <- rnorm(nrow(Test),0,1)
Male.Final <- cbind(Test,RnormOutput)
Male.Final$mc_bca    <- Male.Final$FixedEff + (Male.Final$stddev_u2 *     Male.Final$RnormOutput)
Male.Final$temp      <- ifelse(Lambda.Value*Male.Final$mc_bca+1 > Lambda.Value*Min_bca+1,
                           Lambda.Value*Male.Final$mc_bca+1, Lambda.Value*Min_bca+1)
Male.Final$ginv_a    <- Male.Final$temp^(1/Lambda.Value)
Male.Final$d2ginv_a  <- ifelse(0 > (1-Lambda.Value)*Male.Final$temp^(1/Lambda.Value-2),
                           0, (1-Lambda.Value)*Male.Final$temp^(1/Lambda.Value-2))
Male.Final$mc_amount <- Male.Final$ginv_a + Male.Final$d2ginv_a * Male.Resid.Var / 2

今日课程:

  • 如果您尝试执行我之前的操作,则似乎无法在循环中重新采样分布函数。
  • 您不能像我尝试的那样使用max(),因为它返回列中的最大值,而我想要的是两个值中的最大值。替代该方法的语句是ifelse

我怀疑这可以通过使用replicate和矩阵+数组数学运算来将其简化为一行代码,以便一次性处理所有观测数据。不过,你能否提供一个小的可重现示例,这样我们就可以给出更具体的建议? - John Colby
3
使用 rbind() 函数来增加对象的成本很高。您可以在开始时创建一个空的数据框(例如,用虚拟变量填充它),然后在循环中填充它,这样做会更好。 - Sacha Epskamp
除了@SachaEpskamp所说的之外,内部循环是不必要的。你使用的所有函数都是矢量化的,要充分利用这一点。 - Joshua Ulrich
2
我同意@JohnColby的观点。我认为要么使用复制功能,要么使用“boot”包中的boot函数来完成工作,这样做可能比你现在所做的更具统计学意义。 - IRTFM
根据您想要的max(0,...)的功能,您可以选择使用max(0,...,na.rm=TRUE)或分别测试(1-Lambda.Value)temp组件。 - Ben Bolker
问题在于反向转换方法,负值在转换后的值中是可以接受的,因为这些值与营养摄入量<1有关。我会查看启动包,看看是否能帮助解决问题。说实话,我认为问题的很大一部分在于原始数据中存在一些愚蠢的低值-最小值为0.000145。任何小于等于1的值都会导致Box Cox转换出现问题。 - Michelle
1个回答

4
这里提供一种解决两个速度问题的方法:
  1. 不用循环计算观察值(i),而是一次性计算全部。
  2. 不用循环计算MC重复实验(j),而是使用replicate,它是一个简化的apply函数,专门用于此目的。
首先,我们加载数据集并定义一个函数来执行您之前在做什么。
Male.Distrib = read.table('MaleDistrib.txt', check.names=F)

getMC <- function(df, Lambda.Value=0.4, Male.Resid.Var=12.1029420429778) {
  u2        <- df$stddev_u2 * rnorm(nrow(df), mean = 0, sd = 1)
  mc_bca    <- df$FixedEff + u2
  temp      <- Lambda.Value*mc_bca+1
  ginv_a    <- temp^(1/Lambda.Value)
  d2ginv_a  <- max(0,(1-Lambda.Value)*temp^(1/Lambda.Value-2))
  mc_amount <- ginv_a + d2ginv_a * Male.Resid.Var / 2
  mc_amount
}

然后我们将其复制多次。

> replicate(10, getMC(Male.Distrib))
         [,1]      [,2]     [,3]     [,4]      [,5]     [,6]     [,7]     [,8]     [,9]    [,10]
[1,] 36.72374 44.491777 55.19637 23.53442 23.260609 49.56022 31.90657 25.26383 25.31197 20.58857
[2,] 29.56115 18.593496 57.84550 22.01581 22.906528 22.15470 29.38923 51.38825 13.45865 21.47531
[3,] 61.27075 10.140378 75.64172 28.10286  9.652907 49.25729 23.82104 31.77349 16.24840 78.02267
[4,] 49.42798 22.326136 33.87446 14.00084 25.107143 25.75241 30.20490 33.14770 62.86563 27.33652
[5,] 53.45546  9.673162 22.66676 38.76392 30.786100 23.42267 28.40211 35.95015 43.75506 58.83676
[6,] 34.72440 23.786004 63.57919  8.08238 12.636745 34.11844 14.88339 21.93766 44.53451 51.12331

然后,您可以重新格式化、添加ID等操作,但这是主要计算部分的想法。祝您好运!

注意,用“replicate”替换外部循环只是装饰性的——那里没有速度增益。速度增益来自避免使用“rbind”和逐元素操作。 - Ben Bolker
那里唯一可能导致 NaN 结果的操作是将负数 (temp) 提高到分数幂 (1/Lambda.Value, 1/Lambda.Value-2)。发布 Male.Distribsummary 结果? - Ben Bolker
哎呀,你确实发布了 str(就像summary一样好)。您的标准偏差为2.65,因此您可以预期 u2 正常情况下会降至-5或-6,这可能会使 mc_bca <0 - 尽管它需要<(-2.5)才能使temp为负。无论如何,我认为这是您应该寻找的方向... - Ben Bolker
@BenBolker 不错的提示!我自己从未探究过。 - John Colby
@Michelle 很好,我很高兴它有帮助。关于NaNs,这也可能是尝试像DWin建议的预构建软件包之一的更多原因,因为通常这些常见问题已经得到解决。 - John Colby
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接