在研究中为多个受试者运行R脚本

3

我是一名动物行为学的硕士生,R语言的初学者。在为我的论文做统计分析时,我决定在使用PSPP之前尝试一下R,并参加了一个小型的R语言入门课程,使用了R Studio。

我写了一个非常基础的脚本,其中:

  1. 从csv文件中导入了51个个体的数据,并且还导入了一个csv文件来存储结果。
  2. 创建了一个子集,包含一个个体的数据,然后根据数据提出假设,运行二项检验并判断假设是否被拒绝。每个个体的假设、二项检验的p值以及假设的接受与否都被存储在结果文件中。

当我为每个个体运行它时,这个脚本是完全可用的。我很抱歉代码质量不好,但如果您想查看,请看这里的一个个体脚本:

## Import data and results file
odor_behavior <- read.csv("odor_behavior.csv")
percalf_binomial_odor_results <- read.csv("percalf_binomial_odor_results.csv")

# Create binomial dataset per calf (example here: 958)
pref958 <- subset(odor_behavior, Calf_ID == 958, c("Preference"), drop=FALSE)

# Count the number of testing sessions for this calf
n958 <- nrow(pref958)

# Calculate how many times a suckling behavior appears (Control=1 or Modified=3).
mat958 <- as.matrix(pref958)
fmat958 <- factor(mat958,levels=c(1,3),ordered=TRUE)
fpref958 <- table(fmat958)
data958 <- as.data.frame(fpref958)

# Hypothesize a preference according to the observations and save it. 
# Also, save variables for the binomial test.
## Find the row in which to store the results
row958<- which(percalf_binomial_odor_results$Calf_ID == 958)
## Decide the hypothesis. 
  if (data958[1,2] > data958[2,2]) {
    x958 <- data958[1,2]
    percalf_binomial_odor_results[row958,c("H_preference")] <- "Control"
  } else if (data958[1,2] < data958[2,2]) {
    x958 <- data958[2,2]
    percalf_binomial_odor_results[row958,c("H_preference")] <- "Odor"
  } else if (data958[1,2] == data958[2,2]) {
    x958 <- data958[1,2]
    percalf_binomial_odor_results[row958,c("H_preference")] <- "Either"
  } else {print("Check your data, Maria!")
  }


# Run binomial test
binom.test (x=x958, n=n958, p=0.5, conf.level = 0.95)

# Save the p.value and decide whether the hypothesized preference is real or not
pbin958 <- binom.test (x=x958, n=n958, p=0.5, conf.level = 0.95)$p.value
percalf_binomial_odor_results[row958,c("Bin_odor_pvalue")] <- pbin958
  if (pbin958 <0.05) {
    percalf_binomial_odor_results[row958,c("Real_preference")] <- "Yes"
  } else {
    percalf_binomial_odor_results[row958,c("Real_preference")] <- "No"
  }

如上所述,当我复制并粘贴代码并手动更改“Calf_ID”五十次时,每个人的脚本都能顺利运行。
问题:是否可能让脚本自动运行每个人?我尝试通过将所有51个“Calf_ID”放入矩阵中,并将脚本放入使用矩阵元素依次处理每个个体的函数中来实现。
Lapply返回正确的结果,但仅涉及假设是否被接受/拒绝(即脚本的最后一部分),这使我认为脚本确实为所有51个个体运行。然而,在过程中没有将任何结果存储在相应的文件中(很可能是因为我一开始就做错了某些事情)。当我进行手动复制-粘贴/编辑过程时,所有结果(每个个体的假设、p值和假设结果)都会正确地存储在结果文件中。
我能够通过复制-粘贴方法获得我的结果;现在只是一个效率问题,因为我有51个个体,但如果有一千个该怎么办?知道如何使它工作将是非常好的!
编辑: 根据评论的要求,这里是对958号个体运行脚本的结果:
dput(head(odor_behavior)):
structure(list(Calf_ID = c(958L, 958L, 958L, 958L, 958L, 958L), Preference = c(1L, 1L, 3L, 1L, 1L, 3L)), .Names = c("Calf_ID", "Preference"), row.names = c(NA, 6L), class = "data.frame")

dput(head(percalf_binomial_odor_results)):
structure(list(Calf_ID = c(958L, 7015L, 7017L, 959L, 7018L, 7019L), H_preference = c("Control", NA, NA, NA, NA, NA), Bin_odor_pvalue = c(0.453125, NA, NA, NA, NA, NA), Real_preference = c("No", NA, NA, NA, NA, NA)), .Names = c("Calf_ID", "H_preference", "Bin_odor_pvalue", "Real_preference"), row.names = c(NA, 6L), class = "data.frame")

更新
这里有另一个关于同一主题的例子,基于@alexis_laz为初始脚本提供的答案。在这种情况下,我正在导入一个由3列(Calf_ID、Control_time、Odor_time)组成的数据集,其中包括给定Calf ID的多个控制和气味时间测量值(就像在初始示例中的偏好一样)。该脚本尝试为每个Calf_ID的Control_time - Odor_time对进行Wilcoxon符号秩检验。以下是脚本的外观:

    ### Import data and load library with test
odor_times <- read.csv("odor_times.csv")
library(coin)

### Create a function that examines suckling preference
prefanalysis <- function(Control_time, Odor_time)
{
  sessions <- length(Control_time)

  #Run a Wilcoxon signed rank test. Set y ~ x as control and odor, 
  mypvalue <- pvalue(wilcoxsign_test(Control_time ~ Odor_time))
  myzvalue <- statistic(wilcoxsign_test(Control_time ~ Odor_time))

  #Decide if there is a true difference in suckling times according to p value, and save it
  true_diff <- if(mypvalue < 0.05) "Yes" else "No"

  # Create a data frame with hypothesis, p value and real preference
  data.frame(sessions, myzvalue, mypvalue, true_diff, stringsAsFactors = FALSE)
}

### Run the function for all calves, with preference depending on calf ID. Save results.
results <- as.data.frame(do.call(data.frame, aggregate(cbind(Control_time, Odor_time) ~ Calf_ID, odor_times, prefanalysis)))

在这种情况下,我收到以下错误信息:
     Error in eval(expr, envir, enclos) : 
  argument "Odor_time" is missing, with no default

当使用调试模式重新运行时,我可以看到脚本开始正确读取第一个个体(即具有特定Calf_ID的行)的Control_time。然而,它似乎无法对Odor_time执行相同操作。我无法理解自己在做什么错误的事情。

1
创建函数并使用lapply调用它们可能是解决问题的关键。为了获得一些可重现的数据,您可以分享dput(head(odor_behavior))dput(head(percalf_binomial_odor_results))的结果([编辑]您的问题以添加它们)。 - Tensibai
1
RStudio有一个“提取函数”,对于这个例子来说效果还不错。基本上,你可以使用f <- function(id) {# 在这里编写代码,将“958”替换为“id”},最后一行就是返回的内容,所以也许对于你来说它是list(p.value = pbin958, results = percalf_binomial_odor_results)。然后使用lapply - rawr
1
看起来你的脚本执行了一个操作,类似于(不详细说明你的脚本)aggregate(Preference ~ Calf_ID, odor_behavior, function(x) table(factor(x, levels = c(1, 3), labels = c("control", "modified")))) -- 也就是说,你通过分组应用了一个函数。因此,你可以构建一个函数 - 比如说 f - 它接受“Preferences”并返回结果,并将其用作 aggregate(Preference ~ ID, odor_behavior, f) 或任何类似的分组函数(例如 tapplybylapply(split(what, by), f))或具有这种功能的软件包。 - alexis_laz
1个回答

4

看起来你正在对每个"Calf_ID"分组的"Preference"应用计算。我们可以在首先构建一个适当输入的函数后,利用R的分组函数,而不是重复地为每个id子集odor_behavior$Preference

我尝试在一个函数中简化你的脚本——希望我没有错过难以适应的细节:

ff = function(pref)
{
    fx = factor(pref, levels = c(1L, 3L), labels = c("Control", "Odor"))
    tab = table(fx)

    p = binom.test(max(tab), length(pref), p = 0.5, conf.level = 0.95)$p.value

    real_pref = if(p < 0.05) "yes" else "no"
    H_pref = if(tab["Control"] == tab["Odor"]) "Either" else names(which.max(tab))

    data.frame(p = p, real_pref = real_pref, H_pref = H_pref, stringsAsFactors = FALSE)
}

然后,将其应用于所有“Calf_ID”个体:
do.call(data.frame, aggregate(pref ~ ID, dat, ff))
#   ID     pref.p pref.real_pref pref.H_pref
#1   1 0.07295139             no     Control
#2   2  0.1689778             no     Control
#3   3  0.8919232             no        Odor
#4   4  0.6029232             no        Odor
#5   5          1             no     Control
#6   6  0.7659918             no     Control
#7   7          1             no        Odor
#8   8   0.889884             no     Control
#9   9          1             no      Either
#10 10  0.5758493             no     Control

其中 dat 为:

set.seed(1821)
dat = data.frame(ID = sample(10, 500, TRUE), pref = sample(c(1L, 3L), 500, TRUE))

谢谢@alexis_laz,我现在用我的自己的数据进行了测试,结果非常完美。我添加了几行代码,将结果放入一个数据框并保存为csv文件。 - Maria Malidaki
1
@MariaMalidaki:很高兴我能帮到你。这属于“长”数据的一般“分割-应用-合并”概念。你可以查看?aggregate?split中的链接/示例,以及像“data.table”和“dplyr”这样的包,它们在当前主题上被广泛使用。顺便说一句,需要注意的是,与for循环不同,lapply没有副作用,因此在lapply内部分配变量不如简单循环直接。例如,比较x = numeric(5); for(i in 1:length(x)) x[i] <- i; xx = numeric(5); lapply(1:length(x), function(i) x[i] <- i); x - alexis_laz
在同一主题上,我尝试编辑您的优秀代码,使其适用于函数中的两个变量,这次是为了Wilcoxon检验。如果您有任何想法,我已经更新了帖子以防我可能做错了什么。不必有压力,您已经提供了足够的帮助。 - Maria Malidaki
1
@MariaMalidaki: aggregate 对于具有多个参数的函数不够灵活。您可以使用其工作马:splitlapply。在这里,应该是类似于 do.call(rbind, lapply(split(odor_times, odor_times$Calf_ID), function(x) data.frame(id = x$Calf_ID[1], prefanalysis(x$Control_time, x$Odor_time)))) 的东西,其中 odor_times 基于 "Calf_ID" 进行分离,并且 prefanalysis 分别应用于每个子-"data.frame" 的适当列。 - alexis_laz
谢谢@alexis_laz。我正准备询问是否可以使用split,你证实了我的想法! - Maria Malidaki

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