【问题标题】:How to replace NA with most recent non-NA by group? [duplicate]如何按组用最近的非 NA 替换 NA? [复制]
【发布时间】:2016-08-21 10:36:24
【问题描述】:

我有一个包含一些不完整和重复特征的个体的 DF,如下所示:

    name <- c("A", "A", "B", "B", "B", "C", "D", "D")
    age <- c(28,NA,NA,NA,NA,NA,53,NA)
    birthplace <- c("city1",NA, "city2",NA,NA,NA,NA,NA)
    value <- 100:107
    df <- data.frame(name,age,birthplace,value)

    name age birthplace value
1    A  28      city1   100
2    A  NA       <NA>   101
3    B  NA      city2   102
4    B  NA       <NA>   103
5    B  NA       <NA>   104
6    C  NA       <NA>   105
7    D  53       <NA>   106
8    D  NA       <NA>   107

由于行的值是唯一的。我想用这样的人的详细信息来完成每一行:

       name age birthplace value
    1    A  28      city1   100
    2    A  28      city1   101
    3    B  NA      city2   102
    4    B  NA      city2   103
    5    B  NA      city2   104
    6    C  NA       <NA>   105
    7    D  53       <NA>   106
    8    D  53       <NA>   107

我尝试使用

library(zoo)
library(dplyr)
df <- df %>% group_by(name) %>% na.locf(na.rm=F)

但是效果不是很好。按组实现功能有什么想法吗?

【问题讨论】:

  • @alistaire the question you point to ask for a dplyr solution (即使答案偏离了那个),而这里没有指定约束。
  • @MartinMorgan 问题是,但不是答案,包括 base、zoo 单独、data.table 等。答案没有功能差异; dplyr 只是问题中使用的语法。

标签: r


【解决方案1】:

作为另一种基本 R 解决方案,这里是一个穷人的 na.locf

fill_down <- function(v) {
    if (length(v) > 1) {
        keep <- c(TRUE, !is.na(v[-1]))
        v[keep][cumsum(keep)]
    } else v
}

按组填充,方法是使用tapply()拆分并应用于每个组,split&lt;-将组组合到原始几何体,如

fill_down_by_group <- function(v, grp) {
    ## original 'by hand':
    ##     split(v, grp) <- tapply(v, grp, fill_down)
    ##     v
    ## done by built-in function `ave()`
    ave(v, grp, FUN=fill_down)
}

要处理多列,可能

elts <- c("age", "birthplace")
df[elts] <- lapply(df[elts], fill_down_by_group, df$name)

注意事项

  1. 我有兴趣了解 dplyr 解决方案如何处理许多列,而无需对每个列进行硬编码?回答我自己的问题,我想这是

    library(dplyr); library(tidyr)
    df %>% group_by(name) %>% fill_(elts)
    
  2. 当组已经“分组”(例如,identical(grp, sort(grp)))时,更有效的基本解决方案是

    fill_down_by_grouped <- function(v, grp) {
        if (length(v) > 1) {
            keep <- !(duplicated(v) & is.na(v))
            v[keep][cumsum(keep)]
        } else v
    }
    
  3. 对我来说,fill_down() 在大约 10M 元素的向量上需要大约 225 毫秒; fill_down_by_grouped() 需要约 300 毫秒,与组数无关; fill_down_by_group() 与组数成比例; 10000组~2s,10M组约36s

【讨论】:

  • 这是我第一次看到split&lt;-。真是好东西。
  • 我没有阅读 OP,但看起来 ave*_by_group 的替代品:lapply(df[elts], function(x) ave(x, df$name, FUN = fill_down)) ?
  • @Frank 是的,ave() 是个不错的选择,但我一直忘记,谢谢。
  • 感谢您的解决方案。不过用两个函数来解决问题就有点夸张了吧?
【解决方案2】:

也可以是:

library(dplyr)
library(tidyr)
df %>% group_by(name) %>% fill(age, birthplace)

# Source: local data frame [8 x 4]
# Groups: name [4]

#     name   age birthplace value
#   <fctr> <dbl>     <fctr> <int>
# 1      A    28      city1   100
# 2      A    28      city1   101
# 3      B    NA      city2   102
# 4      B    NA      city2   103
# 5      B    NA      city2   104
# 6      C    NA         NA   105
# 7      D    53         NA   106
# 8      D    53         NA   107

【讨论】:

  • 方便:fill(everything())
  • @alistaire 一如既往,一个更简洁的答案。
【解决方案3】:

您可以将na.locf 包裹在do

df %>% group_by(name) %>% do(na.locf(., na.rm = FALSE))

【讨论】:

  • do() 强制转换为字符;也许mutate(age=na.locf(age, na.rm=FALSE), birthplace=na.locf(birthplace, na.rm=FALSE))
  • 我认为我们应该使用这个df %&gt;% group_by(name) %&gt;% mutate_each(funs(na.locf(.,na.rm = FALSE)))
  • 新版本:df %&gt;% group_by(name) %&gt;% mutate_all(zoo::na.locf, na.rm = FALSE) 或者像 Psidom 的方法一样使用tidyr::fill
【解决方案4】:

根据您接下来要做什么,您可能更喜欢嵌套形式的数据。

(nested <- df %>% 
  group_by(name) %>% 
  summarize(
    age = na.omit(age)[1], 
    birthplace = na.omit(birthplace)[1], 
    value = list(value)
  )
)
## # A tibble: 4 x 4
##     name   age birthplace     value
##   <fctr> <dbl>     <fctr>    <list>
## 1      A    28      city1 <int [2]>
## 2      B    NA      city2 <int [3]>
## 3      C    NA         NA <int [1]>
## 4      D    53         NA <int [2]>

如果您需要对单个 values 进行计算,您可以随时取消嵌套。

nested %>% tidyr::unnest()
## # A tibble: 8 x 4
##     name   age birthplace value
##   <fctr> <dbl>     <fctr> <int>
## 1      A    28      city1   100
## 2      A    28      city1   101
## 3      B    NA      city2   102
## 4      B    NA      city2   103
## 5      B    NA      city2   104
## 6      C    NA         NA   105
## 7      D    53         NA   106
## 8      D    53         NA   107

【讨论】:

    【解决方案5】:

    这是一个基本的 R 解决方案:

    do.call(rbind,lapply(split(df, df$name), function(x) {
        tempdf <- x
        if (nrow(tempdf) > length(which(is.na(x$birthplace)))) {
            tempdf[which(is.na(x$birthplace)),c("age","birthplace")] <- tempdf[which(is.na(x$birthplace))[1]-1,c("age","birthplace")]
        }
        return(tempdf)
    }))
    

    输出:

     name age birthplace value
     A    28  city1      100  
     A    28  city1      101  
     B    NA  city2      102  
     B    NA  city2      103  
     B    NA  <NA>       104  
     C    NA  <NA>       105  
     D    53  <NA>       106  
     D    NA  <NA>       107 
    

    【讨论】:

      【解决方案6】:

      这是一个基本的 R 解决方案。 fill 函数使用 na.omit(x)[1] 调用 ave,就像在 Richie Cotton 的解决方案中一样。

      fill <- function(...) ave(..., FUN = function(x) na.omit(x)[1])
      transform(df, birthplace = fill(birthplace, name), age = fill(age, name))
      

      注意:这也适用于na.locf。将fill 替换为:

      library(zoo)
      fill <- function(...) ave(..., FUN = function(x) na.locf(x, na.rm = FALSE))
      

      【讨论】:

        【解决方案7】:

        您也可以通过合并来实现。只需在名称列上进行连接。然后按值进行分组。

        library(sqldf)
        sqldf('select t1.name, t2.age, t2.birthplace,t1.value from df t1 inner join df t2 on t1.name=t2.name group by t1.value')
        

        【讨论】:

          【解决方案8】:

          还可以考虑一个嵌套的应用基础解决方案,为每列运行滚动 head()

          df <- setNames(data.frame(lapply(names(df), function(d)
                         sapply(1:nrow(df), function(i)
                                head(df[df[1:i, c("name")] == df$name[i], c(d)], 1))
                  )), names(df))
          

          【讨论】:

            猜你喜欢
            • 2014-06-13
            • 2011-12-05
            相关资源
            最近更新 更多