Заменить все значения NA для переменной одной строкой, равной 0

Слегка сложно сформулировать, поскольку, насколько я видел, ни один из подобных вопросов не ответил на мою проблему.

У меня есть data.frame, такой как:

df1 <- data.frame(id = rep(c("a", "b","c"), each = 4),
                  val = c(NA, NA, NA, NA, 1, 2, 2, 3,NA,2,NA,3))

df1

   id val
1   a  NA
2   a  NA
3   a  NA
4   a  NA
5   b   1
6   b   2
7   b   2
8   b   3
9   c  NA
10  c   2
11  c  NA
12  c   3

и я хочу избавиться от всех значений NA (достаточно просто с помощью, например, filter()), но убедитесь, что, если это удаляет все одно значение id (в этом случае он удаляет каждый экземпляр "a"), что одна дополнительная строка вставлен из (например) а = 0

чтобы:

  id val
1  a   0
2  b   1
3  b   2
4  b   2
5  b   3
6  c   2
7  c   3

очевидно, достаточно легко сделать это окольным путем, но мне было интересно, есть ли аккуратный/элегантный способ сделать это. Я думал, что tidyr :: complete() может помочь, но не совсем уверен, как применить его к случаю, подобному этому

Я не забочусь о порядке строк

Ура!

редактировать: обновлено с более четким желаемым выводом может сделать желаемые ответы, представленные до этого, немного менее ясными

Ответ 1

Еще одна идея с использованием dplyr,

library(dplyr)

df1 %>% 
 group_by(id) %>% 
 mutate(val = ifelse(row_number() == 1 & all(is.na(val)), 0, val)) %>% 
 na.omit()

который дает,

# A tibble: 5 x 2
# Groups:   id [2]
  id      val
  <fct> <dbl>
1 a         0
2 b         1
3 b         2
4 b         2
5 b         3

Ответ 2

Мы можем сделать

df1 %>% group_by(id) %>% do(if(all(is.na(.$val))) replace(.[1, ], 2, 0) else na.omit(.))
# A tibble: 5 x 2
# Groups:   id [2]
#   id      val
#   <fct> <dbl>
# 1 a         0
# 2 b         1
# 3 b         2
# 4 b         2
# 5 b         3

После группировки по id, если все в val равно NA, тогда мы оставляем только первую строку со вторым элементом, замененным на 0, в противном случае те же данные возвращаются после применения na.omit.

В более читаемом формате, который будет

df1 %>% group_by(id) %>% 
  do(if(all(is.na(.$val))) data.frame(id = .$id[1], val = 0) else na.omit(.))

(Здесь я предполагаю, что вы действительно хотите избавиться от всех значений NA; в противном случае нет необходимости в na.omit.)

Ответ 3

df1[is.na(df1)] <- 0
df1[!(duplicated(df1$id) & df1$val == 0), ]

  id val
1  a   0
5  b   1
6  b   2
7  b   2
8  b   3

Ответ 4

Опция Base R состоит в том, чтобы найти группы со всеми NA и transform их, изменив их значение val на 0 и выбрать только unique строки, чтобы в каждой группе была только одна строка. Мы rbind этот rbind с группами, которые являются !all_NA.

all_NA <- with(df1, ave(is.na(val), id, FUN = all))
rbind(unique(transform(df1[all_NA, ], val = 0)), df1[!all_NA, ])

#  id val
#1  a   0
#5  b   1
#6  b   2
#7  b   2
#8  b   3

dplyr выглядит некрасиво, но одним из способов является создание двух групп фреймов данных, одна из которых содержит группы всех значений NA а другая - группы всех значений, отличных от NA. Для групп со всеми значениями NA мы добавляем строку с ее id и val как 0 и привязываем это к другой группе.

library(dplyr)

bind_rows(df1 %>%
            group_by(id) %>%
            filter(all(!is.na(val))), 
          df1 %>%
             group_by(id) %>%
             filter(all(is.na(val))) %>%
             ungroup() %>%
             summarise(id = unique(id), 
                       val = 0)) %>%
arrange(id)


#   id      val
#  <fct> <dbl>
#1  a         0
#2  b         1
#3  b         2
#4  b         2
#5  b         3

Ответ 5

Вот вариант тоже:

df1 %>% 
  mutate_if(is.factor,as.character) %>% 
 mutate_all(funs(replace(.,is.na(.),0))) %>% 
  slice(4:nrow(.))

Это дает:

 id val
1  a   0
2  b   1
3  b   2
4  b   2
5  b   3

Альтернатива:

df1 %>% 
  mutate_if(is.factor,as.character) %>% 
 mutate_all(funs(replace(.,is.na(.),0))) %>% 
  unique()

ОБНОВЛЕНИЕ, основанное на других требованиях: некоторые пользователи предложили провести тестирование на этом фрейме данных. Конечно, этот ответ предполагает, что вы посмотрите на все вручную. Может быть менее полезным, если вы должны смотреть на все "рукой", но здесь идет речь:

df1 <- data.frame(id = rep(c("a", "b","c"), each = 4), val = c(NA, NA, NA, NA, 1, 2, 2, 3,NA,2,NA,3))


df1 %>% 
  mutate_if(is.factor,as.character) %>% 
  mutate(val=ifelse(id=="a",0,val)) %>% 
  slice(4:nrow(.))

Это дает:

 id val
1  a   0
2  b   1
3  b   2
4  b   2
5  b   3
6  c  NA
7  c   2
8  c  NA
9  c   3

Ответ 6

Изменен df чтобы сделать пример более исчерпывающим -

df1 <- data.frame(id = rep(c("a", "b","c"), each = 4),
                  val = c(NA, NA, NA, NA, 1, 2, 2, 3,NA,2,NA,3))
library(dplyr)
df1 %>%
  group_by(id) %>%
  mutate(case=sum(is.na(val))==n(), row_num=row_number() ) %>%
  mutate(val=ifelse(is.na(val)&case,0,val)) %>%
  filter( !(case&row_num!=1) ) %>%
  select(id, val)

Выход

  id      val
  <fct> <dbl>
1 a         0
2 b         1
3 b         2
4 b         2
5 b         3
6 c        NA
7 c         2
8 c        NA
9 c         3

Ответ 7

Другой базовый подход, который не поддерживает порядок строк и использует факторы, запоминающие потерянные значения:

df1 <- na.omit(df1)

df1 <- rbind(
  df1, 
  data.frame(
    id  = levels(df1$id)[!levels(df1$id) %in% df1$id], 
    val = 0)
  )

Лично я предпочитаю подход dplyr, предложенный Sotos, так как мне не нравится, когда rbind -ing data.frames воссоединяется, так что это дело вкуса, но это не слишком сложно для моего глаза. Достаточно легко адаптироваться к столбцу id символов с unique(df1$id) переменной.

Ответ 8

Вот базовое решение R.

res <- lapply(split(df1, df1$id), function(DF){
  if(anyNA(DF$val)) {
    i <- is.na(DF$val)
    DF$val[i] <- 0
    DF <- rbind(DF[i & !duplicated(DF[i, ]), ], DF[!i, ])
  }
  DF
})
res <- do.call(rbind, res)
row.names(res) <- NULL
res
#  id val
#1  a   0
#2  b   1
#3  b   2
#4  b   2
#5  b   3

Редактировать.

dplyr решение может быть следующим. Он был протестирован с исходным набором данных, размещенным ОП, с набором данных в ответе Вивека Калянарангана и с набором данных в комментарии df2, переименованными в df2 и df3 соответственно.

library(dplyr)

na2zero <- function(DF){
  DF %>%
    group_by(id) %>%
    mutate(val = ifelse(is.na(val), 0, val),
           crit = val == 0 & duplicated(val)) %>%
    filter(!crit) %>%
    select(-crit)
}

na2zero(df1)
na2zero(df2)
na2zero(df3)

Ответ 9

Можно попробовать это:

df1 = data.frame(id = rep(c("a", "b","c"), each = 4),
                  val = c(NA, NA, NA, NA, 1, 2, 2, 3,NA,2,NA,3))
df1
#   id val
#1   a  NA
#2   a  NA
#3   a  NA
#4   a  NA
#5   b   1
#6   b   2
#7   b   2
#8   b   3
#9   c  NA
#10  c   2
#11  c  NA
#12  c   3

Задача состоит в том, чтобы удалить все строки, соответствующие любому id IFF val для соответствующего id - все NA и добавить новую строку с этим id и val = 0.
В этом примере id = a.

Примечание: val для c также имеет NA но все val соответствующие c, не являются NA поэтому нам нужно удалить соответствующую строку для c где val = NA.

Итак, давайте создадим еще один столбец, скажем, val2 который указывает на 0 означает все NA и 1 в противном случае.

library(dplyr)

df1 = df1 %>% 
     group_by(id) %>%
     mutate(val2 = if_else(condition = all(is.na(val)),true = 0, false =  1))
df1

# A tibble: 12 x 3
# Groups:   id [3]
#   id      val  val2
#   <fct> <dbl> <dbl>
#1 a        NA     0
#2 a        NA     0
#3 a        NA     0
#4 a        NA     0
#5 b         1     1
#6 b         2     1
#7 b         2     1
#8 b         3     1
#9 c        NA     1
#10 c        2     1
#11 c       NA     1
#12 c        3     1

Получить список id с соответствующим val = NA для всех.

all_na = unique(df1$id[df1$val2 == 0])

Затем удалите id из df1 с val = NA.

df1 = na.omit(df1)
df1
# A tibble: 6 x 3
# Groups:   id [2]
# id      val  val2
# <fct> <dbl> <dbl>
# 1 b         1     1
# 2 b         2     1
# 3 b         2     1
# 4 b         3     1
# 5 c         2     1
# 6 c         3     1

И создайте новый фрейм данных с id в all_na и val = 0

all_na_df = data.frame(id = all_na, val = 0) 
all_na_df
# id val
# 1  a   0

затем объедините эти два кадра данных.

df1 = bind_rows(all_na_df, df1[,c('id', 'val')])
df1

#    id val
# 1  a   0
# 2  b   1
# 3  b   2
# 4  b   2
# 5  b   3
# 6  c   2
# 7  c   3

Надеюсь, это поможет, и правки приветствуются :-)