【问题标题】:In R - generate pairwise data.frame from all rows in data.frame在 R 中 - 从 data.frame 中的所有行生成成对的 data.frame
【发布时间】:2017-06-06 16:35:18
【问题描述】:

我有一个名为 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.  

最快的方法是什么?

【问题讨论】:

  • idx &lt;- t(combn(seq_len(nrow(df)), 2));cbind(df[idx[,1],], df[idx[,2],])?
  • tidyr::crossing(df, df)
  • @lukeA 确实!但第二系列变量的列名不同。并将输出作为 data.frame。
  • @alistaire 对!但第一行包含一个副本(即 Pablo-Pablo)。是否可以生成没有重复的输出?
  • 使用cbind.data.frame 或包裹as.data.frame。之后,您可以使用 names(df)&lt;-c("col1", "col2", .....) 重命名列

标签: r dataframe data.table dplyr


【解决方案1】:

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

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

【讨论】:

  • 谢谢阿利斯泰尔!这适用于我的数据的较小子集。但是一旦子集变大,R 就会尝试分配一个大小为 1338 GB 的向量......不知道如何处理这个:-)
  • 是的,所需的行数有阶乘。它还为您提供了 Pablo-Christina 和 Christina-Pablo,即排列,而不是组合。使用combn 进行索引将允许更有限的集合(如果这是您需要的),但它仍然会很快变得非常大。不过,您可能不需要复制数据;您可以将一行中的函数应用到其他行上,然后只存储结果。也许有一个新问题。
【解决方案2】:

您可以使用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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • as.data.frame(cbind(...)) 是一个经常导致类型问题的坏习惯。只需使用data.frame
    【解决方案4】:

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

    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(name, year, v1, v2, stringsAsFactors=FALSE)
    print(df)
    rows = nrow(df)
    n <- rows * (rows - 1) / 2
    ndf <- data.frame(
        name1=character(n),year1=numeric(n), v1_1=numeric(n),v2_1=numeric(n),
        name2=character(n),year2=numeric(n), v1_2=numeric(n),v2_2=numeric(n),
        stringsAsFactors=FALSE
    )
    k <- 1
    for (i in 1:(rows-1))
    {
        for (j in (i+1):rows)
        {
            ndf[k,] <- c(df[i,], df[j,])
            k <- k + 1
        }
    }
    print(ndf)
    
    #        name year v1 v2
    #1      Pablo 2000  4  9
    #2  Christina 2001  2  1
    #3      Steve 2002  2  9
    #4      Diego 2003  5  5
    #5        Ali 2004 10  4
    #6       Brit 2005  5  2
    #7       Ruth 2006  7 10
    #8        Mia 2007  6  7
    #9      David 2008  4 10
    #10     Dylan 2009  7  3
    
    #       name1 year1 v1_1 v2_1     name2 year2 v1_2 v2_2
    #1      Pablo  2000    4    9 Christina  2001    2    1
    #2      Pablo  2000    4    9     Steve  2002    2    9
    #3      Pablo  2000    4    9     Diego  2003    5    5
    #4      Pablo  2000    4    9       Ali  2004   10    4
    #5      Pablo  2000    4    9      Brit  2005    5    2
    #6      Pablo  2000    4    9      Ruth  2006    7   10
    #7      Pablo  2000    4    9       Mia  2007    6    7
    #8      Pablo  2000    4    9     David  2008    4   10
    #9      Pablo  2000    4    9     Dylan  2009    7    3
    #10 Christina  2001    2    1     Steve  2002    2    9
    #...
    

    【讨论】:

    • 此方法不包括相互重复,即不包括 Christeina 2001 2 1 Pablo 2000 4 9。要包含倒数,for 循环都需要从 1:rows 开始,并在内部循环中跳过 i == j 的情况。当然ndf的大小也需要重新计算。
    【解决方案5】:

    不要增加噪音,而是考虑在同一数据帧上使用merge 进行基本 R 交叉连接,同时过滤掉反向重复。请注意,过滤器之前的交叉连接将返回一个 8 磨 X 8 磨记录数据集,因此希望您的 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 中的对应物,这可能会更有效,因为过滤器在连接过程中运行,而不是在之后单独运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 1970-01-01
      • 2021-04-21
      • 1970-01-01
      • 2019-12-10
      • 2011-09-20
      相关资源
      最近更新 更多