Tengo una lista de nombres como:
names = ['A', 'B', 'C', 'D']
y una lista de documentos, que en cada documento se mencionan algunos de estos nombres.
document =[['A', 'B'], ['C', 'B', 'K'],['A', 'B', 'C', 'D', 'Z']]
Me gustaría obtener una salida como una matriz de co-ocurrencias como:
ABCD A 0 2 1 1 B 2 0 2 1 C 1 2 0 1 D 1 1 1 0
Hay una solución ( Crear matriz de co-ocurrencia ) para este problema en R, pero no pude hacerlo en Python. Estoy pensando en hacerlo en Pandas, ¡pero todavía no hay progreso!
Otra opción es usar el constructor csr_matrix((data, (row_ind, col_ind)), [shape=(M, N)])
de scipy.sparse.csr_matrix donde data
, row_ind
y col_ind
satisfacen la relación a[row_ind[k], col_ind[k]] = data[k]
.
El truco consiste en generar row_ind
y col_ind
iterando sobre los documentos y creando una lista de tuplas (doc_id, word_id). data
serían simplemente un vector de otros de la misma longitud.
Multiplicar la matriz docs-words por su transpuesta te daría la matriz de co-ocurrencias.
Además, esto es eficiente en términos de tiempos de ejecución y uso de memoria, por lo que también debe manejar corpus grandes.
import numpy as np import itertools from scipy.sparse import csr_matrix def create_co_occurences_matrix(allowed_words, documents): print(f"allowed_words:\n{allowed_words}") print(f"documents:\n{documents}") word_to_id = dict(zip(allowed_words, range(len(allowed_words)))) documents_as_ids = [np.sort([word_to_id[w] for w in doc if w in word_to_id]).astype('uint32') for doc in documents] row_ind, col_ind = zip(*itertools.chain(*[[(i, w) for w in doc] for i, doc in enumerate(documents_as_ids)])) data = np.ones(len(row_ind), dtype='uint32') # use unsigned int for better memory utilization max_word_id = max(itertools.chain(*documents_as_ids)) + 1 docs_words_matrix = csr_matrix((data, (row_ind, col_ind)), shape=(len(documents_as_ids), max_word_id)) # efficient arithmetic operations with CSR * CSR words_cooc_matrix = docs_words_matrix.T * docs_words_matrix # multiplying docs_words_matrix with its transpose matrix would generate the co-occurences matrix words_cooc_matrix.setdiag(0) print(f"words_cooc_matrix:\n{words_cooc_matrix.todense()}") return words_cooc_matrix, word_to_id
Ejecutar ejemplo:
allowed_words = ['A', 'B', 'C', 'D'] documents = [['A', 'B'], ['C', 'B', 'K'],['A', 'B', 'C', 'D', 'Z']] words_cooc_matrix, word_to_id = create_co_occurences_matrix(allowed_words, documents)
Producción:
allowed_words: ['A', 'B', 'C', 'D'] documents: [['A', 'B'], ['C', 'B', 'K'], ['A', 'B', 'C', 'D', 'Z']] words_cooc_matrix: [[0 2 1 1] [2 0 2 1] [1 2 0 1] [1 1 1 0]]
from collections import OrderedDict document = [['A', 'B'], ['C', 'B'], ['A', 'B', 'C', 'D']] names = ['A', 'B', 'C', 'D'] occurrences = OrderedDict((name, OrderedDict((name, 0) for name in names)) for name in names) # Find the co-occurrences: for l in document: for i in range(len(l)): for item in l[:i] + l[i + 1:]: occurrences[l[i]][item] += 1 # Print the matrix: print(' ', ' '.join(occurrences.keys())) for name, values in occurrences.items(): print(name, ' '.join(str(i) for i in values.values()))
Producción;
ABCD A 0 2 1 1 B 2 0 2 1 C 1 2 0 1 D 1 1 1 0
Obviamente, esto puede extenderse para sus propósitos, pero realiza la operación general en mente:
import math for a in 'ABCD': for b in 'ABCD': count = 0 for x in document: if a != b: if a in x and b in x: count += 1 else: n = x.count(a) if n >= 2: count += math.factorial(n)/math.factorial(n - 2)/2 print '{} x {} = {}'.format(a, b, count)