Заменить все значения 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() может помочь, но не совсем уверен, как применить его к случаю, подобному этому
Я не забочусь о порядке строк
Ура!
редактировать: обновлено с более четким желаемым выводом. может сделать желаемые ответы, представленные до этого, немного менее ясными
9 ответов
Еще одна идея с использованием 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
Мы можем сделать
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
.)
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
Изменил 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
Вот вариант тоже:
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
Опция Base 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 <- na.omit(df1)
df1 <- rbind(
df1,
data.frame(
id = levels(df1$id)[!levels(df1$id) %in% df1$id],
val = 0)
)
Я лично предпочитаю подход dplyr, предложенный Sotos, так как мне не нравится rbind
-в данных.фреймы обратно вместе, так что это дело вкуса, но это не невыносимо сложно для моего глаза. Достаточно легко адаптироваться к персонажу 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
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
в 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
Надеюсь, это поможет, и правки приветствуются:-)
Вот базовое решение 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)