Идентифицировать записи во фрейме данных A, не содержащиеся во фрейме данных B

Пишу сюда впервые, так что будьте любезны ;-)

ИЗМЕНИТЬ Мой вопрос был закрыт до того, как у меня появилась возможность внести предложенные мне изменения. Так что теперь я стараюсь работать лучше, спасибо всем, кто ответил до сих пор!

ВОПРОС

Как определить записи/строки во фрейме данных x.1, которые не содержатся во фрейме данных x.2, на основе всех доступных атрибутов (т. е. всех столбцов) в наиболее эффективном способ?

ПРИМЕР ДАННЫХ

> x.1 <- data.frame(a=c(1,2,3,4,5), b=c(1,2,3,4,5))
> x.1
  a b
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5

> x.2 <- data.frame(a=c(1,1,2,3,4), b=c(1,1,99,3,4))
> x.2
  a  b
1 1  1
2 1  1
3 2 99
4 3  3
5 4  4

ЖЕЛАЕМЫЙ РЕЗУЛЬТАТ

  a b
2 2 2
5 5 5

ЛУЧШЕЕ РЕШЕНИЕ

Проф. Брайан Рипли и Габор Гротендик

> fun.12 <- function(x.1,x.2,...){
+     x.1p <- do.call("paste", x.1)
+     x.2p <- do.call("paste", x.2)
+     x.1[! x.1p %in% x.2p, ]
+ }
> fun.12(x.1,x.2)
  a b
2 2 2
5 5 5
> sol.12 <- microbenchmark(fun.12(x.1,x.2))
> sol.12 <- median(sol.12$time)/1000000000
> sol.12
> [1] 0.000207784

Собрание всех протестированных на данный момент решений доступно по адресу блог

ПОСЛЕДНЯЯ РЕДАКЦИЯ 14.10.2011

Вот лучшее решение, заключенное в функцию «mergeX()»:

setGeneric(
    name="mergeX",
    signature=c("src.1", "src.2"),
    def=function(
        src.1,
        src.2,
        ...
    ){
    standardGeneric("mergeX")    
    }
)

setMethod(
    f="mergeX", 
    signature=signature(src.1="data.frame", src.2="data.frame"), 
    definition=function(
        src.1,
        src.2,
        do.inverse=FALSE,
        ...
    ){
    if(!do.inverse){
        out <- merge(x=src.1, y=src.2, ...)
    } else {
        if("by.y" %in% names(list(...))){
            src.2.0 <- src.2
            src.2 <- src.1
            src.1 <- src.2.0
        }
        src.1p <- do.call("paste", src.1)
        src.2p <- do.call("paste", src.2)
        out <- src.1[! src.1p %in% src.2p, ]
    }
    return(out)    
    }
)

person Rappster    schedule 11.10.2011    source источник
comment
Это не настоящий вопрос. Вы публикуете 8 различных решений своей проблемы, лучшее из которых (от профессора Брайана Рипли) состоит всего из трех строк кода Base R и уже упаковано в функцию. Что вы еще хотите?   -  person Andrie    schedule 11.10.2011
comment
@ Андри, он, наверное, хочет моего решения? :-))   -  person Tomas    schedule 11.10.2011
comment
@Tomas T: Я действительно искал что-то подобное ;-)   -  person Rappster    schedule 11.10.2011
comment
@Andrie: Я боялся, что что-то подобное произойдет ;-) Все же думаю, что я задал вопрос, и почему вопросы не могут быть связаны с набором возможных решений вместо того, чтобы решения были разбросаны повсюду, потому что все используют разные формулировки для описать, что он / она после? Просто хотел немного помочь другим...   -  person Rappster    schedule 11.10.2011
comment
@songpants Было бы лучше, если бы вы удалили все решения из своего вопроса и составили ответ на свой вопрос, содержащий эти решения. Кроме того, действительно хороший вопрос в идеале должен содержать примерные данные и ожидаемые результаты. Тогда все ответы могут использовать это для целей сравнительного анализа.   -  person Andrie    schedule 11.10.2011
comment
@Andrie: хорошо, ты прав. Я новичок в интерфейсе и думаю об этом, действительно было бы лучше предоставить собранные решения в качестве ответов. Тем не менее, я думаю, что предоставил образцы данных (x.1 и x.2) и четко указал, в чем заключалась исходная проблема...   -  person Rappster    schedule 11.10.2011
comment
@songpants Вы все еще можете изменить свой вопрос и превратить его в хороший вопрос.   -  person Andrie    schedule 11.10.2011
comment
@Andrie: да, уже пробовал, но, поскольку я новичок, Stackoverflow говорит мне, что мне нужно подождать 8 часов :-/ Однако спасибо за совет. Вы бы сгруппировали решения в один ответ или сделали бы каждый ответ отдельным, чтобы люди могли проголосовать за него и все такое?   -  person Rappster    schedule 11.10.2011
comment
Выберите один вариант, который вам больше всего нравится, и ответьте на него. Если вы хотите сделать коллекцию классных ответов, напишите блог.   -  person Andrie    schedule 11.10.2011
comment
Кроме того, удалите решения, которые не решают проблему, о которой вы спрашивали (например, Решение 2). Они просто загромождают интересное сравнение.   -  person Josh O'Brien    schedule 12.10.2011
comment
Хорошо, попытался улучшить свой вопрос. Может быть, кто-то может снова открыть его, так как мне действительно любопытно, есть ли еще лучшие способы, поскольку мне приходится применять это к огромным кадрам данных, поэтому эффективность действительно имеет значение. Спасибо!   -  person Rappster    schedule 12.10.2011
comment
Мое мнение, что закрытие этого вопроса было действительно неконструктивным и глупым шагом. Дайте ему шанс улучшить этот вопрос. Таким образом, вы заставите его начать новый. Это лучше?   -  person Tomas    schedule 12.10.2011
comment
@TomasT.: спасибо за понимание ;-) Не хочу делать из этого слишком много шума, но как вы думаете, можно ли отметить это, чтобы кто-то просмотрел и, возможно, снова открыл его? Кстати, я последовал совету блога, и вы найдете все решения здесь: rappster.wordpress.com/2011/10/12/   -  person Rappster    schedule 12.10.2011
comment
@songpants, я думаю, что публиковать все решения совершенно нормально, даже в самом вопросе (они всегда говорят: покажи, что ты пробовал. Если ты не покажешь, они закроются, если ты покажешь, они тоже закроются). Но следуйте комментарию Джоша об удалении решений, которые не являются решениями... Я голосую за повторное открытие.   -  person Tomas    schedule 12.10.2011
comment
Этот вопрос все еще нуждается в голосах для повторного открытия. Я рекомендую отредактировать вопрос (добавить несколько решений) и четко указать, в чем заключается ваш вопрос, а затем отметить его.   -  person Tomas    schedule 12.10.2011
comment
Вам не нужно повторять ответ, который вы предпочитаете в вопросе. Способ использования stackoverflow заключается в том, что вы принимаете решение, которое предпочитаете среди предложенных. Если вы хотите дополнительно прокомментировать это решение, вы можете добавить к нему комментарий, нажав кнопку «Добавить комментарий», связанную с этим ответом.   -  person G. Grothendieck    schedule 12.10.2011
comment
@G.Grothendieck: ну, здесь немного сложнее поступить правильно, чем я думал, но в следующий раз я просто попытаюсь снова ;-) Я понимаю концепцию stackoverflow (но я также сделал несколько ошибок), но трудно понять, когда люди считают опубликованный подход уместным, а когда нет, поскольку он уже считается полноценным ответом (как это было/очевидно в случае с моим сообщением). Я просто отпущу это в этот момент. Еще раз спасибо всем, кто откликнулся! Для тех, кому не все равно: обновлена ​​запись в моем блоге (тестируются только действительные решения).   -  person Rappster    schedule 12.10.2011
comment
Я действительно не понимаю, почему этот вопрос был задан специально для максимальной производительности, но затем с использованием в качестве тестовых данных кадра данных крошечного размера 2X5, где хорошие решения будут отличаться на несколько микросекунд. ИМО имело бы гораздо больше смысла представить большой набор данных для проверки различных ответов. Кстати, вы можете добавить в свою коллекцию этот метод: library(dplyr); anti_join(x.1, x.2). (Я только что понял, что вопросу 3 года :-))   -  person talat    schedule 25.11.2014
comment
data.table также имеет антисоединение: x.1[!x.2, on = names(x.1)] или полный рабочий пример: library(data.table); setDT(x.1)[!setDT(x.2), on = names(x.1)].   -  person Uwe    schedule 07.08.2017


Ответы (2)


Вот несколько способов. #1 и #4 предполагают, что строки x.1 уникальны. (Если строки x.1 не уникальны, они вернут только один дубликат среди дублированных строк.) Остальные возвращают все дубликаты:

# 1
x.1[!duplicated(rbind(x.2, x.1))[-(1:nrow(x.2))],]

# 2
do.call("rbind", setdiff(split(x.1, rownames(x.1)), split(x.2, rownames(x.2))))

# 3
x.1p <- do.call("paste", x.1)
x.2p <- do.call("paste", x.2)
x.1[! x.1p %in% x.2p, ]

# 4
library(sqldf)
sqldf("select * from `x.1` except select * from `x.2`")

РЕДАКТИРОВАТЬ: x.1 и x.2 поменялись местами, и это было исправлено. Также поправили примечание об ограничениях в начале.

person G. Grothendieck    schedule 11.10.2011
comment
Спасибо, Габор! Не все подходы возвращают желаемый результат ((2,2) и (5,5)), но мне есть чему поучиться. Особенно в отношении «sqldf». Никогда не смотрел на это раньше, но, кажется, это действительно круто, спасибо! - person Rappster; 11.10.2011
comment
В некоторых решениях роли x.1 и x.2 частично поменялись местами. Я отредактировал его, и теперь все они должны давать (2,2) и (5,5). - person G. Grothendieck; 11.10.2011
comment
@Gabor: большое спасибо за редактирование - person Rappster; 11.10.2011
comment
Я обновил результаты своих тестов, и ваш третий подход оказался самым быстрым (0,0002066945 секунд)! Спасибо еще раз! - person Rappster; 12.10.2011

Как насчет использования merge - самого простого решения - я думаю, что это также и самое быстрое.

tmp = merge(x.1, cbind(x.2, myid = 1:nrow(x.2)), all.x = TRUE)
    # provided that there's no column myid in both dataframes
tmp[is.na(tmp$myid), 1:ncol(x.1)] # the result

Соответствует:

select x1.* 
from x1 natural left join x2 
where x2.myid is NULL

(для этого вы также можете использовать sqldf).

Обратите внимание, что столбец myid добавлен, чтобы убедиться, что есть какой-то столбец без значений NA. Если вы уверены, что уже есть какой-то столбец, который не содержит значений NULL, вы можете упростить решение:

tmp = merge(x.1, x.2, all.x = TRUE)
    # provided that there's no column myid in both dataframes
tmp[is.na(tmp$some_column), 1:ncol(x.1)] # the result
person Tomas    schedule 11.10.2011
comment
Аккуратное решение, но заявление о том, что оно самое быстрое, заслуживает одобрения, если вы сможете доказать, что ваше утверждение верно. - person Andrie; 11.10.2011
comment
Спасибо за ответ Томас! Это тот, который я искал, но он не самый быстрый ;-) - person Rappster; 11.10.2011
comment
Он занимает 7-е место по сравнению с теми, что я собрал выше (0,002583893 секунды с использованием микробенчмарка). - person Rappster; 11.10.2011
comment
@Andrie, мне лень это делать, я позволил тесту пыхтеть ... Однако вы можете быть правы, это не так очевидно: в SQL это было бы самым быстрым, потому что оно оптимизировано для объединений, с слиянием R I теперь не уверен... - person Tomas; 11.10.2011
comment
@songpants, мы поговорили :-) спасибо за измерения, и да, мои опасения по поводу слияния R сбылись... Спасибо. - person Tomas; 11.10.2011
comment
@songpants, обратите внимание: если вы уверены, что есть какой-то столбец без значений NA, вы можете упростить решение, см. Мое редактирование. - person Tomas; 11.10.2011
comment
@TomasT.: спасибо, я еще раз посмотрю на твой ответ. Спасибо, что нашли время! - person Rappster; 11.10.2011