Как я могу добиться перекрестного соединения в R? Я знаю, что «слияние» может выполнять внутреннее соединение, внешнее соединение. Но я не знаю, как добиться перекрестного соединения в 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 (вектор нулевой длины или NULL), результат r является декартовым произведением x и y, т. Е. Dim (r) = c (nrow (x ) * nrow (y), ncol (x) + ncol (y)).
all
? Не вижу, как all=FALSE
(по умолчанию) повлияет на результат. Также обратите внимание, что merge
устанавливает by.x = by.y = by = intersect(names(x), names(y)
, поэтому x
и y
не могут иметь общие имена столбцов (в противном случае вы не получите перекрестное соединение с настройками по умолчанию).
- person Davor Josipovic; 05.02.2018
Если скорость является проблемой, я предлагаю проверить отличный пакет 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
:
setkey(tablename,keycolumns)
), добавляете фиктивный столбец в другую таблицу, а затем присоединяете их.c(k=1,.SD)
- это один из способов добавления столбцов в начало (по умолчанию их добавление в конец).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)
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
data.table
изменилось с момента моего первоначального ответа. Я обновил его и добавил тайминги. Спасибо.
- person dnlbrky; 07.02.2014
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
allow.cartesian
смягчает это, но data.table предлагает использовать by = .EACHI
. Обратите внимание, что все три метода обеспечивают почти ту же скорость, что и сегодня (бенчмаркинг обеспечивает около 35 миллисекунд на моей машине для всех 3 методов за 1000 репликаций), поэтому нет преимущества в производительности по сравнению с менее читаемой версией CJ.table.2
. (с добавленным аргументом allow.cartesian)
- person Oliver; 19.02.2019
Об этом спрашивали много лет назад, но вы можете использовать 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).
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
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
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)
Используя функцию слияния и ее необязательные параметры:
Внутреннее соединение: слияние (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)
Я не знаю встроенного способа сделать это с помощью 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 ...
Мне бы хотелось узнать, существует ли удобный способ соединения двух таблиц 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
Для data.table используйте
dt1[, as.list(dt2), by = names(dt1)]
Обратите внимание, что это работает, только если нет повторяющихся строк.