Вопрос по r – Как сделать кросс-соединение в R?

23

Как я могу добиться перекрестного соединения в R? Я знаю, что "слияние" может сделать внутреннее соединение, внешнее соединение. Но я не знаю, как добиться перекрестного соединения в R.

Спасибо

Возможный дубликатHow to generate a matrix of combinations Bulat

Ваш Ответ

8   ответов
0

convenient способ объединить два data.tables. Я делаю это так часто, что в конечном итоге свернул свою собственную функцию, что другие могут найти полезным

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
2

Внутреннее объединение: слияние (df1, df2) будет работать для этих примеров, потому что R автоматически объединяет кадры по общим именам переменных, но вы, скорее всего, захотите указать слияние (df1, df2, by = & quot; CustomerId & quot;), чтобы убедиться, что вы соответствовали только тем полям, которые вы хотели. Вы также можете использовать параметры 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)
6

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 объекта и накладные расходы на его производство, значительное увеличение скорости может быть достигнуто путем записи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;
}
To compare, firstly for a large join:

C ++

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

user system elapsed 1.033 0.424 1.462

Таблица данных

system.time({for(i in 1:n){
  CJ(a,b)
}})

user system elapsed 0.602 0.569 2.452

Now for lots of little joins:

C ++

n = 1e5
a = runif(10)
b = runif(10)
system.time({for(i in 1:n){
  crossJoin(a,b)
}})

user system elapsed 0.660 0.077 0.739

Таблица данных

system.time({for(i in 1:n){
  CJ(a,b)
}})

user system elapsed 26.164 0.056 26.271

0

data.frame's but it isn't hard to make.

@danas showed there is an easy built-in way, but I'll leave my answer here in case it is useful for other purposes.

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

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

Создано 2018-12-08представьте пакет (V0.2.0).

Конечно. Позвольте мне отредактировать.
Лучше. Поведение, которое вы показываете (с векторами в качестве входных данных), идентичноbase функцияexpand.grid, Преимуществоcrossing имеет то, что он работает сdata.frame входы (и суть вопроса). Используя пример из принятого ответа,x <- data.frame(id1 = c("a", "b", "c"), vals1 = 1:3); y <- data.frame(id2 = c("d", "e", "f"), vals2 = 4:6), затемcrossing(x, y) работает как положено, покаexpand.grid(x, y) выходит из строя.
37

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

Если вы хотите сделать перекрестное соединение двух таблиц, я не нашел способа использовать 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 линия:

Basically you add a dummy column (k in this example) to one table and set it as the key (setkey(tablename,keycolumns)), add the dummy column to the other table, and then join them. The data.table structure uses column positions and not names in the join, so you have to put the dummy column at the beginning. The c(k=1,.SD) part is one way that I have found to add columns at the beginning (the default is to add them to the end). A standard data.table join has a format of X[Y]. The X in this case is setkey(x2[,c(k=1,.SD)],k), and the Y is y2[,c(k=1,.SD)]. allow.cartesian=TRUE tells data.table to ignore the duplicate key values, and perform a cartesian join (prior versions didn't require this) The [,k:=NULL] at the end just removes the dummy key from the result.

Вы также можете превратить это в функцию, чтобы было удобнее использовать:

# 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 методы намного быстрее, чемmerge метод, предложенный @ danas.zuokas. В этом примере две таблицы с 1000 строками приводят к перекрестному соединению с 1 миллионом строк. Таким образом, даже если ваши оригинальные таблицы маленькие, результат может быстро возрасти, и скорость становится важной.

Наконец, последние версииdata.table потребовать от вас добавитьallow.cartesian=TRUE (как в CJ.table.1) или укажите имена столбцов, которые должны быть возвращены (CJ.table.2). Второй метод (CJ.table.2) кажется более быстрым, но требует более сложного кода, если вы хотите автоматически указать все имена столбцов. И это может не работать с повторяющимися именами столбцов. (Не стесняйтесь предложить более простую версию CJ.table.2)

вариант, обеспечивающий уникальность имени фиктивной переменной:CJ.table.3 <- function(X,Y){ unique_name <- 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] }
Есть ли версия быстрее, чем CJ.table.1, которая работает с таблицей данных? У меня проблема масштабного масштаба, поэтому даже небольшое улучшение экономит много времени!
Вы правы, @StephLocke,data.table поведение изменилось с момента моего первоначального ответа. Я обновил его и добавил некоторые моменты времени. Благодарю.
Во время недавнего обновления data.table больше не допускает последнюю версию, а вместо этого выдает ошибку. добавлениеallow.cartesian облегчает это, но data.table предлагает использоватьby = .EACHI, Обратите внимание, что все три метода обеспечивают почти точно такую же скорость, что и на сегодняшний день (бенчмаркинг обеспечивает около 35 миллисекунд на моей машине для всех 3 методов при 1000 репликациях), так как в этом случае производительность не улучшается по сравнению с менее читаемой версией.CJ.table.2, (с добавленным аргументом allow.cartesian)
Не уверен, что это связано с последующими изменениями пакета, но чтобы это работало, мне пришлось немного изменить функцию, чтобыCJ.table<-function(X,Y) setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL]
29

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:

If by or both by.x and by.y are of length 0 (a length zero vector or NULL), the result, r, is the Cartesian product of x and y, i.e., dim(r) = c(nrow(x)*nrow(y), ncol(x) + ncol(y)).

правильный ответ - слияние (x, y, by = NULL)
Зачемall? Не вижу, какall=FALSE (по умолчанию) будет влиять на результат. Также обратите внимание, чтоmerge наборыby.x = by.y = by = intersect(names(x), names(y) такx а такжеy может не иметь общих имен столбцов (иначе вы не получите перекрестное соединение с настройками по умолчанию).
5

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

Просто для записи, с базовым пакетом, мы можем использоватьby= NULL вместоall=TRUE:

merge(x, y, by= NULL)

Похожие вопросы