A tiny bit of statistics is needed here, if only to point out the need to control the bandwidth and study the sensitivity of the solutions to the bandwidth.
Provided a solution is bracketed closely by data points, it will tend to be stable even when the bandwidth is varied substantially. Here is an example involving datasets with 23 points (black density) and 14 points (light blue).

Red vertical lines mark the solutions. The data are shown as rug plots at the bottom. The default bandwidth for these data will be around $1/2,$ as shown in the middle panel
You can see from this example how one solution (the right hand one in the right panel) persists across all bandwidths. Another solution (the left hand one in the right panel) varies appreciably because data are scarce in its neighborhood. Spurious solutions pop up when using a relatively small bandwidth (left panel).
These examples were created by this R
code.
set.seed(17)
x <- rnorm(23)
y <- rnorm(14, 2, 3/2)
bw <- 0.25 # or 0.5, or 1.5, or even "SP", etc: see the help page for `density`
obj <- intersect(x, y, kernel = "gaussian", n = 512, bw = bw, from = -4, to = 8)
All kernel densities produce a discrete grid of density estimates. The solution implemented by intersect
allows you to exploit the default methods of finding endpoints, bandwidths, etc by first computing a density for the combined data. Those defaults are then used to recompute the densities for the data separately. Because both densities are computed on the same grid, it's a simple matter to locate the places where they cross and interpolate linearly on the grid. Linear interpolation is more than precise enough, because it errs less than the mesh of the grid, which presumably is already small enough for your purposes.
#
# Find all points where density $g$ exceeds density $f.$
#
intersect <- function(x, y, bw = "nrd0", from, to, ...) {
#
# Compute a density for all points combined.
#
largs <- list(x = c(x,y), bw = bw)
if (!missing(from)) largs <- c(largs, from = from)
if (!missing(to)) largs <- c(largs, to = to)
largs <- c(largs, list(...))
obj <- do.call(density, largs) # Compute a common density
#
# Compute densities for the datasets separately.
#
x.0 <- obj$x
f.x <- density(x, bw = obj$bw, from = min(x.0), to = max(x.0), ...)
f.y <- density(y, bw = obj$bw, from = min(x.0), to = max(x.0), ...)
#
# Find the crossings.
#
d <- zapsmall(f.y$y - f.x$y)
abscissae <- sapply(which(d[-1] * d[-length(d)] < 0), function(i) {
w <- d[i+1] - d[i]
if (w > 0) (d[i+1] * x.0[i] - d[i] * x.0[i+1]) / w else (x.0[i] + x.0[i+1]) / 2
})
list(Points = abscissae, xlim = range(x.0), f = f.x, g = f.y)
}
density()
function of R $\endgroup$from
andto
options ofdensity
and linearly interpolate the locations where the differences in density change sign. $\endgroup$d
and locate indexesi
whered[i] * d[i+1] <= 0.
Linear interpolation between the abscissaex[i]
andx[i+1]
does the trick. You should always find at least two intersections. $\endgroup$