2012-03-26 16 views
17

Una classe base/comune in R è chiamata "dist" ed è una rappresentazione relativamente efficiente di una matrice di distanza simmetrica. A differenza di un oggetto "matrix", tuttavia, non sembra esserci supporto per la manipolazione di un'istanza "dist" per coppie di indici mediante l'operatore "[".Come faccio a manipolare/accedere agli elementi di un'istanza della classe "dist" utilizzando il core R?

Ad esempio, il seguente codice restituisce nulla, NULL, o un errore:

# First, create an example dist object from a matrix 
mat1 <- matrix(1:100, 10, 10) 
rownames(mat1) <- 1:10 
colnames(mat1) <- 1:10 
dist1 <- as.dist(mat1) 
# Now try to access index features, or index values 
names(dist1) 
rownames(dist1) 
row.names(dist1) 
colnames(dist1) 
col.names(dist1) 
dist1[1, 2] 

Nel frattempo, i seguenti comandi funzionano, in un certo senso, ma non rendono più facile per l'accesso/manipolare i particolari valori index-pair:

dist1[1] # R thinks of it as a vector, not a matrix? 
attributes(dist1) 
attributes(dist1)$Diag <- FALSE 
mat2 <- as(dist1, "matrix") 
mat2[1, 2] <- 0 

una soluzione - che voglio evitare - è prima convertire l'oggetto "dist" ad un "matrix", manipolare quella matrice, e poi riconvertirlo in "dist". Ciò vuol dire anche che questa non è una question about how to convert un'istanza "dist" in una "matrix" o un'altra classe in cui sono già definiti strumenti di indicizzazione della matrice comuni; poiché questa è stata risolta in diversi modi in different SO question

Esistono strumenti nel pacchetto stats (o forse un altro pacchetto di base R) indicizzazione dedicato/accedere agli elementi di un'istanza di "dist"?

+1

Buona Q. Non ho una risposta per te, ma nota che in R una matrice è solo un vettore con dimensioni. Quindi non è sorprendente che 'dist1 [1:20]' e 'dist1 [5] <- 100' e così via funzionino correttamente. Con un po 'di problemi, potresti probabilmente scrivere una versione bidimensionale, sebbene la mia familiarità con l'atomica sia limitata. –

risposta

5

Non ho una risposta diretta alla domanda, ma se si sta utilizzando la distanza euclidea, dare un'occhiata alla funzione rdist dal pacchetto fields. La sua implementazione (in Fortran) è più veloce di dist e l'output è di classe matrix. Per lo meno, mostra che alcuni sviluppatori hanno scelto di allontanarsi da questa classe dist, forse per la ragione esatta che stai citando. Se si è preoccupati che l'utilizzo di uno matrix completo per la memorizzazione di una matrice simmetrica sia un uso inefficiente della memoria, è possibile convertirlo in una matrice triangolare.

library("fields") 
points <- matrix(runif(1000*100), nrow=1000, ncol=100) 

system.time(dist1 <- dist(points)) 
# user system elapsed 
# 7.277 0.000 7.338 

system.time(dist2 <- rdist(points)) 
# user system elapsed 
# 2.756 0.060 2.851 

class(dist2) 
# [1] "matrix" 
dim(dist2) 
# [1] 1000 1000 
dist2[1:3, 1:3] 
#    [,1]   [,2]   [,3] 
# [1,] 0.0000000001 3.9529674733 3.8051198575 
# [2,] 3.9529674733 0.0000000001 3.6552146293 
# [3,] 3.8051198575 3.6552146293 0.0000000001 
+0

Grazie! Questo è utile sapere. E 'utile sapere che gli strumenti di base di "dist" '-handling in R sono piuttosto spartani. –

2

as.matrix(d) girerà la dist oggetto d in una matrice, mentre as.dist(m) si accende la matrice m indietro in un oggetto dist. Si noti che quest'ultimo non controlla effettivamente che m sia una matrice di distanza valida; estrae solo la parte triangolare inferiore.

+0

Questa risposta è stata già menzionata alla fine della mia domanda (ultimo paragrafo e 'come (dist1," matrice ")' nel codice di esempio. Mi chiedo se esiste una soluzione "in place" - separata da questa soluzione alternativa - - supportato dalla classe '" dist "' in R. Grazie per il commento su 'as.dist' che non verifica la validità dell'istanza' dist'. –

+0

Non vedo la distinzione La soluzione "in place" per l'indicizzazione in stile matrice * è * per usare 'as.matrix' su un oggetto' dist': il generico 'as.matrix' chiama' stats ::: as.matrix.dist', che è il metodo per il 'dist 'classe. –

+0

Ho tentato di chiarire la fine della domanda.Penso che la confusione derivi dall'ambiguità della frase "in atto", che suppongo possa riferirsi a (1) qualsiasi strumento già in R, inclusi approcci che sono indesiderabili per qualche ragione, come il flip-flop di classe che voglio evitare; o (2) uno strumento dedicato a questo scopo e classe che non cambierà la classe del mio oggetto per completare l'attività, anche temporaneamente. –

0

Si potrebbe fare questo:

d <- function(distance, selection){ 
    eval(parse(text = paste("as.matrix(distance)[", 
       selection, "]"))) 
} 

`d<-` <- function(distance, selection, value){ 
    eval(parse(text = paste("as.matrix(distance)[", 
       selection, "] <- value"))) 
    as.dist(distance) 
} 

che permetterebbe di fare questo:

mat <- matrix(1:12, nrow=4) 
mat.d <- dist(mat) 
mat.d 
     1 2 3 
    2 1.7   
    3 3.5 1.7  
    4 5.2 3.5 1.7 

d(mat.d, "3, 2") 
    [1] 1.7 
d(mat.d, "3, 2") <- 200 
mat.d 
      1  2  3 
    2 1.7    
    3 3.5 200.0  
    4 5.2 3.5 1.7 

Tuttavia, le eventuali modifiche apportate al triangolo diagonale o superiore vengono ignorati. Potrebbe essere o non essere la cosa giusta da fare. Se non lo è, dovrai aggiungere una sorta di controllo di sanità mentale o una gestione appropriata per quei casi. E probabilmente altri.

+0

Grazie Tyler. Questo sembra ancora essere un flip-flop di classe (anche se intelligente e utile), che potrebbe anche avere il potenziale per uccidere alcuni degli attributi potenzialmente utili nella tua istanza originale "dist", come "$ call". Sono curioso di cosa pensi della risposta che ho appena postato qui sotto, che include una funzione di accesso funzionante che non modifica la classe, oltre a una funzione di sostituzione non funzionante che non ho ancora risolto. –

+0

@Paul, la tua soluzione sembra buona, anche se per qualche motivo restituisce il valore sbagliato per le diagonali. Non so perché la funzione di sostituzione non funzioni. – Tyler

0

Non sembrano esserci strumenti nel pacchetto stats per questo. Grazie a @flodel per un'implementazione alternativa in un pacchetto non core.

Ho scavato nella definizione della classe "dist" nel sorgente di base R, che è S3 di vecchia scuola senza strumenti nel file di origine dist.R come quello che sto chiedendo in questa domanda.

La documentazione della funzione dist() fa notare, utilmente, che (cito):

Il triangolo inferiore della matrice di distanza memorizzate da colonne in un vettore, diciamo do. Se n è il numero di osservazioni, cioè, n <- attr(do, "Size"), poi per i < j ≤ n, la diversità tra (riga) i e j è:

do[n*(i-1) - i*(i-1)/2 + j-i]

La lunghezza del vettore è n*(n-1)/2, cioè, di ordine n^2.

(fine citazione)

ho approfittato di questo nel seguente codice di esempio per una definizione da-te "dist" di accesso. Nota che questo esempio può solo restituire un valore alla volta.

################################################################################ 
# Define dist accessor 
################################################################################ 
setOldClass("dist") 
getDistIndex <- function(x, i, j){ 
    n <- attr(x, "Size") 
    if(class(i) == "character"){ i <- which(i[1] == attr(x, "Labels")) } 
    if(class(j) == "character"){ j <- which(j[1] == attr(x, "Labels")) } 
    # switch indices (symmetric) if i is bigger than j 
    if(i > j){ 
     i0 <- i 
     i <- j 
     j <- i0 
    } 
    # for i < j <= n 
    return(n*(i-1) - i*(i-1)/2 + j-i) 
} 
# Define the accessor 
"[.dist" <- function(x, i, j, ...){ 
    x[[getDistIndex(x, i, j)]] 
} 
################################################################################ 

E questo sembra funzionare bene, come previsto. Tuttavia, ho problemi a far funzionare la funzione di sostituzione.

################################################################################ 
# Define the replacement function 
################################################################################ 
"[.dist<-" <- function(x, i, j, value){ 
    x[[get.dist.index(x, i, j)]] <- value 
    return(x) 
} 
################################################################################ 

Un test-run di questo nuovo operatore di assegnazione

dist1["5", "3"] <- 7000 

Returns:

"R> Errore in dist1["5", "3"] <- 7000: numero errato di indici su matrice"

As-chiesto , Penso che @flodel abbia risposto meglio alla domanda, ma ho sempre pensato che questa "risposta" potesse essere utile.

Ho anche trovato alcuni esempi S4 di accessori di sostituzione e sostituzione di parentesi quadra nello Matrix package, che potrebbe essere adattato abbastanza facilmente da questo esempio corrente.

8

Non ci sono modi standard per farlo, sfortunatamente. Ecco due funzioni che convergono tra l'indice 1D nelle coordinate della matrice 2D. Non sono belli, ma funzionano, e almeno puoi usare il codice per fare qualcosa di più bello se ne hai bisogno. Sto postando solo perché le equazioni non sono ovvie.

distdex<-function(i,j,n) #given row, column, and n, return index 
    n*(i-1) - i*(i-1)/2 + j-i 

rowcol<-function(ix,n) { #given index, return row and column 
    nr=ceiling(n-(1+sqrt(1+4*(n^2-n-2*ix)))/2) 
    nc=n-(2*n-nr+1)*nr/2+ix+nr 
    cbind(nr,nc) 
} 

Un po 'di test harness per vederlo funziona:

dist(rnorm(20))->testd 
as.matrix(testd)[7,13] #row<col 
distdex(7,13,20) # =105 
testd[105] #same as above 

testd[c(42,119)] 
rowcol(c(42,119),20) # = (3,8) and (8,15) 
as.matrix(testd)[3,8] 
as.matrix(testd)[8,15] 
+0

Questa è una risposta per lo più utile, ma data l'applicazione prevista richiede alcuni chiarimenti. Funziona solo se io j restituisce la risposta sbagliata. Modificando la funzione distdex per restituire 0 quando i == j e per trasporre i e j quando i> j risolve il problema, ho inserito il codice nella mia risposta di seguito in modo che altri potessero semplicemente copiare e incollare. Per essere chiari, questo è solo un problema con le query non standard della matrice delle distanze, quindi non uno scavo sulla risposta di Christian A solo un chiarimento. – csfowler

0

Sembra oggetti dist sono trattati più o meno allo stesso modo come semplici oggetti vettoriali. Per quanto posso vedere è un vettore con attributi. Quindi per ottenere i valori:

x = as.vector(distobject) 

Vedere? dist per una formula per estrarre la distanza tra una specifica coppia di oggetti usando i loro indici.

+0

Questa (coercizione) era già descritta in altre risposte ed è stata annotata nella domanda come qualcosa che sto cercando di evitare. Preferirei anche un codice che tenti almeno un qualche tipo di estrazione o assegnazione in stile matrix '" ["notazione per essere una valida risposta. –

1

Potresti trovare utile [da ??dist]:

The lower triangle of the distance matrix stored by columns in a vector, say ‘do’. If ‘n’ is the number of observations, i.e., ‘n <- attr(do, "Size")’, then for i < j <= n, the dissimilarity between (row) i and j is ‘do[n*(i-1) - i*(i-1)/2 + j-i]’. The length of the vector is n*(n-1)/2, i.e., of order n^2.

+0

La mia risposta parziale ha incluso quella formula per un lungo periodo. Il controllo del documento su '? Dist' è stata la prima cosa che ho fatto, molto prima di postare questa domanda in SO. –

0

Conversione in una matrice era anche fuori questione per me, perché la matrice risultante sarebbe 35K da 35K, così ho lasciato come un vettore (risultato di dist) e ha scritto una funzione per trovare collocati nel vettore in cui la distanza dovrebbe essere:

distXY <- function(X,Y,n){ 
    A=min(X,Y) 
    B=max(X,Y) 

    d=eval(parse(text= 
       paste0("(A-1)*n -",paste0((1:(A-1)),collapse="-"),"+ B-A"))) 

    return(d) 

} 

dove si forniscono X e Y, le file originali degli elementi della matrice da cui si è calcolato dist, ed n è il numero totale di elementi che matrice. Il risultato è la posizione nel vettore dist dove sarà la distanza. Spero che abbia senso.

1

Questa risposta è in realtà solo un seguito prolungato alla risposta precedente di Christian A. È garantito perché alcuni lettori della domanda (me compreso) possono interrogare l'oggetto dist come se fosse simmetrico (non solo (7,13) come sotto ma anche (13,7). Non ho i privilegi di modifica e il risposta precedente era corretta fintanto che l'utente stava trattando l'oggetto dist come oggetto dist e non una matrice sparsa ed è per questo che ho una risposta separata piuttosto che una modifica. Vota Christian A per fare il sollevamento pesante se questa risposta è utile . La risposta originale con le mie modifiche incollato in:

distdex<-function(i,j,n) #given row, column, and n, return index 
    n*(i-1) - i*(i-1)/2 + j-i 

rowcol<-function(ix,n) { #given index, return row and column 
    nr=ceiling(n-(1+sqrt(1+4*(n^2-n-2*ix)))/2) 
    nc=n-(2*n-nr+1)*nr/2+ix+nr 
    cbind(nr,nc) 
} 
#A little test harness to show it works: 

dist(rnorm(20))->testd 
as.matrix(testd)[7,13] #row<col 
distdex(7,13,20) # =105 
testd[105] #same as above 

Ma ... funzione funziona solo

distdex(13,7,20) # =156 
testd[156] #the wrong answer 

cristiana di a se io < j. Per i = j e i> j restituisce la risposta sbagliata. Modifica la funzione distdex per tornare 0 quando i == j e di recepire i e j quando i> j risolve il problema in modo:

distdex2<-function(i,j,n){ #given row, column, and n, return index 
    if(i==j){0 
    }else if(i > j){ 
    n*(j-1) - j*(j-1)/2 + i-j 
    }else{ 
    n*(i-1) - i*(i-1)/2 + j-i 
    } 
} 

as.matrix(testd)[7,13] #row<col 
distdex2(7,13,20) # =105 
testd[105] #same as above 
distdex2(13,7,20) # =105 
testd[105] #the same answer 
2

È possibile acces i atributes di qualsiasi oggetto con str()

per un "dist" oggetto di alcuni dei miei dati (dist1), è simile a questa:

> str(dist1) 
Class 'dist' atomic [1:4560] 7.3 7.43 7.97 7.74 7.55 ... 
    ..- attr(*, "Size")= int 96 
    ..- attr(*, "Labels")= chr [1:96] "1" "2" "3" "4" ... 
    ..- attr(*, "Diag")= logi FALSE 
    ..- attr(*, "Upper")= logi FALSE 
    ..- attr(*, "method")= chr "euclidean" 
    ..- attr(*, "call")= language dist(x = dist1) 

si può vedere che per questo particolare insieme di dati, le "etichette" attributo è una stringa di caratteri di lunghezza = 96 con numeri da 1 a 96 come caratteri.

è possibile cambiare direttamente che stringa di caratteri facendo:

> attr(dist1,"Labels") <- your.labels 

"your.labels" dovrebbero essere un po 'id. o vettore di fattore, presumibilmente nei dati originali da con l'oggetto "dist" è stato fatto.

Problemi correlati