在R中 - 从data.frame中的所有行生成成对data.frame

时间:2017-01-21 21:37:39

标签: r dataframe data.table dplyr

我有一个名为df的data.frame,在4列上有800万次观察:

name <- c("Pablo", "Christina", "Steve", "Diego", "Ali", "Brit", "Ruth", "Mia", "David", "Dylan")
year <- seq(2000, 2009, 1)
v1 <- sample(1:10, 10, replace=T)
v2 <- sample(1:10, 10, replace=T)
df <- data.frame(year, v1)

> df
        name year v1 v2
1      Pablo 2000  2  9
2  Christina 2001  5  3
3      Steve 2002  8  9
4      Diego 2003  7  6
5        Ali 2004  2  4
6       Brit 2005  1  1
7       Ruth 2006 10  9
8        Mia 2007  6  7
9      David 2008 10  9
10     Dylan 2009  3  2

我想生成一个data.frame output,其中df中所有行的成对组合如下所示:

 >output
   name year v1 v2    name_2 year_2 v1_2 v2_2
1 Pablo 2000  2  9 Christina   2001    5    3
2 Pablo 2000  2  9     Steve   2002    8    9
3 Pablo 2000  2  9     Diego   2003    7    6
etc.  

最快的方法是什么?

5 个答案:

答案 0 :(得分:4)

tidyr::crossing将返回所有观察组合,但您需要使用setNames等设置名称。如果您不想进行自我匹配,可以通过在任何唯一ID列上调用dplyr::filter来删除它们。

library(tidyverse)

df_crossed <- df %>% 
    setNames(paste0(names(.), '_2')) %>% 
    crossing(df) %>% 
    filter(name != name_2)

head(df_crossed)
##   name_2 year_2 v1_2 v2_2      name year v1 v2
## 1  Pablo   2000    5    5 Christina 2001  7  3
## 2  Pablo   2000    5    5     Steve 2002  1  9
## 3  Pablo   2000    5    5     Diego 2003  2  8
## 4  Pablo   2000    5    5       Ali 2004  9  5
## 5  Pablo   2000    5    5      Brit 2005  8  5
## 6  Pablo   2000    5    5      Ruth 2006  8  1

修复名称的另一种方法是在janitor::clean_names之后使用crossing,尽管它是一个额外的包。

答案 1 :(得分:2)

希望这会给帖子所有者正在寻找的结果。

.controller('CM_prendasPorM2Ctrl', function($scope, $state, cssInjector, $http) {

    $scope.goToWebsite = function(address){
        window.open(address, '_blank');
    };

答案 2 :(得分:2)

不要添加噪声,而是考虑在同一数据帧上使用merge的基本R交叉连接,同时过滤掉反向重复项。请注意,过滤器之前的交叉连接将返回8 mill X 8 mill记录数据集,因此希望您的RAM足以进行此类操作。

df <- data.frame(name = c("Pablo", "Christina", "Steve", "Diego", "Ali",
                          "Brit", "Ruth", "Mia", "David", "Dylan"), 
                 year = seq(2000, 2009, 1),
                 v1 =sample(1:10, 10, replace=T), 
                 v2 =sample(1:10, 10, replace=T),
                 stringsAsFactors = FALSE)

# MERGE ON KEY, THEN REMOVE KEY COL
df$key <- 1
dfm <- merge(df, df, by="key")[,-1]   

# FILTER OUT SAME NAME AND REVERSE DUPS, THEN RENAME COLUMNS
dfm <- setNames(dfm[(dfm$name.x < dfm$name.y),], 
                c("name_p1", "year_p1", "V1_p1", "V2_p1",
                  "name_p2", "year_p2", "V1_p2", "V2_p2"))

# ALL PABLO PAIRINGS 
dfm[dfm$name_p1=='Pablo' | dfm$name_p2=='Pablo',]

#      name_p1 year_p1 V1_p1 V2_p1 name_p2 year_p2 V1_p2 V2_p2
# 3      Pablo    2000     7     8   Steve    2002     3     1
# 7      Pablo    2000     7     8    Ruth    2006     8     4
# 11 Christina    2001    10    10   Pablo    2000     7     8
# 31     Diego    2003     4     9   Pablo    2000     7     8
# 41       Ali    2004     5     3   Pablo    2000     7     8
# 51      Brit    2005     2     4   Pablo    2000     7     8
# 71       Mia    2007     7     7   Pablo    2000     7     8
# 81     David    2008     1     7   Pablo    2000     7     8
# 91     Dylan    2009     9     2   Pablo    2000     7     8

如果以某种方式从SQL兼容的数据库派生这个大集合,我可以提供SQL中的对应部分,因为过滤器使用连接过程运行而不是单独运行,因此可能更有效。

答案 3 :(得分:1)

您可以使用data.table将名称列交叉连接到自身,并删除重复的案例。这将导致一个较小的结构,在该结构上合并数据而不是完全合并,然后进行过滤。您可以使用两个合并添加其余数据:一次合并与第一个名称列关联的数据,再次合并与第二列关联的数据。

name <- c("Pablo", "Christina", "Steve", "Diego", "Ali", "Brit", "Ruth", "Mia", "David", "Dylan")
year <- seq(2000, 2009, 1)
v1 <- sample(1:10, 10, replace=T)
v2 <- sample(1:10, 10, replace=T)
# stringsAsFactors = FALSE in order for pmin to work properly
df <- data.frame(name, year, v1, v2, stringsAsFactors = FALSE) 

library(data.table)
setDT(df)
setkey(df)

# cross-join name column to itself while removing duplicates and redundancies
name_cj <- setnames(
  CJ(df[, name], df[, name])[V1 < V2], # taking a hint from Parfait's clever solution
  c("name1", "name2"))

# perform 2 merges, once for the 1st name column and
# again for the 2nd name colum
name_cj <- merge(
  merge(name_cj, df, by.x = "name1", by.y = "name"),
  df,
  by.x = "name2", by.y = "name", suffixes = c("_1", "_2"))

# reorder columns as desired with setorder()
head(name_cj)
#      name2     name1 year_1 v1_1 v2_1 year_2 v1_2 v2_2
#1:      Brit       Ali   2004    3    8   2005    4    5
#2: Christina       Ali   2004    3    8   2001    9    8
#3: Christina      Brit   2005    4    5   2001    9    8
#4:     David       Ali   2004    3    8   2008    5    2
#5:     David      Brit   2005    4    5   2008    5    2
#6:     David Christina   2001    9    8   2008    5    2

答案 4 :(得分:1)

@alistaires解决方案的扩展显示了用作索引的交叉矩阵。所述问题需要完整的交叉输出 将是非常大的(大约6400万行,800万项)所以那里 真的没办法满足内存要求。但是,如果 现实世界的用法是处理子集,索引技术 这里显示的可能是一种减少内存使用的方法。跨越整数只能在交叉操作期间使用较少的内存。

library(dplyr)
library(tidyr)
crossed <- as.matrix(crossing(1:nrow(df), 1:nrow(df)))
# bind and name in one step (may be inefficient) so that filter can be applied in one step
output <- as.data.frame(cbind(df[crossed[, 1],], 
                              data.frame(name_2 = df[crossed[, 2], 1],
                                         year_2 = df[crossed[, 2], 2],
                                         v1_2   = df[crossed[, 2], 3],
                                         v2_2   = df[crossed[, 2], 4]) )) %>%
           filter(!(name == name_2 & year == year_2))

# estimated sized for 8 million rows gine this 10 row sample
format(object.size(output) / (10 / 8e6), units="MB")
#[1] "5304 Mb"