Comparez deux data.frames pour trouver les lignes dans data.frame 1 qui ne sont pas présentes dans data.frame 2

J’ai les 2 data.frames suivants:

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

Je veux trouver la ligne a1 a a2 pas.

Existe-t-il une fonction intégrée pour ce type d’opération?

(ps: j’ai écrit une solution pour cela, je suis simplement curieux de savoir si quelqu’un a déjà créé un code plus élaboré)

Voici ma solution:

 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) 

Cela ne répond pas directement à votre question, mais cela vous donnera les éléments communs. Cela peut être fait avec le package de Paul Murrell:

 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 

La fonction compare vous donne beaucoup de souplesse en termes de types de comparaisons autorisés (par exemple, changement d’ordre des éléments de chaque vecteur, changement d’ordre et de nom des variables, raccourcissement des variables, changement de casse des chaînes). De cela, vous devriez être capable de comprendre ce qui manquait de l'un ou l'autre. Par exemple (ce n'est pas très élégant):

 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 fournit une solution intéressante

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

Et les lignes qui sont dans les deux trames de données:

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

La nouvelle version de dplyr a une fonction, anti_join , pour exactement ce genre de comparaisons

 require(dplyr) anti_join(a1,a2) 

Et semi_join pour filtrer les lignes de a1 qui sont aussi dans a2

 semi_join(a1,a2) 

En dplyr :

 setdiff(a1,a2) 

En gros, setdiff(bigFrame, smallFrame) vous donne les enregistrements supplémentaires dans la première table.

Dans le SQLverse on appelle cela un

Gauche excluant le diagramme de Venn

Pour obtenir de bonnes descriptions de toutes les options de jointure et définir les sujets, ceci est l’un des meilleurs résumés que j’ai vu mis en place à ce jour: http://www.vertabelo.com/blog/technical-articles/sql-joins

Mais revenons à cette question – voici les résultats pour le code setdiff() lorsque vous utilisez les données de l’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 

Ou même anti_join(a1,a2) vous obtiendrez les mêmes résultats.
Pour plus d’informations: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

Ce n’est certainement pas efficace pour ce but particulier, mais dans la plupart des cas, je le fais souvent en insérant des variables indicasortingces dans chaque nom de données, puis en fusionnant:

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

les valeurs manquantes dans included_a1 indiqueront quelles lignes sont manquantes dans a1. de même pour a2.

Un problème avec votre solution est que les commandes de colonne doivent correspondre. Un autre problème est qu’il est facile d’imaginer des situations où les lignes sont codées de la même façon, alors qu’elles sont différentes. L'avantage de l'utilisation de la fusion est que vous obtenez gratuitement toutes les vérifications d'erreur nécessaires à une bonne solution.

J’ai écrit un paquet ( https://github.com/alexsanjoseph/compareDF ) car j’avais le même problème.

  > 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 exemple plus compliqué:

 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 

Le paquet a aussi une commande html_output pour une vérification rapide

df_compare $ html_output entrer la description de l'image ici

J’ai adapté la fonction de merge pour obtenir cette fonctionnalité. Sur les grandes structures de données, il utilise moins de mémoire que la solution de fusion complète. Et je peux jouer avec les noms des colonnes clés.

Une autre solution consiste à utiliser le prob bibliothèque.

 # 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 redissortingbute 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 dissortingbuted 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) } 

En diffobj paquet diffobj :

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

entrer la description de l'image ici

entrer la description de l'image ici

Vous pouvez utiliser le package daff (qui enveloppe la bibliothèque daff.js à l’aide du package V8 ):

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

produit l’object de différence suivant:

 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 

Le format diff est décrit dans le format diff de surligneur Coopy pour les tableaux et devrait être assez explicite. Les lignes avec +++ dans la première colonne @@ sont celles qui sont nouvelles dans a1 et non présentes dans a2 .

L’object de différence peut être utilisé pour patch_data() , pour stocker la différence à des fins de documentation en utilisant write_diff() ou pour visualiser la différence en utilisant render_diff() :

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

génère une sortie HTML soignée:

entrer la description de l'image ici

Vos exemples de données n’ont pas de doublons, mais votre solution les gère automatiquement. Cela signifie que certaines des réponses risquent de ne pas correspondre aux résultats de votre fonction en cas de doublons.
Voici ma solution qui traite les doublons de la même manière que la vôtre. Il est également très bien adapté!

 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 

Il a besoin de data.table 1.9.8+

Peut-être que c’est trop simpliste, mais j’ai utilisé cette solution et je la trouve très utile lorsque j’ai une clé primaire que je peux utiliser pour comparer des ensembles de données. J’espère que ça peut aider.

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

Encore une autre solution basée sur match_df en plyr. Voici plyr’s 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] } 

Nous pouvons le modifier pour annuler:

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

Alors:

 diff < - negate_match_df(a1,a2) 

En utilisant un subsetsubset :

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