如何在R中交叉加入?

zjf*_*fdu 25 r cross-join

如何在R中实现交叉连接?我知道"merge"可以做内连接,外连接.但我不知道如何在R中实现交叉连接.

谢谢

dnl*_*rky 41

如果速度是一个问题,我建议检查出色的data.table包装.在最后的例子中它比...快〜90倍merge.

您没有提供示例数据.如果您只想获得两个(或更多个)列的所有组合,则可以使用CJ(交叉连接):

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
Run Code Online (Sandbox Code Playgroud)

如果你想在两个表上进行交叉连接,我还没有找到使用CJ()的方法.但你仍然可以使用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
Run Code Online (Sandbox Code Playgroud)

res线的解释:

  • 基本上,您将一个虚拟列(在此示例中为k)添加到一个表并将其设置为键(setkey(tablename,keycolumns)),将虚拟列添加到另一个表,然后将它们连接起来.
  • data.table结构使用列位置而不是连接中的名称,因此您必须将虚拟列放在开头.该c(k=1,.SD)部分是我发现在开头添加列的一种方式(默认是将它们添加到结尾).
  • 标准data.table联接的格式为X[Y].在这种情况下setkey(x2[,c(k=1,.SD)],k),X是,而Y是y2[,c(k=1,.SD)].
  • allow.cartesian=TRUE告诉data.table忽略重复的键值,并执行笛卡尔连接(之前的版本不需要这个)
  • [,k:=NULL]在年底只是删除从结果虚拟按键.

您也可以将其转换为函数,因此使用起来更清晰:

# 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]")))
}
Run Code Online (Sandbox Code Playgroud)

以下是一些速度基准测试:

# 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
Run Code Online (Sandbox Code Playgroud)

请注意,这些data.table方法比merge@ danas.zuokas建议的方法快得多.在此示例中,具有1,000行的两个表导致具有100万行的交叉连接表.因此,即使您的原始表格很小,结果也会很快变大,速度变得很重要.

最后,最近的版本data.table要求您添加allow.cartesian=TRUE(如在CJ.table.1中)或指定应返回的列的名称(CJ.table.2).第二种方法(CJ.table.2)似乎更快,但如果要自动指定所有列名,则需要更复杂的代码.它可能不适用于重复的列名称.(随意提出一个更简单的CJ.table.2版本)

  • 确保使用的虚拟变量名称唯一的变体:`CJ.table.3 &lt;- function(X,Y){ unique_name &lt;- last(make.unique(c(colnames(X),colnames(Y),"k "))) X[,c(setNames(1,unique_name),.SD)][Y[,c(setNames(1,unique_name),.SD)],on=unique_name,allow.cartesian=TRUE][, (unique_name):=NULL] }` (2认同)

dan*_*kas 32

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)
Run Code Online (Sandbox Code Playgroud)

来自以下文件merge:

如果by.x和by.y的长度为0(长度为零向量或为NULL),则结果r为x和y的笛卡尔乘积,即dim(r)= c(nrow(x) )*nrow(y),ncol(x)+ ncol(y)).

  • 合正的答案是合并(x,y,by = NULL) (7认同)
  • 为什么全部?没有看到“ all = FALSE”(默认)如何影响结果。另请注意,`merge`会设置`by.x = by.y = by = intersect(names(x),names(y)`,因此`x`和`y`可能不会共享任何列名(否则您将不会得到默认设置的交叉连接)。 (2认同)
  • 不知道为什么这是公认的解决方案。正如评论中所指出的,为多个用例提供交叉连接是行不通的。 (2认同)
  • 仅供参考:这只适用于 data.frames,不适用于 data.tables (2认同)

Nic*_*ton 8

如果你想通过data.table来做,这是一种方式:

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)
Run Code Online (Sandbox Code Playgroud)

如上所述,如果你正在进行许多小连接,并且你不需要一个data.table对象和生成它的开销,那么通过c++使用Rcpp等编写代码块可以实现显着的速度提升:

// [[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;
}
Run Code Online (Sandbox Code Playgroud)

要进行比较,首先要进行大型连接:

C++

n = 1
a = runif(10000)
b = runif(10000)
system.time({for(i in 1:n){
  crossJoin(a,b)
}})
Run Code Online (Sandbox Code Playgroud)

用户系统已过去1.033 0.424 1.462


data.table

system.time({for(i in 1:n){
  CJ(a,b)
}})
Run Code Online (Sandbox Code Playgroud)

用户系统经过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)
}})
Run Code Online (Sandbox Code Playgroud)

用户系统已过去0.660 0.077 0.739


data.table

system.time({for(i in 1:n){
  CJ(a,b)
}})
Run Code Online (Sandbox Code Playgroud)

用户系统已过去26.164 0.056 26.271


Eva*_* O. 8

这是几年前提出的,但是您可以tidyr::crossing()用来进行交叉联接。绝对是最简单的解决方案。

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
Run Code Online (Sandbox Code Playgroud)

reprex软件包(v0.2.0)创建于2018-12-08 。

  • 更好的。您显示的行为(以向量作为输入)与 `base` 函数 `expand.grid` 相同。`crossing` 的优势在于它适用于 `data.frame` 输入(以及问题的重点)。使用已接受答案中的示例,`x &lt;- data.frame(id1 = c("a", "b", "c"), vals1 = 1:3); y &lt;- data.frame(id2 = c("d", "e", "f"), vals2 = 4:6)`,然后`crossing(x, y)`按预期工作,而`expand.grid (x, y)` 失败。 (3认同)

mpa*_*nco 6

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")
Run Code Online (Sandbox Code Playgroud)

输出:

  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
Run Code Online (Sandbox Code Playgroud)

只是为了记录,使用基础包,我们可以使用 by= NULL而不是all=TRUE:

merge(x, y, by= NULL)
Run Code Online (Sandbox Code Playgroud)


Ama*_*eet 5

通过使用合并函数及其可选参数:

内部连接:merge(df1, df2) 将适用于这些示例,因为 R 自动通过公共变量名称连接框架,但您很可能希望指定 merge(df1, df2, by = "CustomerId") 以确保您只匹配您想要的字段。如果匹配变量在不同数据框中具有不同名称,您还可以使用 by.x 和 by.y 参数。

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)
Run Code Online (Sandbox Code Playgroud)