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

17

这句话稍微有点难表述,就我所看到的,类似的问题都没有回答我的困惑。

我有一个数据框,如下所示:

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()可能会有所帮助,但不确定如何在这种情况下应用它

我不关心行的顺序

干杯!

编辑:更新了更清晰的期望输出。之前提交的答案可能不太清晰


所以,如果特定“id”的所有值都为0,您想要添加仅包含0的行? - Ronak Shah
只有当特定ID的所有值都为NA时才返回。 - Robert Hickman
1
@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
9个回答

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

2
似乎这里最健壮的答案。使用replace(val, all(is.na(val)) * 1, 0)代替ifelse(...)会稍微更加简洁。 - Mikko Marttila
@MikkoMarttila 好建议。通常我会尽量避免使用 ifelse - Sotos

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

1
@markus,没错,我本来就以为那是目标。谢谢! - Julius Vainora
看起来 op 希望保留第一行,并将该行的 val 列替换为 0,其中对于一组而言,所有 val 都是 NA。请检查我的答案。同意 @markus,这似乎很棘手。 - Vivek Kalyanarangan
1
@VivekKalyanarangan,这是我最初的想法,但“我想要摆脱所有NA值”表明情况并非如此。 - Julius Vainora

2
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

5
这对包含NA和非NAid是否适用?尝试使用 df1 <- data.frame(id = rep(c("a", "b"), each = 2), val = c(NA, 1, 2, 3)) 进行测试。 - markus
我认为这是目前为止最好的(我会再开放一个小时左右来观察)也许会更改为 df %>% replace(is.na(.), 0) %>% .[!(duplicated(.$id) & .$val == 0), ] - Robert Hickman

1
基本的R选项是查找所有NA的组,并通过将它们的val更改为0来转换它们,然后只选择唯一的行,以便每个组仅有一行。我们使用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

1
这里还有一个选项:
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(.))

This yields:

 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

3
4来自哪里? - Sotos
该解决方案产生四个0。我们只对得到1感兴趣? - NelsonGon
如果一个组有4个,另一个组有3个怎么办? - Sotos
抱歉,我只是根据问题回答了。也许我们可以改变一下,但不确定! - NelsonGon
考虑这个例子 - 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 想要仅删除 A 组中的 NA 值,而不是其余组。 - Vivek Kalyanarangan
加上我还不确定OP的意图。似乎每个人都对问题有不同的解释。 - NelsonGon

1
将“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

1

另一种基本方法,不保留行的顺序,并利用因子记住丢失的值:

df1 <- na.omit(df1)

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

我个人更喜欢Sotos提出的dplyr方法,因为我不喜欢将数据框rbind在一起,所以这只是个人口味问题,但在我看来并不难理解。很容易通过使用unique(df1$id)变量来适应字符id列。

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 发布,使用 Vivek Kalyanarangan's answer 中的数据集以及使用 markus' comment 中的数据集,分别重命名为 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)

Rui,请尝试使用df1 <- data.frame(id = rep(c("a", "b"), each = 2), val = c(NA, 1, 2, 3))。不幸的是,你的解决方案无法返回只有三行的数据框。 - markus
@markus 不是这样的。NA 被替换为 0,而 val 的另一个值不是 NA,所以两个值都必须在输出中。至少这就是我理解 OP 的问题的方式。 - Rui Barradas

0
可以尝试这个:
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的行,当相应的idval全部为NA时,并添加新行与此idval = 0
在这个例子中,id = a

注意:cval也有NA,但是所有对应于cval都不是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

获取所有具有相应 val = NAid 列表。

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

创建一个新的数据框,其中 idall_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

希望这能有所帮助,欢迎进行编辑 :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接