Paste a list to a vector, repeating the list for each vector level

Viewed 376

I want to accomplish the following task without having to enter a for loop, but rather in a single apply() command.

I have a list a that I want to repeat N times, where N is the length of vector b, pasting each repetition of a to an element of b.

So far I have done the following MWE:

var <- paste("var", 1:4, sep="")
treat <- c("A","B")
spec <- paste("sp", 1:3, sep="")
a <- combn(var, 2, simplify = FALSE)#this 6 times, for each treatment and species
b <- do.call(paste, c(expand.grid(treat, spec), sep='.'))
a1 <- lapply(a, paste, b[1], sep='.')
a2 <- lapply(a, paste, b[2], sep='.')
a3 <- lapply(a, paste, b[3], sep='.')
a4 <- lapply(a, paste, b[4], sep='.')
a5 <- lapply(a, paste, b[5], sep='.')
a6 <- lapply(a, paste, b[6], sep='.')
a.final <- c(a1,a2,a3,a4,a5,a6)
a.final

It would be optimal if I could paste b before a.

Note my starting points are 3 vectors: var, treat and spec, so feel free to change anything from that point.

3 Answers

Option 1: We can accomplish this without any apply() loops at all. Let's unlist() the a list, paste() it to the replicated b values, then relist() it based on the replicated a lists. Try this:

aa <- relist(
    paste(unlist(a), rep(b, each=sum(lengths(a))), sep="."), 
    rep.int(a, length(b))
)

Check:

identical(aa, a.final)
# [1] TRUE

Option 1 with b before a: Now, to put the b values out front, just swap the arguments in the paste() call:

relist(
    paste(rep(b, each=sum(lengths(a))), unlist(a), sep = "."),  
    rep.int(a, length(b))
)

Option 2: This option does use an apply() loop. Here we use Map() to do one-to-one pasting.

ra <- rep(a, length(b))
aa2 <- Map(paste, ra, relist(rep(b, each=sum(lengths(a))), ra), sep = ".")

Check:

identical(aa2, a.final)
# [1] TRUE

Option 2 with b before a: Just swap the unnamed Map() arguments that are passed to paste().

ra <- rep(a, length(b))
Map(paste, relist(rep(b, each=sum(lengths(a))), ra), ra, sep = ".")

Staying close to OP's approach, this can be solved with nested lapply() using anonymous functions:

unlist(lapply(b, function(x) lapply(a, function(y) paste(x, y, sep = "."))), 
       recursive = FALSE)
[[1]]
[1] "A.sp1.var1" "A.sp1.var2"

[[2]]
[1] "A.sp1.var1" "A.sp1.var3"

[[3]]
[1] "A.sp1.var1" "A.sp1.var4"

...

[[34]]
[1] "B.sp3.var2" "B.sp3.var3"

[[35]]
[1] "B.sp3.var2" "B.sp3.var4"

[[36]]
[1] "B.sp3.var3" "B.sp3.var4"

Note, b is pasted in front of a. unlist() is required to flatten out top level list.

To verify approach is working (a in front of b to compare with a.final):

identical(a.final,
          unlist(lapply(b, function(x) lapply(a, function(y) paste(y, x, sep = "."))), 
                 recursive = FALSE))
[1] TRUE

This is a completely different approach which creates the labels from scratch and returns them in a 36 rows x 2 columns data.table instead of a list with 36 vectors of length 2:

library(data.table)
# cross join of treat, spec, var. Note, full labels will be created in sprintf() below
DT <- CJ(LETTERS[1:2], 1:3, 1:4)
# non equi join as replacement of combn()
DT[DT, on = .(V1, V2, V3 > V3), nomatch = 0L,
   # create labels
   .(sprintf("%s.sp%s.var%i", V1, V2, V3), 
     sprintf("%s.sp%s.var%i", V1, V2, x.V3))]
            V1         V2
 1: A.sp1.var1 A.sp1.var2
 2: A.sp1.var1 A.sp1.var3
 3: A.sp1.var1 A.sp1.var4
 4: A.sp1.var2 A.sp1.var3
 5: A.sp1.var2 A.sp1.var4
 6: A.sp1.var3 A.sp1.var4
 7: A.sp2.var1 A.sp2.var2
...
29: B.sp2.var2 B.sp2.var4
30: B.sp2.var3 B.sp2.var4
31: B.sp3.var1 B.sp3.var2
32: B.sp3.var1 B.sp3.var3
33: B.sp3.var1 B.sp3.var4
34: B.sp3.var2 B.sp3.var3
35: B.sp3.var2 B.sp3.var4
36: B.sp3.var3 B.sp3.var4
            V1         V2
Related