【问题标题】:Using one data.frame to update another使用一个 data.frame 更新另一个
【发布时间】:2011-12-19 18:25:24
【问题描述】:

给定 2 个在列名/数据类型方面相同的数据帧,其中一些列唯一标识行,是否有一种有效的函数/方法可以让一个数据帧“更新”另一个数据帧?

例如,在下面,originalreplacement'Name''Id' 标识。 goal 是在 original 中(通过唯一 ID)查找来自 replacement 的所有行并替换为 Value1Value2 的结果

original = data.frame( Name = c("joe","john") , Id = c( 1 , 2) , Value1 = c(1.2,NA), Value2 = c(NA,9.2) )
replacement = data.frame( Name = c("john") , Id = 2 , Value1 = 2.2 , value2 = 5.9)
goal = data.frame( Name = c("joe","john") , Id = c( 1 , 2) , Value1 = c(1.2,2.2), Value2 = c(NA,5.9) )

该解决方案应该适用于任意长度的originalreplacement(尽管replacement 的行数决不能超过original)。在实践中,我使用了 2 个 id 列。

【问题讨论】:

    标签: r indexing dataframe


    【解决方案1】:

    我会使用data.table 对象。此代码似乎适用于您的示例:

    library(data.table)
    
    # set keys
    original.dt <- data.table(original, key=c("Name", "Id"))        
    replacement.dt <- data.table(replacement, key=c("Name", "Id"))
    
    goal2 <- original.dt
    # subset and reassign
    # goal2[replacement.dt[, list(Name, Id)]] <- replacement.dt
    goal2[replacement.dt] <- replacement.dt  # cleaner and faster, see Matthew's comment
    
    goal2 <- as.data.frame(goal2)
    
    identical(goal, goal2) # FALSE, why? See Joris's comment
    all.equal(goal, goal2) # TRUE
    

    【讨论】:

    • 谢谢!到目前为止,我已经避免了对 data.table 的依赖,并且更喜欢使用基类的解决方案。将等待其他回复。
    • data.table 的 +1。至于为什么相同返回FALSE:在goal2中,Id是一个int向量,而在goal中这是一个num向量。
    • @JorisMeys ...str() 透露。谢谢!
    • 小改进:可以去掉[,list(Name,Id)]位。只是goal2[replacement.dt]&lt;-replacement.dt 更短更快,因为replacement.dt 的密钥已经是"Name,Id"i 不必键入,但如果键入,则连接 i 的键列,并在内部使用更快的合并算法,利用两个表都已排序的事实。
    【解决方案2】:

    这是一种使用digest 包的方法。

    library(digest)
    # generate keys for each row using the md5 checksum based on first two columns
    check1 <- apply(original[,1:2], 1, digest)
    check2 <- apply(replacement[,1:2], 1, digest)
    
    # set goal to original and replace rows in replacement
    goal <- original
    goal[check1 %in% check2,] <- replacement
    

    【讨论】:

    • 这是个好主意,但在性能方面会受到影响。您需要计算比简单地连接标识符要慢的摘要。
    【解决方案3】:
    # limit replacement to elements that have a correspondence in original 
    existing = replacement[is.element(replacement$Id, original$Id),]
    # replace original at positions where IDs from existing match   
    original[match(existing$Id,original$Id),]=existing
    

    【讨论】:

    • 嗯,is.element 很容易泛化(只需对两者的索引数组进行与运算),但我看不到匹配部分的直接方法。
    【解决方案4】:

    只需设置一个唯一的 ID 作为行名。然后就是简单的索引:

    rownames(original) = original$Id
    rownames(replacement) = replacement$Id
    
    original[rownames(replacement), ] = replacement
    

    【讨论】:

    • 如果存在具有相同 ID 的行怎么办?例如。你不能做 rownames(original)
    • 它们必须是独一无二的。例如 paste(idvar1, idvar2, ...)
    • 我的错,arrr。如果原来有好几个地方,用一个id替换,就不清楚应该从哪里替换到哪里
    • 但这里有两个标识符 - Id 和 Name。我认为这种方法不适用于非整数标识符
    • @SFun28,为什么不应该这样做?行名是字符串
    【解决方案5】:
    require(plyr)
    indexes_to_replace <- rownames(match_df(original,replacement,on='Id'))
    indexes_from_replace<-rownames(match_df(replacement,original,on='Id'))
    original[indexes_to_replace,] <- replacement[indexes_from_replace,]
    

    函数match_df 的参数on 也可以采用向量。

    【讨论】:

    • 这与 John Colby 的回答类似,但我认为由于调用 match_df 而慢了一点?
    【解决方案6】:

    使用base R,你可以使用下面的函数replace.df(),它是基于merge.data.frame()的源代码松散的。与其他一些解决方案相反,这个解决方案允许多列进行识别。我在工作中经常使用它。随意复制和使用。

    此函数控制 y 中的行在 x 中找不到的情况。请注意,该功能不会检查组合是否唯一。 match() 只会用第一次出现的组合替换第一次出现。

    函数使用如下:

    > replace.df(original, replacement,by=c('Name','Id'))
      Name Id Value1 Value2
    1  joe  1    1.2     NA
    2 john  2    2.2    9.2
    

    请注意,这可以有效地检测您在原始代码中的书写错误。 replacement 包含一个名为“value2”(小 v)而不是 Value2(大写 V)的变量。改正后,结果变成:

    > replace.df(original, replacement,by=c('Name','Id'))
      Name Id Value1 Value2
    1  joe  1    1.2     NA
    2 john  2    2.2    5.9
    

    您也可以使用该函数仅更改某些列中的值

    > replace.df(original, replacement,by=c('Name','Id'),cols='Value2')
      Name Id Value1 Value2
    1  joe  1    1.2     NA
    2 john  2     NA    5.9
    

    功能:

    replace.df <- function(x,y,by,cols=NULL
               ){
        nx <- nrow(x)
        ny <- nrow(y)
    
        bx <- x[,by,drop=FALSE]
        by <- y[,by,drop=FALSE]
        bz <- do.call("paste", c(rbind(bx, by), sep = "\r"))
    
        bx <- bz[seq_len(nx)]
        by <- bz[nx + seq_len(ny)]
    
        idx <- match(by,bx)
        idy <- match(bx,by)
        idy <- idy[!is.na(idy)]
    
        if(is.null(cols)) {
          cols <- intersect(names(x),names(y))
          cols <- cols[!cols %in% by]
        }
    
        x[idx,cols] <- y[idy,cols]
        x
      }
    

    【讨论】:

    • 对不起,我的意思是说“你没有发生”。修复了帖子。
    • @SFun28 :啊不,但我也许应该开始做那些事情。我还纠正了一个错字(最后几行中的 drop=FALSE 显然是错误的。我不太擅长从头顶复制功能......)
    • @TylerRinker:你看到我的更正了吗?倒数第二行中的 drop=FALSE 是垃圾,应该删除。它不是赋值函数的参数。我从头顶输入了函数,原来的函数在工作。因此犯了错误。
    • @Joris 现在它适用于除cols='Value2' 之外的所有内容,它会引发警告Error in [&lt;-.data.frame(*tmp, idx, cols, value = NULL) : replacement has length zero
    【解决方案7】:

    我制作了一个使用索引方法的函数(参见上面 John Colby 的回答)。希望它对使用来自另一个数据帧的值更新一个数据帧的所有此类需求有用。

    update.df.with.df <- function(original, replacement, key, value) 
    {
        ## PURPOSE: Update a data frame with the values in another data frame
        ## ----------------------------------------------------------------------
        ## ARGUMENT:
        ##   original: a data frame to update,
        ##   replacement: a data frame that has the updated values,
        ##   key: a character vector of variable names to form the unique key
        ##   value: a character vector of variable names to form the values that need to be updated
        ## ----------------------------------------------------------------------
        ## RETURN: The updated data frame from the old data frame "original". 
        ## ----------------------------------------------------------------------
        ## AUTHOR: Feiming Chen,  Date:  2 Dec 2015, 15:08
    
        n1 <- rownames(original) <- apply(original[, key, drop=F], 1, paste, collapse=".")
        n2 <- rownames(replacement) <- apply(replacement[, key, drop=F], 1, paste, collapse=".")
    
        n3 <- merge(data.frame(n=n1), data.frame(n=n2))[[1]] # make common keys
        n4 <- levels(n3)[n3]                # convert factor to character
    
        original[n4, value] <- replacement[n4, value] # update values on the common keys
        original
    }
    if (F) {                                # Unit Test 
        original <- data.frame(x=c(1, 2, 3), y=c(10, 20, 30))
        replacement <- data.frame(x=2, y=25)
        update.df.with.df(original, replacement, key="x", value="y") # data.frame(x=c(1, 2, 3), y=c(10, 25, 30))
    
        original <- data.frame(x=c(1, 2, 3), w=c("a", "b", "c"), y=c(10, 20, 30))
        replacement <- data.frame(x=2, w="b", y=25)
        update.df.with.df(original, replacement, key=c("x", "w"), value="y") # data.frame(x=c(1, 2, 3), w=c("a", "b", "c"), y=c(10, 25, 30))
    
        original = data.frame(Name = c("joe","john") , Id = c( 1 , 2) , Value1 = c(1.2,NA), Value2 = c(NA,9.2))
        replacement = data.frame(Name = c("john") , Id = 2 , Value1 = 2.2 , Value2 = 5.9)
        update.df.with.df(original, replacement, key="Id", value=c("Value1", "Value2"))
        ## goal = data.frame( Name = c("joe","john") , Id = c( 1 , 2) , Value1 = c(1.2,2.2), Value2 = c(NA,5.9) )
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-07
      • 1970-01-01
      相关资源
      最近更新 更多