Заменить все значения NA для переменной одной строкой, равной 0 - PullRequest
0 голосов
/ 03 января 2019

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

У меня есть 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»), из которого вставляется одна дополнительная строка (например,) 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 () может помочь, но не совсем уверен, как применить его к случаю, подобному этому

Меня не волнует порядок строк

Cheers!

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

Ответы [ 9 ]

0 голосов
/ 03 января 2019

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

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).

0 голосов
/ 08 января 2019

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

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 s и 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 s из кадра данных 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 s в 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

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

0 голосов
/ 03 января 2019

Изменено 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
0 голосов
/ 03 января 2019

Еще одна идея с использованием 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
0 голосов
/ 03 января 2019

Здесь также есть опция:

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
0 голосов
/ 03 января 2019

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

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. Не требуется).

0 голосов
/ 03 января 2019

Вот базовое решение 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 и 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)
0 голосов
/ 03 января 2019
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
0 голосов
/ 03 января 2019
Опция

Base R состоит в том, чтобы найти группы со всеми NA s и transform их, изменив их val на 0 и выбрав только unique строки, чтобы в каждой группе была только одна строка. Мы 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
...