Более эффективные способы использовать R, чем циклы for

Я относительный новичок в R, поэтому извините, если есть очевидный ответ на это. Я посмотрел на другие вопросы, и я думаю, что "применить" является ответом, но я не могу понять, как использовать его в этом случае.

У меня есть продольный опрос, где участники приглашаются каждый год. В некоторые годы они не принимают участия, а иногда умирают. Мне нужно определить, какие участники приняли участие в последовательной "полосе" с момента начала опроса (то есть, если они останавливаются, они останавливаются навсегда).

Я сделал это с помощью цикла for, который отлично работает в приведенном ниже примере. Но у меня много лет и много участников, и цикл очень медленный. Есть ли более быстрый подход, который я мог бы использовать?

В примере TRUE означает, что они участвовали в этом году. Цикл создает два вектора: "конечный год" за последний год, в котором они принимали участие, и "полоса", чтобы показать, выполнили ли они все годы до конечного года (т.е. случаи 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

Спасибо!

4 ответа

Решение

Мы могли бы использовать max.col а также rowSums как vectorized подход.

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

Если есть строки без TRUE значения, мы можем убедиться, что вернуть 0 для этой строки, умножив с двойным отрицанием rowSums, FALSE будет приведен к 0 и умножение на 0 возвращает 0 для этой строки.

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

Затем мы создаем столбец 'streak', сравнивая rowSums из колонок 2:6 с тем, что в "конце года"

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)

Циклы for не являются плохими по своей природе в R, но они медленны, если вы увеличиваете векторы итеративно (как вы делаете). Часто есть лучшие способы сделать что-то. Пример решения только с 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]})

Или вариант 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

Вот решение с dplyr а также tidyr,

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
Другие вопросы по тегам