Cálculo de Jaccard u otro coeficiente de asociación para datos binarios usando la multiplicación de matrices

9

Quiero saber si hay alguna forma posible de calcular el coeficiente de Jaccard usando la multiplicación de matrices.

Usé este código

    jaccard_sim <- function(x) {
    # initialize similarity matrix
    m <- matrix(NA, nrow=ncol(x),ncol=ncol(x),dimnames=list(colnames(x),colnames(x)))
    jaccard <- as.data.frame(m)

    for(i in 1:ncol(x)) {
     for(j in i:ncol(x)) {
        jaccard[i,j]= length(which(x[,i] & x[,j])) / length(which(x[,i] | x[,j]))
        jaccard[j,i]=jaccard[i,j]        
       }
     }

Esto está bastante bien para implementar en R. He hecho la similitud de dados, pero me quedé atascado con Tanimoto / Jaccard. ¿Alguien puede ayudar?

usuario4959
fuente
Parece que @ttnphns tiene esto cubierto, pero como estás usando R, pensé que también señalaría que varios índices de similitud (incluidos los de Jaccard) ya están implementados en el veganpaquete. Creo que también tienden a estar bastante bien optimizados para la velocidad.
David J. Harris

Respuestas:

11

Sabemos que Jaccard (calculada entre dos columnas de datos binarios ) es unX , mientras que Rogers-Tanimoto esa+daa+b+c , dondea+da+d+2(b+c)

  • a - número de filas donde ambas columnas son 1
  • b - número de filas donde esta y no la otra columna es 1
  • c - número de filas donde la otra y no esta columna es 1
  • d - número de filas donde ambas columnas son 0

, el número de filas en Xa+b+c+d=nX

Entonces nosotros tenemos:

XX=Aa

(notX)(notX)=Dd

AnD

A+DA+D+2(n(A+D))=A+D2nAD

Verifiqué numéricamente si estas fórmulas dan el resultado correcto. Ellas hacen.


BC

B=[1]XAXBbX

C=B

DnABC

A,B,C,D

ttnphns
fuente
Las fracciones no tienen sentido para las matrices a menos que se conmuten: multiplicar a la derecha por un inverso dará un resultado diferente que multiplicar a la izquierda. Además, generalmente no es el caso que un producto de dos matrices simétricas sea simétrico. ¿Quizás te refieres a la división componente por componente? ¿Podrías arreglar tu notación para reflejar lo que piensas que es la fórmula correcta?
whuber
@whuber No uso inversión ni multiplicación de matrices simétricas cuadradas . X es la matriz de datos binarios y X'X es su matriz SSCP. not Xes X donde 1-> 0, 0-> 1. Y cualquier división aquí es división por elementos. Corrija mi notación si ve que no es apropiada.
ttnphns
¿Cómo calcular el producto interno (notX) ′ (notX) en R?
user4959
@ user4959, no sé R. Aquí se recomienda X; sin embargo, el resultado es booleano VERDADERO / FALSO, no numérico 1/0. Tenga en cuenta que actualicé mi respuesta donde digo que también hay otra forma de llegar a la matriz D.
ttnphns
9

La solución anterior no es muy buena si X es escasa. Porque tomar! X creará una matriz densa, que requerirá una gran cantidad de memoria y cálculo.

Una mejor solución es usar la fórmula Jaccard [i, j] = #common / (#i + #j - #common) . Con matrices dispersas puede hacerlo de la siguiente manera (tenga en cuenta que el código también funciona para matrices no dispersas):

library(Matrix)
jaccard <- function(m) {
    ## common values:
    A = tcrossprod(m)
    ## indexes for non-zero common values
    im = which(A > 0, arr.ind=TRUE)
    ## counts for each row
    b = rowSums(m)

    ## only non-zero values of common
    Aim = A[im]

    ## Jacard formula: #common / (#i + #j - #common)
    J = sparseMatrix(
          i = im[,1],
          j = im[,2],
          x = Aim / (b[im[,1]] + b[im[,2]] - Aim),
          dims = dim(A)
    )

    return( J )
}
usuario41844
fuente
1

Esto puede o no ser útil para usted, dependiendo de cuáles sean sus necesidades. Suponiendo que esté interesado en la similitud entre las tareas de agrupación:

El coeficiente de similitud de Jaccard o índice de Jaccard se pueden usar para calcular la similitud de dos asignaciones de agrupamiento.

Dados los etiquetados L1y L2, Ben-Hur, Elisseeff y Guyon (2002) han demostrado que el índice Jaccard se puede calcular utilizando productos de punto de una matriz intermedia. El siguiente código aprovecha esto para rápidamente calcular el índice Jaccard sin tener que almacenar las matrices intermedias en la memoria.

El código está escrito en C ++, pero se puede cargar en R usando el sourceCppcomando.

/**
 * The Jaccard Similarity Coefficient or Jaccard Index is used to compare the
 * similarity/diversity of sample sets. It is defined as the size of the
 * intersection of the sets divided by the size of the union of the sets. Here,
 * it is used to determine how similar to clustering assignments are.
 *
 * INPUTS:
 *    L1: A list. Each element of the list is a number indicating the cluster
 *        assignment of that number.
 *    L2: The same as L1. Must be the same length as L1.
 *
 * RETURNS:
 *    The Jaccard Similarity Index
 *
 * SIDE-EFFECTS:
 *    None
 *
 * COMPLEXITY:
 *    Time:  O(K^2+n), where K = number of clusters
 *    Space: O(K^2)
 *
 * SOURCES:
 *    Asa Ben-Hur, Andre Elisseeff, and Isabelle Guyon (2001) A stability based
 *    method for discovering structure in clustered data. Biocomputing 2002: pp.
 *    6-17. 
 */
// [[Rcpp::export]]
NumericVector JaccardIndex(const NumericVector L1, const NumericVector L2){
  int n = L1.size();
  int K = max(L1);

  int overlaps[K][K];
  int cluster_sizes1[K], cluster_sizes2[K];

  for(int i = 0; i < K; i++){    // We can use NumericMatrix (default 0) 
    cluster_sizes1[i] = 0;
    cluster_sizes2[i] = 0;
    for(int j = 0; j < K; j++)
      overlaps[i][j] = 0;
  }

  //O(n) time. O(K^2) space. Determine the size of each cluster as well as the
  //size of the overlaps between the clusters.
  for(int i = 0; i < n; i++){
    cluster_sizes1[(int)L1[i] - 1]++; // -1's account for zero-based indexing
    cluster_sizes2[(int)L2[i] - 1]++;
    overlaps[(int)L1[i] - 1][(int)L2[i] - 1]++;
  }

  // O(K^2) time. O(1) space. Square the overlap values.
  int C1dotC2 = 0;
  for(int j = 0; j < K; j++){
    for(int k = 0; k < K; k++){
      C1dotC2 += pow(overlaps[j][k], 2);
    }
  }

  // O(K) time. O(1) space. Square the cluster sizes
  int C1dotC1 = 0, C2dotC2 = 0;
  for(int i = 0; i < K; i++){
    C1dotC1 += pow(cluster_sizes1[i], 2);
    C2dotC2 += pow(cluster_sizes2[i], 2);
  }

  return NumericVector::create((double)C1dotC2/(double)(C1dotC1+C2dotC2-C1dotC2));
}
Ricardo
fuente