2010-07-03 13 views
105

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

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

voglio trovare la A1 riga ha che a2 no.

Esiste una funzione integrata per questo tipo di operazione?

(ps: ho scritto una soluzione per esso, io sono semplicemente curioso di sapere se qualcuno ha già fatto un codice più artigianale)

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) 

risposta

69

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 
# a b 
#1 1 a 
#2 2 b 
#3 3 c 

La funzione compare ti dà un sacco di flessibilità in termini di che tipo di confronti è permesso (ad esempio ordine di elementi di ogni vettore che cambia, l'ordine e nomi di cambiamento variabili, accorciando variabili, cambiando caso di 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 
# a b 
#1 4 d 
#2 5 e 
+0

Trovo questa funzione confusa. Ho pensato che avrebbe funzionato per me, ma sembra funzionare solo come mostrato sopra se un set contiene righe identiche dell'altra serie. Considera questo caso: 'a2 <- data.frame (a = c (1: 3, 1), b = c (letters [1: 3]," c "))'. Lascia 'a1' lo stesso. Ora prova il confronto.Non mi è chiaro nemmeno leggendo le opzioni, il modo corretto è elencare solo elementi comuni. – Hendy

35

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

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

valori mancanti in included_a1 non lo farà e 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.

+0

Quindi ... in cerca di un valore mancante, si crea un altro valore mancante ... Come si trova la val mancante ue (s) in 'included_a1'? : -/ –

+0

uso is.na() e sottoinsieme, o dplyr :: filter –

8

Ho adattato la funzione di unione 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 è utilizzare il probabilit della libreria.

# 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 offre una bella 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 file 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, esattamente per questi tipi di confronti

require(dplyr) 
anti_join(a1,a2) 

E semi_join per filtrare le righe in a1 che sono anche in a2

semi_join(a1,a2) 
+11

Grazie per 'anti_join' e' semi_join'! – drastega

+0

c'è un motivo per cui anti_join restituisce un DF nullo, come farebbe sqldf, ma le funzioni identiche (a1, a2) e all.equal() lo contraddirebbero? –

+0

Volevo solo aggiungere che anti_join e semi_join non funzionerebbero in alcuni casi come il mio. Stavo ricevendo "Errore: le colonne devono essere 1d vettori o liste atomiche" per il mio frame di dati. Forse potrei elaborare i miei dati in modo che queste funzioni funzionino. Sqldf ha funzionato proprio fuori dal cancello! –

15

ho scritto un pacchetto (https://github.com/alexsanjoseph/compareDF) da quando 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 a b 
    1 4   + 4 d 
    2 5   + 5 e 

Un esempio più complesso:

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 un rapido controllo

df_compare$html_output enter image description here

1

Ancora un'altra soluzione basata su match_df in plyr. Ecco 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 modificare a 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) 
2

Il tuo esempio di dati non ha i duplicati, ma la soluzione gestirli 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 del tuo. È 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 data.table 1.9.7 che attualmente può essere installata da fonte repo

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

Forse è troppo semplicistico, ma ho usato questa soluzione e 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,] 
+0

Com'è diverso da ciò che OP ha già provato? Hai usato lo stesso identico codice come Tal per confrontare una singola colonna invece dell'intera riga (che era il requisito) –

5

Uso diffobj pacchetto:

library(diffobj) 

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

enter image description here

enter image description here

21

In dplyr:

setdiff(a1,a2) 

Fondamentalmente, setdiff(bigFrame, smallFrame) ottiene i record aggiuntivi nella prima tabella.

Nel SQLverse questo è chiamato un

Left Excluding Join Venn Diagram

Per buone descrizioni di tutte le opzioni aderire e impostare soggetti, questa è una delle migliori sintesi che ho visto messo insieme fino ad oggi: http://www.vertabelo.com/blog/technical-articles/sql-joins

Ma torniamo a questa domanda - qui sono i risultati per il codice setdiff() quando si utilizza i dati del 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 

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

+0

Poiché l'OP chiede elementi in 'a1' che non sono in' a2', non vuoi usare qualcosa come 'semi_join (a1, a2, by = c ('a', 'b'))'? Nella risposta di "Rickard", vedo che è stato suggerito 'semi_join'. – steveb

+0

Sicuro! Un'altra grande scelta, anche; in particolare se disponi di frame dati con solo una chiave join e nomi di colonne diversi. –

2

È possibile utilizzare il daff package (che avvolge il daff.js library utilizzando il V8 package):

library(daff) 

diff_data(data_ref = a2, 
      data = a1) 

che produce il seguente oggetto differenza:

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 

Il il formato diff è descritto in Coopy highlighter diff format for tables e dovrebbe essere prett y auto-esplicativo. Le righe con +++ nella prima colonna @@ sono quelle nuove in a1 e non presenti in a2.

L'oggetto differenza può essere utilizzato per patch_data(), per memorizzare la differenza a scopo di documentazione utilizzando write_diff() o visualizzare la differenza con render_diff():

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

che genera un'uscita HTML pulito:

enter image description here