简体   繁体   中英

Find elements in vector in R

A matrix I have has exactly 2 rows and n columns example

c(0,0,0,0,1,0,2,0,1,0,1,1,1,0,2)->a1
c(0,2,0,0,0,0,2,1,1,0,0,0,0,2,0)->a2
rbind(a1,a2)->matr

for a specific column ( in this example 9 with 1 in both rows) I do need to find to the left and to the right the first instance of 2/0 or 0/2 - in this example to the left is 2 and the other is 14)

The elements of every row can either be 0,1,2 - nothing else . Is there a way to do that operation on large matrixes (with 2 rows) fast? I need to to it 600k times so speed might be a consideration

library(compiler)
myfun <- cmpfun(function(m, cl) {
  li <- ri <- cl
  nc <- ncol(m)
  repeat {
    li <- li - 1
    if(li == 0 || ((m[1, li] != 1) && (m[1, li] + m[2, li] == 2))) {
      l <- li
      break
    }
  }
  repeat {
    ri <- ri + 1
    if(ri == nc || ((m[1, ri] != 1) && (m[1, ri] + m[2, ri] == 2))) {
      r <- ri
      break
    }
  }
  c(l, r)
})

and, after taking into account @Martin Morgan's observations,

set.seed(1)
N <- 1000000
test <- rbind(sample(0:2, N, replace = TRUE),
              sample(0:2, N, replace = TRUE))

library(microbenchmark)
microbenchmark(myfun(test, N / 2), fun(test, N / 2), foo(test, N / 2),
               AWebb(test, N / 2), RHertel(test, N / 2))
# Unit: microseconds
               expr         min          lq         mean      median          uq         max neval  cld
#    myfun(test, N/2)       4.658      20.033 2.237153e+01      22.536      26.022      85.567   100 a   
#      fun(test, N/2)   36685.750   47842.185 9.762663e+04   65571.546  120321.921  365958.316   100  b  
#      foo(test, N/2) 2622845.039 3009735.216 3.244457e+06 3185893.218 3369894.754 5170015.109   100    d
#    AWebb(test, N/2)  121504.084  142926.590 1.990204e+05  193864.670  209918.770  489765.471   100   c 
#  RHertel(test, N/2)   65998.733   76805.465 1.187384e+05   86089.980  144793.416  385880.056   100  b  

set.seed(123)
test <- rbind(sample(0:2, N, replace = TRUE, prob = c(5, 90, 5)),
              sample(0:2, N, replace = TRUE, prob = c(5, 90, 5)))
microbenchmark(myfun(test, N / 2), fun(test, N / 2), foo(test, N / 2),
               AWebb(test, N / 2), RHertel(test, N / 2))
# Unit: microseconds
#                expr         min          lq         mean      median         uq         max neval  cld
#    myfun(test, N/2)      81.805     103.732     121.9619     106.459     122.36     307.736   100 a   
#      fun(test, N/2)   26362.845   34553.968   83582.9801   42325.755  106303.84  403212.369   100  b  
#      foo(test, N/2) 2598806.742 2952221.561 3244907.3385 3188498.072 3505774.31 4382981.304   100    d
#    AWebb(test, N/2)  109446.866  125243.095  199204.1013  176207.024  242577.02  653299.857   100   c 
#  RHertel(test, N/2)   56045.309   67566.762  125066.9207   79042.886  143996.71  632227.710   100  b  

Combine the information by squaring the rows and adding them. The right result should be 4 . Then, simply find the first column that is smaller than 9 ( rev(which())[1] ) and the first column that is larger than 9 ( which()[1] ).

fun <- function(matr, col){
    valid <- which((matr[1,]^2 + matr[2,]^2) == 4)
    if (length(valid) == 0) return(c(NA,NA))

    left <- valid[rev(which(valid < col))[1]]
    right <- valid[which(valid > col)[1]]

    c(left,right)

    }

fun(matr,9)
# [1]  2 14

fun(matr,1)
# [1] NA  2

fun(matrix(0,nrow=2,ncol=100),9)
# [1] NA NA

Benchmark

set.seed(1)
test <- rbind(sample(0:2,1000000,replace=T),
              sample(0:2,1000000,replace=T))

microbenchmark::microbenchmark(fun(test,9))
# Unit: milliseconds
#         expr     min       lq     mean   median       uq      max neval
# fun(test, 9) 22.7297 27.21038 30.91314 27.55106 28.08437 51.92393   100

Edit: Thanks to @MatthewLundberg for pointing out a lot of mistakes.

I was slower than @Laterow, but anyhow, this is a similar approach

foo  <- function(mtr, targetcol) {
  matr1  <-  colSums(mtr)
  matr2  <- apply(mtr, 2, function(x) x[1]*x[2])
  cols  <-  which(matr1 == 2 & matr2 == 0) - targetcol
  left  <-   cols[cols < 0]
  right  <-  cols[cols > 0]
  c(ifelse(length(left) == 0, NA, targetcol + max(left)),
    ifelse(length(right) == 0, NA, targetcol + min(right)))
}

foo(matr,9) #2 14

That is an interesting question. Here's how I would address it.

First a vector is defined which contains the product of each column:

a3 <- matr[1,]*matr[2,]

Then we can find the columns with pairs of (0/2) or (2/0) rather easily, since we know that the matrix can only contain the values 0, 1, and 2:

the02s <- which(colSums(matr)==2 & a3==0)

Next we want to find the pairs of (0/2) or (2/0) that are closest to a given column number, on the left and on the right of that column. The column number could be 9, for instance:

thecol <- 9

Now we have basically all we need to find the index (the column number in the matrix) of a combination of (0/2) or (2/0) that is closest to the column thecol . We just need to use the output of findInterval() :

pos <- findInterval(thecol,the02s)
pos <- c(pos, pos+1)
pos[pos==0] <- NA # output NA if no column was found on the left

And the result is:

the02s[pos]
#  2 14

So the indices of the closest columns on either side of thecol fulfilling the required condition would be 2 and 14 in this case, and we can confirm that these column numbers both contain one of the relevant combinations:

matr[,14]
#a1 a2 
# 0  2
matr[,2]
#a1 a2 
# 0  2 

Edit: I changed the answer such that NA is returned in the case where no column exists on the left and/or on the right of thecol in the matrix that fulfills the required condition.

If you are doing this many times, precompute all the locations

loc <- which((a1==2 & a2==0) | (a1==0 & a2==2))

You can then find the first to the left and right with findInterval

i<-findInterval(9,loc);loc[c(i,i+1)]
# [1]  2 14

Note that findInterval is vectorized should you care to specify multiple target columns.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM