เปรียบเทียบ data.frames สองรายการเพื่อค้นหาแถวใน data.frame 1 ที่ไม่มีอยู่ใน data.frame 2


161

ฉันมี 2 data.frames ต่อไปนี้:

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

ฉันต้องการค้นหาแถว a1 ที่ a2 ไม่ได้

มีฟังก์ชันในตัวสำหรับการทำงานประเภทนี้หรือไม่?

(ps: ฉันได้เขียนวิธีแก้ปัญหาสำหรับฉันฉันแค่อยากรู้ว่าใครบางคนทำรหัสที่สร้างขึ้นแล้ว)

นี่คือทางออกของฉัน:

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)

คำตอบ:


88

สิ่งนี้ไม่ได้ตอบคำถามของคุณโดยตรง แต่จะให้องค์ประกอบที่เหมือนกัน สามารถทำได้ด้วยแพ็คเกจของ 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

ฟังก์ชั่นcompareให้ความยืดหยุ่นในแง่ของชนิดการเปรียบเทียบที่ได้รับอนุญาต (เช่นการเปลี่ยนลำดับขององค์ประกอบของแต่ละเวกเตอร์การเปลี่ยนลำดับและชื่อของตัวแปรตัวแปรการทำให้สั้นลงการเปลี่ยนขนาดของสตริง) จากนี้คุณควรจะสามารถเข้าใจได้ว่าสิ่งใดหายไปจากสิ่งใดสิ่งหนึ่ง ตัวอย่างเช่น (นี่ไม่ได้สวยงามมาก):

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
ฉันพบว่าฟังก์ชันนี้ทำให้เกิดความสับสน ฉันคิดว่ามันจะใช้งานได้สำหรับฉัน แต่ดูเหมือนว่าจะทำงานได้ดังที่แสดงไว้ด้านบนหากชุดหนึ่งมีแถวที่ตรงกันเหมือนกันของชุดอื่น พิจารณากรณีนี้: a2 <- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c")). ทิ้งa1ไว้เหมือนเดิม ลองเปรียบเทียบดู ฉันยังไม่ชัดเจนแม้แต่ในการอ่านตัวเลือกว่าวิธีที่เหมาะสมคือการแสดงรายการองค์ประกอบทั่วไปเท่านั้น
Hendy

148

SQLDF ให้ทางออกที่ดี

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

และแถวที่อยู่ในกรอบข้อมูลทั้งสอง:

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

เวอร์ชั่นใหม่ของdplyrมีฟังก์ชั่นanti_joinสำหรับการเปรียบเทียบประเภทนี้

require(dplyr) 
anti_join(a1,a2)

และsemi_joinเพื่อกรองแถวที่อยู่ในa1นั้นด้วยa2

semi_join(a1,a2)

18
ขอบคุณสำหรับanti_joinและsemi_join!
drastega

มีเหตุผลทำไม anti_join จะคืนค่า null DF เช่นเดียวกับ sqldf แต่ฟังก์ชั่นที่เหมือนกัน (a1, a2) และ all.equal () จะขัดแย้งกันหรือไม่
3pitt

แค่ต้องการเพิ่มที่นี่ที่ anti_join และ semi_join จะไม่ทำงานในบางกรณีเช่นของฉัน ฉันได้รับ "ข้อผิดพลาด: คอลัมน์ต้องเป็นเวกเตอร์อะตอมหรือรายการ 1d" สำหรับกรอบข้อมูลของฉัน บางทีฉันสามารถประมวลผลข้อมูลของฉันเพื่อให้ฟังก์ชันเหล่านี้ทำงานได้ Sqldf ทำงานออกจากประตู!
Akshay Gaur

@ AkshayGaur ควรเป็นรูปแบบข้อมูลหรือปัญหาการล้างข้อมูล sqldf เป็นเพียง sql ทุกอย่างถูกประมวลผลล่วงหน้าให้เป็นเหมือน nromal DB ซึ่งเราสามารถเรียกใช้ sql กับข้อมูลได้
stucash

75

ในdplyr :

setdiff(a1,a2)

โดยทั่วไปsetdiff(bigFrame, smallFrame)คุณจะได้รับการบันทึกพิเศษในตารางแรก

ใน SQLverse จะเรียกว่า

ไม่รวมซ้ายเข้าร่วมเวนไดอะแกรม

สำหรับคำอธิบายที่ดีเกี่ยวกับตัวเลือกการเข้าร่วมทั้งหมดและการตั้งค่าหัวเรื่องนี่เป็นหนึ่งในบทสรุปที่ดีที่สุดที่ฉันเคยเห็นมา: http://www.vertabelo.com/blog/technical-articles/sql-joins

แต่กลับไปที่คำถามนี้ - นี่คือผลลัพธ์ของsetdiff()รหัสเมื่อใช้ข้อมูลของ 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

หรือแม้กระทั่งanti_join(a1,a2)คุณจะได้รับผลลัพธ์เดียวกัน
สำหรับข้อมูลเพิ่มเติม: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf


2
เนื่องจาก OP ขอรายการa1ที่ไม่ได้อยู่a2คุณไม่ต้องการใช้อะไรอย่างนั้นsemi_join(a1, a2, by = c('a','b'))หรือ ในคำตอบของ "Rickard" ฉันเห็นว่าsemi_joinถูกแนะนำ
steveb

แน่นอน! อีกทางเลือกที่ดีเช่นกัน โดยเฉพาะอย่างยิ่งถ้าคุณมีไฟล์ข้อมูลที่มีคีย์การเข้าร่วมและชื่อคอลัมน์ที่ต่างกันเท่านั้น
leerssej

setdiff มาจาก lubridate :: setdiff และไม่ใช่จาก library (dplyr)
mtelesha

@mtelesha - อืมเอกสารและซอร์สโค้ดสำหรับdplyrแสดงว่ามี: ( dplyr.tidyverse.org/reference/setops.html , github.com/tidyverse/dplyr/blob/master/R/sets ) นอกจากนี้เมื่อห้องสมุด dplyr จะโหลดมันก็รายงานกำบังฐานsetdiff()ฟังก์ชั่นที่ทำงานบนสองเวกเตอร์: stat.ethz.ch/R-manual/R-devel/library/base/html/sets.html บางทีคุณอาจโหลดไลบรารีlubridateหลังจากdplyrและแนะนำว่ามันเป็นแหล่งที่มาในรายการ tabcomplete หรือไม่
leerssej

1
มีข้อขัดแย้งระหว่าง lubridate และ dplyr ให้ดูgithub.com/tidyverse/lubridate/issues/693
slhck

39

แน่นอนว่ามันไม่ได้มีประสิทธิภาพสำหรับวัตถุประสงค์เฉพาะนี้ แต่สิ่งที่ฉันมักทำในสถานการณ์เหล่านี้คือการแทรกตัวแปรตัวบ่งชี้ในแต่ละ data.frame แล้วผสาน:

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

ค่าที่ขาดหายไปใน include_a1 จะทราบว่าแถวใดขาดหายไปใน a1 ในทำนองเดียวกันสำหรับ a2

ปัญหาอย่างหนึ่งของการแก้ปัญหาคือคำสั่งซื้อของคอลัมน์จะต้องตรงกัน ปัญหาอีกประการหนึ่งคือมันเป็นเรื่องง่ายที่จะจินตนาการถึงสถานการณ์ที่มีการเข้ารหัสแถวเหมือนกันเมื่อในความเป็นจริงแตกต่าง ประโยชน์ของการใช้การผสานคือการที่คุณได้รับฟรีตรวจสอบข้อผิดพลาดทั้งหมดที่จำเป็นสำหรับการแก้ปัญหาที่ดี


ดังนั้น ... ในการค้นหาค่าที่หายไปคุณสร้างค่าที่ขาดหายไปอีก ... คุณจะค้นหามูลค่าที่ขาดได้included_a1อย่างไรใน : - /
หลุยส์แมดดอกซ์

1
ใช้ is.na () และเซ็ตย่อย, หรือ dplyr :: filter
Eduardo Leoni

ขอขอบคุณสำหรับการเรียนการสอนวิธีที่ไม่ต้องติดตั้งห้องสมุดใหม่!
Rodrigo

27

ฉันเขียนแพคเกจ ( https://github.com/alexsanjoseph/compareDF ) เนื่องจากฉันมีปัญหาเดียวกัน

  > 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

ตัวอย่างที่ซับซ้อนมากขึ้น:

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

แพคเกจนี้ยังมีคำสั่ง html_output สำหรับการตรวจสอบอย่างรวดเร็ว

df_compare $ html_output ป้อนคำอธิบายรูปภาพที่นี่


comparDF ของคุณเป็นสิ่งที่ฉันต้องการและทำงานได้ดีกับชุดเล็ก ๆ อย่างไรก็ตาม: 1) ไม่ทำงานกับชุด 50 ล้านแถวที่มี 3 คอลัมน์ (พูด) มันบอกว่าหน่วยความจำไม่เพียงพอกับ 32 GB RAM 2) ฉันยังเห็นว่า HTML ต้องใช้เวลาในการเขียนเอาต์พุตเดียวกันสามารถส่งไปยังไฟล์ TEXT ได้หรือไม่
ลึก

1) ใช่ 50 ล้านแถวเป็นข้อมูลจำนวนมากเพียงเพื่อเก็บไว้ในหน่วยความจำ;) ฉันรู้ว่ามันไม่ดีกับชุดข้อมูลขนาดใหญ่ดังนั้นคุณอาจต้องทำการเรียงลำดับบางอย่าง 2) คุณสามารถระบุอาร์กิวเมนต์ - limit_html = 0 เพื่อหลีกเลี่ยงการพิมพ์เป็น HTML เอาต์พุตเดียวกันอยู่ใน compar_output $ compar_df ซึ่งคุณสามารถเขียนไปยัง CSV / TEXT fule โดยใช้ฟังก์ชั่น native R
อเล็กซ์โจเซฟ

ขอบคุณสำหรับการตอบกลับของคุณ @Alex Joseph ฉันจะลองและแจ้งให้คุณทราบว่ามันจะเป็นอย่างไร
ลึก

สวัสดี @Alex Joseph ขอบคุณสำหรับรูปแบบข้อความที่ใช้งานได้ แต่พบปัญหายกขึ้นภายใต้: stackoverflow.com/questions/54880218/…
ลึก

ไม่สามารถจัดการจำนวนคอลัมน์ที่แตกต่างกัน ฉันพบข้อผิดพลาดThe two data frames have different columns!
PeyM87

14

คุณสามารถใช้daffแพ็คเกจ (ซึ่งล้อมdaff.jsไลบรารีโดยใช้V8แพ็คเกจ ):

library(daff)

diff_data(data_ref = a2,
          data = a1)

สร้างวัตถุที่แตกต่างดังต่อไปนี้:

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

รูปแบบ diff ถูกอธิบายในรูปแบบ diff diff ของ Coopy สำหรับตารางและควรอธิบายด้วยตนเอง เส้นที่มี+++ในคอลัมน์แรก@@เป็นคนที่มีความใหม่และไม่ได้อยู่ในa1a2

สามารถใช้วัตถุแตกต่างpatch_data()เพื่อจัดเก็บความแตกต่างเพื่อวัตถุประสงค์ด้านเอกสารโดยใช้write_diff()หรือแสดงภาพความแตกต่างโดยใช้render_diff() :

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

สร้างผลลัพธ์ HTML เรียบร้อย:

ป้อนคำอธิบายรูปภาพที่นี่



10

ฉันปรับตัว mergeฟังก์ชั่นเพื่อรับฟังก์ชั่นนี้ ใน dataframes ขนาดใหญ่จะใช้หน่วยความจำน้อยกว่าโซลูชันการผสานแบบเต็ม และฉันสามารถเล่นกับชื่อของคอลัมน์สำคัญ

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

ข้อมูลตัวอย่างของคุณไม่มีข้อมูลซ้ำซ้อน แต่โซลูชันของคุณจัดการข้อมูลเหล่านั้นโดยอัตโนมัติ ซึ่งหมายความว่าคำตอบบางอย่างอาจไม่ตรงกับผลลัพธ์ของฟังก์ชันของคุณในกรณีที่ซ้ำซ้อน
นี่คือโซลูชันของฉันซึ่งใช้ที่อยู่ซ้ำกันในแบบของคุณ มันยังชั่งดี!

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

มันต้องการ data.table 1.9.8+


2

อาจจะง่ายเกินไป แต่ฉันใช้วิธีนี้และฉันคิดว่ามันมีประโยชน์มากเมื่อฉันมีคีย์หลักที่ฉันสามารถใช้เพื่อเปรียบเทียบชุดข้อมูล หวังว่ามันจะช่วยได้

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

สิ่งนี้แตกต่างจาก OP ที่ได้ลองไปแล้วอย่างไร คุณใช้รหัสเดียวกันที่แน่นอนเช่น Tal เพื่อเปรียบเทียบคอลัมน์เดียวแทนที่จะเป็นทั้งแถว (ซึ่งเป็นข้อกำหนด)
David Arenburg

1

อีกวิธีแก้ไขตาม match_df ใน plyr นี่คือ match_df ของ 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]
}

เราสามารถแก้ไขเพื่อลบล้าง:

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

แล้ว:

diff <- negate_match_df(a1,a2)

1

การใช้subset:

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

คำตอบนี้ใช้ได้กับสถานการณ์ของ OP แล้วกรณีทั่วไปมากขึ้นเมื่อตัวแปร "a" ตรงกับระหว่าง data.frames สองตัว ("a1" และ "a2") แต่ตัวแปร "b" ไม่?
ไบรอัน F

1

รหัสต่อไปนี้ใช้ทั้งสองdata.tableและfastmatchเพื่อเพิ่มความเร็ว

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.