比'for'循环更高效的R语言使用方法

7

我对R语言还比较陌生,如果有显而易见的答案,请见谅。我查看了其他问题,我认为“apply”是答案,但我无法弄清如何在这种情况下使用它。

我有一份纵向调查,参与者每年都会受邀。有些年份他们未能参加,有时他们会去世。我需要确定自调查开始以来连续参与的参与者(即如果他们停止参与,他们就永远停止了)。

我使用了一个“for”循环来完成此操作,在下面的示例中运行良好。但是我有很多年份和很多参与者,循环速度非常慢。是否有更快的方法可用?

在这个例子中,TRUE表示他们在那一年参加了调查。该循环创建两个向量 - “finalyear”表示他们最后一年参加的调查,而“streak”则表示他们是否在最后一年之前完成了所有年份(即情况1、3和5)。

dat <- data.frame(ids = 1:5, "1999" = c(T, T, T, F, T), "2000" = c(T, F, T, F, T), "2001" = c(T, T, T, T, T), "2002" = c(F, T, T, T, T), "2003" = c(F, T, T, T, F))
finalyear <- NULL
streak <- NULL
for (i in 1:nrow(dat)) {
    x <- as.numeric(dat[i,2:6])
    y <- max(grep(1, x))
    finalyear[i] <- y
    streak[i] <- sum(x) == y
}
dat$finalyear <- finalyear
dat$streak <- streak

谢谢!


有很多答案 - 有人想要创建一个更大的数据集并对它们进行基准测试吗?数据集有多大,这样可以制作代表性的测试集以进行基准测试? - Spacedman
大约有250,000个案例和25年的历史。以下所有答案都解决了我的问题 - 感谢大家!如果有人感兴趣,我可以制作一个代表性数据集来测试不同的方法。 - Dan Lewer
4个回答

4

在R语言中,for循环并不是本质上的问题,但如果您迭代地增长向量(就像您正在做的那样),它们会很慢。通常有更好的方法来完成任务。这里是一个只使用apply函数的解决方案的示例:

dat$finalyear <- apply(dat[,2:6],MARGIN=1,function(x){max(which(x))})
dat$streak <-  apply(dat[,2:7],MARGIN=1,function(x){sum(x[1:5])==x[6]})

或者选择第二个选项,基于@Spacedman的评论:
dat$finalyear <- apply(dat[,2:6],MARGIN=1,function(x){max(which(x))})
dat$streak <-  apply(dat[,2:6],MARGIN=1,function(x){max(which(x))==sum(x)})

> dat
  ids X1999 X2000 X2001 X2002 X2003 finalyear streak
1   1  TRUE  TRUE  TRUE FALSE FALSE         3   TRUE
2   2  TRUE FALSE  TRUE  TRUE  TRUE         5  FALSE
3   3  TRUE  TRUE  TRUE  TRUE  TRUE         5   TRUE
4   4 FALSE FALSE  TRUE  TRUE  TRUE         5  FALSE
5   5  TRUE  TRUE  TRUE  TRUE FALSE         4   TRUE

整洁,但要注意它依赖于finalyear直接添加在true/false数据之后,在这种情况下是在第7列。 - Spacedman
谢谢。我有疑问是否应该这样做,还是调用max(which(x))两次。会进行编辑。 - Heroka

4
我们可以使用max.colrowSums作为向量化的方法。
dat$finalyear <- max.col(dat[-1], 'last')

如果有行中没有TRUE的值,我们可以通过将其与rowSums的双重否定相乘来确保返回该行的0。 FALSE会被强制转换为0,并且乘以0会使该行返回0。
dat$finalyear <- max.col(dat[-1], 'last')*!!rowSums(dat[-1])

然后,我们通过比较第2到6列的rowSums与'finalyear'的rowSums来创建“streak”列。

dat$streak <-  rowSums(dat[,2:6])==dat$finalyear
dat
#   ids X1999 X2000 X2001 X2002 X2003 finalyear streak
#1   1  TRUE  TRUE  TRUE FALSE FALSE         3   TRUE
#2   2  TRUE FALSE  TRUE  TRUE  TRUE         5  FALSE
#3   3  TRUE  TRUE  TRUE  TRUE  TRUE         5   TRUE
#4   4 FALSE FALSE  TRUE  TRUE  TRUE         5  FALSE
#5   5  TRUE  TRUE  TRUE  TRUE FALSE         4   TRUE

或者可以使用 @ColonelBeauvel 建议的一行代码(它可以放在一行中,但决定通过两行来使其更加明显)

library(dplyr)
mutate(dat, finalyear=max.col(dat[-1], 'last'), 
            streak=rowSums(dat[-1])==finalyear)

1
最佳简明向量化答案。+1 - Colonel Beauvel
2
使用以下一行代码:mutate(dat, finalyear=max.col(dat[-1], 'last'), streak=rowSums(dat[-1])==finalyear) - Colonel Beauvel

3

这里有一个使用dplyrtidyr的解决方案。

gather(data = dat,year,value,-ids) %>%
  mutate(year=as.integer(gsub("X","",year))) %>%
  group_by(ids) %>%
  summarize(finalyear=last(year[value]),
            streak=!any(value[first(year):finalyear] == FALSE))

输出

  ids finalyear streak
1   1      2001   TRUE
2   2      2003  FALSE
3   3      2003   TRUE
4   4      2003  FALSE
5   5      2002   TRUE

1
这是一个基础版本,使用apply循环遍历行,并使用rle查看状态更改的频率。您的条件似乎等价于状态从TRUE开始,并且只能最多更改为FALSE一次,因此我测试rle是否短于3且第一个值为TRUE:
> dat$streak = apply(dat[,2:6],1,function(r){r[1] & length(rle(r)$length)<=2})
> 
> dat
  ids X1999 X2000 X2001 X2002 X2003 streak
1   1  TRUE  TRUE  TRUE FALSE FALSE   TRUE
2   2  TRUE FALSE  TRUE  TRUE  TRUE  FALSE
3   3  TRUE  TRUE  TRUE  TRUE  TRUE   TRUE
4   4 FALSE FALSE  TRUE  TRUE  TRUE  FALSE
5   5  TRUE  TRUE  TRUE  TRUE FALSE   TRUE

可能有很多方法可以计算finalyear,这个方法只是找到每行最后一个为TRUE的元素:

> dat$finalyear = apply(dat[,2:6], 1, function(r){max(which(r))})
> dat
  ids X1999 X2000 X2001 X2002 X2003 streak finalyear
1   1  TRUE  TRUE  TRUE FALSE FALSE   TRUE         3
2   2  TRUE FALSE  TRUE  TRUE  TRUE  FALSE         5
3   3  TRUE  TRUE  TRUE  TRUE  TRUE   TRUE         5
4   4 FALSE FALSE  TRUE  TRUE  TRUE  FALSE         5
5   5  TRUE  TRUE  TRUE  TRUE FALSE   TRUE         4

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