¿Cómo encontrar los tres valores más cercanos(más cercanos) dentro de un vector?




(3)

Me gustaría encontrar los tres números más cercanos en un vector. Algo como

v = c(10,23,25,26,38,50)
c = findClosest(v,3)
c
23 25 26

Intenté con sort (colSums (as.matrix (dist (x)))) [1: 3], y funciona, pero selecciona los tres números con una distancia total mínima, no los tres números más cercanos.

Ya hay una respuesta para matlab, pero no sé cómo traducirla a R:

%finds the index with the minimal difference in A
minDiffInd = find(abs(diff(A))==min(abs(diff(A))));
%extract this index, and it's neighbor index from A
val1 = A(minDiffInd);
val2 = A(minDiffInd+1);

¿Cómo encontrar dos valores más cercanos (más cercanos) dentro de un vector en MATLAB?


Definamos "números más cercanos" por "números con una suma mínima de distancias L1". Puede lograr lo que desea mediante una combinación de diff y suma de ventanas.

Podrías escribir una función mucho más corta, pero la escribí paso a paso para que sea más fácil de seguir.

v <- c(10,23,25,26,38,50)

#' Find the n nearest numbers in a vector
#'
#' @param v Numeric vector
#' @param n Number of nearest numbers to extract
#'
#' @details "Nearest numbers" defined as the numbers which minimise the
#'   within-group sum of L1 distances.
#'   
findClosest <- function(v, n) {
  # Sort and remove NA
  v <- sort(v, na.last = NA)

  # Compute L1 distances between closest points. We know each point is next to
  # its closest neighbour since we sorted.
  delta <- diff(v)

  # Compute sum of L1 distances on a rolling window with n - 1 elements
  # Why n-1 ? Because we are looking at deltas and 2 deltas ~ 3 elements.
  withingroup_distances <- zoo::rollsum(delta, k = n - 1)

  # Now it's simply finding the group with minimum within-group sum
  # And working out the elements
  group_index <- which.min(withingroup_distances)
  element_indices <- group_index + 0:(n-1)

  v[element_indices]
}

findClosest(v, 2)
# 25 26
findClosest(v, 3)
# 23 25 26

Mi suposición es que para los n valores más cercanos, lo único que importa es la diferencia entre v[i] - v[i - (n-1)] . Es decir, encontrar el mínimo de diff(x, lag = n - 1L) .

findClosest <- function(x, n) {
  x <- sort(x)
  x[seq.int(which.min(diff(x, lag = n - 1L)), length.out = n)]
}

findClosest(v, 3L)

[1] 23 25 26

Una opción base R, la idea es que primero sort el vector y restamos cada elemento i + n - 1 con i + n - 1 elemento en el vector ordenado y seleccionamos el grupo que tiene la mínima diferencia.

closest_n_vectors <- function(v, n) {
   v1 <- sort(v)
   inds <- which.min(sapply(head(seq_along(v1), -(n - 1)), function(x) 
                     v1[x + n -1] - v1[x]))
   v1[inds: (inds + n - 1)]
}

closest_n_vectors(v, 3)
#[1] 23 25 26

closest_n_vectors(c(2, 10, 1, 20, 4, 5, 23), 2)
#[1] 1 2

closest_n_vectors(c(19, 23, 45, 67, 89, 65, 1), 2)
#[1] 65 67

closest_n_vectors(c(19, 23, 45, 67, 89, 65, 1), 3)
#[1]  1 19 23

En caso de empate, esto devolverá los números con el valor más pequeño ya que estamos utilizando which.min .

PUNTOS DE REFERENCIA

Como tenemos bastantes respuestas, vale la pena hacer un punto de referencia de todas las soluciones hasta ahora

set.seed(1234)
x <- sample(100000000, 100000)

identical(findClosest_antoine(x, 3), findClosest_Sotos(x, 3), 
          closest_n_vectors_Ronak(x, 3), findClosest_Cole(x, 3))
#[1] TRUE

microbenchmark::microbenchmark(
    antoine = findClosest_antoine(x, 3),
    Sotos = findClosest_Sotos(x, 3), 
    Ronak  = closest_n_vectors_Ronak(x, 3),
    Cole = findClosest_Cole(x, 3),
    times = 10
)



#Unit: milliseconds
#  expr      min       lq     mean   median       uq      max neval cld
#antoine  148.751  159.071  163.298  162.581  167.365  181.314    10  b 
#  Sotos 1086.098 1349.762 1372.232 1398.211 1453.217 1553.945    10   c
#  Ronak   54.248   56.870   78.886   83.129   94.748  100.299    10 a  
#   Cole    4.958    5.042    6.202    6.047    7.386    7.915    10 a  





r  

r