J'ai deux tableaux numpy qui définissent les axes x et y d'une grille. Par exemple:
x = numpy.array([1,2,3])
y = numpy.array([4,5])
Je voudrais générer le produit cartésien de ces tableaux pour générer:
array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
D'une manière qui n'est pas terriblement inefficace puisque je dois le faire plusieurs fois en boucle. Je suppose que les convertir en une liste Python et utiliser itertools.product
et revenir en un tableau numpy n'est pas la forme la plus efficace.
python
numpy
cartesian-product
Riches
la source
la source
Réponses:
Voir Utilisation de numpy pour créer un tableau de toutes les combinaisons de deux tableaux pour une solution générale pour le calcul du produit cartésien de N tableaux.
la source
meshgrid
+dstack
, bien que plus rapide dans certains cas, peut conduire à des bogues si vous vous attendez à ce que le produit cartésien soit construit dans le même ordre pour des tableaux de même taille.meshgrid
+dstack
. Pouvez-vous poster un exemple?Un canonique
cartesian_product
(presque)Il existe de nombreuses approches à ce problème avec des propriétés différentes. Certains sont plus rapides que d'autres et certains sont plus polyvalents. Après de nombreux tests et ajustements, j'ai constaté que la fonction suivante, qui calcule une n-dimension
cartesian_product
, est plus rapide que la plupart des autres pour de nombreuses entrées. Pour une paire d'approches qui sont légèrement plus complexes, mais qui sont même un peu plus rapides dans de nombreux cas, voir la réponse de Paul Panzer .Compte tenu de cette réponse, ce n'est plus l' implémentation la plus rapide du produit cartésien à
numpy
ma connaissance. Cependant, je pense que sa simplicité continuera à en faire une référence utile pour les améliorations futures:Il convient de mentionner que cette fonction utilise
ix_
de manière inhabituelle; alors que l'utilisation documentée deix_
consiste à générer des indices dans un tableau, il se trouve que des tableaux de la même forme peuvent être utilisés pour l'affectation diffusée. Un grand merci à mgilson , qui m'a inspiré à essayer d'utiliserix_
cette méthode, et à unutbu , qui a fourni des commentaires extrêmement utiles sur cette réponse, y compris la suggestion à utilisernumpy.result_type
.Alternatives notables
Il est parfois plus rapide d'écrire des blocs de mémoire contigus dans l'ordre Fortran. C'est la base de cette alternative,
cartesian_product_transpose
qui s'est avérée plus rapide sur certains matériels quecartesian_product
(voir ci-dessous). Cependant, la réponse de Paul Panzer, qui utilise le même principe, est encore plus rapide. Pourtant, j'inclus ceci ici pour les lecteurs intéressés:Après avoir compris l'approche de Panzer, j'ai écrit une nouvelle version presque aussi rapide que la sienne, et presque aussi simple que
cartesian_product
:Cela semble avoir une surcharge en temps constant qui le rend plus lent que celui de Panzer pour les petites entrées. Mais pour des entrées plus importantes, dans tous les tests que j'ai exécutés, il fonctionne aussi bien que son implémentation la plus rapide (
cartesian_product_transpose_pp
).Dans les sections suivantes, j'inclus quelques tests d'autres alternatives. Celles-ci sont maintenant quelque peu dépassées, mais plutôt que de dupliquer les efforts, j'ai décidé de les laisser ici par intérêt historique. Pour des tests à jour, voir la réponse de Panzer, ainsi que celle de Nico Schlömer .
Tests par rapport aux alternatives
Voici une batterie de tests qui montrent l'amélioration des performances que certaines de ces fonctions fournissent par rapport à un certain nombre d'alternatives. Tous les tests présentés ici ont été effectués sur une machine quadricœur, exécutant Mac OS 10.12.5, Python 3.6.1 et
numpy
1.12.1. Les variations sur le matériel et les logiciels sont connues pour produire des résultats différents, donc YMMV. Exécutez ces tests pour vous-même pour être sûr!Définitions:
Résultats de test:
Dans tous les cas,
cartesian_product
tel que défini au début de cette réponse est le plus rapide.Pour les fonctions qui acceptent un nombre arbitraire de tableaux d'entrée, cela vaut également la peine de vérifier les performances
len(arrays) > 2
. (Jusqu'à ce que je puisse déterminer pourquoicartesian_product_recursive
génère une erreur dans ce cas, je l'ai supprimée de ces tests.)Comme le montrent ces tests,
cartesian_product
reste compétitif jusqu'à ce que le nombre de tableaux d'entrée dépasse (environ) quatre. Après cela,cartesian_product_transpose
a un léger avantage.Il convient de rappeler que les utilisateurs disposant d'autres matériels et systèmes d'exploitation peuvent voir des résultats différents. Par exemple, unutbu rapporte avoir vu les résultats suivants pour ces tests utilisant Ubuntu 14.04, Python 3.4.3 et
numpy
1.14.0.dev0 + b7050a9:Ci-dessous, je vais dans quelques détails sur les tests précédents que j'ai exécutés dans ce sens. Les performances relatives de ces approches ont changé au fil du temps, pour différents matériels et différentes versions de Python et
numpy
. Bien que ce ne soit pas immédiatement utile pour les personnes utilisant des versions à jour denumpy
, cela illustre comment les choses ont changé depuis la première version de cette réponse.Une alternative simple:
meshgrid
+dstack
La réponse actuellement acceptée utilise
tile
etrepeat
pour diffuser deux tableaux ensemble. Mais lameshgrid
fonction fait pratiquement la même chose. Voici la sortie detile
etrepeat
avant d'être passée à transposer:Et voici la sortie de
meshgrid
:Comme vous pouvez le voir, c'est presque identique. Il suffit de remodeler le résultat pour obtenir exactement le même résultat.
Plutôt que de remodeler à ce stade, cependant, nous pourrions passer la sortie de
meshgrid
àdstack
et remodeler ensuite, ce qui économise du travail:Contrairement à ce que prétend ce commentaire , je n'ai vu aucune preuve que différentes entrées produiraient des sorties de forme différente, et comme le montre ce qui précède, elles font des choses très similaires, il serait donc assez étrange qu'elles le fassent. Veuillez me faire savoir si vous trouvez un contre-exemple.
Test
meshgrid
+dstack
vsrepeat
+transpose
La performance relative de ces deux approches a évolué au fil du temps. Dans une version antérieure de Python (2.7), le résultat en utilisant
meshgrid
+dstack
était nettement plus rapide pour les petites entrées. (Notez que ces tests proviennent d'une ancienne version de cette réponse.) Définitions:Pour une entrée de taille moyenne, j'ai vu une accélération significative. Mais j'ai réessayé ces tests avec des versions plus récentes de Python (3.6.1) et
numpy
(1.12.1), sur une machine plus récente. Les deux approches sont désormais presque identiques.Ancien test
Nouveau test
Comme toujours, YMMV, mais cela suggère que dans les versions récentes de Python et de numpy, ceux-ci sont interchangeables.
Fonctions produit généralisées
En général, on peut s'attendre à ce que l'utilisation des fonctions intégrées soit plus rapide pour les petites entrées, tandis que pour les grandes entrées, une fonction spécialement conçue peut être plus rapide. De plus, pour un produit généralisé à n dimensions,
tile
etrepeat
n'aidera pas, car ils n'ont pas d'analogues clairs de dimension supérieure. Il vaut donc la peine d'étudier le comportement des fonctions spécialement conçues à cet effet.La plupart des tests pertinents apparaissent au début de cette réponse, mais voici quelques-uns des tests effectués sur les versions antérieures de Python et
numpy
à titre de comparaison.La
cartesian
fonction définie dans une autre réponse fonctionnait plutôt bien pour des entrées plus importantes. (C'est la même que la fonction appeléecartesian_product_recursive
ci - dessus.) Afin de comparercartesian
àdstack_prodct
, nous utilisons seulement deux dimensions.Là encore, l'ancien test montrait une différence significative, tandis que le nouveau test n'en montrait presque aucune.
Ancien test
Nouveau test
Comme avant,
dstack_product
bat toujourscartesian
à des échelles plus petites.Nouveau test ( ancien test redondant non illustré )
Ces distinctions sont, je pense, intéressantes et méritent d'être enregistrées; mais ils sont finalement académiques. Comme l'ont montré les tests au début de cette réponse, toutes ces versions sont presque toujours plus lentes que
cartesian_product
, défini au tout début de cette réponse - qui est elle-même un peu plus lente que les implémentations les plus rapides parmi les réponses à cette question.la source
dtype=object
dansarr = np.empty( )
permettrait d'utiliser différents types dans le produit, par exemplearrays = [np.array([1,2,3]), ['str1', 'str2']]
.cartesian_product_tranpose
plus rapidement quecartesian_product
selon leur système d'exploitation, leur version python ou numpy. Par exemple, sur Ubuntu 14.04, python3.4.3, numpy 1.14.0.dev0 + b7050a9,%timeit cartesian_product_transpose(x500,y500)
cède1000 loops, best of 3: 682 µs per loop
tandis que%timeit cartesian_product(x500,y500)
cède1000 loops, best of 3: 1.55 ms per loop
. Je trouve aussi quecartesian_product_transpose
peut être plus rapide quandlen(arrays) > 2
.cartesian_product
retourne un tableau de dtype à virgule flottante tandis quecartesian_product_transpose
renvoie un tableau du même dtype que le premier tableau (diffusé). La possibilité de conserver dtype lors de l'utilisation de tableaux d'entiers peut être une raison pour les utilisateurs de privilégiercartesian_product_transpose
.dtype = np.find_common_type([arr.dtype for arr in arrays], [])
l'esprit que quelque chose comme pourrait être utilisé pour trouver le dtype commun de tous les tableaux, au lieu de forcer l'utilisateur à placer le tableau qui contrôle le dtype en premier.Vous pouvez simplement faire une compréhension de liste normale en python
qui devrait vous donner
la source
Cela m'intéressait également et j'ai fait une petite comparaison des performances, peut-être un peu plus claire que dans la réponse de @ senderle.
Pour deux tableaux (le cas classique):
Pour quatre baies:
(Notez que la longueur des tableaux n'est que de quelques dizaines d'entrées ici.)
Code pour reproduire les graphiques:
la source
En me basant sur le travail de terrain exemplaire de @ senderle, j'ai proposé deux versions - une pour les mises en page C et une pour les mises en page Fortran - qui sont souvent un peu plus rapides.
cartesian_product_transpose_pp
est - contrairement à @ senderlecartesian_product_transpose
qui utilise une stratégie complètement différente - une version decartesion_product
qui utilise la disposition de mémoire de transposition la plus favorable + quelques optimisations très mineures.cartesian_product_pp
colle avec la disposition de la mémoire d'origine. Ce qui le rend rapide, c'est son utilisation de la copie contiguë. Les copies contiguës s'avèrent être tellement plus rapides que copier un bloc complet de mémoire même si seulement une partie de celui-ci contient des données valides est préférable à ne copier que les bits valides.Quelques perfplots. J'en ai fait des séparés pour les mises en page C et Fortran, car ce sont des tâches différentes de l'OMI.
Les noms se terminant par «pp» sont mes approches.
1) de nombreux petits facteurs (2 éléments chacun)
2) de nombreux petits facteurs (4 éléments chacun)
3) trois facteurs d'égale longueur
4) deux facteurs d'égale longueur
Code (besoin de faire des exécutions séparées pour chaque tracé b / c je ne pouvais pas comprendre comment réinitialiser; également besoin d'éditer / commenter correctement):
la source
arrays
dans cartésian_product_transpose_pp (tableaux) dépasse une certaine taille,MemoryError
cela se produit. Dans cette situation, j'aimerais que cette fonction donne de plus petits morceaux de résultats. J'ai posté une question à ce sujet. Pouvez-vous répondre à ma question? Merci.Depuis octobre 2017, numpy dispose désormais d'une
np.stack
fonction générique qui prend un paramètre d'axe. En l'utilisant, on peut avoir un "produit cartésien généralisé" en utilisant la technique "dstack and meshgrid":Remarque sur le
axis=-1
paramètre. Il s'agit du dernier axe (le plus à l'intérieur) du résultat. Cela équivaut à utiliseraxis=ndim
.Un autre commentaire, puisque les produits cartésiens explosent très rapidement, à moins que nous ayons besoin de réaliser le tableau en mémoire pour une raison quelconque, si le produit est très grand, nous pouvons vouloir utiliser
itertools
et utiliser les valeurs à la volée.la source
J'ai utilisé la réponse @kennytm pendant un certain temps, mais en essayant de faire la même chose dans TensorFlow, mais j'ai trouvé que TensorFlow n'a pas d'équivalent de
numpy.repeat()
. Après un peu d'expérimentation, je pense avoir trouvé une solution plus générale pour les vecteurs de points arbitraires.Pour numpy:
et pour TensorFlow:
la source
Le package Scikit-learn a une implémentation rapide de exactement ceci:
Notez que la convention de cette implémentation est différente de ce que vous voulez, si vous vous souciez de l'ordre de la sortie. Pour votre commande exacte, vous pouvez faire
la source
Plus généralement, si vous avez deux tableaux 2d numpy a et b, et que vous souhaitez concaténer chaque ligne de a à chaque ligne de b (un produit cartésien de lignes, un peu comme une jointure dans une base de données), vous pouvez utiliser cette méthode :
la source
Le plus rapide que vous pouvez obtenir est soit de combiner une expression de générateur avec la fonction de carte:
Sorties (en fait, toute la liste résultante est imprimée):
ou en utilisant une expression de double générateur:
Sorties (liste complète imprimée):
Tenez compte du fait que la majeure partie du temps de calcul est consacrée à la commande d'impression. Les calculs du générateur sont par ailleurs assez efficaces. Sans impression, les temps de calcul sont:
pour l'expression du générateur + la fonction de carte et:
pour l'expression du double générateur.
Si vous voulez réellement calculer le produit réel de chacune des paires de coordonnées, le plus rapide est de le résoudre comme un produit matriciel numpy:
Les sorties:
et sans impression (dans ce cas, cela ne permet pas d'économiser beaucoup puisque seule une petite partie de la matrice est réellement imprimée):
la source
foo = a[:,None]*b
est plus rapide. En utilisant votre méthode de chronométrage sansprint(foo)
, c'est 0,001103 s contre 0,002225 s. En utilisant timeit, c'est 304 μs contre 1,6 ms. Matrix est connu pour être plus lent que ndarray, j'ai donc essayé votre code avec np.array mais il est toujours plus lent (1,57 ms) que la diffusion.Cela peut également être facilement fait en utilisant la méthode itertools.product
Résultat: tableau ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Temps d'exécution: 0,000155 s
la source
Dans le cas spécifique où vous devez effectuer des opérations simples telles que l'ajout sur chaque paire, vous pouvez introduire une dimension supplémentaire et laisser la diffusion faire le travail:
Je ne sais pas s'il existe un moyen similaire d'obtenir les paires elles-mêmes.
la source
dtype
c'est le cas,float
vous pouvez faire(a[:, None, None] + 1j * b[None, :, None]).view(float)
ce qui est étonnamment rapide.