Подтвердить что ты не робот

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

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

Спасибо

4b9b3361

Ответ 1

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

Ответ 2

Если скорость является проблемой, я предлагаю проверить отличный пакет data.table. В примере в конце это ~ 90x быстрее, чем merge.

Вы не представили данные примера. Если вы просто хотите получить все комбинации из двух (или более отдельных) столбцов, вы можете использовать CJ (cross join):

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)

Ответ 3

Если вы хотите сделать это через 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


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

Ответ 4

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

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

merge(x, y, by= NULL)

Ответ 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)

Ответ 6

Я не знаю встроенного способа сделать это с помощью 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 ...

Ответ 7

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

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

Ответ 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

Создано 2018-12-08 пакетом представлением (v0.2.0).