Сравните два data.frames, чтобы найти строки в data.frame 1, которых нет в data.frame 2

У меня есть следующие 2 data.frames:

a1 <- data.frame(a = 1:5, b=letters[1:5])
a2 <- data.frame(a = 1:3, b=letters[1:3])

Я хочу найти строку a1, которой нет в a2.

Есть ли встроенная функция для этого типа операций?

(p.s: я написал решение для этого, мне просто любопытно, если кто-то уже сделал более продуманный код)

Вот мое решение:

a1 <- data.frame(a = 1:5, b=letters[1:5])
a2 <- data.frame(a = 1:3, b=letters[1:3])

rows.in.a1.that.are.not.in.a2  <- function(a1,a2)
{
    a1.vec <- apply(a1, 1, paste, collapse = "")
    a2.vec <- apply(a2, 1, paste, collapse = "")
    a1.without.a2.rows <- a1[!a1.vec %in% a2.vec,]
    return(a1.without.a2.rows)
}
rows.in.a1.that.are.not.in.a2(a1,a2)

person Tal Galili    schedule 03.07.2010    source источник


Ответы (14)


Это не дает прямого ответа на ваш вопрос, но дает вам общие элементы. Это можно сделать с помощью пакета Пола Мюррелла compare:

library(compare)
a1 <- data.frame(a = 1:5, b = letters[1:5])
a2 <- data.frame(a = 1:3, b = letters[1:3])
comparison <- compare(a1,a2,allowAll=TRUE)
comparison$tM
#  a b
#1 1 a
#2 2 b
#3 3 c

Функция compare дает вам большую гибкость с точки зрения того, какие сравнения разрешены (например, изменение порядка элементов каждого вектора, изменение порядка и имен переменных, сокращение переменных, изменение регистра строк). Исходя из этого, вы сможете определить, чего не хватало в одном или другом. Например (это не очень элегантно):

difference <-
   data.frame(lapply(1:ncol(a1),function(i)setdiff(a1[,i],comparison$tM[,i])))
colnames(difference) <- colnames(a1)
difference
#  a b
#1 4 d
#2 5 e
person nullglob    schedule 03.07.2010
comment
Я нахожу эту функцию запутанной. Я думал, что это сработает для меня, но, похоже, он работает, как показано выше, только если один набор содержит идентично совпадающие строки другого набора. Рассмотрим этот случай: a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). Оставьте a1 без изменений. А теперь попробуйте сравнение. Мне неясно, даже читая варианты, как правильно перечислять только общие элементы. - person Hendy; 08.08.2013

sqldf предлагает хорошее решение

a1 <- data.frame(a = 1:5, b=letters[1:5])
a2 <- data.frame(a = 1:3, b=letters[1:3])

require(sqldf)

a1NotIna2 <- sqldf('SELECT * FROM a1 EXCEPT SELECT * FROM a2')

И строки, которые находятся в обоих фреймах данных:

a1Ina2 <- sqldf('SELECT * FROM a1 INTERSECT SELECT * FROM a2')

В новой версии dplyr есть функция anti_join именно для таких сравнений.

require(dplyr) 
anti_join(a1,a2)

И semi_join для фильтрации строк в a1, которые также находятся в a2

semi_join(a1,a2)
person Rickard    schedule 29.01.2013
comment
Спасибо за anti_join и semi_join! - person drastega; 08.09.2015
comment
есть ли причина, по которой anti_join будет возвращать нулевой DF, как sqldf, но функции, идентичные (a1, a2) и all.equal (), будут противоречить этому? - person 3pitt; 10.10.2017
comment
Просто хотел добавить сюда, что anti_join и semi_join не будут работать в некоторых случаях, как у меня. Я получал сообщение об ошибке: столбцы должны быть 1d атомарными векторами или списками для моего фрейма данных. Может быть, я смогу обработать свои данные, чтобы эти функции работали. Sqldf работал сразу же! - person Akshay Gaur; 27.11.2017
comment
@AkshayGaur, это просто проблема формата данных или очистки данных; sqldf - это просто sql, все предварительно обрабатывается, чтобы быть похожим на обычную БД, так что мы могли просто запустить sql для данных. - person stucash; 20.12.2019

В dplyr:

setdiff(a1,a2)

По сути, setdiff(bigFrame, smallFrame) возвращает вам дополнительные записи в первой таблице.

В SQLverse это называется

«Слева,

Для хорошего описания всех вариантов присоединения и заданных тем это одно из лучших резюме, которые я видел на сегодняшний день: http://www.vertabelo.com/blog/technical-articles/sql-присоединяетсяк

Но вернемся к этому вопросу - вот результаты для кода setdiff() при использовании данных OP:

> a1
  a b
1 1 a
2 2 b
3 3 c
4 4 d
5 5 e

> a2
  a b
1 1 a
2 2 b
3 3 c

> setdiff(a1,a2)
  a b
1 4 d
2 5 e

Или даже anti_join(a1,a2) даст вам те же результаты.
Для получения дополнительной информации: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

person leerssej    schedule 10.11.2016
comment
Поскольку OP запрашивает элементы в a1, которых нет в a2, разве вы не хотите использовать что-то вроде semi_join(a1, a2, by = c('a','b'))? В ответе Рикарда я вижу, что было предложено semi_join. - person steveb; 14.12.2016
comment
Конечно! Еще один отличный выбор; особенно если у вас есть фреймы данных только с ключом соединения и разными именами столбцов. - person leerssej; 20.04.2017
comment
setdiff взят из lubridate :: setdiff, а не из библиотеки (dplyr) - person mtelesha; 25.09.2018
comment
@mtelesha - Хм, документация и исходный код для dplyr показывают, что он там есть: (dplyr.tidyverse.org/reference/setops.html, github.com/tidyverse/dplyr/blob/master/R/sets.). Кроме того, когда библиотека dplyr загружена, она даже сообщает о маскировании базовой функции setdiff(), которая работает с двумя векторами: stat.ethz.ch/R-manual/R-devel/library/base/html/sets.html. Может быть, вы загрузили библиотеку lubridate после dplyr, и она предлагает ее в качестве источника в списке tabcomplete? - person leerssej; 25.09.2018
comment
Существует конфликт между lubridate и dplyr, см. github.com/tidyverse/lubridate/issues/693 < / а> - person slhck; 20.05.2019
comment
Также учтите dplyr::intersection - person bmc; 03.05.2021

Это, конечно, неэффективно для этой конкретной цели, но то, что я часто делаю в таких ситуациях, - это вставляю индикаторные переменные в каждый data.frame, а затем объединяю:

a1$included_a1 <- TRUE
a2$included_a2 <- TRUE
res <- merge(a1, a2, all=TRUE)

пропущенные значения в include_a1 укажут, какие строки отсутствуют в a1. аналогично для a2.

Одна из проблем с вашим решением заключается в том, что порядок столбцов должен совпадать. Другая проблема заключается в том, что легко представить себе ситуации, когда строки имеют одинаковый код, хотя на самом деле они разные. Преимущество использования слияния заключается в том, что вы бесплатно получаете всю проверку ошибок, необходимую для хорошего решения.

person Eduardo Leoni    schedule 03.07.2010
comment
Итак ... ища пропущенное значение, вы создаете другое пропущенное значение ... Как найти пропущенные значения в included_a1? : - / - person Louis Maddox; 12.07.2014
comment
используйте is.na () и подмножество или dplyr :: filter - person Eduardo Leoni; 18.09.2015
comment
Спасибо, что научили способу без установки новой библиотеки! - person Rodrigo; 08.10.2018

Я написал пакет (https://github.com/alexsanjoseph/compareDF), так как у меня была такая же проблема.

  > df1 <- data.frame(a = 1:5, b=letters[1:5], row = 1:5)
  > df2 <- data.frame(a = 1:3, b=letters[1:3], row = 1:3)
  > df_compare = compare_df(df1, df2, "row")

  > df_compare$comparison_df
    row chng_type a b
  1   4         + 4 d
  2   5         + 5 e

Более сложный пример:

library(compareDF)
df1 = data.frame(id1 = c("Mazda RX4", "Mazda RX4 Wag", "Datsun 710",
                         "Hornet 4 Drive", "Duster 360", "Merc 240D"),
                 id2 = c("Maz", "Maz", "Dat", "Hor", "Dus", "Mer"),
                 hp = c(110, 110, 181, 110, 245, 62),
                 cyl = c(6, 6, 4, 6, 8, 4),
                 qsec = c(16.46, 17.02, 33.00, 19.44, 15.84, 20.00))

df2 = data.frame(id1 = c("Mazda RX4", "Mazda RX4 Wag", "Datsun 710",
                         "Hornet 4 Drive", " Hornet Sportabout", "Valiant"),
                 id2 = c("Maz", "Maz", "Dat", "Hor", "Dus", "Val"),
                 hp = c(110, 110, 93, 110, 175, 105),
                 cyl = c(6, 6, 4, 6, 8, 6),
                 qsec = c(16.46, 17.02, 18.61, 19.44, 17.02, 20.22))

> df_compare$comparison_df
    grp chng_type                id1 id2  hp cyl  qsec
  1   1         -  Hornet Sportabout Dus 175   8 17.02
  2   2         +         Datsun 710 Dat 181   4 33.00
  3   2         -         Datsun 710 Dat  93   4 18.61
  4   3         +         Duster 360 Dus 245   8 15.84
  5   7         +          Merc 240D Mer  62   4 20.00
  6   8         -            Valiant Val 105   6 20.22

В пакете также есть команда html_output для быстрой проверки.

df_compare $ html_output введите  здесь описание изображения

person Alex Joseph    schedule 08.03.2016
comment
ваш compareDF - это именно то, что мне нужно, и он хорошо поработал с небольшими наборами. Однако: 1) Не работает с набором 50 миллионов строк с 3 столбцами (скажем), он говорит о нехватке памяти с 32 ГБ ОЗУ. 2) Я также вижу, что для написания HTML требуется некоторое время, можно ли отправить тот же вывод в ТЕКСТОВЫЙ файл? - person Deep; 13.02.2019
comment
1) Да, 50 миллионов строк - это МНОГО данных, просто для хранения в памяти;). Я знаю, что это не очень хорошо для больших наборов данных, поэтому вам, возможно, придется сделать какие-то фрагменты. 2) вы можете указать аргумент limit_html = 0, чтобы он не выводился в HTML. Тот же результат находится в compare_output $ compare_df, который вы можете записать в файл CSV / TEXT, используя собственные функции R. - person Alex Joseph; 13.02.2019
comment
Спасибо за ответ @Alex Joseph, я попробую и расскажу, как это происходит. - person Deep; 21.02.2019
comment
Привет, @Alex Joseph, спасибо за ввод, текстовый формат действительно работал, но обнаружил проблему, поднял ее в разделе: stackoverflow.com/questions/54880218/ - person Deep; 26.02.2019
comment
Он не может обрабатывать разное количество столбцов. У меня ошибка The two data frames have different columns! - person PM0087; 04.06.2020
comment
@ PeyM87 - Если столбцы разные, это очень легко видно по именам (df), верно? Какого поведения вы ожидаете? Если вы можете создать проблемное представление на github, я могу взглянуть на него. - person Alex Joseph; 05.06.2020
comment
@AlexJoseph: у меня есть dataframe1 с количеством столбцов X. Через некоторое время приходят новые данные, и у меня есть dataframe2 с числом столбцов Y, в которых некоторые столбцы всегда общие. Я думал, что это сравнение будет, например, СУММОМ общих столбцов и добавлением столбцов, если есть какие-то новые. - person PM0087; 09.06.2020
comment
Если у вас меняются столбцы, возможно, лучшим подходом будет выполнение setdiff (names (df1), names (df2)). - person Alex Joseph; 10.06.2020

Вы можете использовать daff package (который включает _ 2_ библиотека с использованием пакета V8 ):

library(daff)

diff_data(data_ref = a2,
          data = a1)

создает следующий объект различия:

Daff Comparison: ‘a2’ vs. ‘a1’ 
  First 6 and last 6 patch lines:
   @@   a   b
1 ... ... ...
2       3   c
3 +++   4   d
4 +++   5   e
5 ... ... ...
6 ... ... ...
7       3   c
8 +++   4   d
9 +++   5   e

Табличный формат сравнения описан здесь и не требует пояснений. Строки с +++ в первом столбце @@ являются новыми в a1 и отсутствуют в a2.

Объект различия можно использовать для patch_data(), для хранения разницы для целей документации с помощью write_diff() или для визуализации разницы с помощью render_diff():

render_diff(
    diff_data(data_ref = a2,
              data = a1)
)

генерирует аккуратный вывод HTML:

введите описание изображения здесь

person Salim B    schedule 26.08.2017

Использование пакета diffobj:

library(diffobj)

diffPrint(a1, a2)
diffObj(a1, a2)

введите описание изображения здесь

введите описание изображения здесь

person zx8754    schedule 27.09.2016

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

Другое решение - использовать библиотеку prob.

#  Derived from src/library/base/R/merge.R
#  Part of the R package, http://www.R-project.org
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  http://www.r-project.org/Licenses/

XinY <-
    function(x, y, by = intersect(names(x), names(y)), by.x = by, by.y = by,
             notin = FALSE, incomparables = NULL,
             ...)
{
    fix.by <- function(by, df)
    {
        ## fix up 'by' to be a valid set of cols by number: 0 is row.names
        if(is.null(by)) by <- numeric(0L)
        by <- as.vector(by)
        nc <- ncol(df)
        if(is.character(by))
            by <- match(by, c("row.names", names(df))) - 1L
        else if(is.numeric(by)) {
            if(any(by < 0L) || any(by > nc))
                stop("'by' must match numbers of columns")
        } else if(is.logical(by)) {
            if(length(by) != nc) stop("'by' must match number of columns")
            by <- seq_along(by)[by]
        } else stop("'by' must specify column(s) as numbers, names or logical")
        if(any(is.na(by))) stop("'by' must specify valid column(s)")
        unique(by)
    }

    nx <- nrow(x <- as.data.frame(x)); ny <- nrow(y <- as.data.frame(y))
    by.x <- fix.by(by.x, x)
    by.y <- fix.by(by.y, y)
    if((l.b <- length(by.x)) != length(by.y))
        stop("'by.x' and 'by.y' specify different numbers of columns")
    if(l.b == 0L) {
        ## was: stop("no columns to match on")
        ## returns x
        x
    }
    else {
        if(any(by.x == 0L)) {
            x <- cbind(Row.names = I(row.names(x)), x)
            by.x <- by.x + 1L
        }
        if(any(by.y == 0L)) {
            y <- cbind(Row.names = I(row.names(y)), y)
            by.y <- by.y + 1L
        }
        ## create keys from 'by' columns:
        if(l.b == 1L) {                  # (be faster)
            bx <- x[, by.x]; if(is.factor(bx)) bx <- as.character(bx)
            by <- y[, by.y]; if(is.factor(by)) by <- as.character(by)
        } else {
            ## Do these together for consistency in as.character.
            ## Use same set of names.
            bx <- x[, by.x, drop=FALSE]; by <- y[, by.y, drop=FALSE]
            names(bx) <- names(by) <- paste("V", seq_len(ncol(bx)), sep="")
            bz <- do.call("paste", c(rbind(bx, by), sep = "\r"))
            bx <- bz[seq_len(nx)]
            by <- bz[nx + seq_len(ny)]
        }
        comm <- match(bx, by, 0L)
        if (notin) {
            res <- x[comm == 0,]
        } else {
            res <- x[comm > 0,]
        }
    }
    ## avoid a copy
    ## row.names(res) <- NULL
    attr(res, "row.names") <- .set_row_names(nrow(res))
    res
}


XnotinY <-
    function(x, y, by = intersect(names(x), names(y)), by.x = by, by.y = by,
             notin = TRUE, incomparables = NULL,
             ...)
{
    XinY(x,y,by,by.x,by.y,notin,incomparables)
}
person Henrico    schedule 13.07.2010

Данные вашего примера не имеют дубликатов, но ваше решение обрабатывает их автоматически. Это означает, что потенциально некоторые ответы не будут совпадать с результатами вашей функции в случае дублирования.
Вот мое решение, адрес которого дублируется так же, как ваш. Он также отлично масштабируется!

a1 <- data.frame(a = 1:5, b=letters[1:5])
a2 <- data.frame(a = 1:3, b=letters[1:3])
rows.in.a1.that.are.not.in.a2  <- function(a1,a2)
{
    a1.vec <- apply(a1, 1, paste, collapse = "")
    a2.vec <- apply(a2, 1, paste, collapse = "")
    a1.without.a2.rows <- a1[!a1.vec %in% a2.vec,]
    return(a1.without.a2.rows)
}

library(data.table)
setDT(a1)
setDT(a2)

# no duplicates - as in example code
r <- fsetdiff(a1, a2)
all.equal(r, rows.in.a1.that.are.not.in.a2(a1,a2))
#[1] TRUE

# handling duplicates - make some duplicates
a1 <- rbind(a1, a1, a1)
a2 <- rbind(a2, a2, a2)
r <- fsetdiff(a1, a2, all = TRUE)
all.equal(r, rows.in.a1.that.are.not.in.a2(a1,a2))
#[1] TRUE

It needs data.table 1.9.8+

person jangorecki    schedule 23.04.2016

Возможно, это слишком упрощенно, но я использовал это решение и считаю его очень полезным, когда у меня есть первичный ключ, который я могу использовать для сравнения наборов данных. Надеюсь, это поможет.

a1 <- data.frame(a = 1:5, b = letters[1:5])
a2 <- data.frame(a = 1:3, b = letters[1:3])
different.names <- (!a1$a %in% a2$a)
not.in.a2 <- a1[different.names,]
person Kenia Sousa    schedule 10.05.2016
comment
Чем это отличается от того, что уже пробовал OP? Вы использовали тот же код, что и Tal, для сравнения одного столбца вместо всей строки (что было обязательным требованием). - person David Arenburg; 05.09.2016

Использование subset:

missing<-subset(a1, !(a %in% a2$a))
person Emily    schedule 30.07.2018
comment
Этот ответ работает для сценария OP. Как насчет более общего случая, когда переменная a совпадает между двумя кадрами data.frames (a1 и a2), а переменная b - нет? - person Bryan F; 12.09.2018

Еще одно решение, основанное на match_df в plyr. Вот match_df от plyr:

match_df <- function (x, y, on = NULL) 
{
    if (is.null(on)) {
        on <- intersect(names(x), names(y))
        message("Matching on: ", paste(on, collapse = ", "))
    }
    keys <- join.keys(x, y, on)
    x[keys$x %in% keys$y, , drop = FALSE]
}

Мы можем изменить его, чтобы отрицать:

library(plyr)
negate_match_df <- function (x, y, on = NULL) 
{
    if (is.null(on)) {
        on <- intersect(names(x), names(y))
        message("Matching on: ", paste(on, collapse = ", "))
    }
    keys <- join.keys(x, y, on)
    x[!(keys$x %in% keys$y), , drop = FALSE]
}

Потом:

diff <- negate_match_df(a1,a2)
person chrisendres    schedule 14.04.2016

В следующем коде используются как data.table, так и fastmatch для увеличения скорости.

library("data.table")
library("fastmatch")

a1 <- setDT(data.frame(a = 1:5, b=letters[1:5]))
a2 <- setDT(data.frame(a = 1:3, b=letters[1:3]))

compare_rows <- a1$a %fin% a2$a
# the %fin% function comes from the `fastmatch` package

added_rows <- a1[which(compare_rows == FALSE)]

added_rows

#    a b
# 1: 4 d
# 2: 5 e
person iembry    schedule 07.04.2020

Действительно быстрое сравнение, чтобы подсчитать различия. Использование определенного имени столбца.

colname = "CreatedDate" # specify column name
index <- match(colname, names(source_df)) # get index name for column name
sel <- source_df[, index] == target_df[, index] # get differences, gives you dataframe with TRUE and FALSE values
table(sel)["FALSE"] # count of differences
table(sel)["TRUE"] # count of matches

Для полного фрейма данных не указывайте имя столбца или индекса

sel <- source_df[, ] == target_df[, ] # gives you dataframe with TRUE and FALSE values
table(sel)["FALSE"] # count of differences
table(sel)["TRUE"] # count of matches
person Tokci    schedule 19.11.2020