2010-07-03 13 views
105

mam następujące 2 data.frames:Porównaj dwa data.frames znaleźć wiersze w data.frame 1, które nie są obecne w data.frame 2

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

Chcę odnaleźć a1 wiersz ma że a2 nie.

Czy istnieje funkcja do tego typu operacji?

(ps: Ja nie napisałem rozwiązanie dla niego, jestem po prostu ciekawy, czy ktoś już kod bardziej spreparowane)

Oto moje rozwiązanie:

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) 

Odpowiedz

69

To nie odpowiada bezpośrednio na twoje pytanie, ale daje elementy wspólne. Można to zrobić z pakietem Pawła Murrell za 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 

Funkcja compare daje dużą elastyczność w zakresie, jakiego rodzaju porównań są dozwolone (np kolejność elementów każdego wektora zmienia kolejność i nazwy zmienia zmienne, skracanie zmiennych, zmiana wielkości liter łańcuchów). Z tego powinieneś być w stanie dowiedzieć się, czego brakowało w jednym lub drugim. Na przykład (to niezbyt elegancko):

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

Funkcja ta jest myląca. Pomyślałem, że to zadziała, ale wydaje mi się, że działa tylko tak, jak pokazano powyżej, jeśli jeden zestaw zawiera identycznie pasujące wiersze drugiego zestawu. Rozważmy ten przypadek: 'a2 <- data.frame (a = c (1: 3, 1), b = c (litery [1: 3]," c "))'. Pozostaw 'a1' tak samo. Teraz spróbuj porównania.Nie jest dla mnie jasne, nawet czytając opcje, w jaki sposób należy wymienić tylko wspólne elementy. – Hendy

35

To z pewnością nie jest skuteczny dla ten konkretny cel, ale to, co często w takich sytuacjach jest wstawienie zmiennych wskaźnikowych w każdym data.frame a następnie scalić:

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

brakujących wartości w included_a1 nie będzie e których wierszy brakuje w a1. podobnie dla a2.

Problem z rozwiązaniem polega na tym, że zamówienia kolumn muszą być zgodne. Innym problemem jest to, że łatwo jest wyobrazić sobie sytuacje, w których rzędy są zakodowane jako takie same, gdy w rzeczywistości są różne. Zaletą korzystania z funkcji scalania jest bezpłatne sprawdzanie błędów, które jest niezbędne do dobrego rozwiązania.

+0

Więc ... szukając brakującej wartości, tworzysz kolejną brakującą wartość ... Jak znaleźć brakującą wartość? ue (s) w 'included_a1'? : -/ –

+0

użyj is.na() i podzbioru lub dplyr :: filter –

8

Dostosowałem funkcję scalania, aby uzyskać tę funkcjonalność. W przypadku większych ramek danych wykorzystuje mniej pamięci niż rozwiązanie pełnego scalania. I mogę grać z nazwami kluczowych kolumn.

Innym rozwiązaniem jest użycie probówki biblioteki.

# 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 zapewnia miły rozwiązanie

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

a wiersze, które są w obu ramkach danych:

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

Nowa wersja dplyr posiada funkcję, anti_join, na dokładnie te rodzaje porównań

require(dplyr) 
anti_join(a1,a2) 

I semi_join filtrować wiersze w a1 że są również w a2

semi_join(a1,a2) 
+11

Dzięki za 'anti_join' i' semi_join'! – drastega

+0

czy istnieje powód, dla którego funkcja anti_join zwróciłaby zerową wartość DF, tak jak w przypadku sqldf, ale funkcje identyczne (a1, a2) i all.equal() zaprzeczyłyby temu? –

+0

Chciałem tylko dodać, że funkcje anti_join i semi_join nie działałyby w niektórych przypadkach, jak moje. Otrzymałem komunikat "Błąd: kolumny muszą być 1d wektorami atomowymi lub listami" dla mojej ramki danych. Może mógłbym przetworzyć moje dane, aby te funkcje działały. Sqldf pracował prosto z bramy! –

15

Napisałem pakiet (https://github.com/alexsanjoseph/compareDF), ponieważ miałem ten sam problem.

> 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 

Bardziej skomplikowany przykład:

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 

Pakiet zawiera również polecenie html_output na szybkie sprawdzenie

df_compare$html_output enter image description here

1

Jeszcze inne rozwiązanie oparte na match_df w plyr. Oto plyr za 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] 
} 

Możemy zmodyfikować go do zanegowania:

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

Następnie:

diff <- negate_match_df(a1,a2) 
2

Twój przykład danych nie ma żadnych duplikatów, ale rozwiązanie obsługiwać je automatycznie . Oznacza to, że potencjalnie niektóre z odpowiedzi nie będą pasowały do ​​wyników Twojej funkcji w przypadku duplikatów.
Oto moje rozwiązanie, które adresuje duplikaty w taki sam sposób jak twój. To także doskonale się skaluje!

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 

Trzeba data.table 1.9.7, które obecnie mogą być zainstalowane ze źródła repo

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

Może to jest zbyt uproszczone, ale użyłem tego rozwiązania i Uważam, że jest to bardzo przydatne, gdy mam klucz podstawowy, którego mogę używać do porównywania zestawów danych. Mam nadzieję, że to pomoże.

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

Czym różni się to od tego, co OP już wypróbował? Użyłeś dokładnie tego samego kodu, na przykład Tal, aby porównać pojedynczą kolumnę zamiast całego wiersza (co było wymogiem). –

5

Stosując diffobj pakiet:

library(diffobj) 

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

enter image description here

enter image description here

21

W dplyr:

setdiff(a1,a2) 

Zasadniczo setdiff(bigFrame, smallFrame) dostaje dodatkowe rekordy w pierwszej tabeli.

W SQLverse ten nazywany jest

Left Excluding Join Venn Diagram

Dla dobrych opisów wszystkich opcji i dołączyć zestaw przedmiotów, jest to jeden z najlepszych podsumowań Widziałem umieszczone razem do tej pory: http://www.vertabelo.com/blog/technical-articles/sql-joins

Ale wracając do tego pytania - oto wyniki dla kodu setdiff() przy użyciu danych pO to:

> 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 

Lub nawet anti_join(a1,a2) dostaniesz takie same wyniki.
Aby uzyskać więcej informacji: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

+0

Ponieważ PO prosi o elementy w "a1", które nie są w "a2", nie chcesz używać czegoś takiego jak 'semi_join (a1, a2, by = c ('a', 'b'))'? W odpowiedzi "Rickarda" widzę, że zasugerowano "semi_join". – steveb

+0

Oczywiście! Kolejny świetny wybór; szczególnie jeśli masz ramki danych z tylko kluczem złączenia i różnymi nazwami kolumn. –

2

można użyć daff package (który owija daff.js library pomocą V8 package):

library(daff) 

diff_data(data_ref = a2, 
      data = a1) 

która produkuje następujący obiekt różnica:

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 

format diff jest opisany w Coopy highlighter diff format for tables i powinien być wcześniejszy nie wymaga wyjaśnienia. Linie z +++ w pierwszej kolumnie @@ są nowymi w a1 i nie są obecne w a2.

Przedmiotem różnica może być używany do patch_data(), aby zapisać różnicę dla celów dokumentacji z wykorzystaniem write_diff() lub do uzmysłowić różnicę używając render_diff():

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

który generuje schludny wyjście HTML:

enter image description here

Powiązane problemy