Filtering numeric vectors by proximity to any elements of another vector in R I frequently want to filter and select elements from a long (10,000s to millions of elements) vector in R.  Often, this vector is a column in a data.frame.  I am usually interested in rows of the vector that "match" any one of a separate list of 10 to 100 query values.  However, since these are experimental data (mass spectrometry data for anyone interested), the target values and the query value(s) do not need to match exactly.  They only need to match within a tolerance.

In the field I work in (mass spectrometry), the values are mass-to-charge ratios, or m/z values.  There are two systems for specifying the tolerance or precision desired for comparing two numbers.  The first is the ppm or "parts per million" tolerance, i.e., a relative measure of precision.  The second is in absolute values, which invariably have units of atomic mass units or Daltons.  This explains the variable names I use below.

filter_by_mz &lt;- function(query_mzs, target_mzs, ppm = 10, mz_tol = NULL){
    # ensure that users only supply one mass tolerance argument (either ppm or mz_tol)
    if(!missing(ppm) &amp; !missing(mz_tol)){
        stop("Only a single mz tolerance parameter {mz_tol or ppm} can be supplied, not both.")
    }

    # find min and max mzs
    if(missing(mz_tol)){
        min_mzs &lt;- target_mzs * (1 - ppm / 1e6)
        max_mzs &lt;- target_mzs * (1 + ppm / 1e6)
    } else{
        min_mzs &lt;- target_mzs - mz_tol
        max_mzs &lt;- target_mzs + mz_tol
    }

    # allocate boolean matrix
    n_rows &lt;- length(query_mzs)
    n_cols &lt;- length(target_mzs)
    bool_mat &lt;- matrix(FALSE, nrow = n_rows, ncol = n_cols)

    # loop over target mzs to create columns of bool_mat
    for(idx in seq_along(target_mzs)){
        min_mz &lt;- min_mzs[idx]
        max_mz &lt;- max_mzs[idx]
        bool_mat[, idx] &lt;- query_mzs &gt;= min_mz &amp; query_mzs &lt;= max_mz
    }

    # apply any() to rows of matrix
    return(apply(bool_mat, FUN = any, MARGIN = 1))
}


Now my questions:


How can I get rid of the for loop?  It seems like a smart way of using apply or a similar function would obviate this loop.
Is the argument handling OK?  I wanted users of the function to be able supply either ppm or mz_tol, but not both.  Am I using missing() correctly?
Is the design correct?  As I mentioned, I frequently will invoke this function on the columns of a data.frame.  For example, I'd frequently need to do my_filtered_df &lt;- filter_by_mz(my_df$mz, target_mz, ...).  Would it be better to write the function to work on data.frames rather than just vectors?
I do quite similar stuff.   I find your approach quite lengthy.  Especially the construction of the boolean matrix might be a bottleneck. 

First I would consider to write a second function - say filter_by_mz_ which just does the filtering and have filter_by_mz just take care of the input and call the new function - just to keep things modular.

In your algorithm you basically compare all masses pairwise.  A simpler approach might be to use expand.grid(q=query_mzs, t=target_mzs) this will calculate all pairs.  This list can be then easy checked (let's call it gr) using abs(gr$t-gr$q) &lt; ppm * gr$t - that would be fully vectorized.

A bool vector can usually be easy constructed using %in%.

Just FYI  I find it much easier to write with dplyr - especially since it is possible to keep much additional info in the table.  I often have the constraint that one mz can only be assigned once - extra data is often required to make such decisions.

Typically it looks like this:

Just the setup and some test data:

library(dplyr)

peaks &lt;- data.frame(mz=c(21.01,28.00,30,30.1,35, 21.02), val=c(1,2,3,4,5,6))
info &lt;- data.frame(mz=c(21.008, 28.003, 30.002, 32.02), str=c('wat', 'n2', 'aa' , 'bb'))


The magic happens here:

keyfun &lt;- function(x) sprintf('%.1f',x)
peaks &lt;- mutate(peaks, key=keyfun(mz))
info &lt;- mutate(info, key=keyfun(mz))

inner_join(peaks, info, by='key') %&gt;% filter( abs(mz.x - mz.y) &lt; 0.0005 * mz.x)


Basically I create an temporary key that is too wide - just that I only get to work on combinations that could be.  The inner_join will create all the permutations.  And in a second step I filter out the ones that actually match.

Of course usually I give more informative names and spend some time labelling stuff.  But this should suffice to give an idea of the method.

addition

It came to me that you could avoid the matrix if you could rewrite such that inside the loop you check for all query_mz instead of target_mz - then you could also put the any inside the loop.  I suppose it would come down to just switching the variable names in a number of lines. 
After this transformation the for loop should be replacable by an sapply.

As R stores logicals quite expensive with 4 bytes per logical. That shoud give you extensive savings. (For me these numbers would go into the 100s so I would have >99% less memory usage)