So sánh hai data.frames để tìm các hàng trong data.frame 1 không có trong data.frame 2


161

Tôi có 2 data.frames sau:

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

Tôi muốn tìm hàng a1 có a2 không.

Có một chức năng được xây dựng cho loại hình hoạt động này?

(ps: Tôi đã viết một giải pháp cho nó, tôi chỉ tò mò nếu ai đó đã tạo ra một mã thủ công hơn)

Đây là giải pháp của tôi:

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)

Câu trả lời:


88

Điều này không trả lời trực tiếp câu hỏi của bạn, nhưng nó sẽ cung cấp cho bạn các yếu tố chung. Điều này có thể được thực hiện với gói của 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

Hàm này comparecung cấp cho bạn rất nhiều tính linh hoạt về loại phép so sánh được phép (ví dụ: thay đổi thứ tự các phần tử của mỗi vectơ, thay đổi thứ tự và tên của các biến, rút ​​ngắn biến, trường hợp thay đổi chuỗi). Từ điều này, bạn sẽ có thể tìm ra những gì còn thiếu từ cái này hay cái khác. Ví dụ (điều này không phải là rất thanh lịch):

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
Tôi thấy chức năng này khó hiểu. Tôi nghĩ rằng nó sẽ làm việc cho tôi, nhưng dường như nó chỉ hoạt động như được hiển thị ở trên nếu một bộ chứa các hàng khớp chính xác của bộ khác. Hãy xem xét trường hợp này : a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). Để a1nguyên như vậy. Bây giờ hãy thử so sánh. Tôi không rõ ngay cả khi đọc các tùy chọn, cách thích hợp là chỉ liệt kê các yếu tố phổ biến.
Hendy

148

SQLDF cung cấp một giải pháp tốt đẹp

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

Và các hàng trong cả hai khung dữ liệu:

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

Phiên bản mới của dplyrcó một chức năng anti_join, cho chính xác các loại so sánh này

require(dplyr) 
anti_join(a1,a2)

semi_joinđể lọc các hàng trong a1đó cũng tronga2

semi_join(a1,a2)

18
Cảm ơn anti_joinsemi_join!
drastega

Có lý do tại sao anti_join sẽ trả về một DF không, như sqldf, nhưng các hàm giống hệt nhau (a1, a2) và all.equal () sẽ mâu thuẫn với điều đó?
3pitt

Chỉ muốn thêm vào đây rằng anti_join và semi_join sẽ không hoạt động trong một số trường hợp như của tôi. Tôi đã nhận được "Lỗi: Cột phải là các vectơ nguyên tử hoặc danh sách 1d" cho khung dữ liệu của tôi. Có lẽ tôi có thể xử lý dữ liệu của mình để các chức năng này hoạt động. Sqldf làm việc ngay ngoài cổng!
Bò tót Akshay

@AkshayGaur nó chỉ là một định dạng dữ liệu hoặc vấn đề làm sạch dữ liệu; sqldf chỉ là sql, mọi thứ đều được xử lý trước giống như DB gốc để chúng ta có thể chạy sql trên dữ liệu.
stucash

75

Trong dplyr :

setdiff(a1,a2)

Về cơ bản, setdiff(bigFrame, smallFrame)giúp bạn có được các bản ghi thêm trong bảng đầu tiên.

Trong SQLverse, điều này được gọi là

Còn lại không bao gồm Tham gia biểu đồ Venn

Để mô tả tốt về tất cả các tùy chọn tham gia và thiết lập các chủ đề, đây là một trong những tóm tắt hay nhất mà tôi thấy được đưa ra cho đến nay: http://www.vertabelo.com/blog/technical-articles/sql-joins

Nhưng trở lại câu hỏi này - đây là kết quả cho setdiff()mã khi sử dụng dữ liệu của 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

Hoặc thậm chí anti_join(a1,a2)sẽ giúp bạn có được kết quả tương tự.
Để biết thêm thông tin: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-chcoateet.pdf


2
Vì OP yêu cầu các mục a1không có trong đó a2, bạn không muốn sử dụng cái gì đó như thế semi_join(a1, a2, by = c('a','b'))nào? Trong câu trả lời của "Rickard", tôi thấy điều đó semi_joinđã được đề xuất.
steveb

Chắc chắn rồi! Một lựa chọn tuyệt vời khác, quá; đặc biệt nếu bạn có các tệp dữ liệu chỉ có khóa tham gia và các tên cột khác nhau.
leerssej

setdiff là từ Lubridate :: setdiff chứ không phải từ thư viện (dplyr)
mtelesha

@mtelesha - Hmm, các tài liệu và mã nguồn cho dplyr cho thấy nó đang ở đó: ( dplyr.tidyverse.org/reference/setops.html , github.com/tidyverse/dplyr/blob/master/R/sets. ). Ngoài ra, khi thư viện dplyr được tải, nó thậm chí còn báo cáo che giấu setdiff()hàm cơ sở hoạt động trên hai vectơ: stat.ethz.ch/R-manual/R-devel/l Library / base / html / sets.html . Có lẽ bạn đã tải thư viện Lubridate sau dplyr và nó đang gợi ý nó là nguồn trong danh sách tabcomplete?
leerssej

1
Có mâu thuẫn giữa Lubridate và dplyr, xem github.com/tidyverse/lubridate/issues/693
slhck

39

Nó chắc chắn không hiệu quả cho mục đích cụ thể này, nhưng điều tôi thường làm trong những tình huống này là chèn các biến chỉ báo trong mỗi data.frame và sau đó hợp nhất:

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

các giá trị bị thiếu trong include_a1 sẽ lưu ý những hàng nào bị thiếu trong a1. tương tự cho a2.

Một vấn đề với giải pháp của bạn là các đơn đặt hàng cột phải khớp. Một vấn đề khác là rất dễ hình dung các tình huống trong đó các hàng được mã hóa giống nhau khi thực tế là khác nhau. Ưu điểm của việc sử dụng hợp nhất là bạn nhận được miễn phí tất cả các kiểm tra lỗi cần thiết cho một giải pháp tốt.


Vì vậy, ... khi tìm kiếm một giá trị bị thiếu, bạn tạo một giá trị còn thiếu khác ... Làm thế nào để bạn tìm thấy (các) giá trị còn thiếu trong included_a1? : - /
Louis Maddox

1
sử dụng is.na () và tập hợp con hoặc bộ lọc dplyr ::
Eduardo Leoni

Cảm ơn bạn đã dạy một cách mà không cần cài đặt một thư viện mới!
Rodrigo

27

Tôi đã viết một gói ( https://github.com/alexsanjoseph/compareDF ) kể từ khi tôi gặp vấn đề tương tự.

  > 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

Một ví dụ phức tạp hơn:

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

Gói này cũng có lệnh htmlDefput để kiểm tra nhanh

df_compare $ htmlDefput nhập mô tả hình ảnh ở đây


so sánh của bạn là chính xác những gì tôi cần và đã hoàn thành tốt công việc với các bộ nhỏ. Tuy nhiên: 1) Không hoạt động với một bộ 50 triệu hàng với 3 cột (giả sử) nó nói hết bộ nhớ với RAM 32 GB. 2) Tôi cũng thấy HTML mất một chút thời gian để viết, liệu đầu ra tương tự có thể được gửi đến tệp TEXT không?
Sâu

1) Yeah 50 triệu hàng là RẤT NHIỀU dữ liệu, chỉ để giữ trong bộ nhớ;). Tôi biết rằng nó không tuyệt vời với các bộ dữ liệu lớn, vì vậy bạn có thể phải thực hiện một số thao tác. 2) bạn có thể đưa ra đối số - giới hạn_html = 0, để tránh nó in ra HTML. Kết quả đầu ra tương tự là trong tệp so sánh $ notify_df mà bạn có thể ghi vào một hàm CSV / TEXT bằng các hàm R gốc.
Alex Joseph

Cảm ơn câu trả lời của bạn @Alex Joseph, tôi sẽ dùng thử và cho bạn biết nó diễn ra như thế nào.
Sâu

Xin chào @Alex Joseph, cảm ơn vì đầu vào định dạng văn bản đã hoạt động nhưng đã phát hiện sự cố, đã nêu ra trong: stackoverflow.com/questions/54880218/ Kẻ
Sâu

Nó không thể xử lý số lượng cột khác nhau. Tôi đã gặp lỗiThe two data frames have different columns!
PeyM87

14

Bạn có thể sử dụng daffgói (bao bọc daff.jsthư viện bằng V8gói ):

library(daff)

diff_data(data_ref = a2,
          data = a1)

tạo ra đối tượng khác biệt sau:

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

Định dạng khác được mô tả trong định dạng khác nhau của Coopy highlighter cho các bảng và sẽ khá tự giải thích. Các dòng có +++trong cột đầu tiên @@là những dòng mới a1và không có tronga2 .

Đối tượng khác biệt có thể được sử dụng để patch_data()lưu trữ sự khác biệt cho mục đích tài liệu bằng cách sử dụng write_diff()hoặc để hình dung sự khác biệt bằng cách sử dụngrender_diff() :

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

tạo ra một đầu ra HTML gọn gàng:

nhập mô tả hình ảnh ở đây


10

Sử dụng diffobjgói:

library(diffobj)

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

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


10

Tôi đã điều chỉnh merge chức năng để có được chức năng này. Trên các tệp dữ liệu lớn hơn, nó sử dụng ít bộ nhớ hơn so với giải pháp hợp nhất đầy đủ. Và tôi có thể chơi với tên của các cột chính.

Một giải pháp khác là sử dụng thư viện 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

Dữ liệu mẫu của bạn không có bất kỳ sự trùng lặp nào, nhưng giải pháp của bạn sẽ tự động xử lý chúng. Điều này có nghĩa là có khả năng một số câu trả lời sẽ không khớp với kết quả của chức năng của bạn trong trường hợp trùng lặp.
Đây là giải pháp của tôi mà địa chỉ trùng lặp giống như của bạn. Nó cũng có quy mô tuyệt vời!

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

Nó cần data.table 1.9.8+


2

Có thể nó quá đơn giản, nhưng tôi đã sử dụng giải pháp này và tôi thấy nó rất hữu ích khi tôi có một khóa chính mà tôi có thể sử dụng để so sánh các tập dữ liệu. Hy vọng nó có thể giúp đỡ.

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

Điều này khác với những gì OP đã thử? Bạn đã sử dụng cùng một mã giống như Tal để so sánh một cột duy nhất thay vì toàn bộ hàng (đó là yêu cầu)
David Arenburg

1

Một giải pháp khác dựa trên match_df trong plyr. Đây là match_df của 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]
}

Chúng tôi có thể sửa đổi nó để phủ nhận:

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

Sau đó:

diff <- negate_match_df(a1,a2)

1

Sử dụng subset:

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

Câu trả lời này hoạt động cho kịch bản của OP. Điều gì về trường hợp tổng quát hơn khi biến "a" không khớp giữa hai data.frames ("a1" và "a2"), nhưng biến "b" thì không?
Bryan F

1

Đoạn mã sau sử dụng cả hai data.tablefastmatchđể tăng tốc độ.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.