从一个numpy数组列表创建numpy数组的Python方法

42

我在循环中生成了一个一维numpy数组的列表,然后将此列表转换为2d numpy数组。如果我事先知道项数,我会预先分配一个2d numpy数组,但我不知道,因此我把所有东西都放在一个列表中。

以下是模拟:

>>> list_of_arrays = map(lambda x: x*ones(2), range(5))
>>> list_of_arrays
[array([ 0.,  0.]), array([ 1.,  1.]), array([ 2.,  2.]), array([ 3.,  3.]), array([ 4.,  4.])]
>>> arr = array(list_of_arrays)
>>> arr
array([[ 0.,  0.],
       [ 1.,  1.],
       [ 2.,  2.],
       [ 3.,  3.],
       [ 4.,  4.]])

我的问题如下:

在收集连续的数字数据时(在我的情况下是numpy数组),是否有比将它们放入列表中,然后将其转换为numpy.array(我创建一个新对象并复制数据)更好的方式(在性能上)?是否有一个可扩展的矩阵数据结构可以在一个经过充分测试的模块中使用?

我的二维矩阵的典型大小在100x10到5000x10之间

编辑: 在这个例子中,我使用了map,但在我的实际应用程序中,我有一个for循环。

6个回答

22

使用numpy.concatenate是一种方便的方法。我相信这也比@unutbu的答案更快:

In [32]: import numpy as np 

In [33]: list_of_arrays = list(map(lambda x: x * np.ones(2), range(5)))

In [34]: list_of_arrays
Out[34]: 
[array([ 0.,  0.]),
 array([ 1.,  1.]),
 array([ 2.,  2.]),
 array([ 3.,  3.]),
 array([ 4.,  4.])]

In [37]: shape = list(list_of_arrays[0].shape)

In [38]: shape
Out[38]: [2]

In [39]: shape[:0] = [len(list_of_arrays)]

In [40]: shape
Out[40]: [5, 2]

In [41]: arr = np.concatenate(list_of_arrays).reshape(shape)

In [42]: arr
Out[42]: 
array([[ 0.,  0.],
       [ 1.,  1.],
       [ 2.,  2.],
       [ 3.,  3.],
       [ 4.,  4.]])

21
假设您知道最终的数组 arr 的大小不会超过5000x10,那么您可以预先分配一个最大尺寸的数组,在遍历时填充数据,然后在退出循环后使用 arr.resize 将其缩小到发现的大小。
以下测试表明,无论数组的最终大小如何,这样做都比构造中间 Python 列表稍微快一些。
此外,arr.resize 会释放未使用的内存,因此最终(尽管中间可能不是)内存占用量比 python_lists_to_array 使用的内存占用量小。
这表明 numpy_all_the_way 更快:
% python -mtimeit -s"import test" "test.numpy_all_the_way(100)"
100 loops, best of 3: 1.78 msec per loop
% python -mtimeit -s"import test" "test.numpy_all_the_way(1000)"
100 loops, best of 3: 18.1 msec per loop
% python -mtimeit -s"import test" "test.numpy_all_the_way(5000)"
10 loops, best of 3: 90.4 msec per loop

% python -mtimeit -s"import test" "test.python_lists_to_array(100)"
1000 loops, best of 3: 1.97 msec per loop
% python -mtimeit -s"import test" "test.python_lists_to_array(1000)"
10 loops, best of 3: 20.3 msec per loop
% python -mtimeit -s"import test" "test.python_lists_to_array(5000)"
10 loops, best of 3: 101 msec per loop

这表明numpy_all_the_way使用更少的内存:

% test.py
Initial memory usage: 19788
After python_lists_to_array: 20976
After numpy_all_the_way: 20348

test.py:

import numpy as np
import os


def memory_usage():
    pid = os.getpid()
    return next(line for line in open('/proc/%s/status' % pid).read().splitlines()
                if line.startswith('VmSize')).split()[-2]

N, M = 5000, 10


def python_lists_to_array(k):
    list_of_arrays = list(map(lambda x: x * np.ones(M), range(k)))
    arr = np.array(list_of_arrays)
    return arr


def numpy_all_the_way(k):
    arr = np.empty((N, M))
    for x in range(k):
        arr[x] = x * np.ones(M)
    arr.resize((k, M))
    return arr

if __name__ == '__main__':
    print('Initial memory usage: %s' % memory_usage())
    arr = python_lists_to_array(5000)
    print('After python_lists_to_array: %s' % memory_usage())
    arr = numpy_all_the_way(5000)
    print('After numpy_all_the_way: %s' % memory_usage())

一个需要注意的地方 - python_lists_to_array 假设使用 Python 2,其中 map 会生成一个列表。 - hpaulj
@hpaulj:感谢您的纠正。代码现已更新为Python3版本。 - unutbu
你认为有没有办法跳过 list 部分?我已经提出了一个相关问题,https://dev59.com/na_la4cB1Zd3GeqPuYqs - bers

16

比 @Gill Bates 的答案更简洁的是以下一行代码:

np.stack(list_of_arrays, axis=0)

2
我将添加自己版本的 ~unutbu 的回答。与 numpy_all_the_way 类似,但如果出现索引错误,则动态调整大小。我认为对于小数据集来说会更快一些,但实际上会慢一些——边界检查会使速度变慢太多。
initial_guess = 1000

def my_numpy_all_the_way(k):
    arr=np.empty((initial_guess,M))
    for x,row in enumerate(make_test_data(k)):
        try:
            arr[x]=row
        except IndexError:
            arr.resize((arr.shape[0]*2, arr.shape[1]))
            arr[x]=row
    arr.resize((k,M))
    return arr

2
你正在做的是标准的方式。numpy 数组的一个属性是它们需要连续的内存。我能想到唯一可能出现“空洞”的情况是在 PyArrayObject 的 strides 成员中,但那不影响这里的讨论。由于 numpy 数组具有连续的内存并且是“预分配的”,因此添加新行/列意味着分配新内存、复制数据,然后释放旧内存。如果你经常这样做,效率就不高。

有时候,当列表包含大量数字时,某些人可能不想先创建一个列表,然后最终再将其转换为 numpy 数组:numpy 数组占用的空间比 Python 原生列表占用的空间少得多(因为 Python 原生列表存储 Python 对象)。对于你典型的数组大小,我认为这不是问题。

当你从数组列表创建最终数组时,你正在将所有数据复制到新位置以创建新的(在你的示例中是二维)数组。这仍然比拥有一个 numpy 数组并每次获得新数据时执行 next = numpy.vstack((next, new_row)) 更高效。 vstack() 将为每个“行”复制所有数据。

之前在numpy-discussion邮件列表上有一个关于添加一种新的numpy数组类型以实现高效扩展/追加的讨论,当时似乎引起了相当大的兴趣,不过我不知道是否有进展。你可能需要看看那个帖子。
我认为你所做的非常符合Python风格,而且也很高效,所以除非你真的需要其他功能(比如更高的空间利用率),否则应该没问题。这就是我在不知道数组元素数量的情况下创建numpy数组的方式。

@Alok---感谢您的周到回答。 ~unubuntu的答案中的时间表现出对5%效率的担忧。在你绝对必须拥有那5%之前,这几乎肯定是一个错误。 - telliott99

2

更简单的@fnjn回答

np.vstack(list_of_arrays)

好的,既然在@fnjn的回答中axis=0是默认参数,因此可以省略并使用不那么明确的版本np.stack(list_of_arrays),这甚至更简单。但我猜这不是代码高尔夫;) - bers

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