Вычисление разреженной парной матрицы расстояния в R
У меня есть матрица NxM
, и я хочу вычислить матрицу NxN
евклидовых расстояний между точками M
. В моей проблеме N
составляет около 100 000. Поскольку я планирую использовать эту матрицу для алгоритма k-ближайшего соседа, мне нужно только сохранить наименьшие расстояния k
, поэтому результирующая матрица NxN
очень скудная. Это, в отличие от того, что выходит из dist()
, например, что приведет к плотной матрице (и, возможно, к проблемам хранения для моего размера N
).
Пакеты для kNN, которые я нашел до сих пор (knnflex
, kknn
и т.д.), как представляется, используют плотные матрицы. Кроме того, пакет Matrix
не предлагает функцию попарного расстояния.
Ближе к моей цели, я вижу, что пакет spam
имеет функцию nearest.dist()
, которая позволяет рассматривать только расстояния, меньшие некоторого порога, delta
. В моем случае, однако, конкретное значение delta
может создавать слишком много расстояний (так что я должен хранить матрицу NxN
плотно) или слишком мало расстояний (так что я не могу использовать kNN).
Я видел предыдущую дискуссию о попытке выполнить k-mean clustering с помощью пакетов bigmemory/biganalytics
, но мне кажется, что я не могу использовать эти методы в этот случай.
Кто-нибудь знает функцию/реализацию, которая будет вычислять матрицу расстояний разреженным образом в R? Мой (страшный) план резервного копирования состоит из двух циклов for
и сохранения результатов в объекте Matrix
.
Ответы
Ответ 1
Ну, мы не можем вам прибегать к for-loops, теперь можем ли мы:)
Существует, конечно, вопрос о том, как представить разреженную матрицу. Простым способом является наличие в нем только индексов ближайших точек (и пересчета по мере необходимости). Но в приведенном ниже решении я помещаю как одно расстояние ('d1' и т.д.), Так и индекс ('i1' и т.д.) В одну матрицу:
sparseDist <- function(m, k) {
m <- t(m)
n <- ncol(m)
d <- vapply( seq_len(n-1L), function(i) {
d<-colSums((m[, seq(i+1L, n), drop=FALSE]-m[,i])^2)
o<-sort.list(d, na.last=NA, method='quick')[seq_len(k)]
c(sqrt(d[o]), o+i)
}, numeric(2*k)
)
dimnames(d) <- list(c(paste('d', seq_len(k), sep=''),
paste('i', seq_len(k), sep='')), colnames(m)[-n])
d
}
Пробуждение на 9 2d-точках:
> m <- matrix(c(0,0, 1.1,0, 2,0, 0,1.2, 1.1,1.2, 2,1.2, 0,2, 1.1,2, 2,2),
9, byrow=TRUE, dimnames=list(letters[1:9], letters[24:25]))
> print(dist(m), digits=2)
a b c d e f g h
b 1.1
c 2.0 0.9
d 1.2 1.6 2.3
e 1.6 1.2 1.5 1.1
f 2.3 1.5 1.2 2.0 0.9
g 2.0 2.3 2.8 0.8 1.4 2.2
h 2.3 2.0 2.2 1.4 0.8 1.2 1.1
i 2.8 2.2 2.0 2.2 1.2 0.8 2.0 0.9
> print(sparseDist(m, 3), digits=2)
a b c d e f g h
d1 1.1 0.9 1.2 0.8 0.8 0.8 1.1 0.9
d2 1.2 1.2 1.5 1.1 0.9 1.2 2.0 NA
d3 1.6 1.5 2.0 1.4 1.2 2.2 NA NA
i1 2.0 3.0 6.0 7.0 8.0 9.0 8.0 9.0
i2 4.0 5.0 5.0 5.0 6.0 8.0 9.0 NA
i3 5.0 6.0 9.0 8.0 9.0 7.0 NA NA
И попробуем это по большей проблеме (10k точек). Тем не менее, на 100 тыс. Точек и более измерений это займет много времени (например, 15-30 минут).
n<-1e4; m<-3; m=matrix(runif(n*m), n)
system.time( d <- sparseDist(m, 3) ) # 9 seconds on my machine...
P.S. Просто отметили, что вы отправили ответ, когда я писал это: решение здесь примерно в два раза быстрее, потому что оно не вычисляет одинаковое расстояние дважды (расстояние между точками 1 и 13 совпадает с расстоянием между точками 13 и 1).
Ответ 2
В настоящее время я использую следующее, вдохновленное этим ответом. Вывод представляет собой матрицу n x k
, где элемент (i,k)
- это индекс точки данных, который является k
th ближе всего к i
.
n <- 10
d <- 3
x <- matrix(rnorm(n * d), ncol = n)
min.k.dists <- function(x,k=5) {
apply(x,2,function(r) {
b <- colSums((x - r)^2)
o <- order(b)
o[1:k]
})
}
min.k.dists(x) # first row should be 1:ncol(x); these points have distance 0
dist(t(x)) # can check answer against this
Если кто-то беспокоится о том, как обрабатываются связи и что не так, возможно, следует включить rank()
.
Вышеприведенный код кажется несколько быстрым, но я уверен, что он может быть улучшен (хотя у меня нет времени на маршрут C
или fortran
). Поэтому я все еще открыт для быстрых и разреженных реализаций выше.
Ниже я включаю параллельную версию, в которой я закончил:
min.k.dists <- function(x,k=5,cores=1) {
require(multicore)
xx <- as.list(as.data.frame(x))
names(xx) <- c()
m <- mclapply(xx,function(r) {
b <- colSums((x - r)^2)
o <- order(b)
o[1:k]
},mc.cores=cores)
t(do.call(rbind,m))
}
Ответ 3
Если вы хотите сохранить логику своей функции min.k.dist и вернуть повторяющиеся расстояния, вам может потребоваться немного изменить ее. Кажется бессмысленным вернуть первую строку с 0 расстоянием, верно?... и, добавив некоторые из трюков в мой другой ответ, вы можете ускорить свою версию примерно на 30%:
min.k.dists2 <- function(x, k=4L) {
k <- max(2L, k + 1L)
apply(x, 2, function(r) {
sort.list(colSums((x - r)^2), na.last=NA, method='quick')[2:k]
})
}
> n<-1e4; m<-3; m=matrix(runif(n*m), n)
> system.time(d <- min.k.dists(t(m), 4)) #To get 3 nearest neighbours and itself
user system elapsed
17.26 0.00 17.30
> system.time(d <- min.k.dists2(t(m), 3)) #To get 3 nearest neighbours
user system elapsed
12.7 0.0 12.7