Фрейм данных декартова продукта

У меня есть три или более независимых переменных, представленных в виде векторов 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)

поэтому, если вы знаете способ сделать оба шага одновременно, это также будет полезно.


person zwol    schedule 29.11.2010    source источник
comment
было бы полезно, если бы вы указали, что делает функция f.   -  person Ramnath    schedule 30.11.2010
comment
f является заполнителем для одного из нескольких сложных математических вычислений, но для целей этого вопроса, я думаю, вам нужно знать, что все они берут N векторов соответствующего типа и производят один вектор; все входы должны быть одинаковой длины, и выход также имеет эту длину.   -  person zwol    schedule 30.11.2010
comment
Я бы рекомендовал изменить название этого вопроса... таблица данных теперь означает что-то другое в R.   -  person random_forest_fanatic    schedule 11.11.2015
comment
@random_forest_fanatic Я изменил его на фрейм данных. Если это не то, что вы имели в виду, пожалуйста, уточните. (Я не знаю, о чем вы говорите, но я всегда имел в виду frame данных, и название было действительно небрежным с моей стороны.)   -  person zwol    schedule 11.11.2015
comment
@random_forest_fanatic Не знаю, планируете ли вы систематически выступать за изменение таких названий, но я бы порекомендовал вам этого не делать. Табличные данные - это понятие, которое есть у людей (из sql, excel или где-либо еще), и они вполне могут искать ответы в Google, используя этот термин, не зная тонкостей пакетов R. Я думаю, будет лучше, если мы позволим им это сделать и не будем переписывать вопросы для корректности. Кроме того, R — это data.table, а не data table.   -  person Frank    schedule 11.11.2015
comment
@Frank Причина моей придирчивости в том, что я нашел этот вопрос, потому что искал именно то, что указано в заголовке: как сделать декартово произведение с data.table в R. Этот вопрос не относится к этой теме, и поэтому я предложил изменить его, чтобы избежать путаницы / неправильного направления в будущем.   -  person random_forest_fanatic    schedule 13.11.2015


Ответы (7)


вы можете использовать expand.grid(A, B, C)

РЕДАКТИРОВАТЬ: альтернативой использованию do.call для достижения второй части является функция mdply из пакета plyr. вот код

library(plyr)
d = expand.grid(x = A, y = B, z = C)
d = mdply(d, f)

чтобы проиллюстрировать его использование с помощью тривиальной функции «вставить», вы можете попробовать

d = mdply(d, 'paste', sep = '+');
person Ramnath    schedule 30.11.2010
comment
Ага! Я знал, что для этого должна быть стандартная библиотечная процедура, но не мог найти, как она называется. Однако я собираюсь оставить вопрос открытым на случай, если у кого-то есть ответ на вторую часть. - person zwol; 30.11.2010
comment
если f является пользовательской функцией, вы можете изменить ее, чтобы принять фрейм данных в качестве аргумента и позволить функции обрабатывать разбиение на вектора компонентов - person Ramnath; 30.11.2010
comment
Смотрел на документацию plyr, но не понял, что для этого и нужен mdply. Спасибо. - person zwol; 01.12.2010

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

Он может создавать различные соединения (в терминологии 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

person misssprite    schedule 24.01.2013

С библиотекой 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
person Moody_Mudskipper    schedule 04.06.2018

Рассмотрите возможность использования замечательной библиотеки 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)]
person chris    schedule 31.05.2014

Вот способ сделать и то, и другое, используя предложение Рамната о 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 «как есть», потому что data.frame — это list. Но do.call ожидает, что имена столбцов d будут соответствовать именам аргументов f.

person Joshua Ulrich    schedule 30.11.2010
comment
@Зак: Спасибо; Я обновил свой ответ. Это не однострочник, но вычисление f по-прежнему проще с do.call, чем ввод каждого аргумента. - person Joshua Ulrich; 30.11.2010

Использование перекрестного соединения в 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') 
person OmG    schedule 12.07.2019

Никак не могу вспомнить эту стандартную функцию 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='_'))
person DamonJW    schedule 30.11.2010