【问题标题】:Fill missing values with previous values by row using dplyr使用 dplyr 按行用以前的值填充缺失值
【发布时间】:2022-05-15 18:40:09
【问题描述】:

我正在使用R 中的一个数据框,其中跨行缺少一些值。接下来是数据框(最后添加dput):

df
  id V1 V2 V3 V4
1 01  1  1  1 NA
2 02  2  1 NA NA
3 03  3  1 NA NA
4 04  4  1  2 NA

每一行都是不同的id。如您所见,行有缺失值。我想知道我如何才能以这种风格完成数据框不使用 reshape 来 long 或 pivot 因为我的真实数据非常大

df
  id V1 V2 V3 V4
1 01  1  1  1  1
2 02  2  1  1  1
3 03  3  1  1  1
4 04  4  1  2  2

我试图从tidyr 使用fill,但在行级别我遇到了问题。我看过一些帖子,它与dplyr 函数across 一起使用,但我找不到它。我尝试过使用group_by(id)rowwise,但没有成功。此外,只有以 V 开头的变量/列应填充以前的值。

接下来是数据:

#Data
df <- structure(list(id = c("01", "02", "03", "04"), V1 = c(1, 2, 3, 
4), V2 = c(1, 1, 1, 1), V3 = c(1, NA, NA, 2), V4 = c(NA, NA, 
NA, NA)), class = "data.frame", row.names = c(NA, -4L))

非常感谢您的宝贵时间。

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    一种解决方案可能是使用包zoo 中的na.locf 函数与pmap 函数在逐行操作中结合使用。 na.locf 采用最新的非NA 值并用它替换所有即将出现的NA 值。正如提醒c(...) 在这两种解决方案中在每次迭代的每一行中捕获V1:V4 的所有值一样。但是,我在两者中都排除了id 列,因为它不参与我们的计算。

    library(zoo)
    library(purrr)
    
    df %>%
      mutate(pmap_df(., ~ na.locf(c(...)[-1])))
    
      id V1 V2 V3 V4
    1 01  1  1  1  1
    2 02  2  1  1  1
    3 03  3  1  1  1
    4 04  4  1  2  2
    

    或者我们可以使用来自dplyrcoalesce 函数。我们可以用最后一个非NA 值替换每一行中的每个NA 值,就像我们之前用na.locf 所做的那样。但是这个解决方案有点冗长:

    df %>%
      mutate(pmap_df(., ~ {x <- c(...)[!is.na(c(...))]; 
      coalesce(c(...), x[length(x)])}))
    
      id V1 V2 V3 V4
    1 01  1  1  1  1
    2 02  2  1  1  1
    3 03  3  1  1  1
    4 04  4  1  2  2
    

    或者你也可以使用这个:

    library(purrr)
    
    df %>%
      mutate(across(!id, ~ replace(., is.na(.), invoke(coalesce, rev(df[-1])))))
    
      id V1 V2 V3 V4
    1 01  1  1  1  1
    2 02  2  1  1  1
    3 03  3  1  1  1
    4 04  4  1  2  2
    

    可以忽略警告消息。它实际上是因为我们有 6 个 NA 值而产生的,但是在每个向量上应用 coalesce 的结果是 1 个元素,导致 4 个元素替换 6 个插槽。

    【讨论】:

    • 如果是pmap,那一定是Anoush。干得好
    • 哈哈哈 亲爱的朋友,你真是太好了。
    • 谢谢曼恩!你真是太好了:)
    • 看看这里。你要出名了! stackoverflow.com/questions/68913278/…>
    • 来吧,不要拿我开玩笑,你们两个都非常好:D
    【解决方案2】:

    使用dplyr 的一个选项可能是:

    df %>%
     mutate(across(-id, ~ ifelse(is.na(.), coalesce(!!!select(., V4:V1)), .)))
    
      id V1 V2 V3 V4
    1  1  1  1  1  1
    2  2  2  1  1  1
    3  3  3  1  1  1
    4  4  4  1  2  2
    

    【讨论】:

    • 亲爱的托马斯,请告诉我大爆炸运算符在这里做什么?我试过了,显然把值变成了FALSE
    • !!! 或大帮派操作员所做的是拼接列表的元素,这意味着每个元素都成为一个参数。
    • 我一直称它为“bang bang bang”运算符,但“big bang”要好得多。
    • @IanCampbell 我正在广泛使用它,但从未想过在这种情况下使用它。
    【解决方案3】:

    dplyr 方法

    df <- structure(list(id = c("01", "02", "03", "04"), V1 = c(1, 2, 3, 
                                                                4), V2 = c(1, 1, 1, 1), V3 = c(1, NA, NA, 2), V4 = c(NA, NA, 
                                                                                                                     NA, NA)), class = "data.frame", row.names = c(NA, -4L))
    
    
    library(dplyr, warn.conflicts = F)
    
    df %>% mutate(across(V1:V4, ~ coalesce(., tail(cur_data()[-1][!is.na(cur_data()[-1])],1))))
    #>   id V1 V2 V3 V4
    #> 1 01  1  1  1  2
    #> 2 02  2  1  2  2
    #> 3 03  3  1  2  2
    #> 4 04  4  1  2  2
    

    如果您在id 列上使用group_by,则不必使用[-1] on cur_data()`

    df %>% group_by(id) %>%
      mutate(across(V1:V4, ~ coalesce(., tail(cur_data()[!is.na(cur_data())],1))))
    
    

    【讨论】:

      【解决方案4】:

      data.table 选项与nafill

      > setDT(df)[, setNames(as.list(nafill(unlist(.SD), type = "locf")), names(.SD)), id]
         id V1 V2 V3 V4
      1: 01  1  1  1  1
      2: 02  2  1  1  1
      3: 03  3  1  1  1
      4: 04  4  1  2  2
      

      【讨论】:

        【解决方案5】:

        如果您想要避免重塑的原因是为了节省运行时间,那么如果下面的基准测试继续保持大规模,那么这个想法实际上是错误的。请注意,转置后使用 na.locf 然后转回的 f 是最快的。

        library(microbenchmark)
        library(data.table)
        library(dplyr)
        library(purrr)
        library(zoo)
        
        microbenchmark(times = 10,
          a = df %>% mutate(pmap_df(., ~ na.locf(c(...)[-1]))),
          b = df %>%
            mutate(pmap_df(., ~ {x <- c(...)[!is.na(c(...))]; 
            coalesce(c(...), x[length(x)])})),
          c = df %>%
            mutate(across(-id, ~ ifelse(is.na(.), coalesce(!!!select(., V4:V1)), .))),
          d = df %>% mutate(across(V1:V4, ~ coalesce(., tail(cur_data()[-1][!is.na(cur_data()[-1])],1)))),
          e = as.data.table(df)[, setNames(as.list(nafill(unlist(.SD), type = "locf")), names(.SD)), id],
          f = data.frame(id = df$id, t(na.locf(t(df[-1])))))
        

        给予:

        Unit: milliseconds
         expr       min        lq      mean    median        uq       max neval
            a 11.343302 12.934702 15.032001 13.115151 14.799400 30.135901    10
            b 11.641301 13.116401 14.030551 14.426751 15.012701 15.517501    10
            c 28.201501 30.470801 33.375761 32.672950 36.671101 40.448701    10
            d 25.394901 26.648801 30.044331 27.971251 32.433801 39.570600    10
            e  3.750801  4.023700  8.771401  4.150701  4.367502 50.636700    10
            f  2.454701  2.458201  3.009181  2.603951  2.952302  6.126101    10
        

        【讨论】:

          猜你喜欢
          • 2015-10-06
          • 1970-01-01
          • 1970-01-01
          • 2020-12-07
          • 2017-08-09
          • 1970-01-01
          • 2019-09-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多