Jarak Mahalanobis berpasangan

18

Saya perlu menghitung jarak sampel Mahalanobis dalam R antara setiap pasangan pengamatan dalam matriks kovariat . Saya memerlukan solusi yang efisien, yaitu hanya jarak yang dihitung, dan lebih baik diimplementasikan dalam C / RCpp / Fortran dll. Saya berasumsi bahwa , matriks kovarian populasi, tidak diketahui dan menggunakan sampel matriks kovarians sebagai gantinya.n ( n - 1 ) / 2 Σn×pn(n1)/2Σ

Saya sangat tertarik pada pertanyaan ini karena tampaknya tidak ada metode "konsensus" untuk menghitung jarak Mahalanobis berpasangan dalam R, yaitu tidak diterapkan dalam distfungsi maupun dalam cluster::daisyfungsi. The mahalanobisfungsi tidak menghitung jarak berpasangan tanpa pekerjaan tambahan dari programmer.

Ini sudah diminta di sini jarak Pairwise Mahalanobis di R , tetapi solusi di sana tampaknya salah.

Berikut adalah metode yang benar tetapi sangat tidak efisien (karena jarak dihitung):n×n

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))))

Ini cukup mudah untuk kode diri saya di C, tapi saya merasa seperti sesuatu yang dasar ini harus memiliki solusi yang sudah ada sebelumnya. Apakah ada satu?

Ada solusi lain yang gagal: HDMD::pairwise.mahalanobis()menghitung jarak, ketika hanya jarak unik yang diperlukan. tampaknya menjanjikan, tetapi saya tidak ingin fungsi saya berasal dari paket yang tergantung , yang sangat membatasi kemampuan orang lain untuk menjalankan kode saya. Kecuali jika implementasi ini sempurna, saya lebih suka menulis sendiri. Adakah yang punya pengalaman dengan fungsi ini?n ( n - 1 ) / 2n×nn(n1)/2compositions::MahalanobisDist()rgl

ahfoss
sumber
Selamat datang. Bisakah Anda mencetak dua matriks jarak dalam pertanyaan Anda? Dan apa yang "tidak efisien" bagi Anda?
ttnphns
1
Apakah Anda hanya menggunakan matriks kovarians sampel? Jika demikian, maka ini setara dengan 1) centering X; 2) menghitung SVD dari X berpusat, katakanlah UDV '; 3) menghitung jarak berpasangan antara baris U.
vqv
Terima kasih telah memposting ini sebagai pertanyaan. Saya pikir formula Anda tidak benar. Lihat jawaban saya di bawah ini.
user603
@vqv Ya, sampel matriks kovarians. Posting asli diedit untuk mencerminkan ini.
ahfoss
Lihat juga stats.stackexchange.com/q/33518/3277 pertanyaan yang sangat mirip .
ttnphns

Jawaban:

21

Mulai dari solusi "succint" ahfoss, saya telah menggunakan dekomposisi Cholesky sebagai pengganti SVD.

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

Itu harus lebih cepat, karena penyelesaian-maju sistem segitiga lebih cepat daripada perkalian matriks padat dengan kovarians terbalik ( lihat di sini ). Berikut adalah tolok ukur dengan solusi ahfoss dan whuber di beberapa pengaturan:

 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

Jadi Cholesky tampaknya lebih cepat seragam.

Matteo Fasiolo
sumber
3
+1 Bagus! Saya menghargai penjelasan mengapa solusi ini lebih cepat.
Whuber
Bagaimana maha (), memberikan Anda matriks jarak berpasangan, yang bertentangan dengan hanya jarak ke titik?
sheß
1
Anda benar, tidak, jadi hasil edit saya tidak sepenuhnya relevan. Saya akan menghapusnya, tapi mungkin suatu hari saya akan menambahkan versi maha () berpasangan ke paket. Terima kasih telah menunjukkan ini.
Matteo Fasiolo
1
Itu akan menyenangkan! Menantikannya.
sheß
9

Rumus standar untuk jarak Mahalanobis kuadrat antara dua titik data adalah

D12=(x1x2)TΣ1(x1x2)

di mana adalah vektor p × 1 yang sesuai dengan observasi i . Biasanya, matriks kovarians diperkirakan dari data yang diamati. Tidak menghitung inversi matriks, operasi ini membutuhkan p 2 + p perkalian dan p 2 + 2 p penambahan, masing-masing diulang n ( n - 1 ) / 2 kali.xip×1ip2+pp2+2pn(n1)/2

Pertimbangkan derivasi berikut:

D12=(x1x2)TΣ1(x1x2)=(x1x2)TΣ12Σ12(x1x2)=(x1TΣ-12-x2TΣ-12)(Σ-12x1-Σ-12x2)=(q1T-q2T)(q1-q2)

di mana . Perhatikan bahwaxTiΣ-1qsaya=Σ-12xsaya. Ini bergantung pada fakta bahwaΣ-1xsayaTΣ-12=(Σ-12xsaya)T=qsayaT adalah simetris, yang berlaku karena fakta bahwa untuk setiap matriks yang dapat didiagonalisasi simetrisA=PEPT,Σ-12SEBUAH=PEPT

SEBUAH12T=(PE12PT)T=PTTE12TPT=PE12PT=SEBUAH12

Jika kita membiarkan , dan perhatikan bahwa Σ - 1 simetris, kita melihat bahwa Σ - 1SEBUAH=Σ-1Σ-1 juga harus simetris. JikaXadalahmatriks pengamatann×pdanQadalahmatriksn×psedemikian rupa sehingga barisithdariQadalahqi, makaQdapat secara ringkas dinyatakan sebagaiXΣ-1Σ12Xn×pQn×pithQqiQ . Ini dan hasil sebelumnya menyiratkan ituXΣ12

satu-satunya operasi yang dihitung n ( n - 1 ) / 2 kali adalah p perkalian danpenambahan 2 p (sebagai lawan dari p 2 + p perkalian dan p 2 + 2 p

Dk=i=1p(QkiQi)2.
n(n1)/2p2pp2+pp2+2ppenambahan dalam metode di atas), menghasilkan suatu algoritma yang dari urutan kompleksitas komputasi bukan O asli ( 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)
}
ahfoss
sumber
Menarik. Maaf, saya tidak tahu R. Bisakah Anda menjelaskan apa yang pair.diff()dilakukan dan juga memberikan contoh numerik dengan cetakan dari setiap langkah dari fungsi Anda? Terima kasih.
ttnphns
Saya mengedit jawaban untuk menyertakan derivasi yang membenarkan perhitungan ini, tetapi saya juga memposting jawaban kedua yang berisi kode yang jauh lebih ringkas.
ahfoss
7

Mari kita coba yang sudah jelas. Dari

Dsayaj=(xsaya-xj)Σ-1(xsaya-xj)=xsayaΣ-1xsaya+xjΣ-1xj-2xsayaΣ-1xj

berikut ini kita dapat menghitung vektor

ui=xiΣ1xi

dalam waktu dan matriksO(p2)

V=XΣ1X

dalam waktu , kemungkinan besar menggunakan operasi larik cepat (paralel) yang built-in, dan kemudian membentuk solusiO(pn2+p2n)

D=uu2V

di mana adalah produk luar sehubungan dengan + : ( a b ) i j = a i + b j .+(ab)ij=ai+bj.

Sebuah Rimplementasi ringkas sejajar dengan formulasi matematis (dan mengasumsikan, dengan itu, bahwa sebenarnya dibalik dengan inverse ditulis h di sini):Σ=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)]
}

Perhatikan, untuk kompatibilitas dengan solusi lain, bahwa hanya elemen off-diagonal unik yang dikembalikan, daripada seluruh matriks jarak kuadrat (simetris, nol-di-diagonal). Scatterplots menunjukkan hasilnya setuju dengan yang dari fastPwMahal.

Dalam C atau C ++, RAM dapat digunakan kembali dan dihitung dengan cepat, menghindarkan kebutuhan untuk penyimpanan menengah u u .uuukamu

Studi pengaturan waktu dengan mulai dari 33 hingga 5000 dan p mulai dari 10 hingga 100 menunjukkan implementasi ini 1,5 hingga 5 kali lebih cepat daripada dalam kisaran itu. Peningkatan menjadi lebih baik karena p dan n meningkat. Sebagai konsekuensinya, kita dapat mengharapkan menjadi lebih baik untuk p yang lebih kecil . Titik impas terjadi di sekitar p = 7 untuk n 100n335000hal101001.55fastPwMahalhalnfastPwMahalhalhal=7n100. Apakah keunggulan komputasi yang sama dari solusi langsung ini berkaitan dengan implementasi lain mungkin merupakan masalah seberapa baik mereka mengambil keuntungan dari operasi array vektor.

whuber
sumber
Kelihatan bagus. Saya berasumsi itu bisa dibuat lebih cepat dengan hanya menghitung diagonal yang lebih rendah, meskipun saya tidak bisa begitu saja memikirkan cara untuk melakukan ini dalam R tanpa kehilangan kinerja cepat applydan outer... kecuali untuk keluar Rcpp.
ahfoss
terapkan / luar tidak memiliki keunggulan kecepatan dibandingkan loop vanilla polos.
user603
@ user603 Saya mengerti bahwa pada prinsipnya - tetapi lakukan waktunya. Selain itu, titik utama menggunakan konstruksi ini adalah untuk memberikan bantuan semantik untuk memparalelkan algoritma: perbedaan dalam bagaimana mereka mengekspresikannya adalah penting. (Mungkin perlu mengingat pertanyaan asli mencari implementasi C / Fortran / dll.) Ahfoss, saya berpikir tentang membatasi perhitungan ke segitiga yang lebih rendah juga dan setuju bahwa di Rsana sepertinya tidak ada untungnya dengan hal itu.
whuber
5

Jika Anda ingin menghitung jarak sampel Mahalanobis, maka ada beberapa trik aljabar yang dapat Anda manfaatkan. Mereka semua mengarah pada penghitungan jarak Euclidean berpasangan, jadi mari kita asumsikan kita dapat menggunakannya dist()untuk itu. Biarkan menyatakan n × p matriks data, yang kita asumsikan akan terpusat sehingga bahwa kolom yang memiliki mean 0 dan memiliki peringkat p sehingga matriks kovarians sampel adalah nonsingular. (Pemusatan membutuhkan operasi O ( n p ) .) Kemudian matriks kovarian sampel adalah S = X T X / n .Xn×ppO(np)

S=XTX/n.

Sampel berpasangan jarak Mahalanobis dari adalah sama dengan jarak Euclidean berpasangan dari X L untuk setiap matriks L yang memenuhi L L T = S - 1 , misalnya akar kuadrat atau faktor Cholesky. Ini mengikuti dari beberapa aljabar linier dan itu mengarah ke suatu algoritma yang membutuhkan perhitungan S , S - 1 , dan dekomposisi Cholesky. Kompleksitas kasus terburuk adalah O ( n p 2 + p 3 ) .X

XL.
L.L.L.T=S-1SS-1HAI(nhal2+hal3)

Lebih dalam, jarak ini berhubungan dengan jarak antara komponen utama sampel . Mari X = U D V T menyatakan SVD dari X . Kemudian S = V D 2 V T / n dan S - 1 / 2 = V D - 1 V T n 1 / 2 . Jadi X S - 1 / 2 = U V T n 1XX=UDVTX

S=VD2VT/n
S-1/2=VD-1VTn1/2.
dan jarak sampel Mahalanobis hanyalah jarak Euclidean berpasangan dariU yangdiskalakan dengan faktor
XS-1/2=UVTn1/2
U , karena jarak Euclidean adalah invarian rotasi. Ini mengarah ke suatu algoritma yang membutuhkan perhitungan SVDXyang memiliki kompleksitas kasus terburukO(np2)ketikan>p.nXHAI(nhal2)n>hal

Berikut ini adalah implementasi R dari metode kedua yang tidak dapat saya uji pada iPad yang saya gunakan untuk menulis jawaban ini.

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

Ini adalah solusi yang jauh lebih ringkas. Itu masih didasarkan pada derivasi yang melibatkan matriks kovarians akar kuadrat terbalik (lihat jawaban saya yang lain untuk pertanyaan ini), tetapi hanya menggunakan basis R dan paket statistik. Tampaknya sedikit lebih cepat (sekitar 10% lebih cepat di beberapa tolok ukur yang saya jalankan). Perhatikan bahwa ia mengembalikan jarak Mahalanobis, yang bertentangan dengan jarak Maha kuadrat.

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

Fungsi ini memerlukan matriks kovarians terbalik, dan tidak mengembalikan objek jarak - tetapi saya menduga bahwa versi fungsi ini akan lebih berguna untuk menumpuk pengguna pertukaran.

ahfoss
sumber
3
Ini bisa diperbaiki dengan mengganti SQRTdengan dekomposisi Cholesky chol(invCovMat).
vqv
1

n2

Jika Anda hanya menggunakan fitur-fitur Fortran77 di antarmuka, subrutin Anda masih cukup portabel untuk orang lain.

Horst Grünbusch
sumber
1

Ada cara yang sangat mudah untuk melakukannya menggunakan Paket R "biotools". Dalam hal ini Anda akan mendapatkan Matriks Mahalanobis Distance Squared.

#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)
Jall10
sumber
Bisakah Anda jelaskan apa arti matriks jarak kuadrat? Masing-masing: Saya tertarik pada jarak antara dua titik / vektor jadi apa yang dikatakan matriks?
Ben
1

Ini adalah kode yang diperluas dengan jawaban lama saya pindah ke sini dari utas lainnya .

Saya telah melakukan perhitungan waktu yang lama dari matriks simetris kuadrat dari jarak Mahalanobis berpasangan di SPSS melalui pendekatan matriks topi menggunakan penyelesaian sistem persamaan linear (untuk itu lebih cepat daripada membalikkan matriks kovarians).

Saya bukan pengguna R jadi saya baru saja mencoba mereproduksi @ahfoss ' resep ini di sini di SPSS bersama dengan resep "saya", pada data 1000 kasus dengan 400 variabel, dan saya menemukan cara saya jauh lebih cepat.


H

H(n-1)X(XX)-1XX

Jadi, kolom tengah dari matriks data, hitung matriks topi, kalikan dengan (n-1), dan lakukan operasi yang berlawanan dengan pemusatan ganda. Anda mendapatkan matriks jarak Mahalanobis kuadrat.

hh2h1h2cos

Dalam pengaturan kami, matriks "double-centrate" secara khusus adalah matriks topi (dikalikan dengan n-1), bukan produk skalar euclidean, dan dengan demikian matriks jarak kuadrat yang dihasilkan adalah matriks jarak Mahalanobis kuadrat, bukan matriks jarak euclidean kuadrat.

HH(n-1)H= {H,H,...}DmSebuahhSebuahl2=H+H-2H(n-1)

Kode dalam SPSS dan probe kecepatan di bawah ini.


Kode pertama ini sesuai dengan fungsi @ahfoss fastPwMahaldari jawaban yang dikutip . Ini setara dengan itu secara matematis. Tapi saya menghitung matriks simetris lengkap jarak (melalui operasi matriks) sementara @ahfoss menghitung segitiga matriks simetris (elemen demi elemen).

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

Berikut ini adalah modifikasi saya untuk membuatnya lebih cepat:

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
ttnphns
sumber
0

Rumus yang Anda poskan bukan menghitung apa yang Anda pikir Anda hitung (statistik-U).

Dalam kode yang saya posting, saya menggunakan cov(x1)scaling matrix (ini adalah varian dari perbedaan data berpasangan). Anda menggunakan cov(x0)(ini adalah matriks kovarians dari data asli Anda). Saya pikir ini adalah kesalahan Anda. Inti dari menggunakan perbedaan berpasangan adalah bahwa hal itu membebaskan Anda dari asumsi bahwa distribusi multivariat data Anda simetris di sekitar pusat simetri (atau harus memperkirakan pusat simetri dalam hal ini, karena crossprod(x1)sebanding dengan cov(x1)). Jelas, dengan menggunakan cov(x0)Anda kehilangan itu.

Ini dijelaskan dengan baik dalam makalah yang saya tautkan dalam jawaban asli saya.

pengguna603
sumber
1
Saya pikir kita berbicara tentang dua hal berbeda di sini. Metode saya menghitung jarak Mahalanobis, yang telah saya verifikasi terhadap beberapa rumus lainnya. Formula saya juga sekarang telah diverifikasi secara independen oleh Matteo Fasiolodan (saya asumsikan) whuberdi utas ini. Milikmu berbeda. Saya akan tertarik untuk memahami apa yang Anda hitung, tetapi jelas berbeda dari jarak Mahalanobis seperti yang biasanya didefinisikan.
ahfoss
@ ahfoss: 1) mahalanobis adalah jarak X ke titik simetri dalam metrik mereka. Dalam kasus Anda, X adalah sebuah matriks * * (n-1) / 2 perbedaan berpasangan, pusat simetri mereka adalah vektor 0_p dan metrik mereka adalah apa yang saya sebut cov (X1) dalam kode saya. 2) tanyakan pada diri sendiri mengapa Anda menggunakan statistik-U di tempat pertama, dan ketika makalah ini menjelaskan Anda akan melihat bahwa menggunakan cov (x0) mengalahkan tujuan itu.
user603
XXHAIhal
cov(x0)SGSτL.QD