Декартово произведение точек массива x и y на один массив точек 2D
У меня есть два массива numpy, которые определяют оси x и y сетки. Например:
x = numpy.array([1,2,3])
y = numpy.array([4,5])
Я хотел бы сгенерировать декартово произведение этих массивов для генерации:
array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
В некотором смысле это не ужасно неэффективно, так как мне нужно делать это много раз в цикле. Я предполагаю, что преобразование их в список Python и использование itertools.product
и обратно в массив numpy не является самой эффективной формой.
Ответы
Ответ 1
>>> 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]])
См. Использование numpy для построения массива всех комбинаций из двух массивов для общего решения для вычисления декартова произведения N массивов.
Ответ 2
Канонический cartesian_product
(почти)
Существует много подходов к этой проблеме с разными свойствами. Некоторые быстрее, чем другие, а некоторые более универсальны. После большого тестирования и настройки, я обнаружил, что следующая функция, которая вычисляет n-мерный cartesian_product
, быстрее, чем большинство других для многих входов. Для пары подходов, которые немного сложнее, но во многих случаях даже немного быстрее, см. Ответ Paul Panzer.
Учитывая этот ответ, это уже не самая быстрая реализация декартова продукта в numpy
, о котором я знаю. Однако, я думаю, его простота будет продолжать делать его полезным ориентиром для дальнейшего улучшения:
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)
Стоит отметить, что эта функция использует ix_
необычным образом; тогда как документированное использование ix_
заключается в генерировать индексы в массив, так бывает, что массивы с одинаковой формой могут использоваться для трансляции назначение. Большое спасибо mgilson, который вдохновил меня попробовать использовать ix_
таким образом, и unutbu, который предоставил некоторые чрезвычайно полезные отзывы об этом ответе, включая предложение использовать numpy.result_type
.
Известные альтернативы
Иногда быстрее записывать непрерывные блоки памяти в порядке Fortran. Это основа этой альтернативы, cartesian_product_transpose
, которая оказалась быстрее на каком-то оборудовании, чем cartesian_product
(см. Ниже). Однако ответ Павла Панзера, который использует тот же принцип, еще быстрее. Тем не менее, я включаю это здесь для заинтересованных читателей:
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
После того, как я понял подход Panzer, я написал новую версию, которая почти так же быстро, как и его, и почти такая же простая, как 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
Похоже, что накладные расходы постоянны, что заставляет его работать медленнее, чем Panzer для небольших входов. Но для больших входных данных во всех тестах, которые я выполнял, он работает так же хорошо, как и его самая быстрая реализация (cartesian_product_transpose_pp
).
В следующих разделах я включаю некоторые тесты других альтернатив. Сейчас они несколько устарели, но вместо того, чтобы дублировать усилия, я решил оставить их здесь из исторических интересов. Для получения последних тестов см. Ответ Panzer, а также Nico Schlömer.
Тесты на альтернативы
Вот батарея тестов, которые показывают повышение производительности, которое некоторые из этих функций предоставляют по сравнению с несколькими альтернативами. Все тесты, показанные здесь, выполнялись на четырехъядерной машине, работающей под управлением Mac OS 10.12.5, Python 3.6.1 и numpy
1.12.1. Известно, что вариации на аппаратное и программное обеспечение дают разные результаты, поэтому YMMV. Запустите эти тесты для себя, чтобы быть уверенным!
Определения:
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 /questions/64378/using-numpy-to-build-an-array-of-all-combinations-of-two-arrays/441983#441983
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)]
Результаты тестирования:
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)
Во всех случаях cartesian_product
, как определено в начале этого ответа, является самым быстрым.
Для тех функций, которые принимают произвольное количество входных массивов, стоит проверить производительность при len(arrays) > 2
. (Пока я не могу определить, почему cartesian_product_recursive
выдает ошибку в этом случае, я удалил ее из этих тестов.)
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)
Как показывают эти тесты, cartesian_product
остается конкурентоспособным до тех пор, пока количество входных массивов не будет превышать (примерно) четыре. После этого cartesian_product_transpose
имеет небольшое ребро.
Стоит повторить, что пользователи с другим оборудованием и операционными системами могут видеть разные результаты. Например, unutbu сообщает о следующих результатах для этих тестов с использованием Ubuntu 14.04, Python 3.4.3 и 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
Ниже я расскажу о нескольких более ранних тестах, которые я провел в этих строках. Относительная производительность этих подходов со временем изменилась, для разных аппаратных и различных версий Python и numpy
. Хотя это не сразу полезно для людей, использующих современные версии numpy
, это иллюстрирует, как все изменилось со времени первой версии этого ответа.
Простая альтернатива: meshgrid
+ dstack
В принятом в настоящее время ответе используются tile
и repeat
для трансляции двух массивов. Но функция meshgrid
делает практически одно и то же. Здесь вывод tile
и repeat
перед передачей в транспонирование:
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])]
И здесь вывод meshgrid
:
In [4]: numpy.meshgrid(x, y)
Out[4]:
[array([[1, 2, 3],
[1, 2, 3]]), array([[4, 4, 4],
[5, 5, 5]])]
Как вы можете видеть, он почти идентичен. Нам нужно только изменить результат, чтобы получить точно такой же результат.
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])]
Вместо того, чтобы переделывать в этот момент, мы могли бы передать вывод meshgrid
в dstack
и впоследствии изменить его, что экономит некоторую работу:
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]])
В отличие от претензии в этом комментарии, я не видел никаких доказательств того, что разные входы будут вызывать результаты по-разному, и, как показано выше, они очень похожи вещи, так что было бы довольно странно, если бы они это сделали. Пожалуйста, дайте мне знать, если вы найдете контрпример.
Тестирование meshgrid
+ dstack
против repeat
+ transpose
Относительная производительность этих двух подходов со временем изменилась. В более ранней версии Python (2.7) результат с использованием meshgrid
+ dstack
был заметно быстрее для небольших входов. (Обратите внимание, что эти тесты взяты из старой версии этого ответа.) Определения:
>>> 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)
...
Для ввода среднего размера я видел значительное ускорение. Но я повторил эти тесты с более поздними версиями Python (3.6.1) и numpy
(1.12.1) на более новой машине. Эти два подхода почти идентичны.
Старый тест
>>> 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
Новый тест
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)
Как всегда, YMMV, но это говорит о том, что в последних версиях Python и numpy они взаимозаменяемы.
Обобщенные функции произведения
В общем, мы можем ожидать, что использование встроенных функций будет быстрее для небольших входов, в то время как для больших входов целевая функция может быть быстрее. Кроме того, для обобщенного n-мерного произведения tile
и repeat
не помогут, поскольку у них нет четких высокоразмерных аналогов. Поэтому стоит исследовать поведение специально созданных функций.
Большинство соответствующих тестов появляются в начале этого ответа, но вот несколько тестов, выполненных для более ранних версий Python и numpy
для сравнения.
Функция cartesian
, определенная в другом ответе, использовалась для выполнения больших задач. (Это то же самое, что и функция, названная выше cartesian_product_recursive
.) Чтобы сравнить cartesian
с dstack_prodct
, мы используем только два измерения.
Здесь снова старый тест показал значительную разницу, в то время как новый тест почти не отображается.
Старый тест
>>> 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
Новый тест
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)
Как и раньше, dstack_product
по-прежнему бьет cartesian
в меньших масштабах.
Новый тест (избыточный старый тест не показан)
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)
Эти различия, я думаю, интересны и достойны записи; но в конце они академичны. Как показали тесты в начале этого ответа, все эти версии почти всегда медленнее, чем cartesian_product
, определенные в самом начале этого ответа, что само по себе немного медленнее, чем самые быстрые реализации среди ответов на этот вопрос.
Ответ 3
Вы можете просто выполнить обычное понимание списка в python
x = numpy.array([1,2,3])
y = numpy.array([4,5])
[[x0, y0] for x0 in x for y0 in y]
который должен дать вам
[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
Ответ 4
Я также заинтересовался этим и провел небольшое сравнение производительности, возможно, несколько понятнее, чем в ответе @senderle.
Для двух массивов (классический случай):
![enter image description here]()
Для четырех массивов:
![enter image description here]()
(Обратите внимание, что длина массивов здесь составляет всего несколько десятков записей.)
Код для воспроизведения участков:
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
)
Ответ 5
Основываясь на образцовой работе на основе @senderle, я придумал две версии: одну для C и одну для компоновки Fortran, - которые часто бывают немного быстрее.
-
cartesian_product_transpose_pp
- в отличие от @senderle cartesian_product_transpose
который использует совсем другую стратегию - версию cartesion_product
которая использует более благоприятную компоновку памяти транспонирования + некоторые очень незначительные оптимизации. -
cartesian_product_pp
использует оригинальную макет памяти. То, что делает это быстро, - это использование непрерывного копирования. Смежные копии оказываются намного быстрее, чем копирование полного блока памяти, хотя только часть из них содержит достоверные данные, предпочтительнее только копировать действительные биты.
Некоторые перфорации. Я сделал отдельные для макетов C и Fortran, потому что это разные задачи IMO.
Имена, оканчивающиеся на "pp", - мои подходы.
1) множество крошечных факторов (по 2 элемента)
![enter image description here]()
2) множество мелких факторов (по 4 элемента)
![enter image description here]()
3) три фактора равной длины
![enter image description here]()
4) два фактора равной длины
![enter image description here]()
Код (нужно делать отдельные прогоны для каждого графика b/c. Я не мог понять, как сбросить, а также отредактировать/прокомментировать в/из соответственно):
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
)
Ответ 6
По состоянию на октябрь 2017 года numpy теперь имеет общую функцию np.stack
которая принимает параметр оси. Используя его, мы можем иметь "обобщенное декартово произведение" с использованием техники "dstack и meshgrid":
import numpy as np
def cartesian_product(*arrays):
ndim = len(arrays)
return np.stack(np.meshgrid(*arrays), axis=-1).reshape(-1, ndim)
Обратите внимание на параметр axis=-1
. Это последняя (внутренняя) ось в результате. Это эквивалентно использованию axis=ndim
.
Еще один комментарий, так как декартовы продукты взрываются очень быстро, если нам не нужно каким-то образом реализовать массив в памяти, если продукт очень большой, мы можем захотеть использовать itertools
и использовать значения "на лету".
Ответ 7
Я использовал @kennytm answer какое-то время, но при попытке сделать то же самое в TensorFlow, но я обнаружил, что TensorFlow не имеет эквивалента numpy.repeat()
. После небольшого эксперимента, я думаю, я нашел более общее решение для произвольных векторов точек.
Для 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)
и для 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
Пакет Scikit-learn имеет быструю реализацию именно этого:
from sklearn.utils.extmath import cartesian
product = cartesian((x,y))
Обратите внимание, что соглашение этой реализации отличается от того, что вы хотите, если вам нужен порядок вывода. Для вашего точного заказа вы можете сделать
product = cartesian((y,x))[:, ::-1]
Ответ 9
В более общем плане, если у вас есть два массива 2d numpy a и b, и вы хотите объединить каждую строку a в каждую строку b (декартово произведение строк, вроде объединения в базу данных), вы можете используйте этот метод:
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))
Ответ 10
Самое быстрое, что вы можете получить, это либо комбинировать выражение генератора с функцией карты:
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()))
Выходы (фактически распечатывается весь итоговый список):
[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.253567 s
или с использованием выражения с двойным генератором:
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()))
Выходы (напечатан весь список):
[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.187415 s
Учтите, что большая часть времени вычислений входит в команду печати. Расчеты генератора в остальном прилично эффективны. Без печати время вычисления:
execution time: 0.079208 s
для выражения генератора + функция отображения и:
execution time: 0.007093 s
для выражения двойного генератора.
Если вы действительно хотите, чтобы вычислить фактический продукт каждой из пар координат, наиболее быстрым является его решение в виде матричного продукта:
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()))
Выходы:
[[ 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
и без печати (в этом случае он мало экономит, поскольку на самом деле распечатывается только малая часть матрицы):
execution time: 0.003083 s
Ответ 11
Это также легко сделать с помощью метода 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')
Результат: массив ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Время выполнения: 0,000155 с
Ответ 12
В конкретном случае, когда вам нужно выполнить простые операции, такие как сложение для каждой пары, вы можете ввести дополнительное измерение и позволить вещанию делать свою работу:
>>> 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]])
Я не уверен, есть ли подобный способ на самом деле получить сами пары.