当一列只是相同值的重复时,如何加快合并列的速度?

How to speed up combining columns when one column is just a repetition of the same value?

给定以下数据框:

df <-
  data.frame(one_letter = rep("a", 5),
             other_letters = letters[2:6])

df
#>   one_letter other_letters
#> 1          a             b
#> 2          a             c
#> 3          a             d
#> 4          a             e
#> 5          a             f

我想将两列合并为一列,得到:

#>   all_letters_combined
#> 1                    a
#> 2                    b
#> 3                    c
#> 4                    d
#> 5                    e
#> 6                    f

尽管我可以利用 dplyr&tidyr 并执行以下操作:

library(dplyr, warn.conflicts = FALSE)
library(tidyr)

# yes, it gets the job done
df %>%
  pivot_longer(everything()) %>%
  select(value) %>%
  unique()
#> # A tibble: 6 x 1
#>   value
#>   <chr>
#> 1 a    
#> 2 b    
#> 3 c    
#> 4 d    
#> 5 e    
#> 6 f

尽管如此,我仍在寻找 faster/more 的直接方法。这是因为当我们的 df 是一个包含数据帧的列表列时,速度会成为一个问题。这是一个示例,尽管仍然非常小:

library(nycflights13)
library(babynames)
library(tictoc)


bigger_tib <- 
  tibble(one_df = rep(list(babynames), 10),
         other_dfs = list(starwars, flights, mtcars, trees, women, PlantGrowth, ToothGrowth, co2, Titanic, USArrests))

tic()
bigger_tib %>%
  pivot_longer(everything()) %>%
  select(value) %>%
  unique()
#> # A tibble: 11 x 1
#>    value                   
#>    <list>                  
#>  1 <tibble [1,924,665 x 5]>
#>  2 <tibble [87 x 14]>      
#>  3 <tibble [336,776 x 19]> 
#>  4 <df [32 x 11]>          
#>  5 <df [31 x 3]>           
#>  6 <df [15 x 2]>           
#>  7 <df [30 x 2]>           
#>  8 <df [60 x 3]>           
#>  9 <ts [468]>              
#> 10 <table [4 x 2 x 2 x 2]> 
#> 11 <df [50 x 4]>
toc()
#> 0.97 sec elapsed

我知道这个例子不是很好,因为它没有证明有问题的 运行 时间,但在我的真实数据中,这个过程变得非常慢,我想加快速度。

瓶颈是unique,应用到 数据框列表。 distinct 会更快。另一方面,如果您在旋转数据帧之前已经知道数据帧是唯一的,则为每个数据帧提供唯一的 id 以保持这种关系将是一种更理想的方法。也就是说,请考虑以下基准。

library(dplyr)
library(tidyr)

f1 <- . %>% pivot_longer(everything()) %>% select(value) %>% unique()
f2 <- . %>% pivot_longer(everything()) %>% select(value) %>% distinct()
f3 <- . %>% 
  rename(one_df = one_df, other_df = other_dfs) %>% 
  mutate(one_id = 0L, other_id = row_number()) %>% 
  pivot_longer(starts_with(c("one", "other")), c(NA, ".value"), names_sep = "_") %>% 
  distinct(id, .keep_all = TRUE)

microbenchmark::microbenchmark(f1(bigger_tib), f2(bigger_tib), f3(bigger_tib), times = 10L)

输出

> f3(bigger_tib)
# A tibble: 11 x 2
   df                          id
   <list>                   <int>
 1 <tibble [1,924,665 x 5]>     0
 2 <tibble [87 x 14]>           1
 3 <df [50 x 2]>                2
 4 <df [32 x 11]>               3
 5 <df [31 x 3]>                4
 6 <df [15 x 2]>                5
 7 <df [30 x 2]>                6
 8 <df [60 x 3]>                7
 9 <ts [468]>                   8
10 <table [4 x 2 x 2 x 2]>      9
11 <df [50 x 4]>               10

基准

Unit: milliseconds
           expr      min       lq     mean   median       uq      max neval
 f1(bigger_tib) 619.5852 623.8327 638.0796 634.4866 644.9060 687.6760    10
 f2(bigger_tib) 230.6140 231.6163 234.4957 234.1330 237.1576 238.6012    10
 f3(bigger_tib)   4.0693   5.2220   5.5078   5.2996   5.4089   8.6592    10

关于pivot_longer行的一个特别说明:这意味着我们将"_"之后的字符用作names_to,丢弃"_"之前的字符。如果在 "_".

之后具有相同的字符,则所有值都堆叠在同一列中

如果所述问题是将第一列中的唯一值与第二列合并。如果第一列只是一个重复值而第二列包含所有唯一值,那么一个简单的解决方案是:

data.frame(all_letters_combined=c(df[1,1], df[,2]))

如果您需要从结果列中删除重复项(第 2 列中的重复项或第 1 列在第 2 列中重复)。根据 ekoam 的观察,dplyr::distinct()unique() 快 然后这里有一个选项:

distinct(data.frame(all_letters_combined=c(df[1,1], df[,2])))

当然,如果有更多的列和值的不同可能性,则需要更复杂的解决方案。