使用 NumPy 构建两个数组的所有组合的数组
- 2024-12-02 08:41:00
- admin 原创
- 152
问题描述:
在尝试对其进行任何复杂操作之前,我试图运行六参数函数的参数空间来研究其数值行为,因此我正在寻找一种有效的方法来做到这一点。
我的函数以 6 维 NumPy 数组中给出的浮点值作为输入。我最初尝试这样做:
首先,我创建了一个函数,它接受两个数组并生成一个包含两个数组中所有值组合的数组:
from numpy import *
def comb(a, b):
c = []
for i in a:
for j in b:
c.append(r_[i,j])
return c
然后,我将reduce()
其应用于同一数组的 m 个副本:
def combs(a, m):
return reduce(comb, [a]*m)
最后,我像这样评估我的功能:
values = combs(np.arange(0, 1, 0.1), 6)
for val in values:
print F(val)
这个方法可行,但是速度太慢了。我知道参数空间很大,但是不应该这么慢。在这个例子中我只采样了 10 6(一百万)个点,创建数组就花了 15 秒多values
。
有没有更有效的方法用 NumPy 来做到这一点?
F
如果有必要,我可以修改函数获取参数的方式。
解决方案 1:
在较新版本的 NumPy(>1.8.x)中,numpy.meshgrid()
提供了更快的实现:
对于pv 的解决方案:
In [113]:
%timeit cartesian(([1, 2, 3], [4, 5], [6, 7]))
10000 loops, best of 3: 135 µs per loop
In [114]:
cartesian(([1, 2, 3], [4, 5], [6, 7]))
Out[114]:
array([[1, 4, 6],
[1, 4, 7],
[1, 5, 6],
[1, 5, 7],
[2, 4, 6],
[2, 4, 7],
[2, 5, 6],
[2, 5, 7],
[3, 4, 6],
[3, 4, 7],
[3, 5, 6],
[3, 5, 7]])
numpy.meshgrid()
以前只是二维的,但现在它可以是多维的。在这种情况下,三维的:
In [115]:
%timeit np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)
10000 loops, best of 3: 74.1 µs per loop
In [116]:
np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)
Out[116]:
array([[1, 4, 6],
[1, 5, 6],
[2, 4, 6],
[2, 5, 6],
[3, 4, 6],
[3, 5, 6],
[1, 4, 7],
[1, 5, 7],
[2, 4, 7],
[2, 5, 7],
[3, 4, 7],
[3, 5, 7]])
请注意,最终结果的顺序略有不同。
解决方案 2:
这是一个纯 NumPy 实现。它比使用itertools快约 5 倍。
Python 3:
import numpy as np
def cartesian(arrays, out=None):
"""
Generate a Cartesian product of input arrays.
Parameters
----------
arrays : list of array-like
1-D arrays to form the Cartesian product of.
out : ndarray
Array to place the Cartesian product in.
Returns
-------
out : ndarray
2-D array of shape (M, len(arrays)) containing Cartesian products
formed of input arrays.
Examples
--------
>>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
array([[1, 4, 6],
[1, 4, 7],
[1, 5, 6],
[1, 5, 7],
[2, 4, 6],
[2, 4, 7],
[2, 5, 6],
[2, 5, 7],
[3, 4, 6],
[3, 4, 7],
[3, 5, 6],
[3, 5, 7]])
"""
arrays = [np.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = np.prod([x.size for x in arrays])
if out is None:
out = np.zeros([n, len(arrays)], dtype=dtype)
#m = n / arrays[0].size
m = int(n / arrays[0].size)
out[:,0] = np.repeat(arrays[0], m)
if arrays[1:]:
cartesian(arrays[1:], out=out[0:m, 1:])
for j in range(1, arrays[0].size):
#for j in xrange(1, arrays[0].size):
out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
return out
Python 2:
import numpy as np
def cartesian(arrays, out=None):
arrays = [np.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = np.prod([x.size for x in arrays])
if out is None:
out = np.zeros([n, len(arrays)], dtype=dtype)
m = n / arrays[0].size
out[:,0] = np.repeat(arrays[0], m)
if arrays[1:]:
cartesian(arrays[1:], out=out[0:m, 1:])
for j in xrange(1, arrays[0].size):
out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
return out
解决方案 3:
itertools.combinations通常是从 Python 容器中获取组合的最快方法(如果您确实想要组合,即不重复且与顺序无关的排列;这不是您的代码所做的事情,但我不能判断这是因为您的代码有缺陷还是因为您使用了错误的术语)。
如果您想要与组合不同的东西,也许 itertoolsproduct
或中的其他迭代器permutations
可能会更适合您。例如,您的代码看起来与以下内容大致相同:
for val in itertools.product(np.arange(0, 1, 0.1), repeat=6):
print F(val)
所有这些迭代器都会产生元组,而不是列表或 NumPy 数组,所以如果你的F对获取 NumPy 数组很挑剔,那么你必须接受在每一步构建或清除并重新填充一个数组的额外开销。
解决方案 4:
您可以使用np.array(itertools.product(a, b))
。
解决方案 5:
你可以做这样的事情
import numpy as np
def cartesian_coord(*arrays):
grid = np.meshgrid(*arrays)
coord_list = [entry.ravel() for entry in grid]
points = np.vstack(coord_list).T
return points
a = np.arange(4) # Fake data
print(cartesian_coord(*6*[a])
由此得出
array([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 2],
...,
[3, 3, 3, 3, 3, 1],
[3, 3, 3, 3, 3, 2],
[3, 3, 3, 3, 3, 3]])
解决方案 6:
以下 NumPy 实现的速度应大约是前面给出的答案的两倍:
def cartesian2(arrays):
arrays = [np.asarray(a) for a in arrays]
shape = (len(x) for x in arrays)
ix = np.indices(shape, dtype=int)
ix = ix.reshape(len(arrays), -1).T
for n, arr in enumerate(arrays):
ix[:, n] = arrays[n][ix[:, n]]
return ix
解决方案 7:
看起来您想要一个网格来评估您的功能,在这种情况下,您可以使用numpy.ogrid(打开)或numpy.mgrid(充实):
import numpy
my_grid = numpy.mgrid[[slice(0, 1, 0.1)]*6]
解决方案 8:
这是另一种方法,使用纯 NumPy,没有递归,没有列表理解,也没有显式 for 循环。它比原始答案慢约 20%,并且基于np.meshgrid。
def cartesian(*arrays):
mesh = np.meshgrid(*arrays) # Standard NumPy meshgrid
dim = len(mesh) # Number of dimensions
elements = mesh[0].size # Number of elements, any index will do
flat = np.concatenate(mesh).ravel() # Flatten the whole meshgrid
reshape = np.reshape(flat, (dim, elements)).T # Reshape and transpose
return reshape
例如,
x = np.arange(3)
a = cartesian(x, x, x, x, x)
print(a)
给出
[[0 0 0 0 0]
[0 0 0 0 1]
[0 0 0 0 2]
...,
[2 2 2 2 0]
[2 2 2 2 1]
[2 2 2 2 2]]
解决方案 9:
对于一维数组(或平面 Python 列表)笛卡尔积的纯 NumPy 实现,只需使用meshgrid() ,使用transpose()滚动轴,然后重塑为所需的输出:
def cartprod(*arrays):
N = len(arrays)
return transpose(meshgrid(*arrays, indexing='ij'),
roll(arange(N + 1), -1)).reshape(-1, N)
请注意,这遵循最后一个轴变化最快的惯例(“C 风格”或“行主序”)。
In [88]: cartprod([1,2,3], [4,8], [100, 200, 300, 400], [-5, -4])
Out[88]:
array([[ 1, 4, 100, -5],
[ 1, 4, 100, -4],
[ 1, 4, 200, -5],
[ 1, 4, 200, -4],
[ 1, 4, 300, -5],
[ 1, 4, 300, -4],
[ 1, 4, 400, -5],
[ 1, 4, 400, -4],
[ 1, 8, 100, -5],
[ 1, 8, 100, -4],
[ 1, 8, 200, -5],
[ 1, 8, 200, -4],
[ 1, 8, 300, -5],
[ 1, 8, 300, -4],
[ 1, 8, 400, -5],
[ 1, 8, 400, -4],
[ 2, 4, 100, -5],
[ 2, 4, 100, -4],
[ 2, 4, 200, -5],
[ 2, 4, 200, -4],
[ 2, 4, 300, -5],
[ 2, 4, 300, -4],
[ 2, 4, 400, -5],
[ 2, 4, 400, -4],
[ 2, 8, 100, -5],
[ 2, 8, 100, -4],
[ 2, 8, 200, -5],
[ 2, 8, 200, -4],
[ 2, 8, 300, -5],
[ 2, 8, 300, -4],
[ 2, 8, 400, -5],
[ 2, 8, 400, -4],
[ 3, 4, 100, -5],
[ 3, 4, 100, -4],
[ 3, 4, 200, -5],
[ 3, 4, 200, -4],
[ 3, 4, 300, -5],
[ 3, 4, 300, -4],
[ 3, 4, 400, -5],
[ 3, 4, 400, -4],
[ 3, 8, 100, -5],
[ 3, 8, 100, -4],
[ 3, 8, 200, -5],
[ 3, 8, 200, -4],
[ 3, 8, 300, -5],
[ 3, 8, 300, -4],
[ 3, 8, 400, -5],
[ 3, 8, 400, -4]])
如果要以最快的速度更改第一个轴(“ Fortran风格” 或“列主”),只需更改如下order
参数:reshape()
`reshape((-1, N), order='F')`
解决方案 10:
Pandas 的merge() 方法为这个问题提供了一个简单、快速的解决方案:
# Given the lists
x, y, z = [1, 2, 3], [4, 5], [6, 7]
# Get dataframes with the same, constant index
x = pd.DataFrame({'x': x}, index=np.repeat(0, len(x)))
y = pd.DataFrame({'y': y}, index=np.repeat(0, len(y)))
z = pd.DataFrame({'z': z}, index=np.repeat(0, len(z)))
# Get all permutations stored in a new dataframe
df = pd.merge(x, pd.merge(y, z, left_index=True, right_index=True),
left_index=True, right_index=True)