比较两个 data.frame 以查找 data.frame 1 中不存在于 data.frame 2 中的行

Posted

技术标签:

【中文标题】比较两个 data.frame 以查找 data.frame 1 中不存在于 data.frame 2 中的行【英文标题】:Compare two data.frames to find the rows in data.frame 1 that are not present in data.frame 2 【发布时间】:2011-03-11 10:18:00 【问题描述】:

我有以下 2 个数据帧:

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)

【问题讨论】:

【参考方案1】:

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)

【讨论】:

感谢anti_joinsemi_join anti_join 和 sqldf 一样返回空 DF 是否有原因,但函数 same(a1,a2) 和 all.equal() 会与此相矛盾? 只是想在这里补充一点,anti_join 和 semi_join 在某些情况下(例如我的情况)不起作用。我的数据框收到“错误:列必须是一维原子向量或列表”。也许我可以处理我的数据,以便这些功能起作用。 Sqldf 可以直接使用! @AkshayGaur 应该只是数据格式或数据清理问题; sqldf 只是 sql 一切都被预处理为像正常 DB 这样我们就可以在数据上运行 sql。【参考方案2】:

dplyr中:

setdiff(a1,a2)

基本上,setdiff(bigFrame, smallFrame) 为您获取第一个表中的额外记录。

在 SQLverse 中,这被称为

对于所有加入选项和设置主题的良好描述,这是迄今为止我见过的最好的总结之一:http://www.vertabelo.com/blog/technical-articles/sql-joins

但回到这个问题 - 这是使用 OP 数据时setdiff() 代码的结果:

> 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

【讨论】:

由于 OP 要求 a1 中的项目不在 a2 中,你不想使用类似 semi_join(a1, a2, by = c('a','b')) 的东西吗?在“Rickard”的回答中,我看到建议使用 semi_join 当然!另一个不错的选择;特别是如果您的数据框只有一个连接键和不同的列名。 setdiff 来自 lubridate::setdiff 而不是来自 library(dplyr) @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。也许您在 dplyr 之后加载了 lubridate 库,并且它建议它作为 tabcomplete 列表中的源? lubridate和dplyr有冲突,见github.com/tidyverse/lubridate/issues/693【参考方案3】:

这不会直接回答您的问题,但会为您提供共同的元素。这可以通过 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

【讨论】:

我觉得这个功能很混乱。我认为它对我有用,但它似乎只有在一组包含另一组相同匹配的行时才能如上所示工作。考虑这种情况:a2 &lt;- data.frame(a = c(1:3, 1), b = c(letters[1:3], "c"))。保持a1 不变。现在尝试比较。即使在阅读选项时,我也不清楚仅列出常见元素的正确方法是什么。【参考方案4】:

对于这个特定的目的来说当然效率不高,但是我在这些情况下经常做的就是在每个data.frame中插入指标变量,然后合并:

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

included_a1 中的缺失值将记录 a1 中缺失的行。 a2 也是如此。

您的解决方案的一个问题是列顺序必须匹配。另一个问题是,很容易想象行被编码为相同但实际上不同的情况。使用合并的好处是您可以免费获得一个好的解决方案所必需的所有错误检查。

【讨论】:

所以...在寻找缺失值时,您会创建另一个缺失值...您如何在included_a1 中找到缺失值? :-/ 使用 is.na() 和子集,或 dplyr::filter 感谢您教一种无需安装新库的方法!【参考方案5】:

我写了一个包 (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

【讨论】:

您的 compareDF 正是我所需要的,并且在小型集合方面做得很好。但是:1)不使用一组 5000 万行的 3 列(比如说)它说内存不足 32 GB 内存。 2)我也看到HTML需要一些时间来编写,可以将相同的输出发送到TEXT文件吗? 1) 是的,5000 万行是很多数据,只是为了保存在内存中;)。我知道它不适用于大型数据集,因此您可能必须进行某种分块。 2)您可以提供参数-limit_html = 0,以避免将其打印到HTML。相同的输出在 compare_output$comparison_df 中,您可以使用本机 R 函数将其写入 CSV/TEXT 文件。 感谢@Alex Joseph 的回复,我会试一试,告诉你进展如何。 嗨@Alex Joseph,感谢您输入文本格式确实有效但发现了问题,请在以下位置提出:***.com/questions/54880218/… 它无法处理不同数量的列。我收到一个错误The two data frames have different columns!【参考方案6】:

您可以使用daff package(使用V8 package 包装daff.js library):

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

表格差异格式描述为here,应该很容易解释。第一列@@ 中带有+++ 的行是a1 中的新行,而a2 中不存在。

差异对象可用于patch_data(),使用write_diff() 存储差异以用于文档目的,或使用render_diff() 可视化差异

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

生成整洁的 HTML 输出:

【讨论】:

【参考方案7】:

使用diffobj 包:

library(diffobj)

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

【讨论】:

【参考方案8】:

我调整了merge 函数来获得这个功能。在较大的数据帧上,它使用的内存比完全合并解决方案要少。而且我可以使用关键列的名称。

另一种解决方案是使用库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)

【讨论】:

【参考方案9】:

您的示例数据没有任何重复,但您的解决方案会自动处理它们。这意味着如果出现重复,某些答案可能与您的函数结果不匹配。 这是我的解决方案,地址与您的相同。它的扩展性也很棒!

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+

【讨论】:

【参考方案10】:

也许它太简单了,但是我使用了这个解决方案,当我有一个可以用来比较数据集的主键时,我发现它非常有用。希望对您有所帮助。

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 完全相同的代码来比较单列而不是整行(这是要求)【参考方案11】:

使用subset:

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

【讨论】:

这个答案适用于 OP 的场景。如果变量“a”在两个data.frames(“a1”和“a2”)之间匹配,但变量“b”不匹配,那么更一般的情况呢?【参考方案12】:

另一个基于 plyr 中 match_df 的解决方案。 这是 plyr 的 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]

我们可以修改为否定:

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)

【讨论】:

【参考方案13】:

以下代码同时使用data.tablefastmatch 来提高速度。

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

【讨论】:

【参考方案14】:

真正快速的比较,以计算差异。 使用特定的列名。

colname = "CreatedDate" # specify column name
index <- match(colname, names(source_df)) # get index name for column name
sel <- source_df[, index] == target_df[, index] # get differences, gives you dataframe with TRUE and FALSE values
table(sel)["FALSE"] # count of differences
table(sel)["TRUE"] # count of matches

对于完整的数据框,不要提供列或索引名称

sel <- source_df[, ] == target_df[, ] # gives you dataframe with TRUE and FALSE values
table(sel)["FALSE"] # count of differences
table(sel)["TRUE"] # count of matches

【讨论】:

以上是关于比较两个 data.frame 以查找 data.frame 1 中不存在于 data.frame 2 中的行的主要内容,如果未能解决你的问题,请参考以下文章

在 data.frame 中查找字符串

R重命名没有中间对象的对象/ data.frame

绘制来自两个data.frame的数据时的图例问题

在 data.frame 中查找最常见的值组合

dplyr、lapply 或 Map 以识别来自一个 data.frame 的信息并将其放入另一个 [重复]

如何用(data.frame)查找表中的值标签替换数据框中的数字代码?