Вычисление разреженной матрицы попарных расстояний в R

У меня есть NxM матрица, и я хочу вычислить NxN матрицу евклидовых расстояний между M точками. В моей задаче N составляет около 100 000. Поскольку я планирую использовать эту матрицу для алгоритма k-ближайшего соседа, мне нужно сохранить только k наименьших расстояний, поэтому итоговая матрица NxN будет очень разреженной. Это контрастирует с тем, что получается, например, из dist(), что привело бы к плотной матрице (и, вероятно, к проблемам с хранением для моего размера N).

Пакеты для kNN, которые я нашел до сих пор (knnflex, kknn и т. Д.), Похоже, используют плотные матрицы. Кроме того, пакет Matrix не предлагает функции попарного расстояния.

Ближе к моей цели я вижу, что пакет spam имеет nearest.dist() функцию, которая позволяет учитывать только расстояния, меньшие некоторого порога delta. В моем случае, однако, конкретное значение delta может привести к слишком большому количеству расстояний (так что я должен плотно хранить матрицу NxN) или слишком малому количеству расстояний (так что я не могу использовать kNN).

Я видел предыдущее обсуждение попытки выполнить k-means кластеризация с использованием пакетов bigmemory/biganalytics, но, похоже, я не могу использовать эти методы в этом случае.

Кто-нибудь знает функцию / реализацию, которая будет вычислять матрицу расстояний разреженным образом в R? Мой (ужасный) план резервного копирования - иметь два for цикла и сохранять результаты в Matrix объекте.


person Christopher DuBois    schedule 06.04.2011    source источник
comment
Просто убедитесь ... Вы знаете о dist stat.ethz.ch/R-manual/R-patched/library/stats/html/dist.html, верно?   -  person Benjamin    schedule 06.04.2011
comment
Извините, я не понял, почему dist () не подходит для моей ситуации. В результате получается плотная матрица, и хранение матрицы NxN немного раздражает.   -  person Christopher DuBois    schedule 07.04.2011
comment
Вероятно, вам следует либо принять один из ответов, который, по вашему мнению, действительно отвечает на вопрос (ваш собственный, если вы считаете, что он подходит лучше всего), либо отредактировать свой вопрос, чтобы уточнить, почему они не отвечают.   -  person Tommy    schedule 25.07.2011
comment
немного раздражает, это преуменьшение - если N равно 100000, это матрица 480 ГБ   -  person MichaelChirico    schedule 05.05.2018


Ответы (3)


Что ж, мы не можем заставить вас прибегать к циклам for, теперь можем :)

Конечно, возникает вопрос, как представить разреженную матрицу. Простой способ - сделать так, чтобы он содержал только индексы ближайших точек (и при необходимости пересчитывал их). Но в приведенном ниже решении я помещаю расстояние ('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 двумерных точках:

> 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

И попробуем на более крупной проблеме (10к баллов). Все-таки на 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).

person Tommy    schedule 06.04.2011
comment
Спасибо за этот ответ. Я согласен, это примерно в два раза быстрее. Однако для моего приложения (kNN) я думаю, что наличие только верхнего треугольника матрицы расстояний на самом деле немного неудобно. Я думаю, что могу придерживаться распараллеленной версии отправленного мной кода. Еще раз спасибо! - person Christopher DuBois; 07.04.2011

На данный момент я использую следующее, вдохновленное этот ответ. Результатом является матрица n x k, где элемент (i,k) - это индекс точки данных, которая является k-й ближайшей к 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))
}
person Christopher DuBois    schedule 06.04.2011
comment
Вам нужно выполнить dist (t (x)), чтобы получить сопоставимый ответ. - person Tommy; 06.04.2011

Если вы хотите сохранить логику своей функции min.k.dist и возвращать повторяющиеся расстояния, возможно, вы захотите немного изменить ее. Кажется бессмысленным возвращать первую строку с нулевым расстоянием, не так ли? ... и включив некоторые приемы в мой другой ответ, вы можете ускорить свою версию примерно на 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 
person Tommy    schedule 07.04.2011