Порядкові відстані махаланобіса


18

Мені потрібно обчислити вибірку відстані махаланобіса в R між кожною парою спостережень в матриці коваріатів . Мені потрібно рішення , яке є ефективним, тобто тільки відстані обчислюються, і переважно реалізовані в C / RCpp / Fortran і т.д. Я вважаю , що , матриця коваріації населення, невідомий і використовувати зразок коваріаційна матриця на своєму місці.n ( n - 1 ) / 2 Σн×pн(н-1)/2Σ

Мене особливо цікавить це питання, оскільки, здається, немає "консенсусного" методу обчислення попарно махаланобісових відстаней у R, тобто він не реалізований distні у функції, ні у cluster::daisyфункції. mahalanobisФункція не вирахував попарні відстані без додаткової роботи від програміста.

Про це вже запитували тут Пара-махаланобіс відстань в R , але рішення там здаються неправильними.

Ось правильний, але жахливо неефективний метод (оскільки обчислюється відстаней):н×н

set.seed(0)
x0 <- MASS::mvrnorm(33,1:10,diag(c(seq(1,1/2,l=10)),10))
dM = as.dist(apply(x0, 1, function(i) mahalanobis(x0, i, cov = cov(x0))))

Це досить просто, щоб зашифрувати себе в C, але я відчуваю, що щось таке основне повинно мати попереднє рішення. Є такий?

Існують і інші рішення, які не стикаються: HDMD::pairwise.mahalanobis()обчислює відстаней, коли потрібно лише унікальних відстаней. здається багатообіцяючою, але я не хочу, щоб моя функція походила від пакета, від якого залежить , що суттєво обмежує здатність інших запускати мій код. Якщо ця реалізація не є досконалою, я б краще писав власну. Хтось має досвід роботи з цією функцією?n ( n - 1 ) / 2н×нн(н-1)/2compositions::MahalanobisDist()rgl


Ласкаво просимо. Чи можете ви надрукувати дві матриці відстані у вашому запитанні? А що для вас "неефективно"?
ttnphns

1
Ви використовуєте лише матрицю коваріації зразка? Якщо так, то це еквівалентно 1) центруванню X; 2) обчислення SVD від центру X, скажімо, UDV '; 3) обчислення парних відстаней між рядками U.
vqv

Дякуємо, що опублікували це як питання. Я думаю, що ваша формула не є правильною. Дивіться мою відповідь нижче.
user603

@vqv Так, зразок коваріаційної матриці. Оригінальна публікація відредагована, щоб відобразити це.
ахфосс

Дивіться також дуже схоже запитання stats.stackexchange.com/q/33518/3277 .
ttnphns

Відповіді:


21

Починаючи з розчину «соковитих» ахфоса, я застосував розклад Холеського замість СВД.

cholMaha <- function(X) {
 dec <- chol( cov(X) )
 tmp <- forwardsolve(t(dec), t(X) )
 dist(t(tmp))
}

Це має бути швидше, тому що трикутна система вперед вирішує швидше, ніж щільне множення матриць із зворотною коваріацією ( див. Тут ). Ось орієнтири з рішеннями ahfoss і whuber у кількох налаштуваннях:

 require(microbenchmark)
 set.seed(26565)
 N <- 100
 d <- 10

 X <- matrix(rnorm(N*d), N, d)

 A <- cholMaha( X = X ) 
 A1 <- fastPwMahal(x1 = X, invCovMat = solve(cov(X))) 
 sum(abs(A - A1)) 
 # [1] 5.973666e-12  Ressuring!

   microbenchmark(cholMaha(X),
                  fastPwMahal(x1 = X, invCovMat = solve(cov(X))),
                  mahal(x = X))
Unit: microseconds
expr          min       lq   median       uq      max neval
cholMaha    502.368 508.3750 512.3210 516.8960  542.806   100
fastPwMahal 634.439 640.7235 645.8575 651.3745 1469.112   100
mahal       839.772 850.4580 857.4405 871.0260 1856.032   100

 N <- 10
 d <- 5
 X <- matrix(rnorm(N*d), N, d)

   microbenchmark(cholMaha(X),
                  fastPwMahal(x1 = X, invCovMat = solve(cov(X))),
                  mahal(x = X)
                    )
Unit: microseconds
expr          min       lq    median       uq      max neval
cholMaha    112.235 116.9845 119.114 122.3970  169.924   100
fastPwMahal 195.415 201.5620 205.124 208.3365 1273.486   100
mahal       163.149 169.3650 172.927 175.9650  311.422   100

 N <- 500
 d <- 15
 X <- matrix(rnorm(N*d), N, d)

   microbenchmark(cholMaha(X),
                  fastPwMahal(x1 = X, invCovMat = solve(cov(X))),
                  mahal(x = X)
                    )
Unit: milliseconds
expr          min       lq     median       uq      max neval
cholMaha    14.58551 14.62484 14.74804 14.92414 41.70873   100
fastPwMahal 14.79692 14.91129 14.96545 15.19139 15.84825   100
mahal       12.65825 14.11171 39.43599 40.26598 41.77186   100

 N <- 500
 d <- 5
 X <- matrix(rnorm(N*d), N, d)

   microbenchmark(cholMaha(X),
                  fastPwMahal(x1 = X, invCovMat = solve(cov(X))),
                  mahal(x = X)
                    )
Unit: milliseconds
expr           min        lq      median        uq       max neval
cholMaha     5.007198  5.030110  5.115941  5.257862  6.031427   100
fastPwMahal  5.082696  5.143914  5.245919  5.457050  6.232565   100
mahal        10.312487 12.215657 37.094138 37.986501 40.153222   100

Тож Холеський, здається, рівномірно швидший.


3
+1 Молодці! Я ціную пояснення, чому це рішення швидше.
whuber

Як maha () дає вам парну матрицю відстані на відміну від просто відстані до точки?
sheß

1
Ви маєте рацію, це не так, тому моя редакція не зовсім актуальна. Я видалю його, але, можливо, одного дня я додам в пакет пакетну версію maha (). Дякуємо, що вказали на це.
Маттео Фасіоло

1
Це було б чудово! З нетерпінням чекаю цього.
sheß

9

Стандартна формула відстані між двома точками даних у квадраті махаланобіса

D12=(х1-х2)ТΣ-1(х1-х2)

де - р × 1 вектор, що відповідає спостереженню i . Зазвичай коваріаційна матриця оцінюється за спостережуваними даними. Крім звернення матриці, ця операція виконується р 2 + р умножений і р 2 + 2 р доповнення, кожен з яких повторних п ( п - 1 ) / 2 раз.хip×1ip2+pp2+2pн(н-1)/2

Розглянемо наступне виведення:

D12=(х1-х2)ТΣ-1(х1-х2)=(х1-х2)ТΣ-12Σ-12(х1-х2)=(х1ТΣ-12-х2ТΣ-12)(Σ-12х1-Σ-12х2)=(q1Т-q2Т)(q1-q2)

де . Зауважимо, щоxTiΣ-1qi=Σ12xi. Це спирається на той факт, щоΣ-1xiTΣ12=(Σ12xi)T=qiT є симетричним, що справедливо через те, що для будь-якої симетричної діагоналізуючої матриціA=PEPT,Σ12A=PEPT

A12T=(PE12PT)T=PTTE12TPT=PE12PT=A12

Якщо дозволити , і відзначимо, що Σ - 1 симетричний, ми бачимо, що Σ - 1A=Σ1Σ1 також повинні бути симетричними. ЯкщоX-матрицяn×pспостережень, аQ-матрицяn×pтака, щоithрядкаQдорівнюєqi, тоQможе бути коротко виражений якXΣ-1Σ12Xn×pQn×pithQqiQ . Це випливає з цього та попередніх результатівXΣ12

єдині операції, які обчислюються n ( n - 1 ) / 2 рази, - p множення та 2 p додавання (на відміну відмноження p 2 + p та p 2 + 2 p

Dk=i=1p(QkiQi)2.
n(n1)/2p2pp2+pp2+2pдоповнення у вищевказаному способі), в результаті чого алгоритм, що складається з порядку обчислювальної складності замість вихідного O ( p 2 n 2 ) .O(pn2+p2n)O(p2n2)
require(ICSNP) # for pair.diff(), C implementation

fastPwMahal = function(data) {

    # Calculate inverse square root matrix
    invCov = solve(cov(data))
    svds = svd(invCov)
    invCovSqr = svds$u %*% diag(sqrt(svds$d)) %*% t(svds$u)

    Q = data %*% invCovSqr

    # Calculate distances
    # pair.diff() calculates the n(n-1)/2 element-by-element
    # pairwise differences between each row of the input matrix
    sqrDiffs = pair.diff(Q)^2
    distVec = rowSums(sqrDiffs)

    # Create dist object without creating a n x n matrix
    attr(distVec, "Size") = nrow(data)
    attr(distVec, "Diag") = F
    attr(distVec, "Upper") = F
    class(distVec) = "dist"
    return(distVec)
}

Цікаво. Вибачте, я не знаю R. Чи можете ви розширити, що pair.diff()робить, а також навести числовий приклад з роздруківками кожного кроку вашої функції? Спасибі.
ttnphns

Я відредагував відповідь, щоб включити виведення, що виправдовує ці обчислення, але я також розмістив другу відповідь, що містить код, який є набагато більш стислим.
ахфосс

7

Спробуємо очевидне. З

Dij=(xixj)Σ1(xixj)=xiΣ1xi+xjΣ1xj2xiΣ1xj

випливає, що ми можемо обчислити вектор

ui=xiΣ1xi

в час і матрицяO(p2)

V=XΣ1X

за час , швидше за все, використовуючи вбудовані операції швидкого (паралелізованого) масиву, а потім формують рішення якO(pn2+p2n)

D=uu2V

де - зовнішній добуток відносно + : ( a b ) i j = a i + b j .+(ab)ij=ai+bj.

RРеалізація лаконічно паралельна математична формулювання (передбачається, з ним, що на насправді зверне зі зворотним письмовій ч тут):Σ=Var(X)h

mahal <- function(x, h=solve(var(x))) {
  u <- apply(x, 1, function(y) y %*% h %*% y)
  d <- outer(u, u, `+`) - 2 * x %*% h %*% t(x)
  d[lower.tri(d)]
}

Зауважте, для порівняння з іншими рішеннями повертаються лише унікальні недіагональні елементи, а не вся (симетрична, нульова на діагональ) матриця відстані у квадраті. Scatterplots показують, що його результати згодні з результатами fastPwMahal.

У C або C ++, ОЗУ можуть бути повторно використані , і обчислюється на льоту, усуваючи необхідність в яких - або проміжного зберігання U U .uuuu

Часові дослідження з від 33 до 5000 і p від 10 до 100 показують, що ця реалізація в 1,5 - 5 разів швидша, ніж у межах цього діапазону. Поліпшення покращується в міру збільшення p і n . Отже, ми можемо розраховувати на перевагу для менших p . Беззбитковість виникає навколо p = 7 при n 100n335000p101001.55fastPwMahalpnfastPwMahalpp=7n100. Чи однакові обчислювальні переваги цього прямого рішення стосуються інших реалізацій, може бути питанням того, наскільки добре вони використовують переваги операцій векторного масиву.


Виглядає чудово. Я припускаю, що це можна зробити ще більш швидким, лише обчисливши нижні діагоналі, хоча я не можу не придумати спосіб зробити це в R, не втрачаючи швидкої продуктивності applyта outer... крім вибуху Rcpp.
ахфосс

застосовувати / зовнішні не мають переваги у швидкості порівняно із звичайними ванільними петлями.
user603

@ user603 Я розумію, що в принципі - але робіть терміни. Більше того, головним моментом використання цих конструкцій є надання семантичної допомоги для паралелізації алгоритму: важлива різниця в тому, як вони це виражають . (Можливо, варто згадати, що оригінальне питання стосується реалізації C / Fortran / тощо.) Ахфосс, я теж думав про обмеження обчислення нижчим трикутником, і погоджуюся, що Rтам, здається, нічого не виграє.
whuber

5

Якщо ви хочете обчислити вибірку відстані махаланобіса, то ви можете використати кілька алгебраїчних хитрощів. Всі вони призводять до обчислення парних евклідових відстаней, тому припустимо, що ми можемо використовувати dist()для цього. Нехай позначає матрицю даних n × p , яку ми будемо вважати центрованою, щоб її стовпці мали середнє значення 0 і мали ранг p, щоб матриця коваріації вибірки була несинулярною. (Центрування вимагає операцій O ( n p ) .) Тоді матриця коваріації вибірки дорівнює S = X T X / n .Xn×ppO(np)

S=XTX/n.

Парна вибірка відстаней махаланобіса така ж, як попарно евклідові відстані X L для будь-якої матриці L, що задовольняє L L T = S - 1 , наприклад квадратний корінь або коефіцієнт Холеського. Це випливає з деякої лінійної алгебри, і це призводить до алгоритму, що вимагає обчислення S , S - 1 і розкладання Чолеського. Найгірша складність - O ( n p 2 + p 3 ) .X

XL
LLLT=S1SS1O(np2+p3)

XX=UDVTX

S=VD2VT/n
S1/2=VD1VTn1/2.
XS1/2=UVTn1/2
UnXO(np2)n>p

Ось R реалізація другого методу, який я не можу перевірити на iPad, який я використовую, щоб написати цю відповідь.

u = svd(scale(x, center = TRUE, scale = FALSE), nv = 0)$u
dist(u)
# these distances need to be scaled by a factor of n

2

Це набагато більш лаконічне рішення. Він все ще заснований на деривації, що включає матрицю зворотної квадратної коваріації кореня (див. Мою іншу відповідь на це питання), але використовує лише базу R та пакет статистики. Це здається трохи швидшим (приблизно на 10% швидше в деяких тестах, які я пробіг). Зауважте, що він повертає відстань Махаланобіс на відміну від відстані Маханобіса.

fastPwMahal = function(x1,invCovMat) {
  SQRT = with(svd(invCovMat), u %*% diag(d^0.5) %*% t(v))
  dist(x1 %*% SQRT)
}

Ця функція вимагає зворотної матриці коваріації і не повертає об'єкт відстані, але я підозрюю, що ця зведена версія функції буде більш корисною для збору користувачів обміну.


3
Це можна покращити, замінивши SQRTрозклад Холеського chol(invCovMat).
vqv

1

н2

Якщо ви використовуєте лише функції Fortran77 в інтерфейсі, ваша підпрограма все ще достатньо портативна для інших.


1

Існує дуже простий спосіб зробити це за допомогою "біотуалетів" пакета R Package. У цьому випадку ви отримаєте матрицю квадратичної відстані Mahalanobis.

#Manly (2004, p.65-66)

x1 <- c(131.37, 132.37, 134.47, 135.50, 136.17)
x2 <- c(133.60, 132.70, 133.80, 132.30, 130.33)
x3 <- c(99.17, 99.07, 96.03, 94.53, 93.50)
x4 <- c(50.53, 50.23, 50.57, 51.97, 51.37)

#size (n x p) #Means 
x <- cbind(x1, x2, x3, x4) 

#size (p x p) #Variances and Covariances
Cov <- matrix(c(21.112,0.038,0.078,2.01, 0.038,23.486,5.2,2.844, 
        0.078,5.2,24.18,1.134, 2.01,2.844,1.134,10.154), 4, 4)

library(biotools)
Mahalanobis_Distance<-D2.dist(x, Cov)
print(Mahalanobis_Distance)

Чи можете ви поясніть мені, що означає матриця відстані у квадраті? Відповідно: мене цікавить відстань між двома точками / векторами, так що говорить матриця?
Бен

1

Це розширений кодом мій старий відповідь переміщений сюди з іншої теми .

Я тривалий час робив обчислення квадратної симетричної матриці попарно махаланобісних відстаней у SPSS за допомогою капелюшкового матричного підходу, використовуючи рішення системи лінійних рівнянь (бо це швидше, ніж інвертування коваріаційної матриці).

Я не користувач R, тому я просто спробував відтворити @ahfoss ' цей рецепт тут у SPSS разом із "моїм" рецептом на даних 1000 випадків на 400 змінних, і я знайшов свій шлях значно швидше.


H

H(n1)X(XX)1XX

Отже, по центру стовпців матриці даних обчисліть матрицю капелюхів, помножте на (n-1) і виконайте операцію, протилежну подвійному центруванню. Ви отримуєте матрицю відстаней у квадраті махаланобіса.

hh2h1h2cos

У наших налаштуваннях "двоцентрова" матриця - це конкретно матриця капелюхів (помножена на n-1), а не евклідові скалярні добутки, і отримана в результаті матриця квадратного відстані є таким чином квадратом махаланобіської відстані матриці, а не квадратом евклідової матриці відстані.

HH(n1)H= {H,H,...}Dmahal2=H+H2H(n1)

Код в SPSS і датчик швидкості нижче.


Цей перший код відповідає @ahfoss функції fastPwMahalз процитував відповідь . Він рівнозначний йому математично. Але я обчислюю повну симетричну матрицю відстаней (за допомогою матричних операцій), а @ahfoss обчислює трикутник симетричної матриці (елемент за елементом).

matrix. /*Matrix session in SPSS;
        /*note: * operator means matrix multiplication, &* means usual, elementwise multiplication.
get data. /*Dataset 1000 cases x 400 variables
!cov(data%cov). /*compute usual covariances between variables [this is my own matrix function].
comp icov= inv(cov). /*invert it
call svd(icov,u,s,v). /*svd
comp isqrcov= u*sqrt(s)*t(v). /*COV^(-1/2)
comp Q= data*isqrcov. /*Matrix Q (see ahfoss answer)
!seuclid(Q%m). /*Compute 1000x1000 matrix of squared euclidean distances;
               /*computed here from Q "data" they are the squared Mahalanobis distances.
/*print m. /*Done, print
end matrix.

Time elapsed: 3.25 sec

Далі йде моя модифікація, щоб зробити це швидше:

matrix.
get data.
!cov(data%cov).
/*comp icov= inv(cov). /*Don't invert.
call eigen(cov,v,s2). /*Do sdv or eigen decomposition (eigen is faster),
/*comp isqrcov= v * mdiag(1/sqrt(s2)) * t(v). /*compute 1/sqrt of the eigenvalues, and compose the matrix back, so we have COV^(-1/2).
comp isqrcov= v &* (make(nrow(cov),1,1) * t(1/sqrt(s2))) * t(v). /*Or this way not doing matrix multiplication on a diagonal matrix: a bit faster .
comp Q= data*isqrcov.
!seuclid(Q%m).
/*print m.
end matrix.

Time elapsed: 2.40 sec

X(XX)1X(XX)1Xsolve(X'X,X')

matrix.
get data.
!center(data%data). /*Center variables (columns).
comp hat= data*solve(sscp(data),t(data))*(nrow(data)-1). /*hat matrix, and multiply it by n-1 (i.e. by df of covariances).
comp ss= diag(hat)*make(1,ncol(hat),1). /*Now using its diagonal, the leverages (as column propagated into matrix).
comp m= ss+t(ss)-2*hat. /*compute matrix of squared Mahalanobis distances via "cosine rule".
/*print m.
end matrix.

[Notice that if in "comp ss" and "comp m" lines you use "sscp(t(data))",
 that is, DATA*t(DATA), in place of "hat", you get usual sq. 
 euclidean distances]

Time elapsed: 0.95 sec

0

Формула, яку ви опублікували, не обчислює те, що, на вашу думку, ви робите (U-статистика).

У коді, який я розмістив, я використовую cov(x1)як матрицю масштабування (це дисперсія парних відмінностей даних). Ви використовуєте cov(x0)(це матриця коваріації вихідних даних). Я думаю, що це помилка у вашій частині. Весь сенс використання парних відмінностей полягає в тому, що це позбавляє вас від припущення, що багатофакторний розподіл ваших даних симетричний навколо центру симетрії (або для того, щоб оцінити цей центр симетрії для цього питання, оскільки crossprod(x1)пропорційний cov(x1)). Очевидно, використовуючи cov(x0)ви втрачаєте це.

Це добре пояснено в документі, до якого я посилався в оригінальній відповіді.


1
Я думаю, що ми тут говоримо про дві різні речі. Мій метод обчислює відстань махаланобіса, яку я перевірив за кількома іншими формулами. Моя формула також тепер незалежно перевірена Matteo Fasioloі (я припускаю) whuberу цій темі. Твоя інша. Мені було б цікаво зрозуміти, що ви обчислюєте, але це явно відрізняється від відстані махаланобіса, як зазвичай визначено.
ахфосс

@ahfoss: 1) махаланобіс - це відстань X до точки симетрії в їх метриці. У вашому випадку матриця X - матриця * (n-1) / 2 з парними відмінностями, їх центр симетрії - вектор 0_p, а їх метрика - це те, що я назвав cov (X1) у своєму коді. 2) запитайте себе, чому ви використовуєте U-статистику в першу чергу, і як пояснюється в статті, ви побачите, що використання cov (x0) перемагає цю мету.
user603

ХХОp

Після огляду на cov(x0)SGSτLQD
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.