【问题标题】:Replace all NA values for variable with one row equal to 0将变量的所有 NA 值替换为一行等于 0
【发布时间】:2019-05-30 01:22:21
【问题描述】:

有点难以表达,据我所知,没有一个类似的问题能回答我的问题。

我有一个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”的每个实例)那个插入额外的行(例如)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 行?
  • 仅当它们都是特定 id 的 NA 时
  • @RobertHickman 您想要的输出似乎有些混乱。您能否使用基于此 df1 &lt;- 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 dplyr na


【解决方案1】:

另一个使用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
【解决方案2】:

我们可以做

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 其中 all valNA团体。请检查我的答案。同意@markus,看起来确实很棘手
  • @VivekKalyanarangan,这就是我最初的想法,但“并且我想摆脱所有 NA 值”另有建议。
【解决方案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

【讨论】:

  • 这对包含NAs 和非NAs 的ids 有效吗?试试df1 &lt;- data.frame(id = rep(c("a", "b"), each = 2), val = c(NA, 1, 2, 3))
  • 我认为这是迄今为止最好的(我会再打开一个小时左右看看)可能会更改为 df %>% replace(is.na(.), 0) %>% .[!(duplicated(.$id) & .$val == 0), ]
【解决方案4】:

基本 R 选项是通过将 val 更改为 0 并仅选择 unique 行来查找包含所有 NAs 和 transform 的组,以便每个组只有一行。我们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

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

      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 列很容易。

      【讨论】:

        【解决方案7】:

        这里也有一个选项:

        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 是从哪里来的?
        • 解决方案产生四个 0。我们只对拥有 1 个感兴趣?
        • 如果一组有 4 个,另一个组有 3 个怎么办?
        • 对不起,我只根据问题回答。也许那时我们可以把事情搞砸,但不确定!
        • 考虑这个例子 - df1 &lt;- 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 值,而不是其余的
        【解决方案8】:

        这是一个基本的 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 answermarkus' 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 &lt;- data.frame(id = rep(c("a", "b"), each = 2), val = c(NA, 1, 2, 3))。不幸的是,您的解决方案没有返回只有三行的数据框。
        • @markus 不,它没有。 NA0 替换,val 的另一个值不是 NA,因此两者都必须在输出中。至少我是这样理解 OP 的问题的。
        【解决方案9】:

        大家可以试试这个:

        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对应的所有行@.
        在此示例中,id = a

        注意:val for c 也有 NAs 但所有与c 对应的val 不是NA 因此我们需要删除c 对应的行,其中val = NA .

        所以让我们创建另一个列,val2 表示 0 表示它的全部 NAs,否则为 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
        

        获取ids 的列表以及所有对应的val = NA

        all_na = unique(df1$id[df1$val2 == 0])
        

        然后用val = NA从数据框df1中删除ids。

        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
        

        希望这会有所帮助,并且非常欢迎编辑 :-)

        【讨论】:

          猜你喜欢
          • 2012-06-17
          • 2020-11-19
          • 2023-01-19
          • 2021-10-15
          • 2013-08-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多