Tengo dos matrices numpy que definen los ejes x e y de una cuadrícula. Por ejemplo:
x = numpy.array([1,2,3])
y = numpy.array([4,5])
Me gustaría generar el producto cartesiano de estas matrices para generar:
array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
De una manera que no es terriblemente ineficiente, ya que necesito hacer esto muchas veces en un bucle. Supongo que convertirlos a una lista de Python y usar itertools.product
y volver a una matriz numpy no es la forma más eficiente.
Respuestas:
Consulte Uso de numpy para crear una matriz de todas las combinaciones de dos matrices para obtener una solución general para calcular el producto cartesiano de N matrices.
fuente
meshgrid
+dstack
, aunque es más rápido en algunos casos, puede generar errores si espera que el producto cartesiano se construya en el mismo orden para matrices del mismo tamaño.meshgrid
+dstack
. ¿Podría publicar un ejemplo?Un canónico
cartesian_product
(casi)Hay muchos enfoques para este problema con diferentes propiedades. Algunos son más rápidos que otros, y algunos son más generales. Después de muchas pruebas y ajustes, descubrí que la siguiente función, que calcula un n-dimensional
cartesian_product
, es más rápida que la mayoría de las otras entradas. Para un par de enfoques que son un poco más complejos, pero que son incluso un poco más rápidos en muchos casos, vea la respuesta de Paul Panzer .Dada esa respuesta, esta ya no es la implementación más rápida del producto cartesiano en lo
numpy
que sé. Sin embargo, creo que su simplicidad continuará haciéndolo un punto de referencia útil para futuras mejoras:Vale la pena mencionar que esta función se usa
ix_
de una manera inusual; Mientras que el uso documentado deix_
es generar índices en una matriz, resulta que las matrices con la misma forma pueden usarse para la asignación emitida. Muchas gracias a mgilson , que me inspiró a intentar usarlo deix_
esta manera, y a unutbu , que me proporcionó algunos comentarios extremadamente útiles sobre esta respuesta, incluida la sugerencia de usonumpy.result_type
.Alternativas notables
A veces es más rápido escribir bloques de memoria contiguos en el orden Fortran. Esa es la base de esta alternativa,
cartesian_product_transpose
que ha demostrado ser más rápida en algunos equipos quecartesian_product
(ver más abajo). Sin embargo, la respuesta de Paul Panzer, que utiliza el mismo principio, es aún más rápida. Aún así, incluyo esto aquí para los lectores interesados:Después de comprender el enfoque de Panzer, escribí una nueva versión que es casi tan rápida como la suya, y es casi tan simple como
cartesian_product
:Esto parece tener una sobrecarga de tiempo constante que lo hace correr más lento que el de Panzer para entradas pequeñas. Pero para entradas más grandes, en todas las pruebas que ejecuté, funciona tan bien como su implementación más rápida (
cartesian_product_transpose_pp
).En las siguientes secciones, incluyo algunas pruebas de otras alternativas. Ahora están algo desactualizados, pero en lugar de duplicar el esfuerzo, he decidido dejarlos aquí por interés histórico. Para ver las pruebas actualizadas, vea la respuesta de Panzer, así como la de Nico Schlömer .
Pruebas contra alternativas
Aquí hay una batería de pruebas que muestran el aumento de rendimiento que algunas de estas funciones proporcionan en relación con una serie de alternativas. Todas las pruebas que se muestran aquí se realizaron en una máquina de cuatro núcleos, con Mac OS 10.12.5, Python 3.6.1 y
numpy
1.12.1. Se sabe que las variaciones en hardware y software producen resultados diferentes, por lo que YMMV. ¡Ejecute estas pruebas por sí mismo para estar seguro!Definiciones:
Resultados de la prueba:
En todos los casos,
cartesian_product
como se define al comienzo de esta respuesta, es más rápido.Para aquellas funciones que aceptan un número arbitrario de matrices de entrada, también vale la pena verificar el rendimiento
len(arrays) > 2
. (Hasta que pueda determinar por quécartesian_product_recursive
arroja un error en este caso, lo he eliminado de estas pruebas).Como muestran estas pruebas,
cartesian_product
sigue siendo competitivo hasta que el número de matrices de entrada supera (aproximadamente) cuatro. Después de eso,cartesian_product_transpose
tiene una ligera ventaja.Vale la pena reiterar que los usuarios con otro hardware y sistemas operativos pueden ver resultados diferentes. Por ejemplo, unutbu informa haber visto los siguientes resultados para estas pruebas con Ubuntu 14.04, Python 3.4.3 y
numpy
1.14.0.dev0 + b7050a9:A continuación, entro en algunos detalles sobre las pruebas anteriores que he realizado en este sentido. El rendimiento relativo de estos enfoques ha cambiado con el tiempo, para diferentes hardware y diferentes versiones de Python y
numpy
. Si bien no es inmediatamente útil para las personas que usan versiones actualizadasnumpy
, ilustra cómo han cambiado las cosas desde la primera versión de esta respuesta.Una alternativa simple:
meshgrid
+dstack
La respuesta actualmente aceptada utiliza
tile
yrepeat
para transmitir dos matrices juntas. Pero lameshgrid
función hace prácticamente lo mismo. Aquí está la salida detile
yrepeat
antes de pasar a transponer:Y aquí está el resultado de
meshgrid
:Como puede ver, es casi idéntico. Solo necesitamos remodelar el resultado para obtener exactamente el mismo resultado.
Sin embargo, en lugar de remodelar en este punto, podríamos pasar la salida de
meshgrid
adstack
y remodelar después, lo que ahorra algo de trabajo:Contrariamente a lo que se afirma en este comentario , no he visto evidencia de que diferentes entradas produzcan salidas de formas diferentes, y como lo demuestra lo anterior, hacen cosas muy similares, por lo que sería bastante extraño si lo hicieran. Avíseme si encuentra un contraejemplo.
Prueba
meshgrid
+dstack
vs.repeat
+transpose
El rendimiento relativo de estos dos enfoques ha cambiado con el tiempo. En una versión anterior de Python (2.7), el resultado usando
meshgrid
+dstack
fue notablemente más rápido para entradas pequeñas. (Tenga en cuenta que estas pruebas son de una versión anterior de esta respuesta). Definiciones:Para una entrada de tamaño moderado, vi una aceleración significativa. Pero volví a probar estas pruebas con versiones más recientes de Python (3.6.1) y
numpy
(1.12.1), en una máquina más nueva. Los dos enfoques son casi idénticos ahora.Prueba antigua
Nueva prueba
Como siempre, YMMV, pero esto sugiere que en versiones recientes de Python y numpy, estas son intercambiables.
Funciones de producto generalizadas
En general, podríamos esperar que el uso de funciones integradas sea más rápido para entradas pequeñas, mientras que para entradas grandes, una función especialmente diseñada podría ser más rápida. Además, para un producto n-dimensional generalizado,
tile
yrepeat
no ayudará, porque no tienen análogos claros de dimensiones superiores. Por lo tanto, vale la pena investigar el comportamiento de las funciones especialmente diseñadas también.La mayoría de las pruebas relevantes aparecen al comienzo de esta respuesta, pero aquí hay algunas de las pruebas realizadas en versiones anteriores de Python y
numpy
para comparación.La
cartesian
función definida en otra respuesta solía funcionar bastante bien para entradas más grandes. (Es la misma que la función llamadacartesian_product_recursive
anteriormente.) Con el fin de compararcartesian
adstack_prodct
, utilizamos sólo dos dimensiones.Aquí nuevamente, la prueba anterior mostró una diferencia significativa, mientras que la nueva prueba casi no muestra ninguna.
Prueba antigua
Nueva prueba
Como antes,
dstack_product
todavía latecartesian
a escalas más pequeñas.Nueva prueba ( prueba antigua redundante no mostrada )
Estas distinciones son, creo, interesantes y vale la pena registrarlas; pero son académicos al final. Como lo demostraron las pruebas al comienzo de esta respuesta, todas estas versiones son casi siempre más lentas que las
cartesian_product
definidas al comienzo de esta respuesta, que en sí es un poco más lenta que las implementaciones más rápidas entre las respuestas a esta pregunta.fuente
dtype=object
enarr = np.empty( )
permitiría utilizar diferentes tipos en el producto, por ejemploarrays = [np.array([1,2,3]), ['str1', 'str2']]
.cartesian_product_tranpose
más rápido quecartesian_product
dependiendo del sistema operativo de su máquina, python o versión numpy. Por ejemplo, en Ubuntu 14.04, python3.4.3, numpy 1.14.0.dev0 + b7050a9,%timeit cartesian_product_transpose(x500,y500)
rinde1000 loops, best of 3: 682 µs per loop
mientras que%timeit cartesian_product(x500,y500)
rinde1000 loops, best of 3: 1.55 ms per loop
. También estoy descubriendo quecartesian_product_transpose
puede ser más rápido cuandolen(arrays) > 2
.cartesian_product
devuelve una matriz de dtype de punto flotante, mientras quecartesian_product_transpose
devuelve una matriz del mismo dtype que la primera matriz (emitida). La capacidad de preservar dtype cuando se trabaja con matrices de enteros puede ser una razón para que los usuarios favorezcancartesian_product_transpose
.dtype = np.find_common_type([arr.dtype for arr in arrays], [])
podría usarse para encontrar el tipo de letra común de todas las matrices, en lugar de obligar al usuario a colocar la matriz que controla primero el tipo de letra.Puedes hacer una comprensión normal de la lista en Python
que debería darte
fuente
También estaba interesado en esto e hice una pequeña comparación de rendimiento, tal vez algo más claro que en la respuesta de @ senderle.
Para dos matrices (el caso clásico):
Para cuatro matrices:
(Tenga en cuenta que la longitud de las matrices es de unas pocas docenas de entradas aquí)
Código para reproducir las tramas:
fuente
Sobre la base del trabajo de campo ejemplar de @ senderle, he creado dos versiones, una para C y otra para diseños de Fortran, que a menudo son un poco más rápidas.
cartesian_product_transpose_pp
es, a diferencia de @ senderle,cartesian_product_transpose
que usa una estrategia completamente diferente, una versióncartesion_product
que usa el diseño de memoria de transposición más favorable + algunas optimizaciones menores.cartesian_product_pp
se queda con el diseño de memoria original. Lo que lo hace rápido es usar copias contiguas. Las copias contiguas resultan ser mucho más rápidas que copiar un bloque completo de memoria a pesar de que solo una parte contiene datos válidos es preferible a copiar solo los bits válidos.Algunas parcelas. Hice otros por separado para los diseños C y Fortran, porque estas son tareas diferentes de la OMI.
Los nombres que terminan en 'pp' son mis enfoques.
1) muchos factores pequeños (2 elementos cada uno)
2) muchos factores pequeños (4 elementos cada uno)
3) tres factores de igual longitud
4) dos factores de igual longitud
Código (necesito hacer corridas separadas para cada parcela b / c No pude encontrar la manera de restablecer; también necesito editar / comentar dentro / fuera adecuadamente):
fuente
arrays
in cartesian_product_transpose_pp (arrays) exceda cierto tamaño,MemoryError
ocurrirá. En esta situación, me gustaría que esta función produzca pequeños fragmentos de resultados. He publicado una pregunta sobre este asunto. ¿Puedes abordar mi pregunta? Gracias.A partir de octubre de 2017, numpy ahora tiene una
np.stack
función genérica que toma un parámetro de eje. Al usarlo, podemos tener un "producto cartesiano generalizado" usando la técnica "dstack and meshgrid":Nota sobre el
axis=-1
parámetro. Este es el último eje (más interno) en el resultado. Es equivalente a usaraxis=ndim
.Otro comentario, dado que los productos cartesianos explotan muy rápidamente, a menos que necesitemos realizar la matriz en la memoria por alguna razón, si el producto es muy grande, es posible que deseemos usar
itertools
y usar los valores sobre la marcha.fuente
Utilicé @kennytm answer por un tiempo, pero cuando intenté hacer lo mismo en TensorFlow, pero descubrí que TensorFlow no tiene equivalente
numpy.repeat()
. Después de un poco de experimentación, creo que encontré una solución más general para vectores arbitrarios de puntos.Para numpy:
y para TensorFlow:
fuente
El paquete Scikit-learn tiene una rápida implementación de exactamente esto:
Tenga en cuenta que la convención de esta implementación es diferente de lo que desea, si le importa el orden de la salida. Para su pedido exacto, puede hacer
fuente
En términos más generales, si tiene dos matrices nudosas 2d a y b, y desea concatenar cada fila de a cada fila de b (un producto cartesiano de filas, algo así como una unión en una base de datos), puede usar este método :
fuente
Lo más rápido que puede obtener es combinando una expresión generadora con la función de mapa:
Salidas (en realidad, se imprime toda la lista resultante):
o usando una expresión de doble generador:
Salidas (lista completa impresa):
Tenga en cuenta que la mayor parte del tiempo de cálculo se destina al comando de impresión. Los cálculos del generador son, por lo demás, decentemente eficientes. Sin imprimir los tiempos de cálculo son:
para la expresión del generador + función de mapa y:
para la expresión de doble generador.
Si lo que realmente desea es calcular el producto real de cada uno de los pares de coordenadas, lo más rápido es resolverlo como un producto de matriz numpy:
Salidas:
y sin imprimir (en este caso no ahorra mucho ya que solo se imprime una pequeña parte de la matriz):
fuente
foo = a[:,None]*b
es más rápida. Usando su método de sincronización sinprint(foo)
, es 0.001103 s vs 0.002225 s. Usando timeit, es 304 μs vs 1.6 ms. Se sabe que Matrix es más lento que ndarray, así que probé su código con np.array pero aún es más lento (1.57 ms) que la transmisión.Esto también se puede hacer fácilmente usando el método itertools.product
Resultado: matriz ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Tiempo de ejecución: 0.000155 s
fuente
En el caso específico de que necesite realizar operaciones simples, como la suma de cada par, puede introducir una dimensión adicional y dejar que la transmisión haga el trabajo:
No estoy seguro de si hay alguna forma similar de obtener los pares ellos mismos.
fuente
dtype
esfloat
así, puede hacerlo(a[:, None, None] + 1j * b[None, :, None]).view(float)
sorprendentemente rápido.