Как использовать apply() или эквивалентную функцию для выполнения математических операций над текущими и смежными строками фрейма данных?

#r #dataframe #apply #sapply #tapply

#r #фрейм данных #применить #sapply #tapply

Вопрос:

Я выполняю простые математические операции по столбцам над строками фрейма данных, которые также включают доступ к соседним предыдущим строкам фрейма данных. Хотя приведенный ниже код работает, он громоздкий (по крайней мере, в отношении моего свободного использования cbind() subset() функций и), и мне интересно, есть ли простой способ получить те же результаты, используя apply() ту или иную супер-пупер-функцию R. По возможности в базе R.

Я добавляю и вычитаю значения столбцов в каждой строке фрейма данных (ссылаясь на столбцы ниже, «плюс 1» «плюс 2» — «минус» = «итого»), и если идентификационный номер совпадает с тем, что вы переходите от одной строки к следующей, добавляя значение plus1из предыдущей строки. Смотрите иллюстрацию ниже:

   id   plus1 plus2 minus total [total explained]
1  1     3     5    10    -2
2  2     4     5     9     0
3  3     8     5     8     5   [8   5 - 8 = 5, ignoring "plus1" in row 2 since "id" changed between rows 2 and 3]
4  3     1     4     7     6   [1   4 - 7,   8 from "plus1" col in row 3 since "id" is same in rows 3 and 4, = 6]
5  3     2     5     6     2   [2   5 - 6,   1 from "plus1" col in row 4 since "id" is same in rows 4 and 5, = 2]
6  5     3     6     5     4   [3   6 - 5 = 4, ignoring "plus1" in row 5 since "id" changed between rows 5 and 6]
 

Вот код, который я использовал для создания вышеупомянутого:

 data <- data.frame(id=c(1,2,3,3,3,5), 
                   plus1=c(3,4,8,1,2,3), 
                   plus2=c(5,5,5,4,5,6),
                   minus = c(10,9,8,7,6,5))

data <- cbind(data,
              tmp1=(data[ ,"plus1"]  
                    data[ ,"plus2"] -
                    data[ ,"minus"]
                   )
              )

grp <- with(rle(data$id), rep(seq_along(values), lengths))
data$tmp2 <- with(data,ave(plus1, grp, FUN = function(x) c(0, x[-length(x)])))

data <- cbind(data, total = round((data[ ,"tmp1"]   data[ ,"tmp2"]),2))
data <- subset(data, select = -c(tmp1,tmp2) )
data
 

Я стремлюсь к простоте в мире apply() , потому что я буду использовать множество производных такого рода в своем текущем проекте. Похоже, я имитирую Excel в R, которым я и являюсь.

Ответ №1:

Комментарии:

1. Да, я начинаю видеть свет с dplyr. Код, несомненно, выглядит интуитивно понятным, как для ответов dplyr от Питера, так и для Джо. Хотя пример base R, опубликованный Питером, тоже выглядит довольно простым. Я опасался полагаться на слишком много пакетов, но, похоже, dplyr используется всеми, кто имеет опыт работы с R.

2. В более крупной программе, в которой это будет использоваться, есть гораздо больше переменных для манипулирования, чем в OP. Это решение очень простое при работе со многими переменными.

Ответ №2:

Вот базовое решение R, но не с apply , поэтому может быть неприемлемым, хотя оно кажется менее сложным, чем код OP.

Не уверен, что семейство apply (извините за каламбур) применяется в этом случае, поскольку, насколько я понимаю, обычно функции применяются ко всем столбцам, или строкам, или элементам списка, тогда как в этом случае создается новая переменная. Однако я не очень хорошо знаком с использованием apply и friends, поэтому это может быть неверно.

Для сравнения добавили dplyr решение, хотя я знаю, что OP просил специально для базы R.

 
data$lag_id <- c(0, data$id[-nrow(data)])
data$lag_plus1 <- c(NA, data$plus1[-nrow(data)])

data$total <- with(data, ifelse(id == lag_id, plus1   plus2 - minus   lag_plus1, plus1   plus2 - minus))

data[ , -c(5:6)]
#>   id plus1 plus2 minus total
#> 1  1     3     5    10    -2
#> 2  2     4     5     9     0
#> 3  3     8     5     8     5
#> 4  3     1     4     7     6
#> 5  3     2     5     6     2
#> 6  5     3     6     5     4



library(dplyr)

data %>% 
  mutate(total = case_when(id == lag(id) ~ plus1   plus2 - minus   lag(plus1),
                          TRUE ~ plus1   plus2 - minus))
#>   id plus1 plus2 minus total
#> 1  1     3     5    10    -2
#> 2  2     4     5     9     0
#> 3  3     8     5     8     5
#> 4  3     1     4     7     6
#> 5  3     2     5     6     2
#> 6  5     3     6     5     4
 

Создано 2021-12-11 пакетом reprex (v2.0.1)