Calculando Jaccard ou outro coeficiente de associação para dados binários usando multiplicação de matrizes

9

Quero saber se existe alguma maneira possível de calcular o coeficiente de Jaccard usando a multiplicação de matrizes.

Eu usei esse 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]        
       }
     }

Isso é bastante aceitável para implementar em R. Eu fiz a semelhança de dados, mas fiquei preso com Tanimoto / Jaccard. Alguém pode ajudar?

user4959
fonte
Parece que o @ttnphns cobriu isso, mas como você está usando o R, pensei em apontar também que vários índices de similaridade (incluindo os de Jaccard) já estão implementados no veganpacote. Eu acho que eles tendem a ser bastante otimizados para velocidade também.
David J. Harris

Respostas:

11

Xaa+b+ca+da+d+2(b+c)

  • a - número de linhas em que as duas colunas são 1
  • b - número de linhas em que esta e não a outra coluna é 1
  • c - número de linhas onde a outra e não esta coluna é 1
  • d - número de linhas em que as duas colunas são 0

a+b+c+d=nX

Então nós temos:

XX=Aa

(notX)(notX)=Dd

AnD

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

Eu verifiquei numericamente se essas fórmulas dão resultado correto. Eles fazem.


BC

B=[1]XAXBbX

C=B

DnABC

A,B,C,D

ttnphns
fonte
As frações não fazem sentido para as matrizes, a menos que sejam comutadas: multiplicar à direita por uma inversa dará um resultado diferente do que multiplicar à esquerda. Além disso, geralmente não é o caso de um produto de duas matrizes simétricas ser simétrico. Você quer dizer divisão componente por componente? Você poderia consertar sua notação para refletir qual é a fórmula correta?
whuber
@whuber Não uso inversão nem multiplicação de matrizes simétricas quadradas . X é a matriz de dados binários e X'X é sua matriz SSCP. not Xé X onde 1-> 0, 0-> 1. E qualquer divisão aqui é divisão elementar. Corrija minha notação se você achar que não é apropriado.
ttnphns
Como calcular o produto interno (notX) ′ (notX) em R?
usar o seguinte comando
@ user4959, não sei R. Aqui ! X é recomendado; no entanto, o resultado é booleano TRUE / FALSE, não numérico 1/0. Observe que atualizei minha resposta, onde digo que também há outra maneira de chegar à matriz D.
ttnphns
9

A solução acima não é muito boa se X for escasso. Porque tomar! X criará uma matriz densa, consumindo uma quantidade enorme de memória e computação.

Uma solução melhor é usar a fórmula Jaccard [i, j] = #comum / (#i + #j - #comum) . Com matrizes esparsas, você pode fazer o seguinte (observe que o código também funciona para matrizes não esparsas):

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 )
}
user41844
fonte
1

Isso pode ou não ser útil para você, dependendo de quais são suas necessidades. Supondo que você esteja interessado em similaridade entre atribuições de cluster:

O coeficiente de semelhança Jaccard ou o índice Jaccard pode ser usado para calcular a semelhança de duas atribuições de cluster.

Dadas as etiquetas L1e L2, Ben-Hur, Elisseeff e Guyon (2002) mostraram que o índice de Jaccard pode ser calculado usando produtos pontuais de uma matriz intermediária. O código abaixo aproveita isso para calcular rapidamente o índice Jaccard sem precisar armazenar as matrizes intermediárias na memória.

O código é escrito em C ++, mas pode ser carregado no R usando o 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));
}
Richard
fonte