web-dev-qa-db-fra.com

Comparez deux data.frames pour rechercher les lignes de 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 qui a2 n'a pas.

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

(p.s: 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)
143
Tal Galili

Cela ne répond pas directement à votre question, mais vous donnera les éléments communs. Cela peut être fait avec le paquet de 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 fonction compare vous donne beaucoup de flexibilité en ce qui concerne le type de comparaisons autorisées (par exemple, modification de l'ordre des éléments de chaque vecteur, modification de l'ordre et des noms de variables, réduction des variables, modification de la casse des chaînes). À partir de là, vous devriez pouvoir déterminer ce qui manque à 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
#  a b
#1 4 d
#2 5 e
84
nullglob

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 cadres 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 dans a1 qui sont également dans a2

semi_join(a1,a2)
131
Rickard

Dans dplyr :

setdiff(a1,a2)

Fondamentalement, setdiff(bigFrame, smallFrame) vous permet d'obtenir les enregistrements supplémentaires de la première table.

Dans le SQLverse cela s'appelle un

Left Excluding Join Venn Diagram

Pour une bonne description de toutes les options de jointure et de sujets définis, c’est l’un des meilleurs résumés que j’ai vu rassemblé à 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
  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

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

59
leerssej

Ce n'est certainement pas efficace pour cet objectif particulier, mais ce que je fais souvent dans ces situations est d'insérer des variables d'indicateur dans chaque data.frame, puis de les fusionner:

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

les valeurs manquantes dans included_a1 noteront les lignes manquantes dans a1. de même pour a2.

Un problème avec votre solution est que les ordres de colonne doivent correspondre. Un autre problème est qu’il est facile d’imaginer des situations dans lesquelles les lignes sont codées de la même manière alors qu’elles sont en fait différentes. L'avantage d'utiliser la fusion est que vous obtenez gratuitement toutes les vérifications d'erreur nécessaires à une bonne solution.

39
Eduardo Leoni

J'ai écrit un package ( 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 a b
  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 enter image description here

25
Alex Joseph

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'objet différence suivant:

_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
_

Le format diff est décrit dans format diff du 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’objet 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:

enter image description here

11
Salim B

Utilisation du package diffobj:

library(diffobj)

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

enter image description here

enter image description here

10
zx8754

J'ai adapté la fonction merge pour obtenir cette fonctionnalité. Sur de plus grandes images, 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 la bibliothèque 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)
}
8
Henrico

Vos données d'exemple ne contiennent pas de doublons, mais votre solution les gère automatiquement. Cela signifie que potentiellement, certaines des réponses ne correspondront pas aux résultats de votre fonction en cas de doublons.
Voici ma solution qui adresse les doublons de la même manière que la vôtre. C'est aussi une bonne balance!

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+

5
jangorecki

C'est peut-être 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,]
2
Kenia Sousa

Utiliser subset:

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

Encore une autre solution basée sur match_df dans plyr. Voici le match_df de 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]
}

Nous pouvons le modifier pour nier:

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

Ensuite:

diff <- negate_match_df(a1,a2)
1
chrisendres