Mam dwie tablice numpy, które definiują osie x i y siatki. Na przykład:
x = numpy.array([1,2,3])
y = numpy.array([4,5])
Chciałbym wygenerować iloczyn kartezjański tych tablic, aby wygenerować:
array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
W pewnym sensie nie jest to strasznie nieefektywne, ponieważ muszę to robić wiele razy w pętli. Zakładam, że przekonwertowanie ich na listę Pythona i użycie itertools.product
iz powrotem do tablicy numpy nie jest najbardziej wydajną formą.
python
numpy
cartesian-product
Bogaty
źródło
źródło
Odpowiedzi:
Zobacz Używanie numpy do budowania tablicy wszystkich kombinacji dwóch tablic, aby zapoznać się z ogólnym rozwiązaniem obliczania iloczynu kartezjańskiego N tablic.
źródło
meshgrid
+dstack
Podejście, a szybciej w niektórych przypadkach może prowadzić do błędów, jeśli oczekujesz iloczyn kartezjański zostać skonstruowana w tej samej kolejności dla macierzy o tym samym rozmiarze.meshgrid
+dstack
. Czy mógłbyś zamieścić przykład?Kanoniczny
cartesian_product
(prawie)Istnieje wiele podejść do tego problemu o różnych właściwościach. Niektóre są szybsze niż inne, a niektóre mają bardziej ogólne zastosowanie. Po wielu testach i poprawkach odkryłem, że następująca funkcja, która oblicza n-wymiar
cartesian_product
, jest szybsza niż większość innych dla wielu danych wejściowych. Aby zapoznać się z parą podejść, które są nieco bardziej złożone, ale w wielu przypadkach są nawet nieco szybsze, zobacz odpowiedź Paula Panzera .Biorąc pod uwagę tę odpowiedź, nie jest to już najszybsza implementacja produktu kartezjańskiego
numpy
, o której wiem. Myślę jednak, że jego prostota nadal będzie stanowić przydatny punkt odniesienia dla przyszłych ulepszeń:Warto wspomnieć, że ta funkcja używa
ix_
w nietypowy sposób; podczas gdy udokumentowane użycieix_
polega na generowaniu indeksów w tablicy, tak się składa, że tablice o tym samym kształcie mogą być używane do rozgłaszania. Wielkie podziękowania dla mgilson , który zainspirował mnie do spróbowaniaix_
tego sposobu, oraz dla unutbu , który udzielił niezwykle pomocnych opinii na temat tej odpowiedzi, w tym sugestii użycianumpy.result_type
.Godne uwagi alternatywy
Czasami szybsze jest pisanie ciągłych bloków pamięci w kolejności Fortran. To podstawa tej alternatywy,
cartesian_product_transpose
która okazała się szybsza na niektórych urządzeniach niżcartesian_product
(patrz poniżej). Jednak odpowiedź Paula Panzera, która opiera się na tej samej zasadzie, jest jeszcze szybsza. Mimo to zamieszczam to tutaj dla zainteresowanych czytelników:Po zrozumieniu podejścia Panzera, napisałem nową wersję, która jest prawie tak szybka jak jego i prawie tak prosta, jak
cartesian_product
:Wydaje się, że ma to stałe obciążenie, które sprawia, że działa wolniej niż Panzer przy małych nakładach. Ale w przypadku większych danych wejściowych we wszystkich testach, które przeprowadziłem, działa równie dobrze, jak jego najszybsza implementacja (
cartesian_product_transpose_pp
).W kolejnych sekcjach zamieszczam kilka testów innych alternatyw. Są one teraz nieco nieaktualne, ale zamiast dublować wysiłek, zdecydowałem się je tutaj zostawić z interesu historycznego. Aby zapoznać się z aktualnymi testami, zobacz odpowiedź Panzera, a także odpowiedź Nico Schlömera .
Testy pod kątem alternatyw
Oto zestaw testów, które pokazują wzrost wydajności, jaki zapewniają niektóre z tych funkcji w porównaniu z wieloma alternatywami. Wszystkie pokazane tutaj testy zostały przeprowadzone na czterordzeniowym komputerze z systemem Mac OS 10.12.5, Python 3.6.1 i
numpy
1.12.1. Wiadomo, że różnice w sprzęcie i oprogramowaniu dają różne wyniki, więc YMMV. Przeprowadź te testy dla siebie, aby mieć pewność!Definicje:
Wyniki testu:
We wszystkich przypadkach,
cartesian_product
jak określono na początku tej odpowiedzi, jest najszybsza.W przypadku funkcji, które akceptują dowolną liczbę tablic wejściowych, warto również sprawdzić wydajność
len(arrays) > 2
. (Dopóki nie mogę ustalić, dlaczegocartesian_product_recursive
w tym przypadku jest wyświetlany błąd, usunąłem go z tych testów).Jak pokazują te testy,
cartesian_product
pozostaje konkurencyjny, dopóki liczba tablic wejściowych nie wzrośnie powyżej (w przybliżeniu) czterech. Potemcartesian_product_transpose
ma niewielką przewagę.Warto powtórzyć, że użytkownicy z innym sprzętem i systemami operacyjnymi mogą zobaczyć inne wyniki. Na przykład unutbu raportuje następujące wyniki tych testów przy użyciu Ubuntu 14.04, Python 3.4.3 i
numpy
1.14.0.dev0 + b7050a9:Poniżej przedstawiam kilka szczegółów dotyczących wcześniejszych testów, które przeprowadziłem w ten sposób. Względna wydajność tych podejść zmieniała się w czasie dla różnych urządzeń i różnych wersji języków Python i
numpy
. Chociaż nie jest to od razu przydatne dla osób korzystających z aktualnych wersjinumpy
, ilustruje, jak wiele się zmieniło od czasu pierwszej wersji tej odpowiedzi.Prosta alternatywa:
meshgrid
+dstack
Aktualnie zaakceptowana odpowiedź wykorzystuje
tile
irepeat
do nadawania razem dwóch tablic. Alemeshgrid
funkcja robi praktycznie to samo. Oto wynik działaniatile
irepeat
przed przekazaniem do transpozycji:A oto wynik
meshgrid
:Jak widać, jest prawie identyczny. Musimy tylko zmienić kształt wyniku, aby uzyskać dokładnie ten sam wynik.
Zamiast zmieniać kształt w tym momencie, moglibyśmy przekazać wynik
meshgrid
do,dstack
a następnie zmienić kształt, co oszczędza trochę pracy:Wbrew twierdzeniom zawartym w tym komentarzu , nie widziałem dowodów na to, że różne dane wejściowe będą dawać różnie ukształtowane wyniki, a jak pokazuje powyższe, robią bardzo podobne rzeczy, więc byłoby dość dziwne, gdyby tak było. Daj mi znać, jeśli znajdziesz kontrprzykład.
Testowanie
meshgrid
+dstack
vs.repeat
+transpose
Względna wydajność tych dwóch podejść zmieniała się w czasie. We wcześniejszej wersji Pythona (2.7) wynik użycia
meshgrid
+dstack
był zauważalnie szybszy w przypadku małych danych wejściowych. (Zauważ, że te testy pochodzą ze starej wersji tej odpowiedzi). Definicje:W przypadku wejścia o średnim rozmiarze zauważyłem znaczne przyspieszenie. Ale powtórzyłem te testy z nowszymi wersjami Pythona (3.6.1) i
numpy
(1.12.1) na nowszej maszynie. Te dwa podejścia są teraz prawie identyczne.Stary test
Nowy test
Jak zawsze YMMV, ale sugeruje to, że w najnowszych wersjach Pythona i numpy są one wymienne.
Uogólnione funkcje produktu
Ogólnie rzecz biorąc, możemy oczekiwać, że korzystanie z funkcji wbudowanych będzie szybsze w przypadku małych danych wejściowych, podczas gdy w przypadku dużych danych wejściowych funkcja specjalnie zbudowana może być szybsza. Ponadto dla uogólnionego produktu n-wymiarowego
tile
irepeat
nie pomoże, ponieważ nie mają wyraźnych analogów wyższego wymiaru. Dlatego warto zbadać również zachowanie funkcji specjalnie zaprojektowanych.Większość odpowiednich testów pojawia się na początku tej odpowiedzi, ale oto kilka testów przeprowadzonych na wcześniejszych wersjach Pythona i
numpy
dla porównania.cartesian
Funkcja zdefiniowana w innym odpowiedź wykorzystywane do wykonywania bardzo dobrze dla większych nakładów. (To samo jak funkcja o nazwiecartesian_product_recursive
powyżej). W celu porównaniacartesian
dodstack_prodct
, używamy tylko dwa wymiary.Tutaj ponownie stary test wykazał znaczną różnicę, podczas gdy nowy test prawie nie wykazuje żadnej.
Stary test
Nowy test
Jak poprzednio,
dstack_product
nadal bijecartesian
w mniejszych skalach.Nowy test ( nie pokazano nadmiarowego starego testu )
Myślę, że te rozróżnienia są interesujące i warte odnotowania; ale w końcu są akademickimi. Jak pokazały testy na początku tej odpowiedzi, wszystkie te wersje są prawie zawsze wolniejsze niż
cartesian_product
zdefiniowane na samym początku tej odpowiedzi - co samo w sobie jest nieco wolniejsze niż najszybsze implementacje wśród odpowiedzi na to pytanie.źródło
dtype=object
doarr = np.empty( )
pozwoliłoby na użycie w produkcie różnych typów, nparrays = [np.array([1,2,3]), ['str1', 'str2']]
.cartesian_product_tranpose
szybciej niż wcartesian_product
zależności od systemu operacyjnego, wersji Pythona lub Numpy. Na przykład w systemie Ubuntu 14.04 python3.4.3, numpy 1.14.0.dev0 + b7050a9,%timeit cartesian_product_transpose(x500,y500)
daje wyniki,1000 loops, best of 3: 682 µs per loop
gdy%timeit cartesian_product(x500,y500)
daje1000 loops, best of 3: 1.55 ms per loop
. Uważam też, żecartesian_product_transpose
może być szybciejlen(arrays) > 2
.cartesian_product
zwraca tablicę zmiennoprzecinkowego typu dtype, podczas gdycartesian_product_transpose
zwraca tablicę o tym samym dtype, co pierwsza (rozgłoszona) tablica. Możliwość zachowania dtype podczas pracy z tablicami całkowitymi może być powodem do faworyzowania przez użytkownikówcartesian_product_transpose
.dtype = np.find_common_type([arr.dtype for arr in arrays], [])
można by użyć do znalezienia wspólnego dtype wszystkich tablic, zamiast zmuszać użytkownika do umieszczenia tablicy, która kontroluje dtype jako pierwsza.Możesz po prostu zrobić normalne rozumienie list w Pythonie
co powinno ci dać
źródło
Interesowało mnie to również i dokonałem małego porównania wydajności, być może nieco wyraźniejszego niż w odpowiedzi @ senderle.
Dla dwóch tablic (przypadek klasyczny):
Dla czterech tablic:
(Zauważ, że długość tablic to tylko kilkadziesiąt wpisów tutaj.)
Kod do odtworzenia działek:
źródło
Opierając się na wzorowej pracy naziemnej @ senderle, opracowałem dwie wersje - jedną dla C i jedną dla układów Fortran - które często są nieco szybsze.
cartesian_product_transpose_pp
jest - w przeciwieństwie do @ senderle,cartesian_product_transpose
który używa zupełnie innej strategii - wersja,cartesion_product
która wykorzystuje bardziej korzystny układ pamięci transpozycji + kilka bardzo drobnych optymalizacji.cartesian_product_pp
zachowuje oryginalny układ pamięci. To, co sprawia, że jest szybki, to ciągłe kopiowanie. Ciągłe kopie okazują się być o wiele szybsze, niż kopiowanie całego bloku pamięci, mimo że tylko jego część zawiera prawidłowe dane, a nie tylko kopiowanie ważnych bitów.Niektóre perfplots. Zrobiłem osobne dla układów C i Fortran, bo to są różne zadania IMO.
Nazwy kończące się na „pp” to moje podejście.
1) wiele drobnych czynników (po 2 elementy)
2) wiele małych czynników (po 4 elementy)
3) trzy czynniki o jednakowej długości
4) dwa czynniki o jednakowej długości
Kod (trzeba wykonać oddzielne przebiegi dla każdej działki b / c Nie mogłem wymyślić, jak zresetować; również muszę odpowiednio edytować / komentować w / wy):
źródło
arrays
w cartesian_product_transpose_pp (tablice) przekroczy pewien rozmiar,MemoryError
nastąpi. W tej sytuacji chciałbym, aby ta funkcja dawała mniejsze fragmenty wyników. Wysłałem pytanie w tej sprawie. Czy możesz odpowiedzieć na moje pytanie? Dzięki.Od października 2017 r. Numpy ma teraz funkcję ogólną,
np.stack
która przyjmuje parametr osi. Używając go, możemy mieć „uogólniony produkt kartezjański” przy użyciu techniki „dstack and meshgrid”:Uwaga dotycząca
axis=-1
parametru. Jest to ostatnia (najbardziej wewnętrzna) oś w wyniku. Jest to równoważne użyciuaxis=ndim
.Jeszcze jeden komentarz, ponieważ iloczyn kartezjański szybko się wysadza, chyba że z jakiegoś powodu musimy zrealizować tablicę w pamięci, jeśli iloczyn jest bardzo duży, możemy chcieć
itertools
wykorzystać wartości w locie.źródło
Używałem odpowiedzi @kennytm przez jakiś czas, ale próbując zrobić to samo w TensorFlow, okazało się, że TensorFlow nie ma odpowiednika
numpy.repeat()
. Po krótkich eksperymentach, myślę, że znalazłem bardziej ogólne rozwiązanie dla dowolnych wektorów punktów.Dla numpy:
i dla TensorFlow:
źródło
Pakiet Scikit-learn ma szybką implementację dokładnie tego:
Zauważ, że konwencja tej implementacji różni się od tego, czego chcesz, jeśli zależy Ci na kolejności danych wyjściowych. Aby dokładnie zamówić, możesz to zrobić
źródło
Mówiąc bardziej ogólnie, jeśli masz dwie 2d tablice numpy a i b i chcesz połączyć każdy wiersz a z każdym wierszem b (iloczyn kartezjański wierszy, coś w rodzaju złączenia w bazie danych), możesz użyć tej metody :
źródło
Najszybciej można uzyskać połączenie wyrażenia generatora z funkcją map:
Wyniki (właściwie cała wynikowa lista jest drukowana):
lub używając podwójnego wyrażenia generatora:
Wyniki (drukowana cała lista):
Weź pod uwagę, że większość czasu obliczeń przypada na polecenie drukowania. Poza tym obliczenia generatora są przyzwoicie wydajne. Bez drukowania czasy obliczeń to:
dla wyrażenia generatora + funkcja mapy i:
dla wyrażenia podwójnego generatora.
Jeśli tak naprawdę chcesz obliczyć rzeczywisty iloczyn każdej z par współrzędnych, najszybszym rozwiązaniem jest rozwiązanie jako iloczyn liczbowy macierzy:
Wyjścia:
i bez nadruku (w tym przypadku nie oszczędza dużo, ponieważ drukowany jest tylko malutki kawałek matrycy):
źródło
foo = a[:,None]*b
jest szybsza. Używając metody pomiaru czasu bezprint(foo)
, jest to 0,001103 s w porównaniu z 0,002225 s. Używając timeit, wynosi 304 μs vs 1,6 ms. Wiadomo, że Matrix jest wolniejszy niż ndarray, więc wypróbowałem twój kod z np.array, ale nadal jest wolniejszy (1,57 ms) niż nadawanie.Można to również łatwo zrobić za pomocą metody itertools.product
Wynik: tablica ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Czas wykonania: 0,000155 s
źródło
W konkretnym przypadku, gdy musisz wykonać proste operacje, takie jak dodawanie na każdej parze, możesz wprowadzić dodatkowy wymiar i pozwolić, aby nadawanie wykonało zadanie:
Nie jestem pewien, czy istnieje podobny sposób na uzyskanie samych par.
źródło
dtype
tak,float
możesz to zrobić(a[:, None, None] + 1j * b[None, :, None]).view(float)
zaskakująco szybko.