【问题标题】:Creating a function to replace NAs from one data.frame with values from another创建一个函数以将一个 data.frame 中的 NA 替换为另一个 data.frame 中的值
【发布时间】:2011-12-01 23:35:40
【问题描述】:

我经常遇到需要将 data.frame 中的缺失值替换为其他一些处于不同聚合级别的 data.frame 的值的情况。因此,例如,如果我有一个充满县数据的 data.frame,我可能会将 NA 值替换为存储在另一个 data.frame 中的州值。在写了同样的merge...ifelse(is.na())yada yada 几十次之后,我决定分解并编写一个函数来执行此操作。

这是我做的,以及我如何使用它的示例:

fillNaDf <- function(naDf, fillDf, mergeCols, fillCols){
 mergedDf <- merge(naDf, fillDf, by=mergeCols)
 for (col in fillCols){
   colWithNas <- mergedDf[[paste(col, "x", sep=".")]]
   colWithOutNas <- mergedDf[[paste(col, "y", sep=".")]]
   k <- which( is.na( colWithNas ) )
   colWithNas[k] <- colWithOutNas[k]
   mergedDf[col] <- colWithNas
   mergedDf[[paste(col, "x", sep=".")]] <- NULL
   mergedDf[[paste(col, "y", sep=".")]] <- NULL
 }
 return(mergedDf)
}

## test case
fillDf <- data.frame(a = c(1,2,1,2), b = c(3,3,4,4) ,f = c(100,200, 300, 400), g = c(11, 12, 13, 14))
naDf <- data.frame( a = sample(c(1,2), 100, rep=TRUE), b = sample(c(3,4), 100, rep=TRUE), f = sample(c(0,NA), 100, rep=TRUE), g = sample(c(0,NA), 200, rep=TRUE) )
fillNaDf(naDf, fillDf, mergeCols=c("a","b"), fillCols=c("f","g") )

所以在我运行这个程序之后,我有一种奇怪的感觉,有人可能比我先解决了这个问题,并且以一种更优雅的方式解决了这个问题。这个问题有更好/更容易/更快的解决方案吗?另外,有没有办法消除我的函数中间的循环?那个循环就在那里,因为我经常在不止一列中替换 NA。而且,是的,该函数假定我们正在填充 from 的列名称相同,并且我们正在填充 to 的列名称相同,这同样适用于合并。

任何指导或重构都会有所帮助。

EDIT 12 月 2 日,我意识到我的示例中存在逻辑缺陷,我已修复。

【问题讨论】:

    标签: r na


    【解决方案1】:

    真是个好问题。

    这是data.table 解决方案:

    # Convert data.frames to data.tables (i.e. data.frames with extra powers;)
    library(data.table)
    fillDT <- data.table(fillDf, key=c("a", "b"))
    naDT <- data.table(naDf, key=c("a", "b"))
    
    
    # Merge data.tables, based on their keys (columns a & b)
    outDT <- naDT[fillDT]    
    #      a b  f  g f.1 g.1
    # [1,] 1 3 NA  0 100  11
    # [2,] 1 3 NA NA 100  11
    # [3,] 1 3 NA  0 100  11
    # [4,] 1 3  0  0 100  11
    # [5,] 1 3  0 NA 100  11
    # First 5 rows of 200 printed.
    
    # In outDT[i, j], on the following two lines 
    #   -- i is a Boolean vector indicating which rows will be operated on
    #   -- j is an expression saying "(sub)assign from right column (e.g. f.1) to 
    #        left column (e.g. f)
    outDT[is.na(f), f:=f.1]
    outDT[is.na(g), g:=g.1]
    
    # Just keep the four columns ultimately needed   
    outDT <- outDT[,list(a,b,g,f)]
    #       a b  g   f
    #  [1,] 1 3  0   0
    #  [2,] 1 3 11   0
    #  [3,] 1 3  0   0
    #  [4,] 1 3 11   0
    #  [5,] 1 3 11   0
    # First 5 rows of 200 printed.
    

    【讨论】:

    • 酷。一些评论可能会帮助我理解它。看起来很简洁! :)
    • 好的——我评论了一下。如果您有兴趣了解更多信息,?data.table 的“示例”部分堪称典范,非常值得花大约 20 分钟的时间来完成。尤其是如果您是一个大数据专家——看起来你可能是——这真的值得前期投入时间。
    • 谢谢乔希。这真的很有帮助。
    • 现在应该更改为f:=i.fg:=i.g 吗?
    【解决方案2】:

    这是您方法的稍微更简洁/更强大的版本。您可以将 for 循环替换为对 lapply 的调用,但我发现循环更易于阅读。

    此函数假定mergeCols 中的任何列 都是公平的游戏来填充它们的 NA。我不确定这是否有帮助,但我会抓住选民的机会。

    fillNaDf.ju <- function(naDf, fillDf, mergeCols) {
      mergedDf <- merge(fillDf, naDf, by=mergeCols, suffixes=c(".fill",""))
      dataCols <- setdiff(names(naDf),mergeCols)
      # loop over all columns we didn't merge by
      for(col in dataCols) {
        rows <- is.na(mergedDf[,col])
        # skip this column if it doesn't contain any NAs
        if(!any(rows)) next
        rows <- which(rows)
        # replace NAs with values from fillDf
        mergedDf[rows,col] <- mergedDf[rows,paste(col,"fill",sep=".")]
      }
      # don't return ".fill" columns
      mergedDf[,names(naDf)]
    }
    

    【讨论】:

      【解决方案3】:

      我的偏好是从合并中提取执行匹配的代码并自己执行,这样我就可以保持原始数据帧的顺序不变,无论是按行还是按列。我还使用矩阵索引来避免任何循环,尽管这样做我用修改后的 fillCols 创建了一个新的数据框,并用它替换了原始的列;我以为我可以直接填写它,但显然你不能使用矩阵排序来替换 data.frame 的一部分,所以如果在某些情况下循环名称会更快,我不会感到惊讶。

      使用矩阵索引:

      fillNaDf <- function(naDf, fillDf, mergeCols, fillCols) {
        fillB <- do.call(paste, c(fillDf[, mergeCols, drop = FALSE], sep="\r"))
        naB <- do.call(paste, c(naDf[, mergeCols, drop = FALSE], sep="\r"))
        na.ind <- is.na(naDf[,fillCols])
        fill.ind <- cbind(match(naB, fillB)[row(na.ind)[na.ind]], col(na.ind)[na.ind])
        naX <- naDf[,fillCols]
        fillX <- fillDf[,fillCols]
        naX[na.ind] <- fillX[fill.ind]
        naDf[,colnames(naX)] <- naX
        naDf
      }
      

      有一个循环:

      fillNaDf2 <- function(naDf, fillDf, mergeCols, fillCols) {
        fillB <- do.call(paste, c(fillDf[, mergeCols, drop = FALSE], sep="\r"))
        naB <- do.call(paste, c(naDf[, mergeCols, drop = FALSE], sep="\r"))
        m <- match(naB, fillB)
        for(col in fillCols) {
          fix <- which(is.na(naDf[,col]))
          naDf[fix, col] <- fillDf[m[fix],col]
        }
        naDf
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-03
        • 1970-01-01
        相关资源
        最近更新 更多