Numpy: forma eficiente de generar combinaciones de rangos dados

Tengo una matriz de n dimensiones como se muestra a continuación:

np.array([[0,3],[0,3],[0,10]]) 

En esta matriz, los elementos denotan los valores bajos y altos. Ej: [0,3] refiere a [0,1,2,3]

Necesito generar una combinación de todos los valores usando los rangos dados como se indica arriba. Por ejemplo, quiero [0,0,0], [0,0,1] ... [0,1,0] ... [3,3,10]

He intentado lo siguiente para obtener lo que quiero:

 ds = np.array([[0,3],[0,3],[0,10]]) nItems = int(reduce(lambda a,b: a * (b[1] - b[0] + 1), ds, 1)) myCombinations = np.zeros((nItems,)) nArrays = [] for x in range(ds.shape[0]): low = ds[x][0] high= ds[x][1] nitm = high - low + 1 ar = [x+low for x in range(nitm) ] nArrays.append(ar) myCombinations = cartesian(nArrays) 

La función cartesiana se tomó de Usar numpy para construir una matriz de todas las combinaciones de dos matrices

Necesito hacer esto unos cuantos millones de veces .

Mi pregunta: ¿hay alguna forma mejor / eficiente de hacer esto?

Creo que lo que estás buscando es np.mgrid . Desafortunadamente, esto devuelve la matriz en un formato que es diferente de lo que necesita, por lo que tendrá que hacer un poco de procesamiento posterior:

 a = np.mgrid[0:4, 0:4, 0:11] # All points in a 3D grid within the given ranges a = np.rollaxis(a, 0, 4) # Make the 0th axis into the last axis a = a.reshape((4 * 4 * 11, 3)) # Now you can safely reshape while preserving order 

Explicación

np.mgrid te da un conjunto de puntos de cuadrícula en el espacio N-dimensional. Permítame tratar de mostrar esto con un ejemplo más pequeño, para aclarar las cosas:

 >>> a = np.mgrid[0:2, 0:2] >>> a array([[[0, 0], [1, 1]], [[0, 1], [0, 1]]]) 

Dado que he dado dos conjuntos de rangos, 0:2, 0:2 , obtengo una cuadrícula 2D. Lo que devuelve mgrid son los valores de x y los valores de y correspondientes a los puntos de la cuadrícula (0, 0), (0, 1), (1, 0) y (1, 1) en el espacio 2D. a[0] te dice cuáles son los valores de x de los cuatro puntos, y a[1] te dice cuáles son los valores de y.

Pero lo que realmente desea es esa lista de puntos de cuadrícula reales que he escrito, no los valores x e y de esos puntos por separado. El primer instinto es simplemente remodelar la matriz como se desee:

 >>> a.reshape((4, 2)) array([[0, 0], [1, 1], [0, 1], [0, 1]]) 

Pero claramente esto no funciona, porque efectivamente remodela la matriz aplanada (la matriz obtenida simplemente leyendo todos los elementos en orden), y eso no es lo que quieres.

Lo que quieres hacer es mirar hacia abajo en la tercera dimensión de a , y crear una matriz:

 [ [a[0][0, 0], a[1][0, 0]], [a[0][0, 1], a[1][0, 1]], [a[0][1, 0], a[1][1, 0]], [a[0][1, 1], a[1][1, 1]] ] 

que dice “Primero dime el primer punto (x1, y1), luego el segundo punto (x2, y2), …” y así sucesivamente. Tal vez esto se explica mejor con una figura, de tipo. Esto es lo que parece:

  you want to read in this direction (0, 0) (0, 1) | | | | vv / 0--------0 +----> axis0 x-values | /| /| /| | / | / | axis1 / | \ 1--------1 | L | | | | | v / | 0-----|--1 axis2 y-values | | / | / | |/ |/ \ 0--------1 | | | | vv (1, 0) (1, 1) 

np.rollaxis te da una manera de hacer esto. np.rollaxis(a, 0, 3) en el ejemplo anterior dice “tomar el eje 0 (o el más externo ) y convertirlo en el último (o el más interno ) del eje. (Nota: solo los ejes 0, 1 y 2 existen realmente aquí. Por lo tanto, decir “enviar el eje 0 a la 3ª posición” es una forma de indicar a Python que coloque el eje 0 después del último eje. También es posible que desee leer esto .

 >>> a = np.rollaxis(a, 0, 3) >>> a array([[[0, 0], [0, 1]], [[1, 0], [1, 1]]]) 

Esto comienza a parecerse a lo que quieres, excepto que hay una dimensión de matriz adicional. Queremos combinar las dimensiones 0 y 1 para obtener solo una matriz de puntos de cuadrícula. Pero ahora que la matriz aplanada se lee de la manera que usted espera, puede remodelarla de manera segura para obtener el resultado deseado.

 >>> a = a.reshape((4, 2)) >>> a array([[0, 0], [0, 1], [1, 0], [1, 1]]) 

La versión 3D hace lo mismo, excepto que no podría hacer una figura para eso, ya que estaría en 4D.

Puedes usar itertools.product :

 In [16]: from itertools import product In [17]: values = list(product(range(4), range(4), range(11))) In [18]: values[:5] Out[18]: [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4)] In [19]: values[-5:] Out[19]: [(3, 3, 6), (3, 3, 7), (3, 3, 8), (3, 3, 9), (3, 3, 10)] 

Dada la variedad de rangos, puedes hacer algo como lo siguiente. (Utilicé un par de valores bajos distintos de cero para demostrar el caso general, y para reducir el tamaño de la salida. 🙂

 In [41]: ranges = np.array([[0, 3], [1, 3], [8, 10]]) In [42]: list(product(*(range(lo, hi+1) for lo, hi in ranges))) Out[42]: [(0, 1, 8), (0, 1, 9), (0, 1, 10), (0, 2, 8), (0, 2, 9), (0, 2, 10), (0, 3, 8), (0, 3, 9), (0, 3, 10), (1, 1, 8), (1, 1, 9), (1, 1, 10), (1, 2, 8), (1, 2, 9), (1, 2, 10), (1, 3, 8), (1, 3, 9), (1, 3, 10), (2, 1, 8), (2, 1, 9), (2, 1, 10), (2, 2, 8), (2, 2, 9), (2, 2, 10), (2, 3, 8), (2, 3, 9), (2, 3, 10), (3, 1, 8), (3, 1, 9), (3, 1, 10), (3, 2, 8), (3, 2, 9), (3, 2, 10), (3, 3, 8), (3, 3, 9), (3, 3, 10)] 

Si los valores bajos de todos los rangos son 0, puede usar np.ndindex :

 In [52]: values = list(np.ndindex(4, 4, 11)) In [53]: values[:5] Out[53]: [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4)] In [54]: values[-5:] Out[34]: [(3, 3, 6), (3, 3, 7), (3, 3, 8), (3, 3, 9), (3, 3, 10)]