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

161

У меня есть следующие 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 не имеет.

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

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

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

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)
Таль Галили
источник

Ответы:

88

Это не ответит на ваш вопрос напрямую, но даст вам общие элементы. Это можно сделать с помощью пакета Пола Меррелла 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
nullglob
источник
3
Я нахожу эту функцию запутанной. Я думал, что это будет работать для меня, но, похоже, будет работать, как показано выше, только если один набор содержит одинаково совпадающие строки другого набора. Рассмотрим этот случай: a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). Оставь a1то же самое. Теперь попробуйте сравнение. Даже при чтении опций мне не ясно, как правильно перечислять только общие элементы.
Hendy
148

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)
Рикард
источник
18
Спасибо за anti_joinи semi_join!
Drastega
есть ли причина, по которой anti_join возвращает нулевой DF, как sqldf, но функции, идентичные (a1, a2) и all.equal (), будут противоречить этому?
питта
Просто хотел добавить, что anti_join и semi_join не будут работать в некоторых случаях, как у меня. Я получаю «Ошибка: столбцы должны быть 1d атомарных векторов или списков» для моего фрейма данных. Возможно я мог бы обработать свои данные так, чтобы эти функции работали. Sqldf работал прямо из ворот!
Акшай Гаур
@AkshayGaur это просто проблема с форматом данных или очисткой данных; sqldf - это просто sql, все предварительно обработано, чтобы быть похожим на обычную БД, чтобы мы могли просто запустить sql для данных.
Stucash
75

В дплыр :

setdiff(a1,a2)

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

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

Слева, исключая объединенную диаграмму Венна

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

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

> 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

leerssej
источник
2
Поскольку ОП запрашивает элементы a1, которых нет a2, разве вы не хотите использовать что-то подобное semi_join(a1, a2, by = c('a','b'))? В ответе «Рикард» я вижу, что semi_joinбыло предложено.
Steveb
Конечно! Еще один отличный выбор тоже; особенно если у вас есть фреймы данных только с ключом соединения и разными именами столбцов.
leerssej
setdiff из lubridate :: setdiff, а не из библиотеки (dplyr)
mtelesha
@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?
leerssej
1
Существует конфликт между lubridate и dplyr см github.com/tidyverse/lubridate/issues/693
slhck
39

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

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

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

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

Эдуардо Леони
источник
Итак ... в поисках пропущенного значения вы создаете еще одно пропущенное значение ... Как вы находите пропущенные значения в included_a1? : - /
Луи Мэддокс
1
используйте is.na () и подмножество, или dplyr :: filter
Эдуардо Леони
Спасибо за обучение без установки новой библиотеки!
Родриго
27

Я написал пакет ( 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 введите описание изображения здесь

Алекс Джозеф
источник
Ваш CompareDF - именно то, что мне нужно, и он хорошо поработал с небольшими наборами. Однако: 1) Не работает с набором 50 миллионов строк с 3 столбцами (скажем), он говорит о нехватке памяти с 32 ГБ ОЗУ. 2) Я также вижу, что для написания HTML требуется некоторое время, можно ли отправить тот же вывод в файл TEXT?
Deep
1) Да, 50 миллионов строк - это МНОГО данных, просто для хранения в памяти;). Я знаю, что это плохо для больших наборов данных, так что вам, возможно, придется сделать что-то вроде чанкинга. 2) вы можете дать аргумент - limit_html = 0, чтобы избежать его печати в HTML. Тот же вывод находится в Compare_output $ сравнение_df, который вы можете записать в файл CSV / TEXT, используя собственные функции R.
Алекс Иосиф
Спасибо за ваш ответ @ Алекс Джозеф, я попробую и дам вам знать, как это происходит.
Deep
Привет @ Алекс Джозеф, спасибо за ввод, текстовый формат работал, но нашел проблему, поднял ее по
Deep
Он не может обрабатывать разное количество столбцов. Я получил ошибкуThe two data frames have different columns!
PeyM87
14

Вы можете использовать daffпакет (который оборачивает daff.jsбиблиотеку, используя 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

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

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

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

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

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

Салим Б
источник
10

Используя diffobjпакет:

library(diffobj)

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

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

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

zx8754
источник
10

Я адаптировал 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)
}
Хенрико
источник
7

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

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

Требуется data.table 1.9.8+

jangorecki
источник
2

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

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,]
Кения Соуза
источник
Чем это отличается от того, что OP уже пробовал? Вы использовали точно такой же код, как Tal, чтобы сравнить один столбец вместо целого ряда (что было требованием)
Дэвид Аренбург,
1

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

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)
chrisendres
источник
1

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

missing<-subset(a1, !(a %in% a2$a))
Эмили
источник
Этот ответ работает для сценария ОП. Как насчет более общего случая, когда переменная «a» совпадает между двумя data.frames («a1» и «a2»), а переменная «b» - нет?
Брайан Ф
1

Следующий код использует оба 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
iembry
источник