Confronta due data.frames per trovare le righe in data.frame 1 che non sono presenti in data.frame 2

Ho i seguenti 2 data.frames:

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

Voglio scoprire che la riga a1 ha che a2 no.

Esiste una funzione integrata per questo tipo di operazione?

(ps: Ho scritto una soluzione per questo, sono semplicemente curioso di sapere se qualcuno ha già realizzato un codice più elaborato)

Ecco la mia soluzione:

 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) 

Questo non risponde alla tua domanda direttamente, ma ti darà gli elementi che sono in comune. Questo può essere fatto con il pacchetto di Paul Murrell 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 # ab #1 1 a #2 2 b #3 3 c 

Il compare funzioni offre molta flessibilità in termini di tipo di confronto consentito (ad esempio, modifica dell'ordine degli elementi di ciascun vettore, modifica dell'ordine e dei nomi delle variabili, riduzione delle variabili, modifica del caso delle stringhe). Da questo, dovresti essere in grado di capire cosa mancava da uno o dall'altro. Ad esempio (questo non è molto elegante):

 difference < - data.frame(lapply(1:ncol(a1),function(i)setdiff(a1[,i],comparison$tM[,i]))) colnames(difference) <- colnames(a1) difference # ab #1 4 d #2 5 e 

SQLDF offre una buona soluzione

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

E le righe che si trovano in entrambi i frame di dati:

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

La nuova versione di dplyr ha una funzione, anti_join , proprio per questi tipi di confronti

 require(dplyr) anti_join(a1,a2) 

E semi_join per filtrare le righe in a1 che si trovano anche in a2

 semi_join(a1,a2) 

In dplyr :

 setdiff(a1,a2) 

Fondamentalmente, setdiff(bigFrame, smallFrame) ti setdiff(bigFrame, smallFrame) i record extra nella prima tabella.

Nello SQLverse questo è chiamato a

Escluso a sinistra Unisciti a Venn Diagram

Per una buona descrizione di tutte le opzioni di join e degli argomenti impostati, questo è uno dei migliori riepiloghi che abbia mai visto finora: http://www.vertabelo.com/blog/technical-articles/sql-joins

Ma torniamo a questa domanda – ecco i risultati per il codice setdiff() quando si usano i dati dell’OP:

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

O anche anti_join(a1,a2) otterrà gli stessi risultati.
Per maggiori informazioni: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

Non è certamente efficiente per questo particolare scopo, ma quello che faccio spesso in queste situazioni è inserire variabili indicatore in ogni data.frame e quindi unire:

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

i valori mancanti in included_a1 noteranno quali righe mancano in a1. allo stesso modo per a2.

Un problema con la soluzione è che gli ordini di colonna devono corrispondere. Un altro problema è che è facile immaginare situazioni in cui le righe sono codificate come le stesse quando in realtà sono diverse. Il vantaggio dell'uso di unione è che si ottiene gratuitamente il controllo degli errori necessario per una buona soluzione.

Ho scritto un pacchetto ( https://github.com/alexsanjoseph/compareDF ) poiché ho avuto lo stesso problema.

  > 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 ab 1 4 + 4 d 2 5 + 5 e 

Un esempio più complicato:

 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 

Il pacchetto ha anche un comando html_output per il controllo rapido

df_compare $ html_output inserisci la descrizione dell'immagine qui

Ho adattato la funzione di merge per ottenere questa funzionalità. Nei datafram più grandi utilizza meno memoria rispetto alla soluzione di fusione completa. E posso giocare con i nomi delle colonne chiave.

Un’altra soluzione è usare il prob biblioteca.

 # 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((lb <- length(by.x)) != length(by.y)) stop("'by.x' and 'by.y' specify different numbers of columns") if(lb == 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(lb == 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) } 

Usando il pacchetto diffobj :

 library(diffobj) diffPrint(a1, a2) diffObj(a1, a2) 

inserisci la descrizione dell'immagine qui

inserisci la descrizione dell'immagine qui

È ansible utilizzare il pacchetto daff (che daff.js libreria daff.js utilizzando il pacchetto V8 ):

 library(daff) diff_data(data_ref = a2, data = a1) 

produce il seguente object differenza:

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

Il formato diff è descritto nel formato di evidenziatore di evidenziatore di Coopy per le tabelle e dovrebbe essere abbastanza auto-esplicativo. Le righe con +++ nella prima colonna @@ sono quelle che sono nuove in a1 e non presenti in a2 .

L’object differenza può essere usato per patch_data() , per memorizzare la differenza a scopo di documentazione usando write_diff() o per visualizzare la differenza usando render_diff() :

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

genera un output HTML pulito:

inserisci la descrizione dell'immagine qui

I tuoi dati di esempio non hanno duplicati, ma la tua soluzione li gestisce automaticamente. Ciò significa che potenzialmente alcune risposte non corrisponderanno ai risultati della tua funzione in caso di duplicati.
Ecco la mia soluzione che indirizza i duplicati allo stesso modo dei tuoi. È anche scalabile alla grande!

 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 

Ha bisogno di data.table 1.9.8+

Forse è troppo semplicistico, ma ho usato questa soluzione e lo trovo molto utile quando ho una chiave primaria che posso usare per confrontare i set di dati. Spero che possa aiutare.

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

Ancora un’altra soluzione basata su match_df in plyr. Ecco il match_df di 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] } 

Possiamo modificarlo per negare:

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

Poi:

 diff < - negate_match_df(a1,a2) 

Utilizzando il subset :

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