Zachary
Zachary

Reputation: 381

Indexing position of 1s in matrix using R apply

Given a 0/1 matrix, I am trying to generate a list of the index positions of the 1s in each row. I can do this using a for loop:

B <- matrix(sample(c(0,1),100,replace=T),nrow=10,ncol=10)
ones <- list()
for (row in 1:nrow(B)) {ones[[row]] <- (which(B[row,]==1))}

The resulting object ones is what I want. I assume using apply() will be slightly faster, but I'm having trouble translating the loop into apply. This doesn't yield the same result:

alt <- apply(B, 1, function(x) which(B[x,]==1))

I'd appreciate any advice on how to correct apply so it yields the same result as the loop approach. Thanks!

UPDATE -

I accepted @TarJae’s answer because it works, only requires one line, and is fast. @Ronak’s approach also works, but is slower than either of the other options. Here’s a comparison of the three methods:

library(rbenchmark)
B <- matrix(sample(c(0,1),1000000,replace=T),nrow=1000,ncol=1000)
benchmark(
  "loop" = {
    ones_loop <- list()
    for (row in 1:nrow(B)) {ones_loop[[row]] <- (which(B[row,]==1))}
  },
  "apply" = {
    ones_apply <- apply(B==1,1, which)
  },
  "split" = {
    ones_split <- which(B == 1, arr.ind = TRUE)
    ones_split <- split(ones_split[, 2], ones_split[, 1])
  },
  replications = 500
)
   test replications elapsed relative user.self sys.self user.child sys.child
2 apply          500  17.211    1.000    15.068    2.111          0         0
1  loop          500  19.466    1.131    16.778    2.349          0         0
3 split          500  40.291    2.341    33.757    6.150          0         0

Upvotes: 2

Views: 235

Answers (3)

akrun
akrun

Reputation: 887193

We could use asplit

lapply(asplit(B == 1, 1), which)

-output

[[1]]
[1]  1 10

[[2]]
[1]  3  8  9 10

[[3]]
[1] 1 3 6 7 8 9

[[4]]
[1] 2 5 6 7 9

[[5]]
[1]  1  2  4  5  7 10

[[6]]
[1]  2  8 10

[[7]]
[1] 1 2 6 7 8

[[8]]
[1] 3 7 9

[[9]]
[1]  1  2  4  5  6  7  9 10

[[10]]
[1] 1 4 6 7

Upvotes: 1

Ronak Shah
Ronak Shah

Reputation: 388992

which(matrix, arr.ind = TRUE) as suggested by @Henrik provides you all the necessary information needed. If you need to have a list output as the for loop you can use split.

set.seed(123)
B <- matrix(sample(c(0,1),100,replace=T),nrow=10,ncol=10)
mat <- which(B == 1, arr.ind = TRUE)
split(mat[, 2], mat[, 1])

#`1`
#[1]  2  6  8  9 10

#$`2`
#[1]  2  3  4  5  7  9 10

#$`3`
#[1] 2 5 7

#$`4`
#[1]  1  4  9 10

#$`5`
#[1]  2  4  9 10

#$`6`
#[1] 1 6 7 8 9

#$`7`
#[1] 1 2 3 6 8

#$`8`
#[1]  1  3  5 10

#$`9`
#[1] 6 8 9

#$`10`
#[1] 3 4 8

Upvotes: 1

TarJae
TarJae

Reputation: 78937

You could use apply this was way: Gives the same result as the loop:

apply(B==1,1, which)
> apply(B==1,1, which)
[[1]]
[1]  4  6  7  8 10

[[2]]
[1]  1  4  5 10

[[3]]
[1] 1 2 3 4 5 7 9

[[4]]
[1] 2 3 4 5 9

[[5]]
[1]  2  4  8 10

[[6]]
[1] 4 5 7 8

[[7]]
[1]  1  2  5  8  9 10

[[8]]
[1]  1  3  4  7  8  9 10

[[9]]
[1]  1  2  4  5  6  7 10

[[10]]
[1]  1  3  4  6  7 10

Upvotes: 3

Related Questions