粘贴网格 - expand.grid用于字符串连接

时间:2016-03-01 15:16:51

标签: r combinations string-concatenation

如果我们想要获得两个向量的所有组合,我们可以使用rep /回收规则:

x <- 1:4
y <- 1:2

cbind(rep(x, each = length(y)), rep(y, length(x)))
#      [,1] [,2]
# [1,]    1    1
# [2,]    1    2
# [3,]    2    1
# [4,]    2    2
# [5,]    3    1
# [6,]    3    2
# [7,]    4    1
# [8,]    4    2

expand.grid更好 - 它为我们处理所有重复。

expand.grid(x, y)
#   Var1 Var2
# 1    1    1
# 2    2    1
# 3    3    1
# 4    4    1
# 5    1    2
# 6    2    2
# 7    3    2
# 8    4    2

是否有一个简单的版本用于连接字符串?像paste.grid一样?我有一个命名对象,其中许多对象的名称如x_y_z,其中xyz的变化类似x和{{1}上面。

例如,假设y可以是x"avg""median"可以是y"male""female" }可以是z"height"。我们怎样才能简明地得到这三种组合的所有8种组合?

使用"weight"是一件痛苦的事:

rep

重新调整x <- c("avg", "median") y <- c("male", "female") z <- c("height", "weight") paste(rep(x, each = length(y) * length(z)), rep(rep(y, each = length(z)), length(x)), rep(z, length(x) * length(y)), sep = "_") 有点笨拙(可能效率低下):

expand.grid

我错过了什么吗?有更好的方法吗?

4 个答案:

答案 0 :(得分:8)

是的,这是interaction做的

levels(interaction(x,y,z,sep='_'))

实施与您的rep代码几乎相同。

输出:

[1] "avg_female_height"    "median_female_height" "avg_male_height"      "median_male_height"   "avg_female_weight"   
[6] "median_female_weight" "avg_male_weight"      "median_male_weight"  

答案 1 :(得分:6)

使用data.table的CJ交叉加入函数:

library(data.table)
CJ(x,y,z)[, paste(V1,V2,V3, sep = "_")]
#[1] "avg_female_height"    "avg_female_weight"    "avg_male_height"      "avg_male_weight"     
#[5] "median_female_height" "median_female_weight" "median_male_height"   "median_male_weight"

或者apply方法的变体是:

do.call(paste, c(expand.grid(x, y, z), sep = "_"))
#[1] "avg_male_height"      "median_male_height"   "avg_female_height"    "median_female_height"
#[5] "avg_male_weight"      "median_male_weight"   "avg_female_weight"    "median_female_weight"

答案 2 :(得分:4)

基本的(microbenchmark::microbenchmark)基准测试通过使用显示了相当显着的加速:

library(tidyr)
library(magrittr)

df <- data.frame(x, y, z)

df %>%
  complete(x, y, z) %>%
  unite("combo", x, y, z, sep = "_")

有点慢,但可能是apply技术的更直接和矢量化变体:

df <- expand.grid(x, y, z)
df$combo <- paste(df$Var1, df$Var1, df$Var3, sep = "_")

有人应该采用data.table方式...

基准测试:小网格(256个元素)

set.seed(21034)
x <- sample(letters, 4, TRUE)
y <- sample(letters, 4, TRUE)
z <- sample(letters, 4, TRUE)
a <- sample(letters, 4, TRUE)

library(data.table)
library(microbenchmark)
library(magrittr)
library(tidyr)

microbenchmark(times = 25L,
               DT1 = CJ(x, y, z, a)[ , paste(V1, V2, V3, V4, sep = "_")],
               DT2 = CJ(x, y, z, a)[ , do.call(paste, c(.SD, sep = "_"))],
               app1 = do.call(paste, c(expand.grid(x, y, z, a), sep = "_")),
               app2 = paste((df <- expand.grid(x, y, z, a))$Var1,
                            df$Var2, df$Var3, sep = "_"),
               magg_outer = outer(x, y, paste, sep = "_") %>%
                 outer(z, paste, sep = "_") %>%
                 outer(a, paste, sep = "_") %>% as.vector,
               magg_tidy = data.frame(x, y, z, a) %>% 
                 complete(x, y, z, a) %>%
                 unite("combo", x, y, z, a, sep = "_"),
               interaction = levels(interaction(x, y, z, a, sep = "_")),
               original = apply(expand.grid(x, y, z, a), 1, paste, collapse = "_"),
               rep = paste(rep(x, each = (ny <- length(y)) * (nz <- length(z)) *
                                 (na <- length(a))),
                           rep(rep(y, each = nz * na), (nx <- length(x))),
                           rep(rep(z, each = na), nx * ny), sep = "_"),
               Reduce = Reduce(function(x, y) paste(rep(x, each = length(y)), 
                                                     rep(y, length(x)), sep = "_"), 
                               list(x, y, z, a)))

# Unit: microseconds
#         expr      min        lq       mean    median        uq       max neval    cld
#          DT1  529.578  576.6400  624.00002  589.8270  604.9845  5449.287  1000    d  
#          DT2  561.028  606.4220  639.94659  620.4335  636.2735  5484.514  1000    d  
#         app1  201.043  225.4475  240.36960  233.4795  243.7090  4244.687  1000  b    
#         app2  196.692  225.6130  244.33543  234.0455  243.7925  4110.605  1000  b    
#   magg_outer  164.352  194.1395  205.30300  204.4220  211.1990   456.122  1000  b    
#    magg_tidy 1872.228 2038.1560 2150.98234 2067.8770 2126.1025 21891.884  1000      f
#  interaction  254.885  295.1935  313.54392  306.6680  316.8095  4196.465  1000   c   
#     original  852.018  935.4960  976.24388  954.5115  972.5550  4973.724  1000     e 
#          rep   50.737   54.1515   60.22671   55.3660   56.9220  3823.655  1000 a     
#       Reduce   58.395   65.3860   68.46049   66.8920   68.5640   158.184  1000 a     

基准测试:大型网格(1,000,000个元素)

set.seed(21034)
x <- sprintf("%03d", sample(100))
y <- sprintf("%03d", sample(100))
z <- sprintf("%02d", sample(10))
a <- sprintf("%02d", sample(10))

library(data.table)
library(microbenchmark)
library(magrittr)
library(tidyr)

microbenchmark(times = 25L,
               DT1 = CJ(x, y, z, a)[ , paste(V1, V2, V3, V4, sep = "_")],
               DT2 = CJ(x, y, z, a)[ , do.call(paste, c(.SD, sep = "_"))],
               app1 = do.call(paste, c(expand.grid(x, y, z, a), sep = "_")),
               app2 = paste((df <- expand.grid(x, y, z, a))$Var1,
                            df$Var2, df$Var3, sep = "_"),
               magg_outer = outer(x, y, paste, sep = "_") %>%
                 outer(z, paste, sep = "_") %>%
                 outer(a, paste, sep = "_") %>% as.vector,
               magg_tidy = data.frame(x, y, z, a) %>% 
                 complete(x, y, z, a) %>%
                 unite("combo", x, y, z, a, sep = "_"),
               interaction = levels(interaction(x, y, z, a, sep = "_")),
               original = apply(expand.grid(x, y, z, a), 1, paste, collapse = "_"),
               rep = paste(rep(x, each = (ny <- length(y)) * (nz <- length(z)) *
                                 (na <- length(a))),
                           rep(rep(y, each = nz * na), (nx <- length(x))),
                           rep(rep(z, each = na), nx * ny), sep = "_"),
               Reduce = Reduce(function(x, y) paste(rep(x, each = length(y)), 
                                                     rep(y, length(x)), sep = "_"), 
                               list(x, y, z, a)))

# Unit: milliseconds
#         expr       min        lq      mean    median        uq       max neval  cld
#          DT1  360.6528  467.8408  517.4579  520.1484  549.1756  861.1567    25 ab  
#          DT2  355.0438  504.9642  572.0732  551.9106  615.6621  927.3210    25  b  
#         app1  727.4513  766.3053  926.1888  910.3998  957.7610 1690.1540    25   c 
#         app2  472.5724  567.1121  633.5304  600.3779  634.3158 1135.7535    25  b  
#   magg_outer  384.0112  475.5070  600.6317  525.8936  676.7134  927.6736    25  b  
#    magg_tidy  520.6428  602.5028  695.5500  680.8821  748.8746 1180.1107    25  bc 
#  interaction  353.7317  481.4732  531.0035  518.7084  585.0872  693.5171    25 ab  
#     original 4965.1156 5358.8704 5914.3560 5780.6609 6074.7470 9024.6476    25    d
#          rep  206.0964  236.5811  273.1093  252.8179  285.0910  455.1776    25 a   
#       Reduce  322.0695  390.2595  446.3948  424.9185  508.5235  621.1878    25 ab  

答案 3 :(得分:2)

使用outer()怎么样?你的两个例子变成了

x <- 1:4
y <- 1:2
as.vector(outer(x, y, paste, sep = "_"))
## [1] "1_1" "2_1" "3_1" "4_1" "1_2" "2_2" "3_2" "4_2"

library(magrittr)
x <- c("avg", "median")
y <- c("male", "female")
z <- c("height", "weight")
outer(x, y, paste, sep = "_") %>% outer(z, paste, sep = "_") %>% as.vector
## [1] "avg_male_height"      "median_male_height"   "avg_female_height"    "median_female_height" "avg_male_weight"     
## [6] "median_male_weight"   "avg_female_weight"    "median_female_weight"

使用Reduce()

可以简化第二个示例
Reduce(function(a, b) outer(a, b, paste, sep = "_"), list(x, y, z)) %>% as.vector
然而,这并不高效。使用microbenchmark,我发现使用rep()的解决方案速度提高了大约10倍。