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