Here are 4 options using data.table:
First, set some keys for Intervals and DT for options 1 - 3:
setDT(Intervals, key=c("start","end"))
DT <- as.data.table(x, key="value")
1) using non-equi join and by=.EACHI:
DT[, catg1 := Intervals[DT, on=.(start<=value, end>=value), by=.EACHI, catg]$catg]
2) using 2 rolling joins to find catg based on start or end and is within interval only when both are the same
DT[, catg2 := {
s <- Intervals[DT, on=.(start=value), roll=Inf, mult="first", catg]
e <- Intervals[DT, on=.(end=value), roll=-Inf, mult="last", catg]
fifelse(s==e, s, NA_character_)
}]
3) Also rolling joins but flipping the left and right tables as one is shorter than the other and might be faster:
Intervals[, sr := DT[.SD, on=.(value=start), roll=-Inf, mult="first", which=TRUE]]
Intervals[, er := DT[.SD, on=.(value=end), roll=Inf, mult="last", which=TRUE]]
ix <- Intervals[, .(lst=seq(er ,sr), values=rep(catg, er - sr + 1L)), 1L:nrow(Intervals)]
DT[, catg3 := replace(rep(NA_character_, .N), ix$lst, ix$values)]
4) using foverlaps:
DT2 <- setkey(as.data.table(x)[, c("start", "end") := .(value, value)], start, end)
DT[, catg4 := foverlaps(DT2, Intervals)$catg] #using DT and not DT2 for checking correctness
head of output:
unit value catg catg2 catg3 catg4
1: 8 1 <NA> <NA> <NA> <NA>
2: 82 1 <NA> <NA> <NA> <NA>
3: 100 1 <NA> <NA> <NA> <NA>
4: 49 2 <NA> <NA> <NA> <NA>
5: 1 3 F F F F
6: 2 3 F F F F
7: 3 4 F F F F
8: 4 4 F F F F
9: 45 6 R R R R
10: 18 7 R R R R
11: 81 10 O O O O
12: 59 13 O O O O
13: 65 13 O O O O
data:
library(data.table)
set.seed(0L)
Intervals <- data.frame(start = c( 3, 6, 10, 58, 78), end = c(4, 8, 46, 68, 84 ),
catg = c("F", "R", "O", "S", "X"))
x = data.frame( unit = c(1:100), value = c(3,3,4,4,sample(1:84, 96, replace = TRUE)))
Would be interested to know the performance of each option.