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

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

Ваше здоровье!

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

Значит, вы хотите добавить строки с 0, только если все значения для конкретного id равны 0?

Ronak Shah 03.01.2019 13:47

только если все они NA для определенного идентификатора

Robert Hickman 03.01.2019 13:49

@RobertHickman Кажется, есть некоторая путаница в отношении желаемого результата. Не могли бы вы обновить свой вопрос, указав ожидаемый результат на основе этого 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))? Спасибо @VivekKalyanarangan за данные.

markus 03.01.2019 14:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
17
3
1 203
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Базовый вариант R - найти группы со всеми NA и их 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
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

Будет ли это работать для id, которые содержат NA и не NA? Попробуйте с df1 <- data.frame(id = rep(c("a", "b"), each = 2), val = c(NA, 1, 2, 3))

markus 03.01.2019 14:05

Я думаю, что это лучший вариант (я оставлю его открытым еще на час или около того, чтобы посмотреть), возможно, изменится на df%>% replace (is.na (.), 0)%>%. [! (Дублировано (. $ id) &. $ val == 0),]

Robert Hickman 03.01.2019 14:26

Вот базовое решение 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 могло быть следующим. Он был протестирован с исходным набором данных, опубликованным OP, с набором данных в Ответ Вивека Калянарангана и с набором данных в комментарий Маркуса, переименованным в 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)

Руи, попробуй с df1 <- data.frame(id = rep(c("a", "b"), each = 2), val = c(NA, 1, 2, 3)). К сожалению, ваше решение не возвращает фрейм данных только с тремя строками.

markus 03.01.2019 14:21

@markus Нет, это не так. NA заменяется на 0, а другое значение val не является NA, поэтому оба значения должны присутствовать на выходе. По крайней мере, так я понимаю проблему OP.

Rui Barradas 03.01.2019 15:05

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

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

@markus, да, я предполагал, что это цель. Спасибо!

Julius Vainora 03.01.2019 14:19

Похоже, op хочет сохранить первую строку и заменить столбец val этой строки на 0, где всеval - это NA для группы. Проверьте мои ответы, пожалуйста. Согласитесь с @markus, это действительно сложно

Vivek Kalyanarangan 03.01.2019 14:27

@VivekKalyanarangan, я так изначально думал, но "и я хочу избавиться от всех ценностей NA" говорит об обратном.

Julius Vainora 03.01.2019 14:29

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

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

откуда взялось 4?

Sotos 03.01.2019 14:22

Решение дает четыре нуля. Нас интересует только 1?

NelsonGon 03.01.2019 14:23

Что, если в одной группе 4, а в другой 3?

Sotos 03.01.2019 14:26

Извините, я ответил только на вопрос. Может быть, тогда мы сможем кое-что изменить, хотя не уверен!

NelsonGon 03.01.2019 14:27

Рассмотрим этот пример - 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)), я думаю, здесь OP хочет удалить значения NA только для группы A, а не для остальных

Vivek Kalyanarangan 03.01.2019 14:28

Плюс еще не уверен, в чем намерение ОП. Кажется, все по-разному истолковали вопрос.

NelsonGon 03.01.2019 14:41

Изменен 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
Ответ принят как подходящий

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

(+1) Кажется, здесь самый надежный ответ. Было бы несколько более кратко, используя replace(val, all(is.na(val)) * 1, 0) вместо ifelse(...).

Mikko Marttila 03.01.2019 15:30

@MikkoMarttila Хорошее предложение. Я обычно стараюсь избегать ifelse в целом

Sotos 03.01.2019 15:35

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

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

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

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

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

Другие вопросы по теме