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

Eu tenho os seguintes 2 dados.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 que a2 não possui.

Existe uma function incorporada para este tipo de operação?

(ps: Eu escrevi uma solução para isso, estou simplesmente curioso se alguém já fez um código mais elaborado)

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) 

Isso não responde diretamente à sua pergunta, mas lhe dará os elementos em comum. Isso pode ser feito com o pacote 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 

A compare funções oferece muita flexibilidade em termos de quais tipos de comparações são permitidas (por exemplo, alterar a ordem dos elementos de cada vetor, alterar a ordem e os nomes das variables, encurtar variables, alterar maiúsculas e minúsculas). A partir disso, você deve ser capaz de descobrir o que estava faltando de 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 # ab #1 4 d #2 5 e 

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 em ambos os frameworks de dados:

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

A nova versão do dplyr tem uma function, anti_join , para exatamente esses tipos de comparações

 require(dplyr) anti_join(a1,a2) 

E semi_join para filtrar linhas em a1 que também estão em a2

 semi_join(a1,a2) 

Certamente não é eficiente para esse propósito específico, mas o que eu faço com frequência nessas situações é inserir variables ​​indicadoras em cada data.frame e depois mesclar:

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

valores faltantes em included_a1 notarão quais linhas estão faltando em a1. similarmente para a2.

Um problema com a sua solução é que os pedidos de coluna devem coincidir. Outro problema é que é fácil imaginar situações em que as linhas são codificadas como as mesmas 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.

Em dplyr :

 setdiff(a1,a2) 

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

No SQLverse isso é chamado de

Esquerda Excluindo o diagrama de Venn

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

Mas voltando a essa questão – aqui estão os resultados para o código setdiff() ao usar os dados do 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 até mesmo anti_join(a1,a2) você obterá os mesmos resultados.
Para mais informações: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

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

Eu adaptei a function de merge para obter essa funcionalidade. Em frameworks de dados maiores, ele usa menos memory do que a solução de mesclagem completa. E eu posso brincar com os nomes das colunas-chave.

Outra solução é usar o prob da 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 o pacote diffobj :

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

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Seus dados de exemplo não têm duplicatas, mas sua solução os manipula automaticamente. Isso significa que, potencialmente, algumas das respostas não corresponderão aos resultados de sua function no caso de duplicatas.
Aqui está a minha solução, cujo endereço é duplicado da mesma forma que o seu. Ele também é ó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 

Ele precisa do data.table 1.9.7 que atualmente pode ser instalado a partir do repository de origem

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

Você poderia usar o pacote daff.js (que envolve a biblioteca daff.js usando o pacote V8 ):

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

produz o seguinte object de diferença:

 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 

O formato do diff é descrito no formato de diferenças do marcador Coopy para tabelas e deve ser bastante autoexplicativo. As linhas com +++ na primeira coluna @@ são as que são novas em a1 e não estão presentes em a2 .

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

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

gera uma saída HTML pura:

insira a descrição da imagem aqui

Talvez seja muito simplista, mas eu usei essa solução e acho muito útil quando eu tenho uma chave primária que eu possa 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,] 

Ainda outra solução baseada em match_df em 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) 

Usando subconjunto:

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