如何创建一个具有可变行大小的多维numpy数组?

82
我想创建一个二维的numpy数组,其中每一行包含不同数量的元素。
尝试使用:
cells = numpy.array([[0,1,2,3], [2,3,4]])

出现错误

ValueError: setting an array element with a sequence.
7个回答

60

现在距离提问已经过去了将近7年,你的代码

cells = numpy.array([[0,1,2,3], [2,3,4]])

本代码在numpy 1.12.0和python 3.5上执行,没有产生任何错误,cells包含以下内容:

array([[0, 1, 2, 3], [2, 3, 4]], dtype=object)

你可以通过 cells[0][2] # (=2) 访问你的 cells 元素。
如果你想在新元素(即数组)可用时动态构建 numpy 数组列表,除了 tom10 的解决方案,还可以使用 append
d = []                 # initialize an empty list
a = np.arange(3)       # array([0, 1, 2])
d.append(a)            # [array([0, 1, 2])]
b = np.arange(3,-1,-1) #array([3, 2, 1, 0])
d.append(b)            #[array([0, 1, 2]), array([3, 2, 1, 0])]

13
问题在于您仍然无法使用 d.mean()、d.flatten() 等函数。 - episodeyang
2
这是最优的方式吗?NumPy数组仍然应该是统一长度的2D数组吗?如果是,那么存储可变长度数据的最优方式是什么? - SantoshGupta7

45

虽然Numpy可以处理任意对象的数组,但它更适用于具有固定维度的同质数字数组。如果您确实需要数组中的数组,请使用嵌套列表。但是,根据数据的预期用途,不同的数据结构可能会更好,例如,如果存在一些无效数据点,则可以使用掩蔽数组。

如果您确实需要灵活的Numpy数组,请使用类似以下的方法:

numpy.array([[0,1,2,3], [2,3,4]], dtype=object)

但是这将创建一个存储列表引用的一维数组,这意味着你将失去大部分Numpy的优势(向量处理、局部性、切片等)。


NumPy “知道”任意对象数组是什么意思? - Marcos Pereira
1
请注意:关于内存使用:Python 整数列表比 numpy 整数数组更低效。我的意思是,除了列表或数组对象元数据的一点开销外,具有 N 个整数的列表内容占用 (28 + 8) * N 字节,但 numpy 占用 (8 * N) 或者您可以使用 np.int16np.int32 等将其减少到 2 * N4 * N 字节。 https://webcourses.ucf.edu/courses/1249560/pages/python-lists-vs-numpy-arrays-what-is-the-difference - Thamme Gowda
@ThammeGowda Python将每个整数存储为PyObject,因此其开销比NumPy大得多。 - JiaHao Xu
@JiaHaoXu 这就是我说的!我说Python的列表比NumPy低效 - Thamme Gowda
@ThammeGowda,这个SO是关于存储列表而不是整数的。这个对象dtype数组相对于嵌套列表没有内存(或速度)优势。numpy有编译代码来处理数字dtype以一种内存和速度高效的方式(尽管制作这样的数组有点耗时)。并且像列表一样迭代数组会更慢。 - hpaulj

22

Numpy并不很支持这个(根据定义,几乎到处都是,“二维数组”必须拥有等长的每一行)。Python列表中包含Numpy数组可能是一个不错的解决方案,因为这样你可以在需要使用Numpy时获得它的优势:

cells = [numpy.array(a) for a in [[0,1,2,3], [2,3,4]]]

这实际上解决了我在代码中遇到的问题! - Lasse Nielsen

8

另一种选择是将您的数组存储为一个连续的数组,并存储它们的大小或偏移量。这需要更多的概念思考如何操作您的数组,但令人惊讶的是,很多操作可以像使用具有不同大小的二维数组一样工作。在无法使用时,可以使用np.split来创建calocedrus建议的列表。最简单的操作是ufuncs,因为它们几乎不需要修改。以下是一些示例:

cells_flat = numpy.array([0, 1, 2, 3, 2, 3, 4])
# One of these is required, it's pretty easy to convert between them,
# but having both makes the examples easy
cell_lengths = numpy.array([4, 3])
cell_starts = numpy.insert(cell_lengths[:-1].cumsum(), 0, 0)
cell_lengths2 = numpy.diff(numpy.append(cell_starts, cells_flat.size))
assert np.all(cell_lengths == cell_lengths2)

# Copy prevents shared memory
cells = numpy.split(cells_flat.copy(), cell_starts[1:])
# [array([0, 1, 2, 3]), array([2, 3, 4])]

numpy.array([x.sum() for x in cells])
# array([6, 9])
numpy.add.reduceat(cells_flat, cell_starts)
# array([6, 9])

[a + v for a, v in zip(cells, [1, 3])]
# [array([1, 2, 3, 4]), array([5, 6, 7])]
cells_flat + numpy.repeat([1, 3], cell_lengths)
# array([1, 2, 3, 4, 5, 6, 7])

[a.astype(float) / a.sum() for a in cells]
# [array([ 0.        ,  0.16666667,  0.33333333,  0.5       ]),
#  array([ 0.22222222,  0.33333333,  0.44444444])]
cells_flat.astype(float) / np.add.reduceat(cells_flat, cell_starts).repeat(cell_lengths)
# array([ 0.        ,  0.16666667,  0.33333333,  0.5       ,  0.22222222,
#         0.33333333,  0.44444444])

def complex_modify(array):
    """Some complicated function that modifies array

    pretend this is more complex than it is"""
    array *= 3

for arr in cells:
    complex_modify(arr)
cells
# [array([0, 3, 6, 9]), array([ 6,  9, 12])]
for arr in numpy.split(cells_flat, cell_starts[1:]):
    complex_modify(arr)
cells_flat
# array([ 0,  3,  6,  9,  6,  9, 12])

这是一个很棒的解决方案。巧妙地使用了reduceat。+1 - C. Yduqoli

4
在numpy 1.14.3中,使用append:
d = []                 # initialize an empty list
a = np.arange(3)       # array([0, 1, 2])
d.append(a)            # [array([0, 1, 2])]
b = np.arange(3,-1,-1) #array([3, 2, 1, 0])
d.append(b)            #[array([0, 1, 2]), array([3, 2, 1, 0])]

当您获得一个数组列表(可以具有不同的长度)时,您可以执行像d [0] .mean()这样的操作。另一方面,

cells = numpy.array([[0,1,2,3], [2,3,4]])

结果将返回一个列表数组。

您可能想要执行以下操作:

a1 = np.array([1,2,3])
a2 = np.array([3,4])
a3 = np.array([a1,a2])
a3 # array([array([1, 2, 3]), array([3, 4])], dtype=object)
type(a3) # numpy.ndarray
type(a2) # numpy.ndarray

2

np.array([[0,1,2,3], [2,3,4]], dtype=object) 返回一个由列表组成的“数组”。

a = np.array([np.array([0,1,2,3]), np.array([2,3,4])], dtype=object) 返回一个由数组组成的数组。它已经允许进行像a+1这样的操作。

在此基础上,可以通过子类化来增强功能。

import numpy as np

class Arrays(np.ndarray):
    def __new__(cls, input_array, dims=None):
        obj = np.array(list(map(np.array, input_array))).view(cls)
        return obj
    def __getitem__(self, ij):
        if isinstance(ij, tuple) and len(ij) > 1:
            # handle twodimensional slicing
            if isinstance(ij[0],slice) or hasattr(ij[0], '__iter__'):
                # [1:4,:] or [[1,2,3],[1,2]]
                return Arrays(arr[ij[1]] for arr in self[ij[0]])
            return self[ij[0]][ij[1]] # [1,:] np.array
        return super(Arrays, self).__getitem__(ij)
    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        axis = kwargs.pop('axis', None)
        dimk = [len(arg) if hasattr(arg, '__iter__') else 1 for arg in inputs]
        dim = max(dimk)
        pad_inputs = [([i]*dim if (d<dim) else i) for d,i in zip(dimk, inputs)]
        result = [np.ndarray.__array_ufunc__(self, ufunc, method, *x, **kwargs) for x in zip(*pad_inputs)]
        if method == 'reduce':
            # handle sum, min, max, etc.
            if axis == 1:
                return np.array(result)
            else:
                # repeat over remaining axis
                return np.ndarray.__array_ufunc__(self, ufunc, method, result, **kwargs)
        return Arrays(result)

Now this works:

a = Arrays([[0,1,2,3], [2,3,4]])
a[0:1,0:-1]
# Arrays([[0, 1, 2]])
np.sin(a)
# Arrays([array([0.        , 0.84147098, 0.90929743, 0.14112001]),
#        array([ 0.90929743,  0.14112001, -0.7568025 ])], dtype=object)
a + 2*a
# Arrays([array([0, 3, 6, 9]), array([ 6,  9, 12])], dtype=object)

要使nanfunctions起作用,可以执行以下操作。
# patch for nanfunction that cannot handle the object-ndarrays along with second axis=-1
def nanpatch(func):
    def wrapper(a, axis=None, **kwargs):
        if isinstance(a, Arrays):
            rowresult = [func(x, **kwargs) for x in a]
            if axis == 1:
                return np.array(rowresult)
            else:
                # repeat over remaining axis
                return func(rowresult)
        # otherwise keep the original version
        return func(a, axis=axis, **kwargs)
    return wrapper

np.nanmean = nanpatch(np.nanmean)
np.nansum = nanpatch(np.nansum)
np.nanmin = nanpatch(np.nanmin)
np.nanmax = nanpatch(np.nanmax)

np.nansum(a)
# 15
np.nansum(a, axis=1)
# array([6, 9])

1

虽然略微偏离主题,但并不像人们想象的那样远离,因为急切模式现在是默认设置: 如果您正在使用Tensorflow,您可以这样做:

a = tf.ragged.constant([[0, 1, 2, 3]])
b = tf.ragged.constant([[2, 3, 4]])
c = tf.concat([a, b], axis=0)

然后您仍然可以执行所有数学运算,例如tf.math.reduce_mean等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接