Более эффективные способы использовать 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