How can I use a vectorised function in R in alternate sequence?

Viewed 686

Suppose I have a vector x with n elements. I want to use any vectorised function, say cumprod, on every alternate number of x, i.e. every 1, 3, 5 and so on and another on 2, 4, 6 and so on. I am adding a reprex and code tried. The code works, but it seems I am unnecessarily taking a long route and code can be shortened. Can it be?

x <- 5:14

cumprod((x * (seq_along(x) %% 2)) + (seq_along(x)-1) %% 2) * seq_along(x) %% 2 +
  cumprod((x * ((seq_along(x)-1) %% 2)) + seq_along(x) %% 2) * (seq_along(x)-1) %% 2
#>  [1]     5     6    35    48   315   480  3465  5760 45045 80640

Here cumprod is just an example function. I may have to use other functions in alternate sequence as well.

5 Answers

We could do this in a concise way with rowCumprods after creating a matrix (assuming the vector is of even length)

library(matrixStats)
c(rowCumprods(matrix(x, nrow = 2)))

-output

[1]     5     6    35    48   315   480  3465  5760 45045 80640

if it can be odd length, then just append an NA at the end

 c(rowCumprods(matrix(c(x,  list(NULL, NA)[[1 +
         (length(x) %%2 != 0)]]), nrow = 2)))

-output

 [1]     5     6    35    48   315   480  3465  5760 45045 80640

Or we can do this in a generalized way with ave (works both with even/odd lengths)

ave(x, seq_along(x) %% 2, FUN = cumprod)
 [1]     5     6    35    48   315   480  3465  5760 45045 80640

Select odd (c(TRUE, FALSE)) or even (c(FALSE, TRUE)) indices. Weave the two resulting vectors (c(rbind)

c(rbind(cumprod(x[c(TRUE, FALSE)]), cumprod(x[c(FALSE, TRUE)])))
# [1]     5     6    35    48   315   480  3465  5760 45045 80640

To handle also odd vector length, you need to truncate the result to length of the vector.

x = 1:5

c(rbind(cumprod(x[c(TRUE, FALSE)]), cumprod(x[c(FALSE, TRUE)])))[1:length(x)]
# [1]  1  2  3  8 15

There will be a warning when the shorter result vector, corresponding to the even indices (which has one element less), is recycled in the rbind step.

One option for both even and odd number of elements could be:

c(t(apply(matrix(x, 2, sum(seq_along(x) %% 2)), 1, cumprod)))[1:length(x)]

With x <- 1:5:

[1]  1  2  3  8 15

With x <- 1:6:

[1]  1  2  3  8 15 48

Or a less effective option, however, without any warnings:

y <- Reduce(`c`, sapply(split(setNames(x, seq_along(x)), !seq_along(x) %% 2), cumprod))
y[order(as.numeric(names(y)))]

Another option - take a sequence and then fill the results back in:

x <- 5:14

s <- seq(1, length(x), 2)
o <- x
o[s]  <- cumprod(x[s])
o[-s] <- cumprod(x[-s])
o 
# [1]     5     6    35    48   315   480  3465  5760 45045 80640

Or if you want to code-golf it:

s <- seq(1, length(x), 2)
replace(replace(x, s, cumprod(x[s])), -s, cumprod(x[-s]))
# [1]     5     6    35    48   315   480  3465  5760 45045 80640

Updated Solution

This may sound a bit verbose but it will work with odd and even number of lengths and also @Henrik's custom vector:

x <- 5:14
lapply(split(x, !(seq_len(length(x)) %% 2)), cumprod) |>
  setNames(c("a", "b")) |>
  list2env(globalenv())

c(a, b)[order(c(seq_along(a)*2 - 1, seq_along(b)*2))]

[1]     5     6    35    48   315   480  3465  5760 45045 80640

With an odd vector:

x <- 5:13
[1]     5     6    35    48   315   480  3465  5760 45045

Or with x = c(1, 0, 3, 4)

[1] 1 0 3 0

And in the end with x = c(2, 4, 2, 4):

[1]  2  4  4 16
Related