Замена ведущего NAs по группам на 0, но сохраняйте другие NAs

#r #apply #user-defined-functions #lapply #na

Вопрос:

У меня есть фрейм данных COVID, сгруппированный по состояниям с 60 столбцами. Поскольку COVID начался в разное время в разных штатах, следовательно, для разных штатов существуют значения NAs до. Различные индикаторы (столбец 9) также содержат данные, начинающиеся по-разному. Ниже приведен образец df, который я сделал для демонстрации.

 state <- c(rep("A", 6), rep("B", 6))
time <- c(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6)
x1 <- c(NA, NA, NA, 4, 5, 6, NA, NA, 3, 4, 5, NA)
x2 <- c(NA, 2, 3, NA, 5, 6, NA, NA, NA, 4, 5, 6)
x3 <- c(NA, NA, 3, 4, 5, NA, NA, 2, NA, 4, 5, 6)
df <- data.frame(state, time, x1, x2, x3)
df

   state time x1 x2 x3
1      A    1 NA NA NA
2      A    2 NA  2 NA
3      A    3 NA  3  3
4      A    4  4 NA  4
5      A    5  5  5  5
6      A    6  6  6 NA
7      B    1 NA NA NA
8      B    2 NA NA  2
9      B    3  3 NA NA
10     B    4  4  4  4
11     B    5  5  5  5
12     B    6 NA  6  6
 

Я пытаюсь заменить все ведущие NAs на 0 для каждого штата, но сохранить другие NAs. Результаты должны выглядеть следующим образом:

    state time x1 x2 x3
1      A    1  0  0  0
2      A    2  0  2  0
3      A    3  0  3  3
4      A    4  4 NA  4
5      A    5  5  5  5
6      A    6  6  6 NA
7      B    1  0  0  0
8      B    2  0  0  2
9      B    3  3  0 NA
10     B    4  4  4  4
11     B    5  5  5  5
12     B    6 NA  6  6
 

Одно из решений, которое я придумал, состоит в том, чтобы заменить NAs условием совокупных сумм, как показано ниже:

 df1 <- df %>% 
  group_by(state) %>% 
  mutate(
    check.sum1 = cumsum(replace_na(x1, 0)),
    x1 = if_else(check.sum1 != 0, x1, 0),
    check.sum2 = cumsum(replace_na(x2, 0)),
    x2 = if_else(check.sum2 != 0, x2, 0),
    check.sum3 = cumsum(replace_na(x3, 0)),
    x3 = if_else(check.sum3 != 0, x3, 0)
  )
df1
 

Этот метод работал отлично. Но так как здесь 60 столбцов, я хочу завершить его функцией и/или использовать apply(). Но он выдает сообщения об ошибках:

 df2 <- df %>% 
  group_by(state) %>% 
  apply(
    df[3:5], MARGIN = 2, FUN = function(x) mutate(
      check.sum = cumsum(replace_na(x, 0)),
      x = if_else(check.sum != 0, x, 0)
    ) 
  )

Error in FUN(newX[, i], ...) : unused argument (df[3:5])

#or
func <- function(x) {
  mutate(
    check.sum = cumsum(replace_na(x, 0)),
    x = if_else(check.sum != 0, x, 0)
  )
}

df3 <- df %>% 
  group_by(state) %>% 
  apply(
    df[3:5], MARGIN = 2, func
  )

Error in match.fun(FUN) : 
  'df[3:5]' is not a function, character or symbol
 

Итак, есть три конкретных вопроса:

  1. Как создать пользовательские функции, используя столбцы в качестве аргументов.
  2. Как использовать функцию apply (). и
  3. Существуют ли какие-либо другие способы использования функций выхода, таких как na.locf() или na.trim() для выполнения этой работы?

Спасибо!

Ответ №1:

Использование by и поиск, где столбец is.na и NA не повторяется, т. е. логические diff значения меньше или равны нулю.

 do.call(rbind, by(df, df$state, (x) {
  x[] <- lapply(x, (z) {z[is.na(z) amp; c(0, diff(is.na(z))) <= 0] <- 0; z})
  return(x)
}))
#      state time x1 x2 x3
# A.1      A    1  0  0  0
# A.2      A    2  0  2  0
# A.3      A    3  0  3  3
# A.4      A    4  4 NA  4
# A.5      A    5  5  5  5
# A.6      A    6  6  6 NA
# B.7      B    1  0  0  0
# B.8      B    2  0  0  2
# B.9      B    3  3  0 NA
# B.10     B    4  4  4  4
# B.11     B    5  5  5  5
# B.12     B    6 NA  6  6
 

Примечание: Пожалуйста, используйте обновление R>=4.1> для (x) обозначения функции или записи function(x) .

Ответ №2:

Используя dplyr , мы можем сделать

 library(dplyr)
df %>%
    group_by(state) %>% 
    mutate(across(starts_with('x'), ~ replace(., !cumsum(!is.na(.)), 0))) %>% 
    ungroup
# A tibble: 12 × 5
   state  time    x1    x2    x3
   <chr> <dbl> <dbl> <dbl> <dbl>
 1 A         1     0     0     0
 2 A         2     0     2     0
 3 A         3     0     3     3
 4 A         4     4    NA     4
 5 A         5     5     5     5
 6 A         6     6     6    NA
 7 B         1     0     0     0
 8 B         2     0     0     2
 9 B         3     3     0    NA
10 B         4     4     4     4
11 B         5     5     5     5
12 B         6    NA     6     6