Vergleichen Sie zwei data.frames, um die Zeilen in data.frame 1 zu finden, die in data.frame 2 nicht vorhanden sind


161

Ich habe die folgenden 2 Datenrahmen:

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

Ich möchte die Zeile a1 finden, die a2 nicht hat.

Gibt es eine eingebaute Funktion für diese Art von Operation?

(ps: Ich habe eine Lösung dafür geschrieben, ich bin einfach neugierig, ob jemand bereits einen besser gestalteten Code erstellt hat)

Hier ist meine Lösung:

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)

Antworten:


88

Dies beantwortet Ihre Frage nicht direkt, gibt Ihnen jedoch die gemeinsamen Elemente. Dies kann mit Paul Murrells Paket geschehen 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

Die Funktion comparebietet Ihnen viel Flexibilität hinsichtlich der Art der zulässigen Vergleiche (z. B. Ändern der Reihenfolge der Elemente jedes Vektors, Ändern der Reihenfolge und der Namen von Variablen, Verkürzen von Variablen, Ändern der Groß- und Kleinschreibung von Zeichenfolgen). Daraus sollten Sie herausfinden können, was auf dem einen oder anderen fehlte. Zum Beispiel (das ist nicht sehr elegant):

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

3
Ich finde diese Funktion verwirrend. Ich dachte, es würde für mich funktionieren, aber es scheint nur wie oben gezeigt zu funktionieren, wenn ein Satz identisch übereinstimmende Zeilen des anderen Satzes enthält. Betrachten Sie diesen Fall : a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). Lass a1das gleiche. Versuchen Sie nun den Vergleich. Es ist mir selbst beim Lesen der Optionen nicht klar, wie man nur gemeinsame Elemente auflistet.
Hendy

148

SQLDF bietet eine schöne Lösung

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')

Und die Zeilen, die sich in beiden Datenrahmen befinden:

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

Die neue Version von dplyrhat eine Funktion anti_joinfür genau diese Art von Vergleichen

require(dplyr) 
anti_join(a1,a2)

Und semi_joinZeilen darin zu filtern a1sind auch ina2

semi_join(a1,a2)

18
Danke für anti_joinund semi_join!
Drastega

Gibt es einen Grund, warum anti_join wie sqldf einen Null-DF zurückgeben würde, aber die identischen Funktionen (a1, a2) und all.equal () würden dem widersprechen?
3pitt

Ich wollte hier nur hinzufügen, dass anti_join und semi_join in einigen Fällen wie meinem nicht funktionieren würden. Ich habe für meinen Datenrahmen "Fehler: Spalten müssen 1d-Atomvektoren oder -listen sein" erhalten. Vielleicht könnte ich meine Daten so verarbeiten, dass diese Funktionen funktionieren. Sqldf arbeitete direkt aus dem Tor!
Akshay Gaur

@AkshayGaur sollte es nur ein Datenformat oder ein Datenbereinigungsproblem sein; sqldf ist nur sql. Alles ist vorverarbeitet, um wie eine nromale Datenbank zu sein, sodass wir nur sql für die Daten ausführen können.
Stucash

75

In dplyr :

setdiff(a1,a2)

Grundsätzlich erhalten setdiff(bigFrame, smallFrame)Sie die zusätzlichen Datensätze in der ersten Tabelle.

In der SQLverse wird dies als a bezeichnet

Links ohne Join Venn-Diagramm

Für eine gute Beschreibung aller Join-Optionen und festgelegten Themen ist dies eine der besten Zusammenfassungen, die ich bisher zusammengestellt habe: http://www.vertabelo.com/blog/technical-articles/sql-joins

Aber zurück zu dieser Frage - hier sind die Ergebnisse für den setdiff()Code bei Verwendung der OP-Daten:

> 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

Oder Sie erhalten sogar anti_join(a1,a2)die gleichen Ergebnisse.
Für weitere Informationen: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf


2
Da die OP für Elemente in fragen a1, die nicht in a2, wollen Sie nicht zu nutzen so etwas wie semi_join(a1, a2, by = c('a','b'))? In der Antwort von "Rickard" sehe ich, dass dies semi_joinvorgeschlagen wurde.
Steveb

Sicher! Eine weitere gute Wahl; Insbesondere, wenn Sie Datenrahmen mit nur einem Verknüpfungsschlüssel und unterschiedlichen Spaltennamen haben.
Leerssej

setdiff ist von lubridate :: setdiff und nicht von library (dplyr)
mtelesha

@mtelesha - Hmm, die Dokumente und der Quellcode für dplyr zeigen, dass es dort ist: ( dplyr.tidyverse.org/reference/setops.html , github.com/tidyverse/dplyr/blob/master/R/sets. ) Wenn die dplyr-Bibliothek geladen wird, wird sogar gemeldet, dass die Basisfunktion maskiert wird, die setdiff()für zwei Vektoren funktioniert: stat.ethz.ch/R-manual/R-devel/library/base/html/sets.html . Vielleicht haben Sie die Lubridate- Bibliothek nach dplyr geladen und es wird vorgeschlagen, sie als Quelle in der tabcomplete-Liste zu verwenden?
Leerssej

1
Es gibt einen Konflikt zwischen lubridate und dplyr, siehe github.com/tidyverse/lubridate/issues/693
slhck

39

Es ist sicherlich nicht effizient für diesen speziellen Zweck, aber was ich in diesen Situationen oft mache, ist, Indikatorvariablen in jeden data.frame einzufügen und dann zusammenzuführen:

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

Fehlende Werte in include_a1 notieren, welche Zeilen in a1 fehlen. ähnlich für a2.

Ein Problem bei Ihrer Lösung besteht darin, dass die Spaltenreihenfolgen übereinstimmen müssen. Ein weiteres Problem besteht darin, dass man sich leicht Situationen vorstellen kann, in denen die Zeilen gleich codiert sind, obwohl sie tatsächlich unterschiedlich sind. Der Vorteil der Zusammenführung besteht darin, dass Sie kostenlos alle Fehlerprüfungen erhalten, die für eine gute Lösung erforderlich sind.


Also ... bei der Suche nach einem fehlenden Wert erstellen Sie einen weiteren fehlenden Wert ... Wie finden Sie die fehlenden Werte in included_a1? : - /
Louis Maddox

1
benutze is.na () und subset oder dplyr :: filter
Eduardo Leoni

Vielen Dank, dass Sie einen Weg gelehrt haben, ohne eine neue Bibliothek zu installieren!
Rodrigo

27

Ich habe ein Paket geschrieben ( https://github.com/alexsanjoseph/compareDF ), da ich das gleiche Problem hatte.

  > 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

Ein komplizierteres Beispiel:

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

Das Paket verfügt außerdem über einen Befehl html_output zur schnellen Überprüfung

df_compare $ html_output Geben Sie hier die Bildbeschreibung ein


Ihr compareDF ist genau das, was ich brauche, und hat mit kleinen Sätzen gute Arbeit geleistet. 2) Ich sehe auch, dass das Schreiben von HTML einige Zeit in Anspruch nimmt. Kann dieselbe Ausgabe an die TEXT-Datei gesendet werden?
Tief

1) Ja, 50 Millionen Zeilen sind VIELE Daten, nur um sie im Speicher zu halten;). Ich bin mir bewusst, dass es bei großen Datenmengen nicht besonders gut ist, daher müssen Sie möglicherweise eine Art Chunking durchführen. 2) Sie können das Argument - limit_html = 0 angeben, um zu vermeiden, dass es in HTML gedruckt wird. Die gleiche Ausgabe befindet sich in compare_output $ compare_df, die Sie mit nativen R-Funktionen in eine CSV / TEXT-Datei schreiben können.
Alex Joseph

Vielen Dank für Ihre Antwort @Alex Joseph, ich werde es versuchen und Sie wissen lassen, wie es geht.
Tief

Hallo @Alex Joseph, danke für die Eingabe, das Textformat hat funktioniert, aber ein Problem gefunden, das unter: stackoverflow.com/questions/54880218/…
Deep

Es kann nicht mit einer unterschiedlichen Anzahl von Spalten umgehen. Ich habe einen Fehler erhaltenThe two data frames have different columns!
PeyM87

14

Sie könnten das daffPaket verwenden (das die daff.jsBibliothek mit dem V8Paket umschließt ):

library(daff)

diff_data(data_ref = a2,
          data = a1)

erzeugt das folgende Differenzobjekt:

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

Das Diff-Format wird im Coopy-Textmarker-Diff-Format für Tabellen beschrieben und sollte ziemlich selbsterklärend sein. Die Zeilen mit +++in der ersten Spalte @@sind diejenigen, die neu sind a1und in nicht vorhanden sinda2 .

Das Differenzobjekt kann verwendet werden patch_data(), um den Unterschied zu Dokumentationszwecken zu speichern write_diff()oder um den Unterschiedrender_diff() zu visualisieren, indem :

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

generiert eine ordentliche HTML-Ausgabe:

Geben Sie hier die Bildbeschreibung ein


10

Mit diffobjPaket:

library(diffobj)

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

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein


10

Ich habe das angepasst merge Funktion , um diese Funktionalität zu erhalten. Bei größeren Datenrahmen wird weniger Speicher benötigt als bei der vollständigen Zusammenführungslösung. Und ich kann mit den Namen der Schlüsselspalten spielen.

Eine andere Lösung besteht darin, die Bibliothek zu verwenden 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

Ihre Beispieldaten enthalten keine Duplikate, aber Ihre Lösung verarbeitet sie automatisch. Dies bedeutet, dass möglicherweise einige der Antworten bei Duplikaten nicht mit den Ergebnissen Ihrer Funktion übereinstimmen.
Hier ist meine Lösung, deren Adresse genauso dupliziert wird wie Ihre. Es skaliert auch großartig!

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

Es benötigt data.table 1.9.8+


2

Vielleicht ist es zu einfach, aber ich habe diese Lösung verwendet und finde sie sehr nützlich, wenn ich einen Primärschlüssel habe, mit dem ich Datensätze vergleichen kann. Hoffe es kann helfen.

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,]

Wie unterscheidet sich das von dem, was OP bereits versucht hat? Sie haben genau den gleichen Code wie Tal verwendet, um eine einzelne Spalte anstelle der gesamten Zeile zu vergleichen (was die Voraussetzung war)
David Arenburg

1

Noch eine andere Lösung basierend auf match_df in plyr. Hier ist plyrs 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]
}

Wir können es ändern, um zu negieren:

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]
}

Dann:

diff <- negate_match_df(a1,a2)

1

Verwenden von subset:

missing<-subset(a1, !(a %in% a2$a))

Diese Antwort funktioniert für das OP-Szenario. Was ist mit dem allgemeineren Fall, wenn die Variable "a" zwischen den beiden Datenrahmen ("a1" und "a2") übereinstimmt, die Variable "b" jedoch nicht?
Bryan F

1

Der folgende Code verwendet beide data.tableund fastmatchfür eine höhere Geschwindigkeit.

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.