用等于 0 的一行替换变量的所有 NA 值

Replace all NA values for variable with one row equal to 0

词组有点难,据我所见none个类似问题回答了我的问题。

我有一个 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 值(在这种情况下它删除了 [=28= 的每个实例]) 插入一个额外的行 (例如) 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

显然很容易以迂回的方式做到这一点,但我想知道是否有 tidy/elegant 的方法来做到这一点。我认为 tidyr::complete() 可能会有所帮助,但不完全确定如何将其应用于这种情况

我不关心行的顺序

干杯!

编辑:更新了更清晰的期望输出。可能会使之前提交的所需答案变得不太清楚

Base R 选项是通过将 val 更改为 0 和 select 仅 unique 行来查找包含所有 NAtransform 的组这样每组只有一行。我们 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 值的组,我们将其 idval 的行添加为 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

这是一个基本的 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 发布的原始数据集进行了测试,数据集在 and with the dataset in 中,分别重命名为 df2df3

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 %>% 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 %>% 
  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()

根据其他要求更新: 一些用户建议在此数据框上进行测试。当然,这个答案假设您会亲手查看所有内容。如果您必须按 "hand" 查看所有内容,可能用处不大,但这里有:

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

更改了 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

另一种基本方法,它不维护行的顺序并利用记住丢失值的因素:

df1 <- na.omit(df1)

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

我个人更喜欢 Sotos 给出的 dplyr 方法,因为我不喜欢 rbind-ing data.frames 重新组合在一起,所以这是一个品味问题,但这并不是难以忍受的复杂在我眼里。使用 unique(df1$id) 变量适应字符 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

Task是移除所有对应于任何id的行 IFF val for the corresponding id is all NA s 并使用 idval = 0.
添加新行 在这个例子中,id = a.

注意:cval也有NA,但是c对应的所有val都不是NA,因此我们需要删除 c where 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])

然后用 val = NA.

从数据帧 df1 中删除 id
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

并在 all_naval = 0

中创建一个包含 ids 的新数据框
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

希望这对您有所帮助,欢迎编辑:-)