web-dev-qa-db-fra.com

Comment faire cross-join dans R?

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

20
zjffdu

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

30
danas.zuokas

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:

  • En gros, vous ajoutez une colonne factice (k dans cet exemple) à une table et la définissez comme clé (setkey(tablename,keycolumns)), ajoutez la colonne factice à l'autre table, puis vous les joignez.
  • La structure data.table utilise des positions de colonne et non des noms dans la jointure. Vous devez donc placer la colonne factice au début. La partie 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).
  • Une jointure data.table standard a un format de 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)
  • Le [,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)

34
dnlbrky

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

Pour comparer, tout d'abord pour une jointure importante:

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  


Maintenant pour beaucoup de petites jointures:

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  

6
Nicholas Hamilton

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)
5
mpalanco

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

3
Evan O.

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)
2
Amarjeet

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 ...
0
Brian Diggs

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
0
Ben