Compare dois data.frames para encontrar as linhas no data.frame 1 que não estão presentes no data.frame 2


161

Eu tenho os seguintes 2 data.frames:

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

Eu quero encontrar a linha a1 tem que a2 não.

Existe uma função integrada para esse tipo de operação?

(ps: eu escrevi uma solução para isso, estou simplesmente curioso para saber se alguém já criou um código mais criado)

Aqui está a minha solução:

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)

Respostas:


88

Isso não responde diretamente à sua pergunta, mas fornece os elementos comuns. Isso pode ser feito com o pacote 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

A função compareoferece muita flexibilidade em termos de que tipo de comparações são permitidas (por exemplo, ordem de mudança de elementos de cada vetor, ordem de mudança e nomes de variáveis, encurtamento de variáveis, mudança de maiúsculas e minúsculas). Com isso, você deve conseguir descobrir o que estava faltando em um ou outro. Por exemplo (isso não é muito 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

3
Acho essa função confusa. Eu pensei que funcionaria para mim, mas parece funcionar apenas como mostrado acima, se um conjunto contiver linhas idênticas do outro conjunto. Considere este caso: a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). Deixe a1o mesmo. Agora tente a comparação. Não está claro para mim, mesmo lendo as opções, qual é a maneira correta de listar apenas elementos comuns.
Hendy

148

SQLDF fornece uma boa solução

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 as linhas que estão nos dois quadros de dados:

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

A nova versão do dplyrtem uma função, anti_joinexatamente para esse tipo de comparação

require(dplyr) 
anti_join(a1,a2)

E semi_joinpara filtrar linhas a1que também estão ema2

semi_join(a1,a2)

18
Obrigado por anti_joine semi_join!
drastega

existe uma razão pela qual anti_join retornaria um DF nulo, como o sqldf, mas as funções idênticas (a1, a2) e all.equal () contradizem isso?
3pitt 10/10

Só queria acrescentar aqui que anti_join e semi_join não funcionariam em alguns casos como o meu. Eu estava recebendo "Erro: as colunas devem ser vetores ou listas atômicas 1d" para o meu quadro de dados. Talvez eu possa processar meus dados para que essas funções funcionem. Sqldf trabalhou direto do portão!
Akshay Gaur

@AkshayGaur, deve ser apenas um formato de dados ou um problema de limpeza de dados; sqldf é apenas sql, tudo é pré-processado para ser como um banco de dados nromal, de forma que possamos executar o sql nos dados.
Stucash

75

No dplyr :

setdiff(a1,a2)

Basicamente, setdiff(bigFrame, smallFrame)você obtém os registros extras na primeira tabela.

No SQLverse, isso é chamado de

Esquerda Excluindo o diagrama de junção Venn

Para obter boas descrições de todas as opções de junção e definir assuntos, este é um dos melhores resumos que já vi reunidos até hoje: http://www.vertabelo.com/blog/technical-articles/sql-joins

Mas voltando a esta pergunta - eis os resultados do setdiff()código ao usar os dados do 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 você ainda anti_join(a1,a2)terá os mesmos resultados.
Para mais informações: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf


2
Como o OP solicita itens a1que não estão a2, você não deseja usar algo parecido semi_join(a1, a2, by = c('a','b'))? Na resposta de "Rickard", vejo que isso semi_joinfoi sugerido.
precisa

Certo! Outra ótima opção também; especialmente se você tiver quadros de dados com apenas uma chave de junção e nomes de colunas diferentes.
precisa

setdiff é de lubridate :: setdiff e não de biblioteca (dplyr)
mtelesha

@mtelesha - Hmm, os documentos e o código-fonte do dplyr mostram que ele está lá: ( dplyr.tidyverse.org/reference/setops.html , github.com/tidyverse/dplyr/blob/master/R/sets ). Além disso, quando a biblioteca dplyr é carregada, ela também informa setdiff()que oculta a função base que funciona em dois vetores: stat.ethz.ch/R-manual/R-devel/library/base/html/sets.html . Talvez você tenha carregado a biblioteca de lubrificados após o dplyr e está sugerindo isso como a fonte na listagem de tabcomplete?
Leerssej # 25/18

1
Existe um conflito entre lubridato e dplyr, consulte github.com/tidyverse/lubridate/issues/693
slhck

39

Certamente não é eficiente para esse propósito específico, mas o que costumo fazer nessas situações é inserir variáveis ​​indicadoras em cada data.frame e depois mesclar:

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

valores ausentes em included_a1 observarão quais linhas estão faltando em a1. da mesma forma para a2.

Um problema com sua solução é que os pedidos das colunas devem corresponder. Outro problema é que é fácil imaginar situações em que as linhas são codificadas da mesma forma quando na verdade são diferentes. A vantagem de usar a mesclagem é que você obtém gratuitamente toda a verificação de erros necessária para uma boa solução.


Então ... ao procurar um valor ausente, você cria outro valor ausente ... Como você encontra os valores ausentes included_a1? : - /
Louis Maddox

1
usar is.na () e subconjunto, ou dplyr :: filtro
Eduardo Leoni

Obrigado por ensinar uma maneira sem instalar uma nova biblioteca!
Rodrigo

27

Eu escrevi um pacote ( https://github.com/alexsanjoseph/compareDF ) desde que tive o mesmo 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

Um exemplo mais complicado:

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

O pacote também possui um comando html_output para verificação rápida

df_compare $ html_output insira a descrição da imagem aqui


seu compareDF é exatamente o que eu preciso e fiz um bom trabalho com conjuntos pequenos. No entanto: 1) Não está trabalhando com um conjunto de 50 milhões de linhas com 3 colunas (digamos), ele diz que falta de memória com 32 GB de RAM. 2) Também vejo que o HTML leva algum tempo para escrever. A mesma saída pode ser enviada para o arquivo TEXT?
Profundo

1) Sim, 50 milhões de linhas são MUITOS dados, apenas para armazenar na memória;). Estou ciente de que não é ótimo com grandes conjuntos de dados, portanto, talvez você precise fazer algum tipo de divisão. 2) você pode dar o argumento - limit_html = 0, para evitar a impressão em um HTML. A mesma saída está em compare_output $ compare_df, que você pode gravar em um arquivo CSV / TEXT usando funções R nativas.
Alex Joseph

Obrigado pela sua resposta @Alex Joseph, vou tentar e informar como vai.
Profundo

Oi @Alex Joseph, obrigado pela entrada, o formato do texto funcionou, mas encontrou um problema, levantou-o em: stackoverflow.com/questions/54880218/…
Deep

Ele não pode lidar com diferentes números de colunas. Eu tenho um erroThe two data frames have different columns!
PeyM87

14

Você pode usar o daffpacote (que envolve a daff.jsbiblioteca usando o V8pacote ):

library(daff)

diff_data(data_ref = a2,
          data = a1)

produz o seguinte objeto de diferença:

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

O formato diff é descrito no formato diff da marca-texto Coopy para tabelas e deve ser bastante auto-explicativo. As linhas com +++na primeira coluna @@são as que são novas a1e não estão presentes a2.

O objeto de diferença pode ser usado para patch_data()armazenar a diferença para fins de documentação usando write_diff()ou para visualizar a diferença usandorender_diff() :

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

gera uma saída HTML pura:

insira a descrição da imagem aqui


10

Usando o diffobjpacote:

library(diffobj)

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

insira a descrição da imagem aqui

insira a descrição da imagem aqui


10

Eu adaptei a mergefunção para obter essa funcionalidade. Em quadros de dados maiores, ele usa menos memória que a solução de mesclagem completa. E eu posso brincar com os nomes das colunas principais.

Outra solução é usar a biblioteca 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)
}

7

Seus dados de exemplo não possuem duplicatas, mas sua solução lida com eles automaticamente. Isso significa que, potencialmente, algumas das respostas não corresponderão aos resultados da sua função no caso de duplicatas.
Aqui está minha solução cujo endereço é duplicado da mesma maneira que o seu. Também escala ótimo!

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

Precisa de data.table 1.9.8+


2

Talvez seja muito simplista, mas usei essa solução e acho muito útil quando tenho uma chave primária que posso usar para comparar conjuntos de dados. Espero que possa ajudar.

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

Como isso é diferente do que o OP já tentou? Você já usou o mesmo código exato como Tal comparar uma única coluna em vez de toda a linha (que foi a exigência)
David Arenburg

1

Mais uma solução baseada em match_df no plyr. Aqui está o 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]
}

Podemos modificá-lo para negar:

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

Então:

diff <- negate_match_df(a1,a2)

1

Usando subset:

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

Essa resposta funciona para o cenário do OP. E o caso mais geral, quando a variável "a" corresponde entre os dois data.frames ("a1" e "a2"), mas a variável "b" não?
11138 Bryan F é

1

O código a seguir usa ambos data.tablee fastmatchpara aumentar a velocidade.

library("data.table")
library("fastmatch")

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

compare_rows <- a1$a %fin% a2$a
# the %fin% function comes from the `fastmatch` package

added_rows <- a1[which(compare_rows == FALSE)]

added_rows

#    a b
# 1: 4 d
# 2: 5 e
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.