#r #indexing #dataframe
#r #индексирование #фрейм данных
Вопрос:
Учитывая 2 фрейма данных, которые идентичны с точки зрения имен столбцов / типов данных, где некоторые столбцы однозначно идентифицируют строки, существует ли эффективная функция / метод для одного data.frame для «обновления» другого?
Например, в следующем, original
и replacement
идентифицируются с помощью 'Name'
и 'Id'
. goal
является результатом поиска всех строк из replacement
in 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 столбца идентификаторов.
Ответ №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
Комментарии:
1. Спасибо! До сих пор я избегал зависимости от data.table и предпочел бы решение с использованием базовых классов. Будет ждать других ответов.
2. 1 для таблицы данных. Что касается того, почему идентичный возвращает FALSE: в goal2 Id является вектором int, тогда как в goal это вектор num.
3. @JorisMeys … как
str()
показывает. Спасибо!4. Небольшое улучшение: может отбросить
[,list(Name,Id)]
бит. Простоgoal2[replacement.dt]<-replacement.dt
короче и быстрееreplacement.dt
, потому что ключ уже"Name,Id"
есть.i
не обязательно вводить ключ, но когда это так,i
объединяются ключевые столбцы, и внутри используется более быстрый алгоритм слияния, который использует тот факт, что обе таблицы отсортированы.
Ответ №2:
Просто установите уникальный идентификатор в качестве имен строк. Тогда это простая индексация:
rownames(original) = original$Id
rownames(replacement) = replacement$Id
original[rownames(replacement), ] = replacement
Комментарии:
1. что, если будут строки с одинаковым идентификатором? например вы не можете использовать имена строк(оригинал) <- c(1,1,1)
2. Они просто должны быть уникальными. Например, вставить(idvar1, idvar2, …)
3. моя вина, аррр. если в оригинале есть несколько мест и замена на один идентификатор, то непонятно, откуда куда следует заменять
4. но здесь есть два идентификатора — Id и Name. Я не думаю, что этот подход работает для нецелочисленных идентификаторов
5. @SFun28, почему этого не должно быть? имена строк — это строки
Ответ №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’ (маленькая 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
}
Комментарии:
1. извините, я хотел сказать «вас не БЫВАЕТ». исправлено сообщение.
2. @SFun28: ах, нет, но, возможно, мне стоит начать делать это. Я также исправил опечатку (drop=FALSE в последних строках был явно ошибочным. Я не настолько хорош в воспроизведении функций с макушки моей головы …)
3. @TylerRinker: Вы видели мое исправление? Drop=FALSE во второй последней строке является мусором и должен быть удален. Это не аргумент для функции присваивания. Я ввел функцию из головы, оригинал работает. Отсюда и ошибка.
4. @Joris Теперь он работает со всем, кроме:
cols='Value2'
который выдает предупреждениеError in [<-.data.frame(*tmp, idx, cols, value = NULL) : replacement has length zero
Ответ №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
Комментарии:
1. Это хорошая идея, но она страдает с точки зрения производительности. Вам нужно вычислить дайджест, который будет медленнее, чем простое объединение идентификаторов.
Ответ №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
Комментарии:
1. Да, is.element легко обобщается (просто добавляя индексные массивы для обоих), но я не вижу простого способа для части сопоставления.
Ответ №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
также может принимать векторы.
Комментарии:
1. Это похоже на ответ Джона Колби, но я думаю, что немного медленнее из-за вызова 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) )
}