python arrays performance numpy

python - La forma más rápida de convertir una lista de índices a una matriz numpy 2D de unos



arrays performance (5)

¿Qué hay de usar la indexación de matriz? Si supiera más acerca de su entrada, podría deshacerse de la penalización por tener que convertir primero a una matriz lineal.

import numpy as np def main(): row_count = 4 col_count = 5 a = [[1,2,4],[0,2,3],[1,3,4],[0,2]] # iterate through each row, concatenate all indices and convert them to linear # numpy append performs copy even if you don''t want it, list append is faster b = [] for row_idx, row in enumerate(a): b.append(np.array(row, dtype=np.int64) + (row_idx * col_count)) linear_idxs = np.hstack(b) #could skip previous steps if given index inputs well before hand, or in linear index order. c = np.zeros(row_count * col_count) c[linear_idxs] = 1 c = c.reshape(row_count, col_count) print(c) if __name__ == "__main__": main() #output # [[0. 1. 1. 0. 1.] # [1. 0. 1. 1. 0.] # [0. 1. 0. 1. 1.] # [1. 0. 1. 0. 0.]]

Tengo una lista de indices

a = [ [1,2,4], [0,2,3], [1,3,4], [0,2]]

¿Cuál es la forma más rápida de convertir esto en una gran variedad de unidades, donde cada índice muestra la posición en la que ocurriría 1?

Es decir lo que quiero es:

output = array([ [0,1,1,0,1], [1,0,1,1,0], [0,1,0,1,1], [1,0,1,0,0]])

Sé de antemano el tamaño máximo de la matriz. Sé que podría recorrer cada lista e insertar un 1 en cada posición del índice, pero ¿hay una forma más rápida / vectorizada de hacerlo?

Mi caso de uso podría tener miles de filas / cols y tengo que hacer esto miles de veces, así que cuanto más rápido mejor.


En caso de que pueda y quiera usar Cython , puede crear una solución legible (al menos si no le importa la escritura) y rápida.

Aquí estoy usando los enlaces IPython de Cython para compilarlo en un cuaderno Jupyter:

%load_ext cython

%%cython cimport cython cimport numpy as cnp import numpy as np @cython.boundscheck(False) # remove this if you cannot guarantee that nrow/ncol are correct @cython.wraparound(False) cpdef cnp.int_t[:, :] mseifert(list a, int nrow, int ncol): cdef cnp.int_t[:, :] out = np.zeros([nrow, ncol], dtype=int) cdef list subl cdef int row_idx cdef int col_idx for row_idx, subl in enumerate(a): for col_idx in subl: out[row_idx, col_idx] = 1 return out

Para comparar el rendimiento de las soluciones presentadas aquí, uso mi biblioteca simple_benchmark :

Tenga en cuenta que esto utiliza el eje logarítmico para mostrar simultáneamente las diferencias para matrices grandes y pequeñas. Según mi punto de referencia, mi función es en realidad la más rápida de las soluciones, sin embargo, también vale la pena señalar que todas las soluciones no están demasiado lejos.

Aquí está el código completo que utilicé para el punto de referencia:

import numpy as np from simple_benchmark import BenchmarkBuilder, MultiArgument import itertools b = BenchmarkBuilder() @b.add_function() def pp(a, nrow, ncol): sz = np.fromiter(map(len, a), int, nrow) out = np.zeros((nrow, ncol), int) out[np.arange(nrow).repeat(sz), np.fromiter(itertools.chain.from_iterable(a), int, sz.sum())] = 1 return out @b.add_function() def ts(a, nrow, ncol): out = np.zeros((nrow, ncol), int) for i, ix in enumerate(a): out[i][ix] = 1 return out @b.add_function() def u9(a, nrow, ncol): out = np.zeros((nrow, ncol), int) for i, (x, y) in enumerate(zip(a, out)): y[x] = 1 out[i] = y return out b.add_functions([mseifert]) @b.add_arguments("number of rows/columns") def argument_provider(): for n in range(2, 13): ncols = 2**n a = [ sorted(set(np.random.randint(0, ncols, size=np.random.randint(0, ncols)))) for _ in range(ncols) ] yield ncols, MultiArgument([a, ncols, ncols]) r = b.run() r.plot()


Esta podría no ser la forma más rápida. Tendrá que comparar los tiempos de ejecución de estas respuestas utilizando matrices grandes para encontrar la manera más rápida. Aquí está mi solución

output = np.zeros((4,5)) for i, ix in enumerate(a): output[i][ix] = 1 # output -> # array([[0, 1, 1, 0, 1], # [1, 0, 1, 1, 0], # [0, 1, 0, 1, 1], # [1, 0, 1, 0, 0]])


Puede que no sea la mejor manera, sino la única en la que puedo pensar:

output = np.zeros((4,5)) for i, (x, y) in enumerate(zip(a, output)): y[x] = 1 output[i] = y print(output)

Qué salidas:

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


Qué tal esto:

ncol = 5 nrow = len(a) out = np.zeros((nrow, ncol), int) out[np.arange(nrow).repeat([*map(len,a)]), np.concatenate(a)] = 1 out # array([[0, 1, 1, 0, 1], # [1, 0, 1, 1, 0], # [0, 1, 0, 1, 1], # [1, 0, 1, 0, 0]])

Aquí hay tiempos para una matriz binaria de 1000x1000, tenga en cuenta que uso una versión optimizada de lo anterior, vea la función pp continuación:

pp 21.717635259992676 ms ts 37.10938713003998 ms u9 37.32933565042913 ms

Código para producir tiempos:

import itertools as it import numpy as np def make_data(n,m): I,J = np.where(np.random.random((n,m))<np.random.random((n,1))) return [*map(np.ndarray.tolist, np.split(J, I.searchsorted(np.arange(1,n))))] def pp(): sz = np.fromiter(map(len,a),int,nrow) out = np.zeros((nrow,ncol),int) out[np.arange(nrow).repeat(sz),np.fromiter(it.chain.from_iterable(a),int,sz.sum())] = 1 return out def ts(): out = np.zeros((nrow,ncol),int) for i, ix in enumerate(a): out[i][ix] = 1 return out def u9(): out = np.zeros((nrow,ncol),int) for i, (x, y) in enumerate(zip(a, out)): y[x] = 1 out[i] = y return out nrow,ncol = 1000,1000 a = make_data(nrow,ncol) from timeit import timeit assert (pp()==ts()).all() assert (pp()==u9()).all() print("pp", timeit(pp,number=100)*10, "ms") print("ts", timeit(ts,number=100)*10, "ms") print("u9", timeit(u9,number=100)*10, "ms")