r语言 - 如何使用循环计算增加样本量的 P 值



我在创建 for 循环时遇到问题。 我想将样本数量从 1 增加到 200,并在每次新添加的观测值后计算 p 值。 因此,首先我样本 1 个观测值 - 计算第一个 p 值,然后样本 2 个观测值 - 计算第二个 p 值,然后是 3...最多 200 个观测值,以便我得到 200 个 p 值。
观测值将从数据框的一列中全部采样(带替换)。

假设数据框的列称为 data$column1。 样本数量从 1:200 开始,每"轮次"增加 1。

如何创建一个 for 循环,以便对于每个"轮次",再采样一个观测值并计算新的 p 值? 最后,我想绘制所有 p 值。

n <- 1:200
for i in length(n) {
sample(data$column1,n, replace = TRUE)
pvalue <- t.test(data$column1, alternative = "greater")
}

虽然我知道你可能想使用for循环,但这是使用sapplylapply的好机会。我将使用iris演示替代方案。虽然我将对所有样本使用"不等于 5"的简化测试iris$Sepal.Length,但您应该更新特定数据的alternative=和其他参数。

选择1:如果你只需要p值,我们可以捕获这个......或者我们可以捕获整个模型并对 p 值进行第二阶段检索。

选择 2:我们可以使用*apply函数之一,它读起来很好(一旦你更习惯了 R 矢量码),或者你可以坚持使用for循环。第一个选项具有可读性优势,尽管您可能更习惯for循环,在这种情况下,您应该真正预先分配列表/向量。(预定义长而空的列表/向量的原因:虽然您可以轻松地将向量outout <- c(out, newstuff)连接起来,但从长远来看,重复这样做效率非常低。我非常不鼓励"大规模"这样做。

在前面,一些注意事项:

  • 我为每个都使用set.seed(2),以便结果相同。除非/除非您需要严格的可重复性,否则不应使用它。通常不需要用于生产/学术报告。
  • 我做seq_len而不是2:length(...)是因为习惯模式:当以编程方式做事时,让它优雅地失败是件好事。如果出于某种原因,您将来使用1:length(nrow(x))并且x结果为 0 行,那么1:0会产生一个长度为 2 的向量,这是违反直觉的(并且几乎肯定会破坏后续代码)。相反,seq_len(0)生成长度为 2 的向量,这是一件好事。同样,这里不那么重要,但这是一个好习惯。(顺便说一句:seq_along(0)仍然输出长度为 1 的向量,因此也容易出现此问题。
  • 我用seq_len(...)[-1]丢弃了"1",因为无法完成具有单个基准面的 t 检验。一个人也可以做1 + seq_len(nrow(x)-1).

1:for循环,仅 p 值

set.seed(2)
out <- rep(NA, nrow(iris))
for (i in seq_len(nrow(iris))[-1]) {
thisdat <- sample(iris$Sepal.Length, size = i)
out[i] <- t.test(thisdat, mu = 5)$p.value
}
summary(out)
#      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.      NA's 
# 0.0000000 0.0000000 0.0000000 0.0080013 0.0000001 0.4156151         1 

(您可以假设所有后续示例out都是相同的,所以我不会展示它。

2.*apply,仅p值

set.seed(2)
out <- sapply(seq_len(nrow(iris))[-1], function(i) {
thisdat <- sample(iris$Sepal.Length, size = i)
t.test(thisdat, mu = 5)$p.value
})

sapply采用向量,通常返回以下之一:

  • vector如果所有返回值的长度都完全为 1;
  • matrix如果所有返回值都是长度完全相同的向量;或者
  • list任何其他时间。

正因为如此,一些程序员更喜欢lapply(总是返回list)或vapply(您必须声明您期望的返回值类型......当弹出其他内容时它会失败)。一种可能会做:

set.seed(2)
out <- vapply(seq_len(nrow(iris))[-1], function(i) {
thisdat <- sample(iris$Sepal.Length, size = i)
t.test(thisdat, mu = 5)$p.value
}, numeric(1))

(尝试将numeric(1)更改为numeric(2),您将看到values must be length 2, but FUN(X[[1]]) result is length 1的错误。

对于lapply选项,它与下面的第四种方法非常相似。

请注意,这里的length(out)将是nrow(iris)-1,因为我们在seq_len(nrow(iris))[-1]的输入向量上跳过它。这意味着从技术上讲,summary(out)会有所不同:不会有NA.所有数字在其他方面都是相等的。

3.for循环,完整模型

在这里,我们需要存储的不仅仅是一个数字,因此我们需要将其存储在list.

set.seed(2)
out <- vector("list", nrow(iris))
for (i in seq_len(nrow(iris))[-1]) {
thisdat <- sample(iris$Sepal.Length, size = i)
out[[i]] <- t.test(thisdat, mu = 5)
}
str(out[1:3])
# List of 3
#  $ : NULL
#  $ :List of 9
#   ..$ statistic  : Named num 1.31
#   .. ..- attr(*, "names")= chr "t"
#   ..$ parameter  : Named num 1
#   .. ..- attr(*, "names")= chr "df"
#   ..$ p.value    : num 0.416
#   ..$ conf.int   : num [1:2] -2.41 14.11
#   .. ..- attr(*, "conf.level")= num 0.95
#   ..$ estimate   : Named num 5.85
#   .. ..- attr(*, "names")= chr "mean of x"
#   ..$ null.value : Named num 5
#   .. ..- attr(*, "names")= chr "mean"
#   ..$ alternative: chr "two.sided"
#   ..$ method     : chr "One Sample t-test"
#   ..$ data.name  : chr "thisdat"
#   ..- attr(*, "class")= chr "htest"
#  $ :List of 9
#   ..$ statistic  : Named num 1.76
#   .. ..- attr(*, "names")= chr "t"
#   ..$ parameter  : Named num 2
#   .. ..- attr(*, "names")= chr "df"
#   ..$ p.value    : num 0.22
#   ..$ conf.int   : num [1:2] 3.61 8.33
#   .. ..- attr(*, "conf.level")= num 0.95
#   ..$ estimate   : Named num 5.97
#   .. ..- attr(*, "names")= chr "mean of x"
#   ..$ null.value : Named num 5
#   .. ..- attr(*, "names")= chr "mean"
#   ..$ alternative: chr "two.sided"
#   ..$ method     : chr "One Sample t-test"
#   ..$ data.name  : chr "thisdat"
#   ..- attr(*, "class")= chr "htest"

列表很长,但你可以看到(1)第一个元素是空的,这并不奇怪,因为我们跳过了1i;(2)之后的每个元素都包含你期望模型拥有的所有内容。

好的,让我们来看看。我们首先分配完整列表,然后像以前一样运行for循环。循环中唯一的区别是我们存储整个模型(需要out[[i]]而不是out[i]),而不仅仅是$p.value。现在,为了能够获得 p 值,我们可以使用for循环或sapply,我将演示后者:

head(sapply(out[-1], `[[`, "p.value"))
# [1] 0.41561507 0.22019340 0.05766889 0.08544124 0.03243253 0.09059092
# more verbose, same thing though, showing the "anonymous-function" definition
head(sapply(out[-1], function(m) m$p.value))

我用out[-1]因为我们知道第一个是空的。我们可以很容易地在上面的for循环之后立即完成out <- out[-1]

通过使用我上面演示的"匿名函数"定义,可以从模型中获取任何其他属性,例如模型系数。

4.*sapply,全型号

这可能不会让您感到惊讶。

set.seed(2)
out <- lapply(seq_len(nrow(iris))[-1], function(i) {
thisdat <- sample(iris$Sepal.Length, size = i)
out[[i]] <- t.test(thisdat, mu = 5)
})

如果你看一下这些,第一个元素不是空的(类似于上面的sapply例子),因为我们甚至没有为它运行或预分配。

然后,可以对单个列表元素执行任何操作:

out[[1]]$p.value
# [1] 0.4156151
str(out[[17]])
# List of 9
#  $ statistic  : Named num 3.98
#   ..- attr(*, "names")= chr "t"
#  $ parameter  : Named num 17
#   ..- attr(*, "names")= chr "df"
#  $ p.value    : num 0.000974
#  $ conf.int   : num [1:2] 5.48 6.57
#   ..- attr(*, "conf.level")= num 0.95
#  $ estimate   : Named num 6.03
#   ..- attr(*, "names")= chr "mean of x"
#  $ null.value : Named num 5
#   ..- attr(*, "names")= chr "mean"
#  $ alternative: chr "two.sided"
#  $ method     : chr "One Sample t-test"
#  $ data.name  : chr "thisdat"
#  - attr(*, "class")= chr "htest"
out[[19]]$statistic
#        t 
# 3.420489 

如果你想检索所有的检验统计量,类似于获取p值,你可以这样做:

head(sapply(out, `[[`, "statistic"))
#        t        t        t        t        t        t 
# 1.307692 1.761625 3.000000 2.273030 2.935307 2.014477 

@r2evans有一个很好的答案。我将只关注您的代码并尝试使其绘制。

改进包括:

  1. for语法for (i in seq_along(n))遍历每个 i。在您的情况下,您实际上想要执行for (i in 2:200)i==1因为将无法计算 p.值。
  2. 需要将数据样本分配给变量。照原样,什么也没发生。或者,您可以直接将sample语句放在t.test()调用中。
  3. 您希望将每个循环的结果保存为 pvalue。如果它按原样工作,pvalue最终会得到循环的最后一个值。

我喜欢apply系列,因为您不必明确地预先分配任何内容。

set.seed(1)
n <- 50
results <- sapply(seq(2, n)
, function(n) {
t.test(sample(iris$Sepal.Length, n, replace = T), mu = 5.5, alternative = 'greater')$p.value
})
plot(y = results, x = seq(2, n))

理论上,您需要做的就是用data$column1替换iris$Sepal.Length并使用您喜欢的任何n

最新更新