Есть ли эффективный способ сравнения двух фреймов данных
У меня есть два фрейма данных с разным количеством строк, но одинаковым количеством столбцов. В приведенном ниже примере кадр данных 1 равен 4 x 2, кадр данных 2 - 3 x 2. Мне нужна логическая матрица 4 x 3, где TRUE указывает, что все строки в кадрах данных совпадают. Этот пример работает, но он занимает очень много времени для работы с большими фреймами данных (я пробую два фрейма данных с примерно 5000 строк, но все еще только два столбца). Есть ли более эффективный способ сделать это?
> df1 <- data.frame(row.names=1:4, var1=c(TRUE, TRUE, FALSE, FALSE), var2=c(1,2,3,4))
> df2 <- data.frame(row.names=5:7, var1=c(FALSE, TRUE, FALSE), var2=c(5,2,3))
>
> m1 <- t(as.matrix(df1))
> m2 <- as.matrix(df2)
>
> apply(m2, 1, FUN=function(x) { apply(m1, 2, FUN=function(y) { all(x==y) } ) })
5 6 7
1 FALSE FALSE FALSE
2 FALSE TRUE FALSE
3 FALSE FALSE TRUE
4 FALSE FALSE FALSE
Заранее благодарю за любую помощь.
3 ответа
Меня привлекла ваша публикация на R-блогерах: http://jason.bryer.org/posts/2013-01-24/Comparing_Two_Data_Frames.html
Если, как вы говорите, ваши данные не имеют числовых векторов, то я думаю, что могу предложить более быстрый подход. Он состоит в:
- превратить ваши два data.frames в две матрицы целых чисел
- вычислить евклидово расстояние между рядами ваших двух данных
Быстрый пример использования ваших данных:
mat1 <- as.matrix(sapply(df1, as.integer))
mat2 <- as.matrix(sapply(df2, as.integer))
library(fields)
rdist(mat1, mat2) < 1e-9
# [,1] [,2] [,3]
# [1,] FALSE FALSE FALSE
# [2,] FALSE TRUE FALSE
# [3,] FALSE FALSE TRUE
# [4,] FALSE FALSE FALSE
Несколько комментариев:
- если ваши данные содержат векторы символов, вам придется преобразовать их в факторы и убедиться, что они имеют одинаковые уровни факторов.
- Я использовал
fields
пакет для вычисления евклидова расстояния. Он использует реализацию на Фортране и, насколько мне известно, является самым быстрым пакетом R для этой задачи (и я много тестировал, поверьте мне).
Я честно не уверен, что это будет быстрее, но вы можете попробовать:
foo <- Vectorize(function(x,y) {all(df1[x,] == df2[y,])})
> outer(1:4,1:3,FUN = foo)
[,1] [,2] [,3]
[1,] FALSE FALSE FALSE
[2,] FALSE TRUE FALSE
[3,] FALSE FALSE TRUE
[4,] FALSE FALSE FALSE
Я чувствую себя обязанным хотя бы упомянуть об опасности при использовании ==
для сравнения в отличие от all.equal
или же identical
, Я предполагаю, что вы достаточно комфортно относитесь к типам данных, что это не будет проблемой.
Я подозреваю, что оптимальное решение зависит от того, сколько уникальных строк и сколько у вас всего строк.
Для примера в вашем блоге, где есть 1000-1500 строк, но только 20 уникальных значений (для заданного вами начального числа), я думаю, что это быстрее сделать:
- назначить идентификаторы для каждой уникальной строки, а затем
- запускать external по вектору идентификаторов, видимых в каждом data.frame.
Вот представление, которое я получил. подход @flodel делает примерно то же самое на моем компьютере; это третий ниже. Отказ от ответственности: я не знаю много о проведении таких тестов.
> set.seed(2112)
> df1 <- data.frame(row.names=1:1000,
+ var1=sample(c(TRUE,FALSE), 1000, replace=TRUE),
+ var2=sample(1:10, 1000, replace=TRUE) )
> df2 <- data.frame(row.names=1001:2500,
+ var1=sample(c(TRUE,FALSE), 1500, replace=TRUE),
+ var2=sample(1:10, 1500, replace=TRUE))
>
> # candidate method on blog
> system.time({
+ df1$var3 <- apply(df1, 1, paste, collapse='.')
+ df2$var3 <- apply(df2, 1, paste, collapse='.')
+ df6 <- sapply(df2$var3, FUN=function(x) { x == df1$var3 })
+ dimnames(df6) <- list(row.names(df1), row.names(df2))
+ })
user system elapsed
1.13 0.00 1.14
>
> rownames(df1) <- NULL # in case something weird happens to rownames on merge
> rownames(df2) <- NULL
> # id method
> system.time({
+ df12 <- unique(rbind(df1,df2))
+ df12$id <- rownames(df12)
+
+ id1 <- merge(df12,df1)$id
+ id2 <- merge(df12,df2)$id
+
+ x <- outer(id1,id2,`==`)
+ })
user system elapsed
0.11 0.02 0.13
>
> library(fields)
> # rdlist from fields method
> system.time({
+ mat1 <- as.matrix(sapply(df1, as.integer))
+ mat2 <- as.matrix(sapply(df2, as.integer))
+ rdist(mat1, mat2) < 1e-9
+ })
user system elapsed
0.15 0.00 0.16
Я думаю, rbind
и merge
Это сделало бы это решение относительно более дорогостоящим с другими данными.