I'm trying to match values in a numeric vector to a discrete set of values, but due to rounding errors they're not necessarily an exact match. Consider the following example, converting spherical coordinates to cartesian, and trying to match the resulting distance against the original r values,
phithetar <- expand.grid(theta=seq(0,pi,length=10),
phi = seq(0, 2*pi, length=10),
r = seq(1,10))
xyz <- transform(phithetar, x = r*sin(theta)*cos(phi),
y = r*sin(theta)*sin(phi),
z = r*cos(theta))
xyz <- transform(xyz, newr = sqrt(x^2+y^2+z^2))
with(xyz, sum(newr != r))
This is non-zero, as the operations have results in lost precision.
Here's my slow implementation to match values within a given tolerance; the iteration over x makes it slow. An alternative would be to expand all (x,y) combinations but that'd be memory-heavy for large vectors.
match_closest <- function(x, y, tol=1e-5){
match_one <- function(.x) {
difs <- abs(.x - y)
best <- which.min(difs)
if(difs[best] > tol) message(.x, " ain't so close to ", y[best])
y[best]
}
vapply(x, match_one, 0)
}
rvalues <- unique(phithetar$r)
xyz <- transform(xyz, matchedr = match_closest(newr, rvalues))
with(xyz, sum(matchedr != r))
I tried another idea with cut(),
match_closenough <- function(x, y, tol=1e-5){
levs <- c(y-0.5*tol, y+0.5*tol)
f <- cut(x, breaks=levs, include.lowest = TRUE)
y[f]
}
but it doesn't quite work. Similarly, this rounding trick
match_closenough <- function(x, y, tol=1e-3){
dig <- -log10(tol)
y[match(round(x, dig), round(y, dig), nomatch = NA)]
}
kind of works and is faster, but feels too fragile.