Рамка данных декартовых произведений
У меня есть три или более независимых переменных, представленных как R-векторы, например:
A <- c(1,2,3)
B <- factor(c('x','y'))
C <- c(0.1,0.5)
и я хочу взять декартово произведение всех из них и поместить результат в кадр данных, например:
A B C
1 x 0.1
1 x 0.5
1 y 0.1
1 y 0.5
2 x 0.1
2 x 0.5
2 y 0.1
2 y 0.5
3 x 0.1
3 x 0.5
3 y 0.1
3 y 0.5
Я могу сделать это, вручную выписав вызовы rep
:
d <- data.frame(A = rep(A, times=length(B)*length(C)),
B = rep(B, times=length(A), each=length(C)),
C = rep(C, each=length(A)*length(B))
но должен быть более элегантный способ сделать это, да? product
в itertools
выполняет часть задания, но я не могу найти способ поглотить вывод итератора и поместить его в фрейм данных. Любые предложения?
p.s. Следующий шаг в этом вычислении выглядит как
d$D <- f(d$A, d$B, d$C)
поэтому, если вы знаете способ сделать оба действия одновременно, это также будет полезно.
Ответы
Ответ 1
вы можете использовать expand.grid(A, B, C)
EDIT: альтернатива использованию do.call для достижения второй части - это функция mdply. вот код
d = expand.grid(x = A, y = B, z = C)
d = mdply(d, f)
чтобы проиллюстрировать его использование с помощью тривиальной функции "вставить", вы можете попробовать
d = mdply(d, 'paste', sep = '+');
Ответ 2
Есть функция манипулирования dataframe, которая полезна в этом случае.
Он может создавать различные объединения (в терминологии SQL), а декартово произведение является особым случаем.
Вы должны сначала преобразовать переменные в фреймы данных, потому что они принимают фрейм данных в качестве параметров.
так что-то вроде этого будет делать:
A.B=merge(data.frame(A=A), data.frame(B=B),by=NULL);
A.B.C=merge(A.B, data.frame(C=C),by=NULL);
Единственное, о чем нужно заботиться, это то, что строки сортируются не так, как вы изображали. Вы можете отсортировать их вручную, как вы хотите.
merge(x, y, by = intersect(names(x), names(y)),
by.x = by, by.y = by, all = FALSE, all.x = all, all.y = all,
sort = TRUE, suffixes = c(".x",".y"),
incomparables = NULL, ...)
"Если by или оба by.x и by.y имеют длину 0 (вектор нулевой длины или NULL), результат r является декартовым произведением x и y"
см. этот URL для подробностей: http://stat.ethz.ch/R-manual/R-patched/library/base/html/merge.html
Ответ 3
Вот способ сделать то и другое, используя предложение Рамнатха expand.grid
:
f <- function(x,y,z) paste(x,y,z,sep="+")
d <- expand.grid(x=A, y=B, z=C)
d$D <- do.call(f, d)
Обратите внимание, что do.call
работает на d
"as-is", потому что data.frame
является list
. Но do.call
ожидает, что имена столбцов d
соответствуют именам аргументов f
.
Ответ 4
Подумайте об использовании замечательной библиотеки data.table для выразительности и скорости. Он обрабатывает много вариантов использования plyr (реляционная группа), а также преобразование, подмножество и реляционное объединение с использованием довольно простого единообразного синтаксиса.
library(data.table)
d <- CJ(x=A, y=B, z=C) # Cross join
d[, w:=f(x,y,z)] # Mutates the data.table
или в одной строке
d <- CJ(x=A, y=B, z=C)[, w:=f(x,y,z)]
Ответ 5
С библиотекой tidyr
можно использовать tidyr::crossing
(порядок будет такой же, как в OP):
library(tidyr)
crossing(A,B,C)
# A tibble: 12 x 3
# A B C
# <dbl> <fct> <dbl>
# 1 1 x 0.1
# 2 1 x 0.5
# 3 1 y 0.1
# 4 1 y 0.5
# 5 2 x 0.1
# 6 2 x 0.5
# 7 2 y 0.1
# 8 2 y 0.5
# 9 3 x 0.1
# 10 3 x 0.5
# 11 3 y 0.1
# 12 3 y 0.5
Следующим шагом будет использование tidyverse
и особенно семейства purrr::pmap*
:
library(tidyverse)
crossing(A,B,C) %>% mutate(D = pmap_chr(.,paste,sep="_"))
# A tibble: 12 x 4
# A B C D
# <dbl> <fct> <dbl> <chr>
# 1 1 x 0.1 1_1_0.1
# 2 1 x 0.5 1_1_0.5
# 3 1 y 0.1 1_2_0.1
# 4 1 y 0.5 1_2_0.5
# 5 2 x 0.1 2_1_0.1
# 6 2 x 0.5 2_1_0.5
# 7 2 y 0.1 2_2_0.1
# 8 2 y 0.5 2_2_0.5
# 9 3 x 0.1 3_1_0.1
# 10 3 x 0.5 3_1_0.5
# 11 3 y 0.1 3_2_0.1
# 12 3 y 0.5 3_2_0.5
Ответ 6
Я никогда не помню эту стандартную функцию expand.grid
. Итак, другая версия.
crossproduct <- function(...,FUN='data.frame') {
args <- list(...)
n1 <- names(args)
n2 <- sapply(match.call()[1+1:length(args)], as.character)
nn <- if (is.null(n1)) n2 else ifelse(n1!='',n1,n2)
dims <- sapply(args,length)
dimtot <- prod(dims)
reps <- rev(cumprod(c(1,rev(dims))))[-1]
cols <- lapply(1:length(dims), function(j)
args[[j]][1+((1:dimtot-1) %/% reps[j]) %% dims[j]])
names(cols) <- nn
do.call(match.fun(FUN),cols)
}
A <- c(1,2,3)
B <- factor(c('x','y'))
C <- c(.1,.5)
crossproduct(A,B,C)
crossproduct(A,B,C, FUN=function(...) paste(...,sep='_'))
Ответ 7
Использование перекрестного соединения в sqldf
:
library(sqldf)
A <- data.frame(c1 = c(1,2,3))
B <- data.frame(c2 = factor(c('x','y')))
C <- data.frame(c3 = c(0.1,0.5))
result <- sqldf('SELECT * FROM (A CROSS JOIN B) CROSS JOIN C')