Reputation: 99321
I need some help finding the values in a vector that are between key values, non-inclusive.
For example, with the following vectors x
and y
x <- c(2, 6, 10)
y <- c(7, 1, 9, 12, 4, 6, 3)
I'd like to find all the values in y
that are between x
but not equal to x
, so that the result would be
list(y[y > 2 & y < 6], y[y > 6 & y < 10])
# [[1]]
# [1] 4 3
#
# [[2]]
# [1] 7 9
So in the above result,
I've been working on this for a little while now and I'm stumped. I'd show you the code but it's just plain ugly.
How can I quickly find the values in one vector what are between the values in another vector?
Upvotes: 3
Views: 219
Reputation: 24535
Try:
z =list()
for(j in 1:(length(x)-1)) {
v=NULL
for(i in 1:length(y))
if(y[i]>x[j] && y[i]<x[j+1])
v[length(v)+1]=y[i]
z[[length(z)+1]] = v
}
z
[[1]]
[1] 4 3
[[2]]
[1] 7 9
Upvotes: 0
Reputation: 193497
Maybe this will work for you:
lapply(split(y[y > min(x) & y < max(x)],
findInterval(y[y > min(x) & y < max(x)], x)),
function(z) z[!z %in% x])
# $`1`
# [1] 4 3
#
# $`2`
# [1] 7 9
Of course, it might be better to keep it DRY and subset "y" before splitting, for example, by using between
(or %between%
) from "data.table":
library(data.table)
Z <- y[y %between% range(x) & !y %in% x]
split(Z, findInterval(Z, x))
# $`1`
# [1] 4 3
#
# $`2`
# [1] 7 9
For reference, all three options so far are pretty fast:
set.seed(1)
x <- sort(sample(100000, 20, FALSE))
y <- sample(100000, 100000, TRUE)
AM <- function(x, y) {
Z <- y[y %between% range(x) & !y %in% x]
split(Z, findInterval(Z, x))
}
DA <- function(x, y) {
indx <- Map(function(x, z) x + seq_len(z), x[-length(x)], diff(x) - 1)
lapply(indx, function(x) y[y %in% x])
}
user <- function(x, y) {
m <- t(diff(sign(outer(x, y, "-"))) == 2)
split((m*y)[m], col(m)[m])
}
library(microbenchmark)
microbenchmark(AM(x, y), DA(x, y), user(x, y))
# Unit: milliseconds
# expr min lq mean median uq max neval
# AM(x, y) 22.58939 23.24731 26.29092 23.79639 25.64548 140.5610 100
# DA(x, y) 149.46997 157.48534 162.47526 160.01823 164.74851 287.0808 100
# user(x, y) 327.38835 437.44064 445.71955 446.65938 467.97784 637.3121 100
Upvotes: 7
Reputation: 25844
Another variation. I think you could use outer
and sign
; going across the columns if there is a change from 1 to -1 then the y
value is within the x
range (that is, where consecutive columns sum to zero). The loop to extract the values is a bit messy though.
EDIT @flodel offered a nice alternative in the comments
m <- t(diff(sign(outer(x, y, "-"))) == 2)
split((m*y)[m], col(m)[m])
Original
(o <- sign(outer(y, x, "-")))
# [,1] [,2] [,3]
# [1,] 1 1 -1
# [2,] -1 -1 -1
# [3,] 1 1 -1
# [4,] 1 1 1
# [5,] 1 -1 -1
# [6,] 1 0 -1
# [7,] 1 -1 -1
lapply(1:(length(x)-1), function(i) y[o[,i] + o[,i+1]==0])
# [[1]]
# [1] 4 3
#
# [[2]]
# [1] 7 9
Upvotes: 4
Reputation: 92282
Here's another approach
indx <- Map(function(x, z) x + seq_len(z), x[-length(x)], diff(x) - 1)
lapply(indx, function(x) y[y %in% x])
# [[1]]
# [1] 4 3
#
# [[2]]
# [1] 7 9
Upvotes: 5