J'ai les 2 data.frames suivants:
a1 <- data.frame(a = 1:5, b=letters[1:5])
a2 <- data.frame(a = 1:3, b=letters[1:3])
Je veux trouver la ligne a1 qui a2 n'a pas.
Existe-t-il une fonction intégrée pour ce type d'opération?
(p.s: J'ai écrit une solution pour cela, je suis simplement curieux de savoir si quelqu'un a déjà créé un code plus élaboré)
Voici ma solution:
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)
Cela ne répond pas directement à votre question, mais vous donnera les éléments communs. Cela peut être fait avec le paquet de 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
La fonction compare
vous donne beaucoup de flexibilité en ce qui concerne le type de comparaisons autorisées (par exemple, modification de l'ordre des éléments de chaque vecteur, modification de l'ordre et des noms de variables, réduction des variables, modification de la casse des chaînes). À partir de là, vous devriez pouvoir déterminer ce qui manque à l’un ou l’autre. Par exemple (ce n'est pas très élégant):
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
SQLDF
fournit une solution intéressante
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')
Et les lignes qui sont dans les deux cadres de données:
a1Ina2 <- sqldf('SELECT * FROM a1 INTERSECT SELECT * FROM a2')
La nouvelle version de dplyr
a une fonction, anti_join
, pour exactement ce genre de comparaisons
require(dplyr)
anti_join(a1,a2)
Et semi_join
pour filtrer les lignes dans a1
qui sont également dans a2
semi_join(a1,a2)
Dans dplyr :
setdiff(a1,a2)
Fondamentalement, setdiff(bigFrame, smallFrame)
vous permet d'obtenir les enregistrements supplémentaires de la première table.
Dans le SQLverse cela s'appelle un
Pour une bonne description de toutes les options de jointure et de sujets définis, c’est l’un des meilleurs résumés que j’ai vu rassemblé à ce jour: http://www.vertabelo.com/blog/technical-articles/sql-joins
Mais revenons à cette question - voici les résultats pour le code setdiff()
lorsque vous utilisez les données de l'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
Ou même anti_join(a1,a2)
vous obtiendrez les mêmes résultats.
Pour plus d'infos: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf
Ce n'est certainement pas efficace pour cet objectif particulier, mais ce que je fais souvent dans ces situations est d'insérer des variables d'indicateur dans chaque data.frame, puis de les fusionner:
a1$included_a1 <- TRUE
a2$included_a2 <- TRUE
res <- merge(a1, a2, all=TRUE)
les valeurs manquantes dans included_a1 noteront les lignes manquantes dans a1. de même pour a2.
Un problème avec votre solution est que les ordres de colonne doivent correspondre. Un autre problème est qu’il est facile d’imaginer des situations dans lesquelles les lignes sont codées de la même manière alors qu’elles sont en fait différentes. L'avantage d'utiliser la fusion est que vous obtenez gratuitement toutes les vérifications d'erreur nécessaires à une bonne solution.
J'ai écrit un package ( https://github.com/alexsanjoseph/compareDF ) car j'avais le même problème.
> 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
Un exemple plus compliqué:
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
Le paquet a aussi une commande html_output pour une vérification rapide
Vous pouvez utiliser le package daff
(qui enveloppe la bibliothèque _daff.js
_) à l'aide du package V8
):
_library(daff)
diff_data(data_ref = a2,
data = a1)
_
produit l'objet différence suivant:
_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
_
Le format diff est décrit dans format diff du surligneur Coopy pour les tableaux et devrait être assez explicite. Les lignes avec _+++
_ dans la première colonne _@@
_ sont celles qui sont nouvelles dans _a1
_ et non présentes dans _a2
_.
L’objet différence peut être utilisé pour patch_data()
, pour stocker la différence à des fins de documentation en utilisant write_diff()
ou pour visualiser la différence en utilisant render_diff()
:
_render_diff(
diff_data(data_ref = a2,
data = a1)
)
_
génère une sortie HTML soignée:
J'ai adapté la fonction merge
pour obtenir cette fonctionnalité. Sur de plus grandes images, il utilise moins de mémoire que la solution de fusion complète. Et je peux jouer avec les noms des colonnes clés.
Une autre solution consiste à utiliser la bibliothèque 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)
}
Vos données d'exemple ne contiennent pas de doublons, mais votre solution les gère automatiquement. Cela signifie que potentiellement, certaines des réponses ne correspondront pas aux résultats de votre fonction en cas de doublons.
Voici ma solution qui adresse les doublons de la même manière que la vôtre. C'est aussi une bonne balance!
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
Il a besoin de data.table 1.9.8+
C'est peut-être trop simpliste, mais j'ai utilisé cette solution et je la trouve très utile lorsque j'ai une clé primaire que je peux utiliser pour comparer des ensembles de données. J'espère que ça peut aider.
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,]
Utiliser subset
:
missing<-subset(a1, !(a %in% a2$a))
Encore une autre solution basée sur match_df dans plyr. Voici le match_df de 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]
}
Nous pouvons le modifier pour nier:
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]
}
Ensuite:
diff <- negate_match_df(a1,a2)