Использование одного файла data.frame для обновления другого

Учитывая два кадра данных, которые идентичны в терминах имен столбцов/типов данных, когда некоторые столбцы однозначно идентифицируют строки, существует ли эффективная функция/метод для одного файла данных. "Обновить" другое?

Например, в дальнейшем original и replacement идентифицируются 'Name' и 'Id'. goal - результат поиска всех строк из replacement в original (по уникальным идентификаторам) и замены Value1 и Value2

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) )

Решение должно работать для original и replacement произвольной длины (хотя replacement никогда не должно содержать больше строк, чем original). На практике я использую 2 столбца id.

Ответы

Ответ 1

Просто установите уникальный идентификатор в качестве имен строк. Тогда это простое индексирование:

rownames(original) = original$Id
rownames(replacement) = replacement$Id

original[rownames(replacement), ] = replacement

Ответ 2

Я бы использовал объекты 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 comment

goal2 <- as.data.frame(goal2)

identical(goal, goal2) # FALSE, why? See Joris comment
all.equal(goal, goal2) # TRUE

Ответ 3

Используя базу 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' (small 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
  }

Ответ 4

Вот подход, использующий пакет 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

Ответ 5

# 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

Ответ 6

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,]

Параметр on функции match_df также может принимать векторы.

Ответ 7

Я создал функцию, которая использует метод индексирования (см. ответ Джона Колби выше). Надеюсь, это может быть полезно для всех таких потребностей обновления одного фрейма данных со значениями из другого фрейма данных.

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) )
}