Data.frame 2'deki data.frame 2'deki satırları bulmak için iki data.frame'i karşılaştırın.


161

Aşağıdaki 2 veri.frames var:

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

A1'in a2'de olmadığı satırı bulmak istiyorum.

Bu tür bir işlem için yerleşik bir fonksiyon var mı?

(ps: Bunun için bir çözüm yazdım, sadece birinin daha hazırlanmış bir kod yaptığını merak ediyorum)

İşte benim çözümüm:

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)

Yanıtlar:


88

Bu, sorunuza doğrudan cevap vermez, ancak size ortak olan unsurları verecektir. Bu Paul Murrell'in paketi ile yapılabilir 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

İşlev compare, ne tür karşılaştırmalara izin verildiği konusunda size çok fazla esneklik sağlar (örneğin, her vektörün elemanlarının sırasını değiştirme, değişkenlerin sırasını ve adlarını değiştirme, değişkenleri kısaltma, dizeleri değiştirme). Bundan, birinden veya diğerinden neyin eksik olduğunu anlayabilmelisiniz. Örneğin (bu çok zarif değil):

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
Bu işlevi kafa karıştırıcı buluyorum. Benim için işe yarayacağını düşündüm, ancak sadece bir küme diğer kümenin aynı eşleşen satırlarını içeriyorsa yukarıda gösterildiği gibi çalışıyor gibi görünüyor. Bu durum göz önünde bulundurun: a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). a1Aynısını bırak . Şimdi karşılaştırmayı deneyin. Seçenekleri okurken bile sadece ortak unsurları listelemenin doğru yolunun ne olduğu açık değil.
Hendy

148

SQLDF güzel bir çözüm sunar

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

Ve her iki veri çerçevesindeki satırlar:

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

Yeni sürümünün tam olarak bu tür karşılaştırmalar için dplyrbir işlevi vardıranti_join

require(dplyr) 
anti_join(a1,a2)

Ve semi_joiniçindeki satırları filtrelemek a1içina2

semi_join(a1,a2)

18
İçin teşekkürler anti_joinve semi_join!
drastega

anti_join öğesinin sqldf gibi boş bir DF döndürmesinin bir nedeni var mı, ancak aynı (a1, a2) ve all.equal () işlevleri bununla çelişir mi?
3pitt

Sadece anti_join ve semi_join 'in benim gibi bazı durumlarda işe yaramayacağını eklemek istedim. Veri çerçevem ​​için "Hata: Sütunlar 1d atom vektörleri veya listeler olmalı" alıyordum. Belki de bu işlevlerin çalışması için verilerimi işleyebilirim. Sqldf kapıdan çıktı!
Akshay Gaur

@AkshayGaur sadece bir veri formatı veya veri temizleme problemi olmalıdır; sqldf sadece sql her şeyi sadece veri üzerinde sql çalıştırmak böylece nromal DB gibi olmak için önceden işlenir.
stucash

75

In dplyr :

setdiff(a1,a2)

Temel olarak, setdiff(bigFrame, smallFrame)ilk tablodaki ekstra kayıtları alır.

SQLverse'te buna

Sol Venn Diyagramı Hariç

Tüm birleştirme seçeneklerinin ve ayarlanan konuların iyi açıklamaları için, bugüne kadar bir araya getirildiğini gördüğüm en iyi özetlerden biri: http://www.vertabelo.com/blog/technical-articles/sql-joins

Ancak bu soruya geri dönelim - İşte setdiff()OP verilerini kullanırken kodun sonuçları :

> 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

Ya da anti_join(a1,a2)aynı sonuçları elde edersiniz.
Daha fazla bilgi için: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf


2
OP içinde a1olmayan öğeleri a2istediğinden, böyle bir şey kullanmak istemez misiniz semi_join(a1, a2, by = c('a','b'))? "Rickard" cevabında bunun semi_joinönerildiğini görüyorum .
steveb

Elbette! Başka bir harika seçim de; özellikle yalnızca birleştirme anahtarına ve farklı sütun adlarına sahip veri çerçeveleriniz varsa.
leerssej

setdiff lubridate :: setdiff ve kütüphaneden değil (dplyr)
mtelesha

@mtelesha - Hmm, için dokümanlar ve kaynak kodu dplyr orada olmak gösterisi: ( dplyr.tidyverse.org/reference/setops.html , github.com/tidyverse/dplyr/blob/master/R/sets. ). Ek olarak, dplyr kütüphanesi yüklendiğinde setdiff(), iki vektör üzerinde çalışan temel işlevi maskelediğini bile bildirir : stat.ethz.ch/R-manual/R-devel/library/base/html/sets.html . Belki de dplyr'den sonra lubridate kütüphanesini yüklediniz ve tabcomplete listesindeki kaynak olarak öneriyoruz?
leerssej

1
Lubridate ve dplyr arasında bir çakışma var, bkz. Github.com/tidyverse/lubridate/issues/693
slhck

39

Bu özel amaç için kesinlikle verimli değildir, ancak bu durumlarda sık sık yaptığım her veriye gösterge değişkenleri eklemek ve daha sonra birleştirmektir:

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

dahil_a1 içindeki eksik değerler a1'de hangi satırların eksik olduğunu belirtir. a2 için benzer şekilde.

Çözümünüzle ilgili bir sorun, sütun siparişlerinin eşleşmesi gerektiğidir. Başka bir sorun, aslında farklı olduğunda satırların aynı şekilde kodlandığı durumları hayal etmenin kolay olmasıdır. Birleştirme kullanmanın avantajı, iyi bir çözüm için gerekli olan tüm hata denetimlerini ücretsiz olarak almanızdır.


Öyleyse ... eksik bir değer ararken başka bir eksik değer yaratırsınız ... Eksik değerleri nasıl bulabilirsiniz included_a1? : - /
Louis Maddox

1
is.na () ve altkümeyi kullanın veya dplyr :: filter
Eduardo Leoni

Yeni bir kütüphane kurmadan bir yol öğrettiğiniz için teşekkür ederiz!
Rodrigo

27

Aynı sorunu yaşadığım için bir paket ( https://github.com/alexsanjoseph/compareDF ) yazdım .

  > 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

Daha karmaşık bir örnek:

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

Paket ayrıca hızlı kontrol için bir html_output komutuna sahiptir

df_compare $ html_output resim açıklamasını buraya girin


CompareDF tam olarak ihtiyacım olan şeydir ve küçük setler ile iyi bir iş çıkardılar.Ancak: 1) 3 sütunlu bir set 50 Milyon satır ile çalışmıyorum (diyelim) 32 GB RAM ile bellek dışı diyor. 2) Ayrıca HTML'nin yazılmasının biraz zaman aldığını görüyorum, aynı çıktı TEXT dosyasına gönderilebilir mi?
Derin

1) Evet 50 milyon satır, sadece bellekte tutmak için bir sürü veri;). Büyük veri kümeleri ile harika olmadığının farkındayım, bu yüzden bir çeşit parça yapmak zorunda kalabilirsiniz. 2) HTML'ye yazdırmasını önlemek için - limit_html = 0 bağımsız değişkenini verebilirsiniz. Aynı çıktı yerel R işlevlerini kullanarak bir CSV / TEXT kaynağına yazabileceğiniz Compare_output $ compar_df içindedir.
Alex Joseph

Cevabınız için teşekkürler @Alex Joseph, bir deneyeceğim ve nasıl gittiğini size bildireceğim.
Derin

Merhaba @Alex Joseph, giriş için teşekkürler metin biçimi işe yaradı, ancak bir sorun buldu, altında kaldırdı: stackoverflow.com/questions/54880218/…
Derin

Farklı sayıda sütun işleyemez. Bir hata aldımThe two data frames have different columns!
PeyM87

14

Sen kullanabilirsiniz daffpaketi (sarar daff.jskütüphane kullanarak V8paketi ):

library(daff)

diff_data(data_ref = a2,
          data = a1)

aşağıdaki fark nesnesini üretir:

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

Fark biçimi tablolar için Coopy vurgulayıcı fark biçiminde açıklanmıştır ve kendinden açıklamalı olmalıdır. +++İlk sütundaki satırlar @@yeni olan a1ve mevcut olmayan satırlardır a2.

Fark nesnesi aşağıdakileri patch_data()kullanarak dokümantasyon amacıyla farkı saklamak write_diff()veya farkı görselleştirmek için kullanılabilirrender_diff() :

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

düzgün bir HTML çıktısı oluşturur:

resim açıklamasını buraya girin


10

diffobjPaketi kullanarak :

library(diffobj)

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

resim açıklamasını buraya girin

resim açıklamasını buraya girin


10

Adapte ettim mergeBu işlevi almak işlevi . Daha büyük veri karelerinde tam birleştirme çözümünden daha az bellek kullanır. Ve anahtar sütunların isimleri ile oynayabilirim.

Başka bir çözüm kütüphaneyi kullanmaktır 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

Örnek verilerinizde kopya yok, ancak çözümünüz bunları otomatik olarak ele alıyor. Bu, potansiyel olarak bazı cevapların kopyalar durumunda işlevinizin sonuçlarıyla eşleşmeyeceği anlamına gelir.
İşte sizinki gibi çoğaltan adres çözümüm. Ayrıca harika ölçekler!

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

Veriye ihtiyacı var. Tablo 1.9.8+


2

Belki çok basit, ama bu çözümü kullandım ve veri kümelerini karşılaştırmak için kullanabileceğim bir birincil anahtarım olduğunda çok yararlı buluyorum. Umarım yardımcı olabilir.

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

Bu OP'nin zaten denediklerinden nasıl farklı? Tüm satır yerine tek bir sütunu karşılaştırmak için Tal gibi tam olarak aynı kodu kullandınız (gereksinim
buydu

1

Plyr'de match_df tabanlı başka bir çözüm. İşte plyr'in match_df dosyası:

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

Reddetmek için değiştirebiliriz:

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

Sonra:

diff <- negate_match_df(a1,a2)

1

Kullanma subset:

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

Bu cevap OP'nin senaryosu için geçerlidir. "A" değişkeni iki data.frames ("a1" ve "a2") arasında eşleştiğinde, ancak "b" değişkeni eşleşmediğinde daha genel durum ne olacak?
Bryan F

1

Aşağıdaki kod hem kullanır data.tableve fastmatchartan hız için.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.