Home > Software engineering >  No, map() or apply() cannot systematically replace for() loop, in R
No, map() or apply() cannot systematically replace for() loop, in R

Time:05-02

I have the following vector a = c(1,0,0,1,0,0,0,0). I want to obtain the following one: c(1,2,3,1,2,3,4,5). That is, if a[i] == 1, let it 1, otherwise a[i] should be a[i-1] 1. Doing that with for is easy:

for (i in seq(1,length(a))) {
  a[i] = ifelse(a[i]==1,1,a[i-1] 1)
a
 [1] 1 2 3 1 2 3 4 5

The loop works because for() changes the a vector directly on the global environment. map() (or apply() functions) is often presented as a better option than loop. Now in that case, it simply cannot do the job, because it does not change the object in the global environment.

b = c(1,0,0,1,0,0,0,0)

map(
  .x = seq(1,(length(b))),
  .f = function(x) {
    b[x] <- ifelse(b[x]==1, b[x], b[x-1] 1)})
b
[1] 1 0 0 1 0 0 0 0

How can use map() here?

CodePudding user response:

Without loops or map:

cs <- cumsum(a)
seq_along(cs) - match(cs, cs)   1
## [1] 1 2 3 1 2 3 4 5

or

ave(a, cumsum(a), FUN = seq_along)
## [1] 1 2 3 1 2 3 4 5

Reduce also works:

Reduce(\(x, y) if (y) 1 else x 1, a, acc = TRUE)
## [1] 1 2 3 1 2 3 4 5

or

Reduce(\(x, y) y   (x   1) * !y, a, acc = TRUE)
## [1] 1 2 3 1 2 3 4 5

Benchmark

library(purrr)
library(microbenchmark)

a <- c(1, 0, 0, 1, 0, 0, 0, 0)

microbenchmark(
  A = { cs <- cumsum(a); seq_along(cs) - match(cs, cs)   1},
  B = ave(a, cumsum(a), FUN = seq_along),
  C = Reduce(\(x, y) if (y) 1 else x 1, a, acc = TRUE),
  D = Reduce(\(x, y) y   (x   1) * !y, a, acc = TRUE),
  E = { b <- a; walk(
  .x = seq(1,(length(b))),
  .f = function(x) {
    b[x] <<- ifelse(b[x]==1, b[x], b[x-1] 1)})
  },
  F = { b <- a;
    map(
     .x = seq(1,(length(b))),
     .f = function(x) {
      b[x] <<- ifelse(b[x]==1, b[x], b[x-1] 1)})
  }
)

giving:

Unit: microseconds
 expr   min     lq     mean  median      uq      max neval cld
    A   6.3  10.65   15.810   13.80   19.80     57.9   100  a 
    B 305.3 326.40  397.376  396.35  406.05    977.0   100  a 
    C  41.4  48.25  126.469   57.45   71.70   6597.0   100  a 
    D  49.5  58.45  137.924   67.40   80.85   6452.5   100  a 
    E 133.2 183.15 4796.402 4397.95 8720.40  35341.7   100   b
    F 131.8 174.10 6070.244 4385.25 8756.20 139827.0   100   b

Note

a <- c(1, 0, 0, 1, 0, 0, 0, 0)

CodePudding user response:

Using purrr::walk:

library(purrr)

b = c(1,0,0,1,0,0,0,0)

walk(
  .x = seq(1,(length(b))),
  .f = function(x) {
    b[x] <<- ifelse(b[x]==1, b[x], b[x-1] 1)})
b

#> [1] 1 2 3 1 2 3 4 5

Or even using purrr:map:

library(purrr)

b = c(1,0,0,1,0,0,0,0)

map(
  .x = seq(1,(length(b))),
  .f = function(x) {
    b[x] <<- ifelse(b[x]==1, b[x], b[x-1] 1)})

#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3
#> 
#> [[4]]
#> [1] 1
#> 
#> [[5]]
#> [1] 2
#> 
#> [[6]]
#> [1] 3
#> 
#> [[7]]
#> [1] 4
#> 
#> [[8]]
#> [1] 5

b

#> [1] 1 2 3 1 2 3 4 5
  • Related