2010-07-03 8 views
105

nicht vorhanden sind Ich habe folgende 2 data.frames:Vergleichen Sie zwei data.frames die Zeilen in data.frame 1 zu finden, die in data.frame 2

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

ich die Zeile a1 finden wollen hat das a2 nicht.

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

(ps: Ich habe eine Lösung für sie geschrieben habe, bin ich einfach nur neugierig, wenn jemand bereits eine gearbeiteten Code gemacht)

Hier ist meine Lösung ist:

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) 

Antwort

69

Dies beantwortet nicht Ihre Frage direkt, aber es gibt Ihnen die Elemente, die gemeinsam sind.

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 compare gibt Ihnen viel Flexibilität in Bezug auf welche Art von Vergleichen sind erlaubt (zB Reihenfolge der Elemente jedes Vektors zu ändern, zu ändern, um und Namen: Dies kann mit Paul Murrell Paket compare getan werden Variablen, verkürzende Variablen, ändern Fall von Zeichenfolgen). Daraus sollten Sie herausfinden können, was von dem einen oder anderen fehlt. 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 
+0

Ich finde diese Funktion verwirrend. Ich dachte, es würde für mich funktionieren, aber es scheint nur zu funktionieren, wie oben gezeigt, wenn ein Satz identische übereinstimmende Reihen des anderen Satzes enthält. Betrachte diesen Fall: 'a2 <- data.frame (a = c (1: 3, 1), b = c (Buchstaben [1: 3]," c "))'. Lassen Sie 'a1' gleich. Jetzt versuche den Vergleich.Es ist mir nicht einmal beim Lesen der Optionen klar, was der richtige Weg ist, nur gemeinsame Elemente aufzulisten. – Hendy

35

Es ist sicherlich nicht effizient dieser bestimmte Zweck, aber was ich tue oft in diesen Situationen ist Indikatorvariablen in jedem data.frame einfügen und dann verschmelzen:

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

Werte in included_a1 fehlen wird nicht Welche Zeilen fehlen in a1? ähnlich für a2.

Ein Problem mit Ihrer Lösung besteht darin, dass die Spaltenbestellungen übereinstimmen müssen. Ein anderes Problem besteht darin, dass es leicht ist, sich Situationen vorzustellen, in denen die Zeilen als dieselben codiert sind, wenn sie tatsächlich verschieden sind. Der Vorteil der Verwendung von Zusammenführen ist, dass Sie alle Fehlerprüfung kostenlos erhalten, die für eine gute Lösung erforderlich ist.

+0

So ... in der Suche nach einem fehlenden Wert, erstellen Sie einen anderen fehlenden Wert ... Wie finden Sie das fehlende val Ue (s) in 'included_a1'? : -/ –

+0

Verwenden Sie is.na() und Teilmenge, oder dplyr :: Filter –

8

Ich angepasst die Zusammenführungsfunktion, 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 ist, die Bibliothek Prob zu verwenden.

# 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) 
} 
106

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 in beiden Datenrahmen:

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

Die neue Version von dplyr hat eine Funktion, anti_join, für genau diese Arten von Vergleichen

require(dplyr) 
anti_join(a1,a2) 

Und semi_join zu filtern Zeilen in a1, die auch in a2

semi_join(a1,a2) 
+11

Danke für 'anti_join' und' semi_join'! – drastega

+0

gibt es einen Grund, warum anti_join eine Null DF zurückgeben würde, wie würde sqldf, aber die Funktionen identisch (a1, a2) und all.equal() würde dem widersprechen? –

+0

Ich wollte nur hinzufügen, dass anti_join und semi_join in einigen Fällen nicht funktionieren würden. Ich bekam "Fehler: Spalten müssen 1d atomare Vektoren oder Listen sein" für meinen Datenrahmen. Vielleicht könnte ich meine Daten so verarbeiten, dass diese Funktionen funktionieren. Sqldf arbeitete direkt aus dem Tor! –

15

ich ein Paket (https://github.com/alexsanjoseph/compareDF) geschrieben, 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 hat auch einen html_output Befehl für die schnelle

Prüfung

df_compare$html_output enter image description here

1

Noch eine andere Lösung basiert auf match_df in plyr. Hier plyr der 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, 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) 
2

Ihre Beispieldaten haben keine Duplikate, aber Ihre Lösung mit ihnen umgehen automatisch . Dies bedeutet, dass möglicherweise einige der Antworten im Falle von Duplikaten nicht mit den Ergebnissen Ihrer Funktion übereinstimmen.
Hier ist meine Lösung, die Adresse dupliziert die gleiche Weise 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 braucht data.table 1.9.7, die derzeit aus Quelle Repo

install.packages("data.table", type = "source", 
    repos = "https://Rdatatable.github.io/data.table") 
2

Vielleicht installiert es ist zu einfach, aber ich habe diese Lösung und Ich finde es sehr nützlich, wenn ich einen Primärschlüssel habe, mit dem ich Datensätze vergleichen kann. Ich 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,] 
+0

Wie unterscheidet sich das von dem, was OP bereits versucht? Sie haben den gleichen Code wie Tal verwendet, um eine einzelne Spalte anstelle der ganzen Zeile zu vergleichen (was die Anforderung war) –

5

Mit diffobj Paket:

library(diffobj) 

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

enter image description here

enter image description here

21

In dplyr:

setdiff(a1,a2) 

Im Grunde erhält setdiff(bigFrame, smallFrame) Sie die zusätzlichen Datensätze in der ersten Tabelle.

Im SQLverse dies ein

genannt wird

Left Excluding Join Venn Diagram

Für eine gute Beschreibung aller Optionen verbinden und Themen setzen, ist dies eines der besten Zusammenfassungen habe ich bisher zusammen gesehen: http://www.vertabelo.com/blog/technical-articles/sql-joins

Aber zurück zu dieser Frage - hier sind die Ergebnisse für den setdiff() Code, wenn die Daten des OP mit:

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

+0

Da das OP nach Elementen in 'a1' fragt, die nicht in' a2' sind, wollen Sie nicht etwas wie 'semi_join (a1, a2, by = c ('a', 'b'))' '? In der Antwort von "Rickard" sehe ich, dass "semi_join" vorgeschlagen wurde. – steveb

+0

Sicher! Eine weitere gute Wahl; insbesondere wenn Sie Datenrahmen mit nur einem Join-Schlüssel und unterschiedlichen Spaltennamen haben. –

2

Sie die daff package (die die daff.js library mit hüllt den V8 package) verwenden:

library(daff) 

diff_data(data_ref = a2, 
      data = a1) 

, die folgende Differenz erzeugt Objekt:

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 

Die diff-Format ist in Coopy highlighter diff format for tables beschrieben und sollte prett sein y selbsterklärend. Die Zeilen mit +++ in der ersten Spalte @@ sind diejenigen, die in a1 neu sind und in a2 nicht vorhanden sind.

Der Unterschied Objekt kann patch_data() verwendet werden, um die Differenz zu Dokumentationszwecken write_diff() Verwendung zu speichern oder zu den Unterschied visualisieren Verwendung render_diff():

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

, die eine saubere HTML-Ausgabe erzeugt:

enter image description here