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 en utilisant itertools.product
et retour à un tableau numpy n’est pas la forme la plus efficace.
>>> numpy.transpose([numpy.tile(x, len(y)), numpy.repeat(y, len(x))])
array([[1, 4],
[2, 4],
[3, 4],
[1, 5],
[2, 5],
[3, 5]])
Voir tilisation de numpy pour construire un tableau de toutes les combinaisons de deux tableaux pour une solution générale permettant de calculer le produit cartésien de N tableaux.
cartesian_product
Canonique (presque)Il existe de nombreuses approches à ce problème avec différentes propriétés. Certains sont plus rapides que d'autres et d'autres plus polyvalents. Après de nombreux tests et ajustements, nous avons constaté que la fonction suivante, qui calcule une dimension cartesian_product
À n dimensions, est plus rapide que la plupart des autres pour de nombreuses entrées. Pour une paire d'approches légèrement plus complexes, mais encore plus rapides dans de nombreux cas, voir la réponse par Paul Panzer .
Étant donné cette réponse, ce n'est plus la la plus rapide implémentation du produit cartésien dans numpy
dont je suis au courant. Cependant, je pense que sa simplicité continuera à en faire un repère utile pour des améliorations futures:
def cartesian_product(*arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[...,i] = a
return arr.reshape(-1, la)
Il est à noter que cette fonction utilise ix_
De manière inhabituelle; alors que l'utilisation documentée de ix_
consiste à générer des indices en un tableau, il se trouve que des tableaux de même forme peuvent être utilisés pour la diffusion affectation. Merci beaucoup à mgilson , qui m'a incité à utiliser ix_
De cette façon, et à nutb , qui a fourni des commentaires extrêmement utiles sur cette réponse, y compris le suggestion d'utiliser numpy.result_type
.
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 révélée plus rapide sur certains matériels que cartesian_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:
def cartesian_product_transpose(*arrays):
broadcastable = numpy.ix_(*arrays)
broadcasted = numpy.broadcast_arrays(*broadcastable)
rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
dtype = numpy.result_type(*arrays)
out = numpy.empty(rows * cols, dtype=dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
Après avoir compris l’approche de Panzer, j’ai écrit une nouvelle version presque aussi rapide que la sienne et aussi simple que cartesian_product
:
def cartesian_product_simple_transpose(arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([la] + [len(a) for a in arrays], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[i, ...] = a
return arr.reshape(la, -1).T
Cela semble avoir une surcharge de temps constant qui le rend plus lent que Panzer pour les petites entrées. Mais pour des entrées plus importantes, dans tous les tests que j'ai effectués, cela 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 faire double emploi, j'ai décidé de les laisser ici par intérêt historique. Pour les tests les plus récents, voir la réponse de Panzer, ainsi que celle de Nico Schlömer .
Voici une batterie de tests montrant l'amélioration des performances fournie par certaines de ces fonctions par rapport à un certain nombre d'alternatives. Tous les tests présentés ici ont été réalisés sur un ordinateur quadricœur fonctionnant sous Mac OS 10.12.5, Python 3.6.1 et numpy
1.12.1. Variations sur le matériel et logiciels sont connus pour produire des résultats différents, donc YMMV. Exécutez ces tests pour vous-même pour être sûr!
Définitions:
import numpy
import itertools
from functools import reduce
### Two-dimensional products ###
def repeat_product(x, y):
return numpy.transpose([numpy.tile(x, len(y)),
numpy.repeat(y, len(x))])
def dstack_product(x, y):
return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
### Generalized N-dimensional products ###
def cartesian_product(*arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[...,i] = a
return arr.reshape(-1, la)
def cartesian_product_transpose(*arrays):
broadcastable = numpy.ix_(*arrays)
broadcasted = numpy.broadcast_arrays(*broadcastable)
rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
dtype = numpy.result_type(*arrays)
out = numpy.empty(rows * cols, dtype=dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(*arrays, out=None):
arrays = [numpy.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = numpy.prod([x.size for x in arrays])
if out is None:
out = numpy.zeros([n, len(arrays)], dtype=dtype)
m = n // arrays[0].size
out[:,0] = numpy.repeat(arrays[0], m)
if arrays[1:]:
cartesian_product_recursive(arrays[1:], out=out[0:m,1:])
for j in range(1, arrays[0].size):
out[j*m:(j+1)*m,1:] = out[0:m,1:]
return out
def cartesian_product_itertools(*arrays):
return numpy.array(list(itertools.product(*arrays)))
### Test code ###
name_func = [('repeat_product',
repeat_product),
('dstack_product',
dstack_product),
('cartesian_product',
cartesian_product),
('cartesian_product_transpose',
cartesian_product_transpose),
('cartesian_product_recursive',
cartesian_product_recursive),
('cartesian_product_itertools',
cartesian_product_itertools)]
def test(in_arrays, test_funcs):
global func
global arrays
arrays = in_arrays
for name, func in test_funcs:
print('{}:'.format(name))
%timeit func(*arrays)
def test_all(*in_arrays):
test(in_arrays, name_func)
# `cartesian_product_recursive` throws an
# unexpected error when used on more than
# two input arrays, so for now I've removed
# it from these tests.
def test_cartesian(*in_arrays):
test(in_arrays, name_func[2:4] + name_func[-1:])
x10 = [numpy.arange(10)]
x50 = [numpy.arange(50)]
x100 = [numpy.arange(100)]
x500 = [numpy.arange(500)]
x1000 = [numpy.arange(1000)]
Résultats de test:
In [2]: test_all(*(x100 * 2))
repeat_product:
67.5 µs ± 633 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
dstack_product:
67.7 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product:
33.4 µs ± 558 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_transpose:
67.7 µs ± 932 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_recursive:
215 µs ± 6.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_itertools:
3.65 ms ± 38.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [3]: test_all(*(x500 * 2))
repeat_product:
1.31 ms ± 9.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
dstack_product:
1.27 ms ± 7.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product:
375 µs ± 4.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_transpose:
488 µs ± 8.88 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_recursive:
2.21 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
105 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [4]: test_all(*(x1000 * 2))
repeat_product:
10.2 ms ± 132 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
dstack_product:
12 ms ± 120 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product:
4.75 ms ± 57.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.76 ms ± 52.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_recursive:
13 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
422 ms ± 7.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
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, il est utile de vérifier les performances lorsque len(arrays) > 2
également. (Jusqu'à ce que je puisse déterminer pourquoi cartesian_product_recursive
génère une erreur dans ce cas, je l'ai supprimée de ces tests.)
In [5]: test_cartesian(*(x100 * 3))
cartesian_product:
8.8 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.87 ms ± 91.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
518 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [6]: test_cartesian(*(x50 * 4))
cartesian_product:
169 ms ± 5.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
184 ms ± 4.32 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_itertools:
3.69 s ± 73.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [7]: test_cartesian(*(x10 * 6))
cartesian_product:
26.5 ms ± 449 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
16 ms ± 133 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
728 ms ± 16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [8]: test_cartesian(*(x10 * 7))
cartesian_product:
650 ms ± 8.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_transpose:
518 ms ± 7.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_itertools:
8.13 s ± 122 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Comme le montrent ces tests, cartesian_product
Reste compétitif jusqu'à ce que le nombre de tableaux d'entrées dépasse quatre (environ). Après cela, cartesian_product_transpose
A un léger avantage.
Il est utile de rappeler que les utilisateurs de matériel et de systèmes d'exploitation différents peuvent obtenir des résultats différents. Par exemple, unutbu signale les résultats suivants pour ces tests sous Ubuntu 14.04, Python 3.4.3 et numpy
1.14.0.dev0 + b7050a9:
>>> %timeit cartesian_product_transpose(x500, y500)
1000 loops, best of 3: 682 µs per loop
>>> %timeit cartesian_product(x500, y500)
1000 loops, best of 3: 1.55 ms per loop
Ci-dessous, je vais donner quelques détails sur les tests précédents que j'ai effectués dans ce sens. Les performances relatives de ces approches ont évolué avec le temps, pour différents matériels et différentes versions de Python et numpy
. Bien que cela ne soit pas immédiatement utile pour les personnes utilisant des versions à jour de numpy
, il illustre l’évolution de la situation depuis la première version de cette réponse.
meshgrid
+ dstack
La réponse actuellement acceptée utilise tile
et repeat
pour diffuser deux tableaux simultanément. Mais la fonction meshgrid
fait pratiquement la même chose. Voici le résultat de tile
et repeat
avant d’être passé à transposer:
In [1]: import numpy
In [2]: x = numpy.array([1,2,3])
...: y = numpy.array([4,5])
...:
In [3]: [numpy.tile(x, len(y)), numpy.repeat(y, len(x))]
Out[3]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]
Et voici le résultat de meshgrid
:
In [4]: numpy.meshgrid(x, y)
Out[4]:
[array([[1, 2, 3],
[1, 2, 3]]), array([[4, 4, 4],
[5, 5, 5]])]
Comme vous pouvez le voir, c'est presque identique. Il suffit de remodeler le résultat pour obtenir exactement le même résultat.
In [5]: xt, xr = numpy.meshgrid(x, y)
...: [xt.ravel(), xr.ravel()]
Out[5]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]
Plutôt que de refaçonner à ce stade, cependant, nous pourrions passer le résultat de meshgrid
à dstack
et le remodeler par la suite, ce qui économisera du travail:
In [6]: numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
Out[6]:
array([[1, 4],
[2, 4],
[3, 4],
[1, 5],
[2, 5],
[3, 5]])
Contrairement à l’affirmation de ce commentaire , je n’ai trouvé aucune preuve que différentes entrées produiraient des sorties de formes différentes, et comme le montre la démonstration ci-dessus, elles font des choses très similaires, il serait donc très étrange qu’elles fait. S'il vous plaît laissez-moi savoir si vous trouvez un contre-exemple.
meshgrid
+ dstack
contre repeat
+ transpose
La performance relative de ces deux approches a évolué avec le temps. Dans une version antérieure de Python (2.7), le résultat obtenu avec meshgrid
+ dstack
était nettement plus rapide pour les petites entrées. (Notez que ces tests proviennent d'un ancienne version de cette réponse.) Définitions:
>>> def repeat_product(x, y):
... return numpy.transpose([numpy.tile(x, len(y)),
numpy.repeat(y, len(x))])
...
>>> def dstack_product(x, y):
... return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
...
Pour une entrée de taille moyenne, j'ai constaté une accélération significative. Mais j'ai retenté 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 presque identiques maintenant .
Ancien test
>>> x, y = numpy.arange(500), numpy.arange(500)
>>> %timeit repeat_product(x, y)
10 loops, best of 3: 62 ms per loop
>>> %timeit dstack_product(x, y)
100 loops, best of 3: 12.2 ms per loop
Nouveau test
In [7]: x, y = numpy.arange(500), numpy.arange(500)
In [8]: %timeit repeat_product(x, y)
1.32 ms ± 24.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [9]: %timeit dstack_product(x, y)
1.26 ms ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Comme toujours, YMMV, mais cela suggère que dans les versions récentes de Python et numpy, ceux-ci sont interchangeables.
En général, on peut s’attendre à ce que l’utilisation de fonctions intégrées soit plus rapide pour les petites entrées, tandis que pour les grandes entrées, une fonction spécifique peut être plus rapide. De plus, pour un produit n-dimensionnel généralisé, tile
et repeat
ne seront d'aucun secours, car ils n'ont pas d'analogues clairs de dimension supérieure. Il est donc intéressant d’examiner également le comportement des fonctions spécifiques.
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
à des fins de comparaison.
La fonction cartesian
définie dans autre réponse était assez performante pour les entrées plus volumineuses. (C'est la même chose que la fonction appelée cartesian_product_recursive
Ci-dessus.) Afin de comparer cartesian
à dstack_prodct
, Nous n'utilisons que deux dimensions.
Là encore, l'ancien test montrait une différence significative, tandis que le nouveau test n'en montrait quasiment aucune.
Ancien test
>>> x, y = numpy.arange(1000), numpy.arange(1000)
>>> %timeit cartesian([x, y])
10 loops, best of 3: 25.4 ms per loop
>>> %timeit dstack_product(x, y)
10 loops, best of 3: 66.6 ms per loop
Nouveau test
In [10]: x, y = numpy.arange(1000), numpy.arange(1000)
In [11]: %timeit cartesian([x, y])
12.1 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [12]: %timeit dstack_product(x, y)
12.7 ms ± 334 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Comme auparavant, dstack_product
Bat toujours cartesian
à des échelles plus petites.
Nouveau test ( ancien test redondant non représenté)
In [13]: x, y = numpy.arange(100), numpy.arange(100)
In [14]: %timeit cartesian([x, y])
215 µs ± 4.75 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [15]: %timeit dstack_product(x, y)
65.7 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Je pense que ces distinctions sont intéressantes et méritent d’être enregistrées; mais ils sont académiques à la fin. Comme le montrent les tests au début de cette réponse, toutes ces versions sont presque toujours plus lentes que cartesian_product
, Définies au tout début de cette réponse - ce qui est un peu plus lent que les implémentations les plus rapides parmi les réponses. à cette question.
Vous pouvez juste faire une compréhension de liste normale en python
x = numpy.array([1,2,3])
y = numpy.array([4,5])
[[x0, y0] for x0 in x for y0 in y]
qui devrait vous donner
[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
Cela m’intéressait aussi 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 tableaux:
(Notez que la longueur des tableaux est seulement de quelques dizaines d'entrées ici.)
Code pour reproduire les parcelles:
from functools import reduce
import itertools
import numpy
import perfplot
def dstack_product(arrays):
return numpy.dstack(
numpy.meshgrid(*arrays, indexing='ij')
).reshape(-1, len(arrays))
# Generalized N-dimensional products
def cartesian_product(arrays):
la = len(arrays)
dtype = numpy.find_common_type([a.dtype for a in arrays], [])
arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[..., i] = a
return arr.reshape(-1, la)
def cartesian_product_transpose(arrays):
broadcastable = numpy.ix_(*arrays)
broadcasted = numpy.broadcast_arrays(*broadcastable)
rows, cols = reduce(numpy.multiply, broadcasted[0].shape), len(broadcasted)
dtype = numpy.find_common_type([a.dtype for a in arrays], [])
out = numpy.empty(rows * cols, dtype=dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(arrays, out=None):
arrays = [numpy.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = numpy.prod([x.size for x in arrays])
if out is None:
out = numpy.zeros([n, len(arrays)], dtype=dtype)
m = n // arrays[0].size
out[:, 0] = numpy.repeat(arrays[0], m)
if arrays[1:]:
cartesian_product_recursive(arrays[1:], out=out[0:m, 1:])
for j in range(1, arrays[0].size):
out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
return out
def cartesian_product_itertools(arrays):
return numpy.array(list(itertools.product(*arrays)))
perfplot.show(
setup=lambda n: 4*(numpy.arange(n, dtype=float),),
n_range=[2**k for k in range(6)],
kernels=[
dstack_product,
cartesian_product,
cartesian_product_transpose,
cartesian_product_recursive,
cartesian_product_itertools
],
logx=True,
logy=True,
xlabel='len(a), len(b)',
equality_check=None
)
S'appuyant sur l'excellent travail de base de @ senderle, j'ai proposé deux versions - une pour les dispositions C et l'autre pour les dispositions Fortran - qui sont souvent un peu plus rapides.
cartesian_product_transpose_pp
est - contrairement à @ de senderle cartesian_product_transpose
qui utilise une stratégie totalement différente - une version de cartesion_product
qui utilise la disposition de mémoire transposée 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 est son utilisation de la copie contiguë. Les copies contiguës s'avèrent tellement plus rapides que copier un bloc complet de mémoire, même si une partie seulement de celui-ci contient des données valides, est préférable à la copie des bits valides.Quelques perfplots. J'ai fait des séparations pour les dispositions C et Fortran, car ce sont des tâches différentes pour l'OMI.
Les noms se terminant par 'pp' sont mes approches.
1) de nombreux facteurs minuscules (2 éléments chacun)
2) beaucoup de petits facteurs (4 éléments chacun)
3) trois facteurs d'égale longueur
4) deux facteurs d'égale longueur
Code (besoin de faire des analyses séparées pour chaque tracé b/c, je ne pouvais pas comprendre comment réinitialiser; j'ai également besoin d'éditer/commenter en entrée/en sortie de manière appropriée):
import numpy
import numpy as np
from functools import reduce
import itertools
import timeit
import perfplot
def dstack_product(arrays):
return numpy.dstack(
numpy.meshgrid(*arrays, indexing='ij')
).reshape(-1, len(arrays))
def cartesian_product_transpose_pp(arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty((la, *map(len, arrays)), dtype=dtype)
idx = slice(None), *itertools.repeat(None, la)
for i, a in enumerate(arrays):
arr[i, ...] = a[idx[:la-i]]
return arr.reshape(la, -1).T
def cartesian_product(arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[...,i] = a
return arr.reshape(-1, la)
def cartesian_product_transpose(arrays):
broadcastable = numpy.ix_(*arrays)
broadcasted = numpy.broadcast_arrays(*broadcastable)
rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
dtype = numpy.result_type(*arrays)
out = numpy.empty(rows * cols, dtype=dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
from itertools import accumulate, repeat, chain
def cartesian_product_pp(arrays, out=None):
la = len(arrays)
L = *map(len, arrays), la
dtype = numpy.result_type(*arrays)
arr = numpy.empty(L, dtype=dtype)
arrs = *accumulate(chain((arr,), repeat(0, la-1)), np.ndarray.__getitem__),
idx = slice(None), *itertools.repeat(None, la-1)
for i in range(la-1, 0, -1):
arrs[i][..., i] = arrays[i][idx[:la-i]]
arrs[i-1][1:] = arrs[i]
arr[..., 0] = arrays[0][idx]
return arr.reshape(-1, la)
def cartesian_product_itertools(arrays):
return numpy.array(list(itertools.product(*arrays)))
# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(arrays, out=None):
arrays = [numpy.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = numpy.prod([x.size for x in arrays])
if out is None:
out = numpy.zeros([n, len(arrays)], dtype=dtype)
m = n // arrays[0].size
out[:, 0] = numpy.repeat(arrays[0], m)
if arrays[1:]:
cartesian_product_recursive(arrays[1:], out=out[0:m, 1:])
for j in range(1, arrays[0].size):
out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
return out
### Test code ###
if False:
perfplot.save('cp_4el_high.png',
setup=lambda n: n*(numpy.arange(4, dtype=float),),
n_range=list(range(6, 11)),
kernels=[
dstack_product,
cartesian_product_recursive,
cartesian_product,
# cartesian_product_transpose,
cartesian_product_pp,
# cartesian_product_transpose_pp,
],
logx=False,
logy=True,
xlabel='#factors',
equality_check=None
)
else:
perfplot.save('cp_2f_T.png',
setup=lambda n: 2*(numpy.arange(n, dtype=float),),
n_range=[2**k for k in range(5, 11)],
kernels=[
# dstack_product,
# cartesian_product_recursive,
# cartesian_product,
cartesian_product_transpose,
# cartesian_product_pp,
cartesian_product_transpose_pp,
],
logx=True,
logy=True,
xlabel='length of each factor',
equality_check=None
)
Depuis octobre 2017, numpy a maintenant un np.stack
fonction qui prend un paramètre d’axe. En l'utilisant, nous pouvons avoir un "produit cartésien généralisé" en utilisant la technique "dstack and meshgrid":
import numpy as np
def cartesian_product(*arrays):
ndim = len(arrays)
return np.stack(np.meshgrid(*arrays), axis=-1).reshape(-1, ndim)
Note sur le axis=-1
paramètre. C'est le dernier axe (le plus interne) du résultat. Cela équivaut à utiliser axis=ndim
.
Autre commentaire, puisque les produits cartésiens explosent très rapidement, à moins que nous nécessité de réaliser le tableau en mémoire pour une raison quelconque, si le produit est très volumineux, nous voudrons peut-être utiliser itertools
et utilisez les valeurs à la volée.
J'ai utilisé @kennytm answer pendant un certain temps, mais en essayant de faire la même chose dans TensorFlow, mais j'ai constaté que TensorFlow n'a pas d'équivalent de numpy.repeat()
. Après quelques expériences, je pense avoir trouvé une solution plus générale pour des vecteurs de points arbitraires.
Pour numpy:
import numpy as np
def cartesian_product(*args: np.ndarray) -> np.ndarray:
"""
Produce the cartesian product of arbitrary length vectors.
Parameters
----------
np.ndarray args
vector of points of interest in each dimension
Returns
-------
np.ndarray
the cartesian product of size [m x n] wherein:
m = prod([len(a) for a in args])
n = len(args)
"""
for i, a in enumerate(args):
assert a.ndim == 1, "arg {:d} is not rank 1".format(i)
return np.concatenate([np.reshape(xi, [-1, 1]) for xi in np.meshgrid(*args)], axis=1)
et pour TensorFlow:
import tensorflow as tf
def cartesian_product(*args: tf.Tensor) -> tf.Tensor:
"""
Produce the cartesian product of arbitrary length vectors.
Parameters
----------
tf.Tensor args
vector of points of interest in each dimension
Returns
-------
tf.Tensor
the cartesian product of size [m x n] wherein:
m = prod([len(a) for a in args])
n = len(args)
"""
for i, a in enumerate(args):
tf.assert_rank(a, 1, message="arg {:d} is not rank 1".format(i))
return tf.concat([tf.reshape(xi, [-1, 1]) for xi in tf.meshgrid(*args)], axis=1)
Le package Scikit-learn a une implémentation rapide de exactement ceci:
from sklearn.utils.extmath import cartesian
product = cartesian((x,y))
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
product = cartesian((y,x))[:, ::-1]
Plus généralement, si vous avez deux tableaux numpy 2d a et b, et que vous voulez 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. :
import numpy
def join_2d(a, b):
assert a.dtype == b.dtype
a_part = numpy.tile(a, (len(b), 1))
b_part = numpy.repeat(b, len(a), axis=0)
return numpy.hstack((a_part, b_part))
Le plus rapidement possible est soit de combiner une expression génératrice avec la fonction map:
import numpy
import datetime
a = np.arange(1000)
b = np.arange(200)
start = datetime.datetime.now()
foo = (item for sublist in [list(map(lambda x: (x,i),a)) for i in b] for item in sublist)
print (list(foo))
print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))
Sorties (en fait, toute la liste résultante est imprimée):
[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.253567 s
ou en utilisant une expression à double générateur:
a = np.arange(1000)
b = np.arange(200)
start = datetime.datetime.now()
foo = ((x,y) for x in a for y in b)
print (list(foo))
print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))
Résultats (liste complète imprimée):
[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.187415 s
Tenez compte du fait que la majeure partie du temps de calcul va dans la commande d’impression. Les calculs du générateur sont par ailleurs décemment efficaces. Sans imprimer les temps de calcul sont:
execution time: 0.079208 s
pour l'expression du générateur + fonction de carte et:
execution time: 0.007093 s
pour l'expression 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 en tant que produit numpy matrix:
a = np.arange(1000)
b = np.arange(200)
start = datetime.datetime.now()
foo = np.dot(np.asmatrix([[i,0] for i in a]), np.asmatrix([[i,0] for i in b]).T)
print (foo)
print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))
Les sorties:
[[ 0 0 0 ..., 0 0 0]
[ 0 1 2 ..., 197 198 199]
[ 0 2 4 ..., 394 396 398]
...,
[ 0 997 1994 ..., 196409 197406 198403]
[ 0 998 1996 ..., 196606 197604 198602]
[ 0 999 1998 ..., 196803 197802 198801]]
execution time: 0.003869 s
et sans impression (dans ce cas, cela ne permet pas de gagner beaucoup, car seule une infime partie de la matrice est réellement imprimée):
execution time: 0.003083 s
Dans le cas spécifique où vous devez effectuer des opérations simples, telles que l’addition de chaque paire, vous pouvez introduire une dimension supplémentaire et laisser la diffusion jouer le rôle:
>>> a, b = np.array([1,2,3]), np.array([10,20,30])
>>> a[None,:] + b[:,None]
array([[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
Je ne sais pas s'il existe un moyen similaire de récupérer les paires elles-mêmes.
Cela peut aussi être facilement fait en utilisant la méthode itertools.product
from itertools import product
import numpy as np
x = np.array([1, 2, 3])
y = np.array([4, 5])
cart_prod = np.array(list(product(*[x, y])),dtype='int32')
Résultat: array ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Temps d'exécution: 0.000155 s