Comment puis-je réaliser une jointure croisée dans R? Je sais que la "fusion" peut faire une jointure interne, une jointure externe. Mais je ne sais pas comment réaliser un croisement dans R.
Merci
Est-ce juste all=TRUE
?
x<-data.frame(id1=c("a","b","c"),vals1=1:3)
y<-data.frame(id2=c("d","e","f"),vals2=4:6)
merge(x,y,all=TRUE)
D'après la documentation de merge
:
Si by ou x et x.y ont la longueur 0 (un vecteur de longueur zéro ou NULL), le résultat, r, est le produit cartésien de x et y, c'est-à-dire que dim (r) = c (nrow (x ) * nrow (y), ncol (x) + ncol (y)).
Si la vitesse est un problème, je suggère de vérifier l'excellent paquet data.table
. Dans l'exemple à la fin, il est environ 90 fois plus rapide que merge
.
Vous n'avez pas fourni de données d'exemple. Si vous voulez seulement obtenir toutes les combinaisons de deux colonnes (ou plus), vous pouvez utiliser CJ
(jointure croisée):
library(data.table)
CJ(x=1:2,y=letters[1:3])
# x y
#1: 1 a
#2: 1 b
#3: 1 c
#4: 2 a
#5: 2 b
#6: 2 c
Si vous voulez faire une jointure croisée sur deux tables, je n'ai pas trouvé le moyen d'utiliser CJ (). Mais vous pouvez toujours utiliser data.table
:
x2<-data.table(id1=letters[1:3],vals1=1:3)
y2<-data.table(id2=letters[4:7],vals2=4:7)
res<-setkey(x2[,c(k=1,.SD)],k)[y2[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL]
res
# id1 vals1 id2 vals2
# 1: a 1 d 4
# 2: b 2 d 4
# 3: c 3 d 4
# 4: a 1 e 5
# 5: b 2 e 5
# 6: c 3 e 5
# 7: a 1 f 6
# 8: b 2 f 6
# 9: c 3 f 6
#10: a 1 g 7
#11: b 2 g 7
#12: c 3 g 7
Explication de la ligne res
:
setkey(tablename,keycolumns)
), ajoutez la colonne factice à l'autre table, puis vous les joignez.c(k=1,.SD)
est un moyen que j’ai trouvé d’ajouter des colonnes au début (la valeur par défaut est de les ajouter à la fin).X[Y]
. Le X dans ce cas est setkey(x2[,c(k=1,.SD)],k)
, et le Y est y2[,c(k=1,.SD)]
.allow.cartesian=TRUE
indique à data.table
d'ignorer les valeurs de clé dupliquées et d'effectuer une jointure cartésienne (les versions précédentes ne l'exigeaient pas)[,k:=NULL]
à la fin supprime simplement la clé factice du résultat.Vous pouvez aussi transformer cela en une fonction, donc c'est plus propre à utiliser:
# Version 1; easier to write:
CJ.table.1 <- function(X,Y)
setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL]
CJ.table.1(x2,y2)
# id1 vals1 id2 vals2
# 1: a 1 d 4
# 2: b 2 d 4
# 3: c 3 d 4
# 4: a 1 e 5
# 5: b 2 e 5
# 6: c 3 e 5
# 7: a 1 f 6
# 8: b 2 f 6
# 9: c 3 f 6
#10: a 1 g 7
#11: b 2 g 7
#12: c 3 g 7
# Version 2; faster but messier:
CJ.table.2 <- function(X,Y) {
eval(parse(text=paste0("setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],list(",paste0(unique(c(names(X),names(Y))),collapse=","),")][,k:=NULL]")))
}
Voici quelques points de repère de vitesse:
# Create a bigger (but still very small) example:
n<-1e3
x3<-data.table(id1=1L:n,vals1=sample(letters,n,replace=T))
y3<-data.table(id2=1L:n,vals2=sample(LETTERS,n,replace=T))
library(microbenchmark)
microbenchmark(merge=merge.data.frame(x3,y3,all=TRUE),
CJ.table.1=CJ.table.1(x3,y3),
CJ.table.2=CJ.table.2(x3,y3),
times=3, unit="s")
#Unit: seconds
# expr min lq median uq max neval
# merge 4.03710225 4.23233688 4.42757152 5.57854711 6.72952271 3
# CJ.table.1 0.06227603 0.06264222 0.06300842 0.06701880 0.07102917 3
# CJ.table.2 0.04740142 0.04812997 0.04885853 0.05433146 0.05980440 3
Notez que ces méthodes data.table
sont beaucoup plus rapides que la méthode merge
proposée par @ danas.zuokas. Les deux tables avec 1 000 lignes dans cet exemple donnent une table croisée avec 1 million de lignes. Ainsi, même si vos tables d'origine sont petites, le résultat peut devenir rapide et la vitesse devient importante.
Enfin, les versions récentes de data.table
nécessitent l’ajout du allow.cartesian=TRUE
(comme dans CJ.table.1) ou la spécification des noms des colonnes à renvoyer (CJ.table.2). La deuxième méthode (CJ.table.2) semble être plus rapide, mais nécessite du code plus compliqué si vous souhaitez spécifier automatiquement tous les noms de colonne. Et cela peut ne pas fonctionner avec des noms de colonnes en double. (N'hésitez pas à suggérer une version simplifiée de CJ.table.2)
Si vous voulez le faire via data.table, voici un moyen:
cjdt <- function(a,b){
cj = CJ(1:nrow(a),1:nrow(b))
cbind(a[cj[[1]],],b[cj[[2]],])
}
A = data.table(ida = 1:10)
B = data.table(idb = 1:10)
cjdt(A,B)
Cela dit, si vous effectuez de nombreuses petites jointures et que vous n'avez pas besoin d'un objet data.table
ni de la charge de production correspondante, vous pouvez augmenter considérablement la vitesse en écrivant un bloc de code c++
à l'aide de Rcpp
, etc.
// [[Rcpp::export]]
NumericMatrix crossJoin(NumericVector a, NumericVector b){
int szA = a.size(),
szB = b.size();
int i,j,r;
NumericMatrix ret(szA*szB,2);
for(i = 0, r = 0; i < szA; i++){
for(j = 0; j < szB; j++, r++){
ret(r,0) = a(i);
ret(r,1) = b(j);
}
}
return ret;
}
C++
n = 1
a = runif(10000)
b = runif(10000)
system.time({for(i in 1:n){
crossJoin(a,b)
}})
système utilisateur écoulé 1.033 0.424 1.462
data.table
system.time({for(i in 1:n){
CJ(a,b)
}})
système utilisateur écoulé 0.602 0.569 2.452
C++
n = 1e5
a = runif(10)
b = runif(10)
system.time({for(i in 1:n){
crossJoin(a,b)
}})
système utilisateur écoulé 0,660 0,077 0,739
data.table
system.time({for(i in 1:n){
CJ(a,b)
}})
système utilisateur écoulé 26.164 0.056 26.271
Usig sqldf
:
x <- data.frame(id1 = c("a", "b", "c"), vals1 = 1:3)
y <- data.frame(id2 = c("d", "e", "f"), vals2 = 4:6)
library(sqldf)
sqldf("SELECT * FROM x
CROSS JOIN y")
Sortie:
id1 vals1 id2 vals2
1 a 1 d 4
2 a 1 e 5
3 a 1 f 6
4 b 2 d 4
5 b 2 e 5
6 b 2 f 6
7 c 3 d 4
8 c 3 e 5
9 c 3 f 6
Pour mémoire, avec le paquet de base, nous pouvons utiliser le by= NULL
au lieu de all=TRUE
:
merge(x, y, by= NULL)
Cette question a été posée il y a des années, mais vous pouvez utiliser tidyr::crossing()
pour effectuer une jointure croisée. Certainement la solution la plus simple du groupe.
library(tidyr)
league <- c("MLB", "NHL", "NFL", "NBA")
season <- c("2018", "2017")
tidyr::crossing(league, season)
#> # A tibble: 8 x 2
#> league season
#> <chr> <chr>
#> 1 MLB 2017
#> 2 MLB 2018
#> 3 NBA 2017
#> 4 NBA 2018
#> 5 NFL 2017
#> 6 NFL 2018
#> 7 NHL 2017
#> 8 NHL 2018
Créé le 2018-12-08 par le paquet reprex (v0.2.0).
En utilisant la fonction de fusion et ses paramètres optionnels:
Jointure interne: merge (df1, df2) fonctionnera pour ces exemples car R joint automatiquement les cadres par des noms de variables communs, mais vous voudrez probablement spécifier la fusion (df1, df2, par = "CustomerId") pour vous assurer que ne correspondaient que sur les champs que vous désiriez. Vous pouvez également utiliser les paramètres by.x et by.y si les variables correspondantes ont des noms différents dans les différentes trames de données.
Outer join: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)
Left outer: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)
Right outer: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)
Cross join: merge(x = df1, y = df2, by = NULL)
Je ne connais pas de méthode intégrée pour le faire avec les data.frame
mais ce n'est pas difficile à créer.
@danas a montré qu'il existe un moyen simple et intégré, mais je vais laisser ma réponse ici au cas où elle serait utile à d'autres fins.
cross.join <- function(a, b) {
idx <- expand.grid(seq(length=nrow(a)), seq(length=nrow(b)))
cbind(a[idx[,1],], b[idx[,2],])
}
et en montrant qu'il fonctionne avec certains ensembles de données intégrés:
> tmp <- cross.join(mtcars, iris)
> dim(mtcars)
[1] 32 11
> dim(iris)
[1] 150 5
> dim(tmp)
[1] 4800 16
> str(tmp)
'data.frame': 4800 obs. of 16 variables:
$ mpg : num 21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
$ cyl : num 6 6 4 6 8 6 8 4 4 6 ...
$ disp : num 160 160 108 258 360 ...
$ hp : num 110 110 93 110 175 105 245 62 95 123 ...
$ drat : num 3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
$ wt : num 2.62 2.88 2.32 3.21 3.44 ...
$ qsec : num 16.5 17 18.6 19.4 17 ...
$ vs : num 0 0 1 1 0 1 0 1 1 1 ...
$ am : num 1 1 1 0 0 0 0 0 0 0 ...
$ gear : num 4 4 4 3 3 3 3 4 4 4 ...
$ carb : num 4 4 1 1 2 1 4 2 2 4 ...
$ Sepal.Length: num 5.1 5.1 5.1 5.1 5.1 5.1 5.1 5.1 5.1 5.1 ...
$ Sepal.Width : num 3.5 3.5 3.5 3.5 3.5 3.5 3.5 3.5 3.5 3.5 ...
$ Petal.Length: num 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 ...
$ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 ...
$ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
J'aimerais savoir s'il existe un pratique moyen de joindre deux data.tables. Je le fais si souvent que j'ai fini par créer ma propre fonction que d'autres pourraient trouver utile
library(data.table)
cartesian_join <- function(i, j){
# Cartesian join of two data.tables
# If i has M rows and j has N rows, the result will have M*N rows
# Example: cartesian_join(as.data.table(iris), as.data.table(mtcars))
# Check inputs
if(!is.data.table(i)) stop("'i' must be a data.table")
if(!is.data.table(j)) stop("'j' must be a data.table")
if(nrow(i) == 0) stop("'i' has 0 rows. Not sure how to handle cartesian join")
if(nrow(j) == 0) stop("'j' has 0 rows. Not sure how to handle cartesian join")
# Do the join (use a join column name that's unlikely to clash with a pre-existing column name)
i[, MrJoinyJoin := 1L]
j[, MrJoinyJoin := 1L]
result <- j[i, on = "MrJoinyJoin", allow.cartesian = TRUE]
result[, MrJoinyJoin := NULL]
i[, MrJoinyJoin := NULL]
j[, MrJoinyJoin := NULL]
return(result[])
}
foo <- data.frame(Foo = c(1,2,3))
foo
Foo
1 1
2 2
3 3
bar <- data.frame(Bar = c("a", "b", "c"))
bar
Bar
1 a
2 b
3 c
cartesian_join(as.data.table(foo), as.data.table(bar))
Bar Foo
1: a 1
2: b 1
3: c 1
4: a 2
5: b 2
6: c 2
7: a 3
8: b 3
9: c 3