从 numpy 数组列表创建 numpy 数组的 Pythonic 方法

Posted

技术标签:

【中文标题】从 numpy 数组列表创建 numpy 数组的 Pythonic 方法【英文标题】:Pythonic way to create a numpy array from a list of numpy arrays 【发布时间】:2011-01-07 13:21:12 【问题描述】:

我在循环中生成一维 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(我正在创建一个新的obj 和复制数据)?在经过良好测试的模块中是否有可用的“可扩展”矩阵数据结构?

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

编辑:在这个例子中我使用的是地图,但在我的实际应用中我有一个 for 循环

【问题讨论】:

【参考方案1】:

假设您知道最终数组 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 生成 list @hpaulj:感谢您的更正。现在为 Python3 更新了代码。 您认为有办法跳过list 部分吗?我问了一个问题,***.com/questions/53117531【参考方案2】:

更简单的@fnjn 答案

np.vstack(list_of_arrays)

【讨论】:

好吧,由于@fnjn 的答案中的axis=0 是默认参数,因此可以省略它,并且他的np.stack(list_of_arrays) 的不太明确的版本更简单。但这不是代码高尔夫,我猜 ;)【参考方案3】:

比@Gill Bates 的回答更简单,这里有一行代码:

np.stack(list_of_arrays, axis=0)

【讨论】:

【参考方案4】:

方便的方式,使用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.]])

【讨论】:

【参考方案5】:

我将添加我自己的 ~unutbu 答案版本。类似于 numpy_all_the 方式,但如果出现索引错误,您可以动态调整大小。我认为对于小型数据集来说它会快一点,但它会慢一点 - 边界检查让事情变得太慢了。

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

【讨论】:

【参考方案6】:

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

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

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

前段时间有一个thread on numpy-discussion mailing list 讨论了添加允许有效扩展/附加的新numpy 数组类型的可能性。当时似乎对此很感兴趣,尽管我不知道是否有什么结果。你可能想看看那个帖子。

我会说你正在做的是非常 Pythonic 和高效的,所以除非你真的需要别的东西(也许是更多的空间效率?),你应该没问题。当我一开始不知道数组中元素的数量时,这就是我创建 numpy 数组的方式。

【讨论】:

@Alok---感谢您的周到回答。 ~unubuntu 的答案中的时间显示了对 5% 效率的担忧。这几乎可以肯定是一个错误,直到你达到绝对必须拥有那 5% 的程度。

以上是关于从 numpy 数组列表创建 numpy 数组的 Pythonic 方法的主要内容,如果未能解决你的问题,请参考以下文章

从嵌套列表创建数组时抑制 Numpy 中的科学记数法

使用列表中的随机元素创建 numpy 数组

Python 列表理解 - numpy 数组 [关闭]

Numpy 创建数组的常用函数

NumPy来自现有数据的数组

如何使用 numpy 或 pandas 创建(或更改)数组/列表的维度?