如何在R中交叉连接?
我如何在R中实现交叉连接? 我知道“合并”可以做内连接,外连接。 但是我不知道如何实现R的交叉连接。
谢谢
它只是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)
从merge
文档:
如果by.x和by.y的长度为0(长度为0的vector或NULL),则结果r是x和y的笛卡尔乘积,即dim(r)= c(nrow(x )* nrow(y),ncol(x)+ ncol(y))。
如果速度是一个问题,我build议检查出优秀的data.table
包。 在这个例子中,它比merge
快了90倍。
您没有提供示例数据。 如果您只想获得两个(或更多个人)列的所有组合,则可以使用CJ
(交叉连接):
library(data.table) CJ(x=1:2,y=letters[1:3]) # xy #1: 1 a #2: 1 b #3: 1 c #4: 2 a #5: 2 b #6: 2 c
如果你想在两个表上进行交叉连接,我还没有find使用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
res
行的解释:
- 基本上,你可以在一个表中添加一个虚拟列(k,在这个例子中),并将其设置为关键字(
setkey(tablename,keycolumns)
),将虚拟列添加到另一个表中,然后join它们。 - data.table结构使用列位置而不是连接中的名字,所以你必须把dummy列放在开头。
c(k=1,.SD)
部分是我发现在开始时添加列的一种方式(默认是将它们添加到末尾)。 - 标准的data.table连接的格式为
X[Y]
。 在这种情况下,X是setkey(x2[,c(k=1,.SD)],k)
,Y是y2[,c(k=1,.SD)]
。 -
allow.cartesian=TRUE
告诉data.table
忽略重复键值,并执行笛卡尔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]"))) }
这里有一些速度基准:
# 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
请注意,这些data.table
方法比@ danas.zuokas提出的merge
方法快得多。 在这个例子中有两行1000行的表导致了一个有100万行的交叉表。 所以即使您的原始表格很小,结果也会变得很快,速度变得很重要。
最后, data.table
最新版本要求您添加allow.cartesian=TRUE
(如CJ.table.1中所示)或者指定应该返回的列的名称(CJ.table.2)。 第二种方法(CJ.table.2)似乎更快,但如果要自动指定所有列名称,则需要更复杂的代码。 而且它可能不适用于重复的列名称。 (随意推荐一个更简单的CJ.table.2版本)
如果你想通过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)
如上所述,如果您正在进行许多小连接,并且不需要data.table
对象和生成它的开销,那么通过使用Rcpp
等编写c++
代码块可以显着提高速度:
// [[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) }})
用户系统经过1.033 0.424 1.462
data.table
system.time({for(i in 1:n){ CJ(a,b) }})
用户系统经过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) }})
用户系统已用完0.660 0.077 0.739
data.table
system.time({for(i in 1:n){ CJ(a,b) }})
用户系统已过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")
输出:
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
为了logging,使用基础包,我们可以使用by= NULL
而不是all=TRUE
:
merge(x, y, by= NULL)
通过使用合并函数及其可选参数:
内部连接:合并(df1,df2)将适用于这些示例,因为R通过公共variables名自动连接框架,但是您很可能想要指定合并(df1,df2,by =“CustomerId”)以确保您只匹配你想要的领域。 如果匹配variables在不同数据框中具有不同的名称,则也可以使用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)
我不知道用 data.frame
做一个内置的方法,但是做起来并不困难。
@丹纳斯显示有一个简单的内置的方式,但我会留下我的答案在这里,以防其他用途的情况下。
cross.join <- function(a, b) { idx <- expand.grid(seq(length=nrow(a)), seq(length=nrow(b))) cbind(a[idx[,1],], b[idx[,2],]) }
并显示它与一些内置的数据集一起工作:
> 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 ...