Matriz de relleno de ocurrencias de matrices columna / fila de índices

Estoy buscando una forma eficiente de crear una matriz de ocurrencias a partir de dos matrices que contengan índices, uno representa los índices de fila en esta matriz, el otro, los de columna.

p.ej. Yo tengo:

#matrix will be size 4x3 in this example #array of rows idxs, with values from 0 to 3 [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3] #array of columns idxs, with values from 0 to 2 [0, 1, 1, 1, 2, 2, 0, 1, 2, 0, 2, 2, 2, 2] 

Y necesitamos crear una matriz de ocurrencias como:

 [[1 0 0] [0 2 0] [0 1 2] [2 1 5]] 

Puedo crear una matriz de uno de los vectores calientes en una forma simple, pero no puedo hacer que funcione cuando hay más de una aparición:

 n_rows = 4 n_columns = 3 #data rows = np.array([0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3]) columns = np.array([0, 1, 1, 1, 2, 2, 0, 1, 2, 0, 2, 2, 2, 2]) #empty matrix new_matrix = np.zeros([n_rows, n_columns]) #adding 1 for each [row, column] occurrence: new_matrix[rows, columns] += 1 print(new_matrix) 

Que devuelve:

 [[ 1. 0. 0.] [ 0. 1. 0.] [ 0. 1. 1.] [ 1. 1. 1.]] 

Parece que indexar y agregar un valor como este no funciona cuando hay más de una aparición / índice, además de imprimir parece funcionar bien:

 print(new_matrix[rows, :]) 

:

 [[ 1. 0. 0.] [ 0. 1. 0.] [ 0. 1. 0.] [ 0. 1. 1.] [ 0. 1. 1.] [ 0. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.] [ 1. 1. 1.]] 

¿Entonces tal vez me estoy perdiendo algo? ¿O esto no se puede hacer y necesito buscar otra forma de hacerlo?

Use np.add.at , especificando una tupla de índices:

 >>> np.add.at(new_matrix, (rows, columns), 1) >>> new_matrix array([[ 1., 0., 0.], [ 0., 2., 0.], [ 0., 1., 2.], [ 2., 1., 5.]]) 

np.add.at opera en la matriz en el lugar , agregando 1 tantas veces a los índices según lo especificado por la tupla (row, columns) .

Enfoque # 1

Podemos convertir esos pares en índices lineales y luego usar np.bincount

 def bincount_app(rows, columns, n_rows, n_columns): # Get linear index equivalent lidx = (columns.max()+1)*rows + columns # Use binned count on the linear indices return np.bincount(lidx, minlength=n_rows*n_columns).reshape(n_rows,n_columns) 

Ejecución de la muestra

 In [242]: n_rows = 4 ...: n_columns = 3 ...: ...: rows = np.array([0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3]) ...: columns = np.array([0, 1, 1, 1, 2, 2, 0, 1, 2, 0, 2, 2, 2, 2]) In [243]: bincount_app(rows, columns, n_rows, n_columns) Out[243]: array([[1, 0, 0], [0, 2, 0], [0, 1, 2], [2, 1, 5]]) 

Enfoque # 2

Alternativamente, podemos ordenar los índices lineales y obtener los recuentos utilizando la slicing para tener nuestro segundo enfoque, como así:

 def mask_diff_app(rows, columns, n_rows, n_columns): lidx = (columns.max()+1)*rows + columns lidx.sort() mask = np.concatenate(([True],lidx[1:] != lidx[:-1],[True])) count = np.diff(np.flatnonzero(mask)) new_matrix = np.zeros([n_rows, n_columns],dtype=int) new_matrix.flat[lidx[mask[:-1]]] = count return new_matrix 

Enfoque # 3

Esto parece ser directo con la matriz dispersa csr_matrix también, como lo hace la acumulación por sí sola para índices repetidos. El beneficio es la eficiencia de la memoria, ya que es una matriz dispersa, que se notaría si está llenando un pequeño número de lugares en la salida y una salida de matriz dispersa está bien.

La implementación se vería algo así:

 from scipy.sparse import csr_matrix def sparse_matrix_app(rows, columns, n_rows, n_columns): out_shp = (n_rows, n_columns) data = np.ones(len(rows),dtype=int) return csr_matrix((data, (rows, columns)), shape=out_shp) 

Si necesita una matriz regular / densa, simplemente haga

 sparse_matrix_app(rows, columns, n_rows, n_columns).toarray() 

Salida de muestra –

 In [319]: sparse_matrix_app(rows, columns, n_rows, n_columns).toarray() Out[319]: array([[1, 0, 0], [0, 2, 0], [0, 1, 2], [2, 1, 5]]) 

Benchmarking

Otro (s) enfoque (s) –

 # @cᴏʟᴅsᴘᴇᴇᴅ's soln def add_at_app(rows, columns, n_rows, n_columns): new_matrix = np.zeros([n_rows, n_columns],dtype=int) np.add.at(new_matrix, (rows, columns), 1) 

Tiempos

Caso # 1: Matriz de salida de forma (1000, 1000) y núm. de índices = 10k

 In [307]: # Setup ...: n_rows = 1000 ...: n_columns = 1000 ...: rows = np.random.randint(0,1000,(10000)) ...: columns = np.random.randint(0,1000,(10000)) In [308]: %timeit add_at_app(rows, columns, n_rows, n_columns) ...: %timeit bincount_app(rows, columns, n_rows, n_columns) ...: %timeit mask_diff_app(rows, columns, n_rows, n_columns) ...: %timeit sparse_matrix_app(rows, columns, n_rows, n_columns) 1000 loops, best of 3: 1.05 ms per loop 1000 loops, best of 3: 424 µs per loop 1000 loops, best of 3: 1.05 ms per loop 1000 loops, best of 3: 1.41 ms per loop 

Caso # 2: Matriz de salida de forma (1000, 1000) y núm. de índices = 100k

 In [309]: # Setup ...: n_rows = 1000 ...: n_columns = 1000 ...: rows = np.random.randint(0,1000,(100000)) ...: columns = np.random.randint(0,1000,(100000)) In [310]: %timeit add_at_app(rows, columns, n_rows, n_columns) ...: %timeit bincount_app(rows, columns, n_rows, n_columns) ...: %timeit mask_diff_app(rows, columns, n_rows, n_columns) ...: %timeit sparse_matrix_app(rows, columns, n_rows, n_columns) 100 loops, best of 3: 11.4 ms per loop 1000 loops, best of 3: 1.27 ms per loop 100 loops, best of 3: 7.44 ms per loop 10 loops, best of 3: 20.4 ms per loop 

Caso # 3: Sparse-ness en la salida

Como se indicó anteriormente, para que el método disperso funcione mejor, necesitaríamos escaso conocimiento. Un caso así sería así:

 In [314]: # Setup ...: n_rows = 5000 ...: n_columns = 5000 ...: rows = np.random.randint(0,5000,(1000)) ...: columns = np.random.randint(0,5000,(1000)) In [315]: %timeit add_at_app(rows, columns, n_rows, n_columns) ...: %timeit bincount_app(rows, columns, n_rows, n_columns) ...: %timeit mask_diff_app(rows, columns, n_rows, n_columns) ...: %timeit sparse_matrix_app(rows, columns, n_rows, n_columns) 100 loops, best of 3: 11.7 ms per loop 100 loops, best of 3: 11.1 ms per loop 100 loops, best of 3: 11.1 ms per loop 1000 loops, best of 3: 269 µs per loop 

Si necesita una matriz densa, perdemos la eficiencia de la memoria y, por lo tanto, también el rendimiento:

 In [317]: %timeit sparse_matrix_app(rows, columns, n_rows, n_columns).toarray() 100 loops, best of 3: 11.7 ms per loop