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

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

Спасибо


person zjffdu    schedule 15.05.2012    source источник
comment
Возможный дубликат Как сгенерировать матрицу комбинаций   -  person Bulat    schedule 17.05.2016


Ответы (9)


Это просто 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 (вектор нулевой длины или NULL), результат r является декартовым произведением x и y, т. Е. Dim (r) = c (nrow (x ) * nrow (y), ncol (x) + ncol (y)).

person danas.zuokas    schedule 15.05.2012
comment
Почему all? Не вижу, как all=FALSE (по умолчанию) повлияет на результат. Также обратите внимание, что merge устанавливает by.x = by.y = by = intersect(names(x), names(y), поэтому x и y не могут иметь общие имена столбцов (в противном случае вы не получите перекрестное соединение с настройками по умолчанию). - person Davor Josipovic; 05.02.2018
comment
Не уверен, почему это приемлемое решение. Как указано в комментариях, перекрестное соединение не работает для нескольких вариантов использования. - person thelatemail; 20.11.2019
comment
Просто к сведению: это работает только с data.frames, а не с data.tables. - person Fierr; 27.11.2019

Если скорость является проблемой, я предлагаю проверить отличный пакет 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:

  • Обычно вы добавляете фиктивный столбец (k в этом примере) в одну таблицу и устанавливаете его как ключ (setkey(tablename,keycolumns)), добавляете фиктивный столбец в другую таблицу, а затем присоединяете их.
  • Структура data.table использует позиции столбцов, а не имена в объединении, поэтому вы должны поместить фиктивный столбец в начало. Часть 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 игнорировать повторяющиеся значения ключей и выполнять декартово соединение (в предыдущих версиях этого не требовалось)
  • [,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 методы намного быстрее, чем merge метод, предложенный @ danas.zuokas. Две таблицы с 1000 строками в этом примере приводят к перекрестно объединенной таблице с 1 миллионом строк. Таким образом, даже если ваши исходные таблицы маленькие, результат может быстро стать большим, и скорость становится важной.

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

person dnlbrky    schedule 04.01.2013
comment
Не уверен, что это связано с последующими изменениями пакета, но для того, чтобы это заработало, мне пришлось немного изменить функцию до CJ.table<-function(X,Y) setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL] - person Steph Locke; 06.02.2014
comment
Вы правы, @StephLocke, поведение data.table изменилось с момента моего первоначального ответа. Я обновил его и добавил тайминги. Спасибо. - person dnlbrky; 07.02.2014
comment
вариант, обеспечивающий уникальность имени фиктивной переменной: 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] } - person jan-glx; 28.09.2016
comment
Во время недавнего обновления data.table больше не поддерживает последнюю версию, но вместо этого выдает ошибку. добавление allow.cartesian смягчает это, но data.table предлагает использовать by = .EACHI. Обратите внимание, что все три метода обеспечивают почти ту же скорость, что и сегодня (бенчмаркинг обеспечивает около 35 миллисекунд на моей машине для всех 3 методов за 1000 репликаций), поэтому нет преимущества в производительности по сравнению с менее читаемой версией CJ.table.2. (с добавленным аргументом allow.cartesian) - person Oliver; 19.02.2019
comment
Есть ли версия быстрее, чем CJ.table.1, которая работает с таблицей данных? У меня проблема огромного масштаба, поэтому даже небольшое улучшение экономит много времени! - person wolfsatthedoor; 09.03.2019
comment
@dnlbrky может ли это быть расширено более чем на две таблицы data.tables? И CJ.table.1, и CJ.table.2 у меня не работают, в отличие от res. Благодарность - person TcM; 19.05.2020
comment
Отмечая, что это больше не работает в самой последней версии таблицы данных. - person Cauder; 11.09.2020

Об этом спрашивали много лет назад, но вы можете использовать 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

Создано 08.12.2018 с помощью пакета REPEX (v0.2.0).

person Evan O.    schedule 07.12.2018
comment
Лучше. Показываемое вами поведение (с векторами в качестве входных данных) идентично 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) не работает. - person Gregor Thomas; 09.12.2018
comment
Кажется, это намного быстрее, чем merge() - person Richard DiSalvo; 02.07.2021

Если вы хотите сделать это через 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 и накладные расходы на его создание, значительного увеличения скорости можно достичь, написав блок кода 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;
}

Для сравнения, во-первых, для большого соединения:

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


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

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


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

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

пользовательская система истекла 26,164 0,056 26,271

person Nicholas Hamilton    schedule 08.08.2015
comment
Объединение атомарных векторов сильно отличается от объединения data.frames. По сути, вы сильно его упростили. Ни CJ, ни ваше решение не решают данную проблему. - person jangorecki; 11.05.2020

Усиг 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)
person mpalanco    schedule 10.08.2015

Используя функцию слияния и ее необязательные параметры:

Внутреннее соединение: слияние (df1, df2) будет работать для этих примеров, потому что R автоматически объединяет кадры по общим именам переменных, но вы, скорее всего, захотите указать слияние (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)
person Amarjeet    schedule 23.10.2015

Я не знаю встроенного способа сделать это с помощью data.frame, но это несложно сделать.

@danas показал, что есть простой встроенный способ, но я оставлю здесь свой ответ на случай, если он будет полезен для других целей.

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 ...
person Brian Diggs    schedule 15.05.2012

Мне бы хотелось узнать, существует ли удобный способ соединения двух таблиц 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
person Ben    schedule 06.10.2018

Для data.table используйте

dt1[, as.list(dt2), by = names(dt1)]

Обратите внимание, что это работает, только если нет повторяющихся строк.

person LBogaardt    schedule 18.11.2020