Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generalize R %in% operator to match tuples

I spent a while the other day looking for a way to check if a row vector is contained in some set of row vectors in R. Basically, I want to generalize the %in% operator to match a tuple instead of each entry in a vector. For example, I want:

row.vec = c("A", 3)
row.vec
# [1] "A" "3"

data.set = rbind(c("A",1),c("B",3),c("C",2))
data.set
#      [,1] [,2]
# [1,] "A"  "1" 
# [2,] "B"  "3" 
# [3,] "C"  "2" 

row.vec %tuple.in% data.set
# [1] FALSE

for my made-up operator %tuple.in% because the row vector c("A",3) is not a row vector in data.set. Using the %in% operator gives:

row.vec %in% data.set
# [1] TRUE TRUE

because "A" and 3 are in data.set, which is not what I want.

I have two questions. First, are there any good existing solutions to this?

Second, since I couldn't find them (even if they exist), I tried to write my own function to do it. It works for an input matrix of row vectors, but I'm wondering if any experts have proposed improvements:

is.tuple.in <- function(matrix1, matrix2){

     # Apply rbind() so that matrix1 has columns even if it is a row vector.
     matrix1 = rbind(matrix1)

     if(ncol(matrix1) != ncol(matrix2)){ 
      stop("Matrices must have the same number of columns.") }

     # Now check for the first row and handle other rows recursively
     row.vec = matrix1[1,]
     tuple.found = FALSE
     for(i in 1:nrow(matrix2)){
          # If we find a match, then this row exists in matrix 2 and we can break the loop
          if(all(row.vec == matrix2[i,])){
               tuple.found = TRUE
               break
          }
     }

     # If there are more rows to be checked, use a recursive call
     if(nrow(matrix1) > 1){
          return(c(tuple.found, is.tuple.in(matrix1[2:nrow(matrix1),],matrix2)))
     } else {
          return(tuple.found)
     }
}

I see a couple problems with that that I'm not sure how to fix. First, I'd like the base case to be clear at the start of the function. I didn't manage to do this because I pass matrix1[2:nrow(matrix1),] in the recursive call, which produces an error if matrix1 has one row. So instead of getting to a case where matrix1 is empty, I have an if condition at the end deciding if more iterations are necessary.

Second, I think the use of rbind() at the start is sloppy, but I needed it for when matrix1 had been reduced to a single row. Without using rbind(), ncol(matrix1) produced an error in the 1-row case. I figure my trouble here has to do with a lack of knowledge about R data types.

Any help would be appreciated.

like image 969
ASV Avatar asked Mar 14 '14 19:03

ASV


1 Answers

I'm wondering if you have made this a bit more complicated than it is. For example,

set.seed(1618)
vec <- c(1,3)
mat <- matrix(rpois(1000,3), ncol = 2)
rownames(mat) <- 1:nrow(mat)


mat[sapply(1:nrow(mat), function(x) all(vec %in% mat[x, ])), ]

# gives me this
#     [,1] [,2]
# 6      3    1
# 38     3    1
# 39     3    1
# 85     1    3
# 88     1    3
# 89     1    3
# 95     3    1
# 113    1    3
# ...

you could subset this further if you care about the order or you could modify the function slightly:

mat[sapply(1:nrow(mat), function(x) 
  all(paste(vec, collapse = '') %in% paste(mat[x, ], collapse = ''))), ]

#      [,1] [,2]
# 85     1    3
# 88     1    3
# 89     1    3
# 113    1    3
# 133    1    3
# 139    1    3
# 187    1    3
# ...

another example with a longer vector

set.seed(1618)
vec <- c(1,4,5,2)
mat <- matrix(rpois(10000, 3), ncol = 4)
rownames(mat) <- 1:nrow(mat)

mat[sapply(1:nrow(mat), function(x) all(vec %in% mat[x, ])), ]

#      [,1] [,2] [,3] [,4]
# 57      2    5    1    4
# 147     1    5    2    4
# 279     1    2    5    4
# 303     1    5    2    4
# 437     1    5    4    2
# 443     1    4    5    2
# 580     5    4    2    1
# ...

I see a couple that match:

mat[sapply(1:nrow(mat), function(x) 
  all(paste(vec, collapse = '') %in% paste(mat[x, ], collapse = ''))), ]

#      [,1] [,2] [,3] [,4]
# 443     1    4    5    2
# 901     1    4    5    2
# 1047    1    4    5    2

but only three

for your single row case:

vec <- c(1,4,5,2)
mat <- matrix(c(1,4,5,2), ncol = 4)
rownames(mat) <- 1:nrow(mat)

mat[sapply(1:nrow(mat), function(x) 
  all(paste(vec, collapse = '') %in% paste(mat[x, ], collapse = ''))), ]

# [1] 1 4 5 2

here is a simple function with the above code

is.tuplein <- function(vec, mat, exact = TRUE) {  
  rownames(mat) <- 1:nrow(mat)
  if (exact) 
    tmp <- mat[sapply(1:nrow(mat), function(x) 
      all(paste(vec, collapse = '') %in% paste(mat[x, ], collapse = ''))), ]
  else tmp <- mat[sapply(1:nrow(mat), function(x) all(vec %in% mat[x, ])), ]
  return(tmp)
}

is.tuplein(vec = vec, mat = mat)
# [1] 1 4 5 2

seems to work, so let's make our own %in% operator:

`%tuple%` <- function(x, y) is.tuplein(vec = x, mat = y, exact = TRUE)
`%tuple1%` <- function(x, y) is.tuplein(vec = x, mat = y, exact = FALSE)

and try her out

set.seed(1618)
c(1,2,3) %tuple% matrix(rpois(1002,3), ncol = 3)

#     [,1] [,2] [,3]
# 133    1    2    3
# 190    1    2    3
# 321    1    2    3

set.seed(1618)
c(1,2,3) %tuple1% matrix(rpois(1002,3), ncol = 3)

#     [,1] [,2] [,3]
# 48     2    3    1
# 64     2    3    1
# 71     1    3    2
# 73     3    1    2
# 108    3    1    2
# 112    1    3    2
# 133    1    2    3
# 166    2    1    3
like image 87
rawr Avatar answered Sep 28 '22 07:09

rawr