比 'for' 循环更有效地使用 R

More efficient ways to use R than 'for' loops

我是 R 的新手,所以如果有明显的答案,我很抱歉。我看过其他问题,我认为 'apply' 是答案,但我不知道在这种情况下如何使用它。

我有一个纵向调查,每年都会邀请参与者。有些年份他们没有参加,有时甚至死亡。我需要确定哪些参与者从调查开始就一直参与 'streak'(即,如果他们停止,他们将永远停止)。

我用 '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

谢谢!

这是 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

这是一个基本版本,使用 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

For 循环在 R 中并不是天生的坏,但如果你迭代地增长向量(就像你正在做的那样),它们会很慢。通常有更好的方法来做事。仅具有应用功能的解决方案示例:

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]})

或选项 2,基于@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

我们可以使用 max.colrowSums 作为 vectorized 方法。

dat$finalyear <- max.col(dat[-1], 'last')

如果有行没有 TRUE 值,我们可以通过乘以 rowSums 的双重否定来确保该行的 return 0。 FALSE 将被强制为 0 并与该行的 0 returns 0 相乘。

dat$finalyear <- max.col(dat[-1], 'last')*!!rowSums(dat[-1])

然后,我们通过比较 2:6 列的 rowSums 和 'finalyear'

列来创建 '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)