2010-07-03 15 views
105

que tienen las siguientes 2 data.frames:comparar dos data.frames para encontrar las filas de hoja.de.datos 1 que no están presentes en hoja.de.datos 2

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

que quieren encontrar la fila tiene a1 que a2 no.

¿Existe una función incorporada para este tipo de operación?

(PS: Yo escribo una solución para ello, simplemente estoy ansioso por ver si alguien ya hizo un código más elaborado)

Aquí está mi solución:

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) 

Respuesta

69

Esto no responde su pregunta directamente, pero le dará los elementos que son comunes. Esto se puede hacer con el paquete de Pablo 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 función compare le da una gran flexibilidad en términos de lo que está permitido tipo de comparaciones (por ejemplo, cambiar el orden de los elementos de cada vector, el orden y los nombres de los cambios variables, variables de acortamiento, cambio de mayúsculas y minúsculas). A partir de esto, debería ser capaz de descubrir qué le faltaba a uno u otro. Por ejemplo (esto no es muy 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

Encuentro esta función confusa. Pensé que funcionaría para mí, pero parece funcionar solo como se muestra arriba si un conjunto contiene filas idénticas del otro conjunto. Considere este caso: 'a2 <- data.frame (a = c (1: 3, 1), b = c (letras [1: 3]," c "))'. Deje 'a1' lo mismo. Ahora intenta la comparación.No me queda claro, incluso al leer las opciones, cuál es la forma correcta de enumerar solo los elementos comunes. – Hendy

35

Ciertamente, no es eficiente para este propósito en particular, pero lo hago a menudo en estas situaciones es insertar variables indicadoras en cada hoja.de.datos y luego fusionar:

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

valores perdidos en included_a1 no lo hará e qué filas faltan en a1. de manera similar para a2.

Un problema con su solución es que los pedidos de columna deben coincidir. Otro problema es que es fácil imaginar situaciones en las que las filas están codificadas como iguales cuando en realidad son diferentes. La ventaja de usar merge es que obtienes de forma gratuita todas las comprobaciones de error necesarias para una buena solución.

+0

Entonces ... al buscar un valor perdido, crea otro valor faltante ... ¿Cómo encuentra el valor perdido? ue (s) en 'included_a1'? : -/ –

+0

use is.na() y subconjunto, o dplyr :: filtro –

8

He adaptado la función de fusión para obtener esta funcionalidad. En marcos de datos más grandes, utiliza menos memoria que la solución de fusión completa. Y puedo jugar con los nombres de las columnas clave.

Otra solución es utilizar el problema de la 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((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 proporciona una buena solución

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

y las filas que están en ambas tramas de datos:

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

La nueva versión de dplyr tiene una función, anti_join, precisamente por estos tipos de comparaciones

require(dplyr) 
anti_join(a1,a2) 

Y semi_join para filtrar las filas de a1 que también están en a2

semi_join(a1,a2) 
+11

Gracias por 'anti_join' y' semi_join'! – drastega

+0

¿hay alguna razón por la cual anti_join devolvería un DF nulo, como lo haría sqldf, pero las funciones idénticas (a1, a2) y all.equal() lo contradecirían? –

+0

Solo quería agregar aquí que anti_join y semi_join no funcionarían en algunos casos como el mío. Obtenía "Error: las columnas deben ser 1d vectores o listas atómicas" para mi marco de datos. Tal vez podría procesar mis datos para que estas funciones funcionen. ¡Sqldf trabajó directamente desde la puerta! –

15

me escribió un paquete (https://github.com/alexsanjoseph/compareDF) ya que tenía el mismo 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 ejemplo más 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 

El paquete también tiene un comando html_output para la comprobación rápida

df_compare$html_output enter image description here

1

Sin embargo, otra solución basada en match_df en plyr. Aquí es 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] 
} 

Nos puede modificarlo 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] 
} 

continuación:

diff <- negate_match_df(a1,a2) 
2

Sus datos de ejemplo no tiene ningún duplicados, pero su solución a manejar de forma automática . Esto significa que potencialmente algunas de las respuestas no coincidirán con los resultados de su función en caso de duplicados.
Aquí está mi solución, cuya dirección se duplica de la misma manera que la suya. ¡También escalas genial!

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 

Se necesita data.table 1.9.7 que en la actualidad se puede instalar a partir de recompra fuente

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

Tal vez es demasiado simplista, pero he usado esta solución y Lo encuentro muy útil cuando tengo una clave principal que puedo usar para comparar conjuntos de datos. Espero que pueda ayudar.

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

¿En qué se diferencia esto de lo que OP ya intentó? Has usado exactamente el mismo código como Tal para comparar una sola columna en lugar de toda la fila (que era el requisito) –

5

Usando diffobj paquete:

library(diffobj) 

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

enter image description here

enter image description here

21

En dplyr:

setdiff(a1,a2) 

Básicamente, setdiff(bigFrame, smallFrame) le ofrece los registros adicionales en la primera tabla.

En el SQLverse esto se llama un

Left Excluding Join Venn Diagram

Por buenas descripciones de todas las opciones de unirse y establecer temas, este es uno de los mejores resúmenes que he visto juntar para la fecha: http://www.vertabelo.com/blog/technical-articles/sql-joins

Pero volviendo a esta pregunta - aquí están los resultados para el código setdiff() al utilizar los datos de la 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 incluso anti_join(a1,a2) obtendrá los mismos resultados.
Para más información: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

+0

Como OP pide elementos en 'a1' que no están en' a2', ¿no quieres? usar algo como 'semi_join (a1, a2, by = c ('a', 'b'))'? En la respuesta de "Rickard", veo que se sugirió 'semi_join'. – steveb

+0

¡Claro! Otra gran elección, también; especialmente si tiene marcos de datos con solo una clave de combinación y diferentes nombres de columna. –

2

podría utilizar el daff package (que envuelve el daff.js library utilizando el V8 package):

library(daff) 

diff_data(data_ref = a2, 
      data = a1) 

que produce el objeto siguiente diferencia:

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 

El el formato diff se describe en Coopy highlighter diff format for tables y debe ser prett y se explica por sí mismo. Las líneas con +++ en la primera columna @@ son las nuevas en a1 y no están presentes en a2.

El objeto diferencia se puede utilizar para patch_data(), para almacenar la diferencia para fines de documentación utilizando write_diff() o para visualizar la diferencia usando render_diff():

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

que genera una salida de HTML puro:

enter image description here

Cuestiones relacionadas