Q:

Getting counts of overlapping dataframe values without a for loop in R

I have two dataframes, one called segments containing numeric "start" and "stop" values
segments <- as.data.frame(
  cbind(
    rep(seq(1, 22, 1), 2),
    seq(500000, 3000000, 57000),
    seq(1000000, 3500000, 57000)
  )
)
colnames(segments) <- c("chr", "segment.start", "segment.end")

And another, called positions which contains numeric values. 
positions <- as.data.frame(cbind(1, seq(750000, 2000000, 56000)))
colnames(positions) <- c("chr", "pos")

I am interested in counting the number of rows in segments where the area between the "start" and "stop" values overlap each of the values in positions and adding those counts to a new column of positions.
positions$count <- 0

I can get these counts using the following for loop, but on large datasets this is very slow.
for (n in 1:nrow(segments)) {
  segment <- segments[n, ]
  to.update <- which(
    positions$pos >= segment$segment.start &
      positions$pos <= segment$segment.end & 
      positions$chr == segment$chr
    )
  positions[to.update, "count"] <- positions[to.update, "count"] + 1
}

Does anyone know how to get these counts without a for loop?

A:

Without verification, I think this can be done well with data.table. I'm sure it can be done with other tools (base or tidyverse), but this was fast and uses a tool I've recently been working a lot with: foverlaps.
library(data.table)
setDT(segments)
setDT(positions)
positions[, pos2 := pos ]

setkey(segments, segment.start, segment.end)
setkey(positions, pos, pos2)

As a point of explanation, foverlaps requires that both frames have two fields, with the premise of the function being an overlap in the range of one frame with the overlap of the other frame. As much as one might argue that having a single column option in this overlap-check might be useful, the addition of a second column (pos2 in this case) is trivial and enables exactly the same functionality without changing data.table code.
Edit: updated to include "by chr" logic.
Edit 2: inverted, resulting in positions as the primary:
foverlaps(positions, segments) [
    , .(count = sum(!is.na(segment.start))), by = .(chr, pos, pos2) ][
    , pos2 := NULL ]
#     chr     pos count
#  1:   1  750000     1
#  2:   1  806000     1
#  3:   1  862000     1
#  4:   1  918000     1
#  5:   1  974000     1
#  6:   1 1030000     0
#  7:   1 1086000     0
#  8:   1 1142000     0
#  9:   1 1198000     0
# 10:   1 1254000     0
# 11:   1 1310000     0
# 12:   1 1366000     0
# 13:   1 1422000     0
# 14:   1 1478000     0
# 15:   1 1534000     0
# 16:   1 1590000     0
# 17:   1 1646000     0
# 18:   1 1702000     0
# 19:   1 1758000     1
# 20:   1 1814000     1
# 21:   1 1870000     1
# 22:   1 1926000     1
# 23:   1 1982000     1
#     chr     pos count

