Сложно сформулировать, насколько я видел, ни один из подобных вопросов не дал ответа на мою проблему.
У меня есть 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: обновлен с более четким желаемым результатом. может сделать желаемые ответы, представленные до этого, менее ясными
только если все они NA для определенного идентификатора
@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 за данные.





Базовый вариант 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))
Я думаю, что это лучший вариант (я оставлю его открытым еще на час или около того, чтобы посмотреть), возможно, изменится на df%>% replace (is.na (.), 0)%>%. [! (Дублировано (. $ id) &. $ val == 0),]
Вот базовое решение 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 Нет, это не так. NA заменяется на 0, а другое значение val не является NA, поэтому оба значения должны присутствовать на выходе. По крайней мере, так я понимаю проблему OP.
Мы можем сделать
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, да, я предполагал, что это цель. Спасибо!
Похоже, op хочет сохранить первую строку и заменить столбец val этой строки на 0, где всеval - это NA для группы. Проверьте мои ответы, пожалуйста. Согласитесь с @markus, это действительно сложно
@VivekKalyanarangan, я так изначально думал, но "и я хочу избавиться от всех ценностей NA" говорит об обратном.
Вот и вариант:
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?
Решение дает четыре нуля. Нас интересует только 1?
Что, если в одной группе 4, а в другой 3?
Извините, я ответил только на вопрос. Может быть, тогда мы сможем кое-что изменить, хотя не уверен!
Рассмотрим этот пример - 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, а не для остальных
Плюс еще не уверен, в чем намерение ОП. Кажется, все по-разному истолковали вопрос.
Изменен 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(...).
@MikkoMarttila Хорошее предложение. Я обычно стараюсь избегать ifelse в целом
Другой базовый подход, который не поддерживает порядок строк и использует факторы, запоминающие потерянные значения:
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
Надеюсь, это поможет, и изменения приветствуются :-)
Значит, вы хотите добавить строки с 0, только если все значения для конкретного
idравны 0?