Ближайшее другое значение в том же векторе - PullRequest
4 голосов
/ 21 февраля 2020

У меня есть вектор

set.seed(2)
x <- sample.int(20, 5)

[1]  4 14 11  3 16

Теперь для каждого элемента я хочу найти

элемент с минимальным расстоянием (min(abs(x[i]-x[-i])) для элемента i), который здесь будет

[1]  3 16 14  4 14

(первый) индекс элемента с минимальным расстоянием, которое здесь будет

[1] 4 5 2 1 2

Дело в том, что сам элемент не рассматривается, а только все остальные элементы, поэтому этот R - самый быстрый способ найти ближайшее значение в векторе не является ответом.

Если фактический ответ там, извините - я не нашел его.

Ответы [ 4 ]

5 голосов
/ 21 февраля 2020

1) Rfast Используя dista в Rfast, мы получаем индексы ближайших двух. Возьмите второе самое близкое, так как самое близкое будет то же самое значение.

library(Rfast)
x <- c(4, 14, 11, 3, 16) # input

x[ dista(x, x, k = 2, index = TRUE)[, 2] ]
## [1]  3 16 14  4 14

2) sqldf Используя SQL, мы можем левее присоединить DF к себе, исключая то же самое значение значения, и взять строка с минимальным расстоянием.

DF <- data.frame(x)   # x is from (1)
sqldf("select a.x, b.x nearest, min(abs(a.x - b.x)) 
  from DF a 
  left join DF b on a.x != b.x 
  group by a.rowid")[1:2]

, дающая:

   x nearest
1  4       3
2 14      16
3 11      14
4  3       4
5 16      14

3) zoo Отсортируйте входные данные, примите значение, соответствующее наименьшей разнице на любом из сторона каждого элемента и заказать его обратно.

library(zoo)
ix <- order(x)
least <- function(x) if (x[2] - x[1] < x[3] - x[2]) x[1] else x[3]
rollapply(c(-Inf, x[ix], Inf), 3, least)[order(ix)]
## [1]  3 16 14  4 14

4) База R Используя ix и least из (3), мы можем имитировать c (3) используя только базовые функции следующим образом.

apply(embed(c(-Inf, x[ix], Inf),  3)[, 3:1], 1, least)[order(ix)]
## [1]  3 16 14  4 14

4a) Этот немного более короткий вариант также будет работать:

-apply(embed(-c(-Inf, x[ix], Inf),  3), 1, least)[order(ix)]
## [1]  3 16 14  4 14

4b) Упрощение в дальнейшем у нас есть следующее базовое решение, где, опять же, ix от (3):

xx <- x[ix]
x1 <- c(-Inf, xx[-length(xx)])
x2 <- c(xx[-1], Inf)
ifelse(xx - x1 < x2 - xx, x1, x2)[order(ix)]
## [1]  3 16 14  4 14

Дубликаты

В примере в вопросе не было дубликатов, но если были дубликаты, есть некоторый вопрос относительно определения проблемы. Например, если у нас было c(1, 3, 4, 1), то, если мы посмотрим на первое значение, 1, есть другое значение, точно равное ему, поэтому самое близкое значение равно 1. Другая интерпретация состоит в том, что должно быть возвращено самое близкое значение, не равное 1, которое в этот случай равен 3. В приведенных выше кодах решение sqldf дает самое близкое значение, не равное текущему значению, тогда как другие дают самое близкое значение среди оставшихся значений.

Если мы хотели интерпретацию самого близкого не равный для тех, кто отличается от sqldf, тогда мы могли бы использовать rle после заказа, чтобы сжать его до уникальных значений, а затем использовать inverse.rle впоследствии, как показано на измененном 4b:

x <- c(1, 3, 4, 1)
ix <- order(x)
r <- rle(x[ix])
xx <- r$values
x1 <- c(-Inf, xx[-length(xx)])
x2 <- c(xx[-1], Inf)
r$values <- ifelse(xx - x1 < x2 - xx, x1, x2)
inverse.rle(r)[order(ix)]
## [1] 3 4 3 3
4 голосов
/ 21 февраля 2020

Меня очень заинтересовал этот вопрос и подходы, предложенные в других ответах, поэтому я сравнил их с точки зрения времени их выполнения (и добавил другой подход, используя пакет RANN). Код прилагается ниже. TL; DR: базовая версия R 4b от пользователя G. Grothendieck была наиболее эффективной и со значительным отрывом.

library(RANN)
library(zoo)
library(data.table)
library(Rfast)
library(sqldf)

# All functions take a vector as argument, 
# and return the values of nearest neighbours (not their index)

# Using base R, by ThomasIsCoding
base_nn <- function(x) {
  d <- data.frame(`diag<-`(as.matrix(dist(x)),Inf))
  id <- unlist(Map(which.min,d))
  x[id]
}

# Using Rfast, by G. Grothendieck
rfast_nn <- function(x) {
  x[ dista(x, x, k = 2, index = TRUE)[, 2] ]
}

# Using sqldf, by G. Grothendieck
sqldf_nn <- function(x) {
  DF <- data.frame(x)   # x is from (1)
  unname(
    unlist(sqldf("select a.x, b.x nearest, min(abs(a.x - b.x)) 
            from DF a 
            left join DF b on a.x != b.x 
            group by a.rowid")[2])
  )
}

# Using `zoo`, by G. Grothendieck
zoo_nn <- function(x) {
  ix <- order(x)
  least <- function(x) if (x[2] - x[1] < x[3] - x[2]) x[1] else x[3]
  rollapply(c(-Inf, x[ix], Inf), 3, least)[order(ix)]
}

# Using base R (v 4), by G. Grothendieck
base2_nn <- function(x) {
  ix <- order(x)
  least <- function(x) if (x[2] - x[1] < x[3] - x[2]) x[1] else x[3]
  apply(embed(c(-Inf, x[ix], Inf),  3)[, 3:1], 1, least)[order(ix)]
}

# Using base R (v 4a), by G. Grothendieck
base3_nn <- function(x) {
  ix <- order(x)
  least <- function(x) if (x[2] - x[1] < x[3] - x[2]) x[1] else x[3]
  -apply(embed(-c(-Inf, x[ix], Inf),  3), 1, least)[order(ix)]
}

# Using base R (v 4b), by G. Grothendieck
base4_nn <- function(x) {
  ix <- order(x)
  xx <- x[ix]
  x1 <- c(-Inf, xx[-length(xx)])
  x2 <- c(xx[-1], Inf)
  ifelse(xx - x1 < x2 - xx, x1, x2)[order(ix)]
}

# Using data.table, by IceCreamToucan
dt_nn <- function(x) {
  dt <- setkey(data.table(x), x)
  dt[dt, on = .(x > x), mult = 'first', lowx := i.x][, lowx := fcoalesce(lowx + .0, -Inf)]
  dt[dt, on = .(x < x), mult = 'last', highx := i.x][, highx := fcoalesce(highx + .0, Inf)]
  dt[, closex := fifelse(x - lowx < highx - x, lowx, highx)]
  unname(unlist(dt[, .(closex)]))
}

# Using, RANN, by me
rann_nn <- function(x) {
  id <- RANN::nn2(as.matrix(x), k = 2)$nn.idx[, 2]
  x[id]
}



### Apply all methods

# Test that all have the same output:
x <- c(4, 14,11,3,16)

rann_nn(x)
# [1]  3 16 14  4 14
base_nn(x)
# [1]  3 16 14  4 14
rfast_nn(x)
# [1]  3 16 14  4 14
sqldf_nn(x)
# [1]  3 16 14  4 14
zoo_nn(x)
# [1]  3 16 14  4 14
base2_nn(x)
# [1]  3 16 14  4 14
base3_nn(x)
# [1]  3 16 14  4 14
base4_nn(x)
# [1]  3 16 14  4 14
dt_nn(x) # differently ordered for some reason
# [1]  4  3 14 16 14


# Compare running times
library(microbenchmark)

# Compare for N = 1000 elements
benchmark_data <- rnorm(1000)
microbenchmark(
  rann_nn(benchmark_data),
  base_nn(benchmark_data),
  rfast_nn(benchmark_data),
  sqldf_nn(benchmark_data),
  zoo_nn(benchmark_data),
  base2_nn(benchmark_data),
  base3_nn(benchmark_data),
  base4_nn(benchmark_data),
  dt_nn(benchmark_data)
)
# Unit: microseconds
#                      expr        min          lq        mean      median          uq        max neval
#   rann_nn(benchmark_data)    641.180    684.1975    776.5467    711.6680    775.3635   3822.023   100
#   base_nn(benchmark_data) 166523.177 179240.8130 209471.1333 187633.0515 249740.8425 330864.712   100
#  rfast_nn(benchmark_data)  45160.603  47032.5225  47681.0557  47594.0075  48308.8440  50579.839   100
#  sqldf_nn(benchmark_data) 133916.594 138769.8175 143505.9315 140543.3250 143830.2765 211873.960   100
#    zoo_nn(benchmark_data)   4359.359   4604.0275   5008.4291   4785.1515   5037.9705  14999.802   100
#  base2_nn(benchmark_data)   1292.322   1407.4875   1747.8404   1462.7295   1588.1580  11297.321   100
#  base3_nn(benchmark_data)   1263.644   1396.9210   1615.7495   1472.9940   1571.8575  11828.015   100
#  base4_nn(benchmark_data)    119.543    146.1080    254.5075    178.1065    197.4265   7726.156   100
#     dt_nn(benchmark_data)   5290.337   6580.6965   7111.1816   6892.3800   7351.3795  29469.815   100


# For N = 100000, leaving out the slowest versions (e.g., `base_nn()`
# no longer works because a distance matrix cannot be computed for 
# N = 100000)
benchmark_data <- rnorm(100000)
microbenchmark(
  rann_nn(benchmark_data),
  zoo_nn(benchmark_data),
  base2_nn(benchmark_data),
  base3_nn(benchmark_data),
  base4_nn(benchmark_data),
  dt_nn(benchmark_data)
)
# Unit: milliseconds
#                      expr        min        lq      mean    median        uq      max neval
#   rann_nn(benchmark_data) 130.957025 141.02904 149.94052 148.60184 156.14506 271.1882   100
#    zoo_nn(benchmark_data) 606.690004 673.88980 720.12545 717.51658 766.98190 886.4397   100
#  base2_nn(benchmark_data) 142.554407 176.30358 198.58375 193.34812 212.33885 329.5470   100
#  base3_nn(benchmark_data) 142.074126 168.78195 189.65122 184.45025 205.89414 287.0740   100
#  base4_nn(benchmark_data)   9.354764  10.46687  17.22086  12.36354  14.22882 166.4758   100
#     dt_nn(benchmark_data)  96.503882 104.06914 117.95408 108.20284 121.11428 247.2092   100
3 голосов
/ 21 февраля 2020

Вот базовое решение R

d <- data.frame(`diag<-`(as.matrix(dist(x)),Inf))
ids <- unlist(Map(which.min,d))
val <- x[ids]

такое, что

> ids
X1 X2 X3 X4 X5 
 4  5  2  1  2 

> val
[1]  3 16 14  4 14

ДАННЫЕ

x <- c(4, 14,11,3,16)
2 голосов
/ 21 февраля 2020

Опция с data.table без выравнивания

dt <- setkey(data.table(x), x)

dt[dt, on = .(x > x), mult = 'first', lowx := i.x][, lowx := fcoalesce(lowx + .0, -Inf)]
dt[dt, on = .(x < x), mult = 'last', highx := i.x][, highx := fcoalesce(highx + .0, Inf)]
dt[, closex := fifelse(x - lowx < highx - x, lowx, highx)]
dt[, .(x, closex)]

#     x closex
# 1:  3      4
# 2:  4      3
# 3: 11     14
# 4: 14     16
# 5: 16     14
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...