web-dev-qa-db-fra.com

Produit cartésien des points de matrice x et y en un seul tableau de points 2D

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.

129
Rich
>>> 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.

76
kennytm

Un 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 .

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 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 .

Tests contre des alternatives

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.

Une alternative simple: 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.

Tester 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.

Fonctions produit généralisées

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.

128
senderle

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]]
37
ozooxo

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):

enter image description here

Pour quatre tableaux:

enter image description here

(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
    )
22
Nico Schlömer

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)

enter image description hereenter image description here

2) beaucoup de petits facteurs (4 éléments chacun)

enter image description hereenter image description here

3) trois facteurs d'égale longueur

enter image description hereenter image description here

4) deux facteurs d'égale longueur

enter image description hereenter image description here

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
    )
13
Paul Panzer

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.

9
MrDrFenner

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)
8
Sean McVeigh

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]
5
jmd_dk

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))
4
Jonathan

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
3
mosegui

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.

0
Caagr98

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

0