如何从一个可迭代的元组中填充两个(或更多)numpy 数组?

Posted

技术标签:

【中文标题】如何从一个可迭代的元组中填充两个(或更多)numpy 数组?【英文标题】:How do I fill two (or more) numpy arrays from a single iterable of tuples? 【发布时间】:2013-02-11 02:42:34 【问题描述】:

我遇到的实际问题是我想在 RAM 中存储 (float, str) 元组的长排序列表。一个普通的列表不适合我的 4Gb RAM,所以我想我可以使用两个 numpy.ndarrays。

数据源是 2 元组的可迭代对象。 numpy 有一个 fromiter 函数,但是我该如何使用它呢?迭代中的项目数是未知的。由于内存限制,我不能先将它消耗到列表中。想到了itertools.tee,不过这里好像增加了不少内存开销。

我想我可以做的是分块使用迭代器并将它们添加到数组中。那么我的问题是,如何有效地做到这一点?我是否应该制作 2 个二维数组并向它们添加行? (然后我需要将它们转换为 1D)。

或者也许有更好的方法?我真正需要的只是在对数时间内通过相应数字的值搜索字符串数组(这就是我想按浮点值排序的原因)并使其尽可能紧凑。

附:可迭代对象未排序。

【问题讨论】:

使用np.fromiter 构建一个包含两列的数组就足够了吗? @unutbu ...我不知道为什么我没有考虑过 :) 听起来是个好主意。然后我只是沿着较长的轴对其进行排序并保持这种方式,对吗?我想您可以将其发布为答案。 【参考方案1】:

也许使用np.fromiter 构建一个单一的结构化数组:

import numpy as np


def gendata():
    # You, of course, have a different gendata...
    for i in xrange(N):
        yield (np.random.random(), str(i))

N = 100

arr = np.fromiter(gendata(), dtype='<f8,|S20')

按第一列排序,使用第二列进行决胜局将花费 O(N log N) 时间:

arr.sort(order=['f0','f1'])

使用searchsorted 在 O(log N) 时间内通过第一列中的值查找行:

# Some pseudo-random value in arr['f0']
val = arr['f0'][10]
print(arr[10])
# (0.049875262239617246, '46')

idx = arr['f0'].searchsorted(val)
print(arr[idx])
# (0.049875262239617246, '46')

您在 cmets 中提出了许多重要问题;让我在这里尝试回答:

numpybook 中解释了基本的 dtype。可能有一个或 两个额外的 dtypes(比如 float16 从那以后添加了 书是写的,但基础知识都在那里解释。)

也许更彻底的讨论在online documentation。这是对你提到的例子的一个很好的补充here。

Dtypes 可用于定义具有列名的结构化数组,或者 使用默认列名。 'f0''f1' 等为默认列 名字。由于我将 dtype 定义为 '&lt;f8,|S20' 我未能提供 列名,因此 NumPy 将第一列命名为 'f0',第二列 'f1'。如果我们使用了

dtype='[('fval','<f8'), ('text','|S20')]

那么结构化数组arr 将具有列名'fval''text'.

不幸的是,dtype 必须在调用np.fromiter 时进行修复。你 可以想象遍历gendata 一次以发现 字符串的最大长度,构建您的 dtype,然后调用 np.fromiter(并再次遍历 gendata),但是 那是相当繁重的。如果你知道的话当然更好 推进字符串的最大大小。 (|S20 定义字符串 字段具有 20 字节的固定长度。) NumPy 数组放置数据 固定大小的数组中的预定义大小。将数组(甚至是多维数组)视为一维内存的连续块。 (这是一种过度简化——存在不连续的数组——但会帮助您对以下内容进行想象。)NumPy 通过利用固定大小(由dtype 设置)来快速计算偏移量来获得其大部分速度需要访问数组中的元素。如果字符串具有可变大小,那么它 NumPy 很难找到正确的偏移量。硬,我的意思是 NumPy 需要一个索引或以某种方式重新设计。 NumPy 根本不是 以这种方式构建的。 NumPy 确实有一个object dtype,它允许您放置一个 4 字节 指向您想要的任何 Python 对象的指针。这样,您就可以拥有 NumPy 具有任意 Python 数据的数组。不幸的是,np.fromiter 函数不允许您创建 dtype object 的数组。我不知道为什么会有这个限制...... 请注意,np.fromitercount 为 指定的。通过知道count(行数)和 dtype(以及每行的大小)NumPy 可以预先分配 为结果数组提供足够的内存。如果您不指定 count,然后 NumPy 会猜测 数组,如果太小,它会尝试调整数组的大小。如果 原来的内存块可以扩展你很幸运。但如果 NumPy 必须分配一个全新的内存块,然后是所有旧的 必须将数据复制到新位置,这会减慢速度 性能显着。

【讨论】:

哇,这里对我来说有很多新东西,例如fX 索引语法,但主要是您使用的 dtype。首先,是否记录了可能的 dtypes?我找到了this,但我会使用一些解释而不仅仅是示例。大小是否必须固定(我想如果它是一个普通数组)?因为在理想世界中,我既不希望它有上限,也不希望短字符串占用额外空间。我能得到这样的东西吗? 如果不指定count,会不会np.fromiter不用先从迭代器中构建一个列表,然后再转换成数组? @Jaime:如果你不指定count,那么当数据超出预分配的输出数组时,np.fromiter 将不得不调整 numpy 数组的大小。如果你有足够的连续内存,它就不必在调整大小时复制数据,并且在任何时候都不会使用 Python 列表。 再次感谢。从书中看来,object_ 的 dtype 仅引用了要存储在数组中的对象。如果是真的,我可以用吗? 这涉及到我不喜欢的 NumPy 领域。根据我对 C 源代码的理解,当dtype(或其一部分)的类型为object 时,对PyDataType_REFCHK(dtype) 的调用会失败。我对C 的理解不强,所以我只好将你推荐给the source。【参考方案2】:

这是一种从 N-tuples 生成器中构建 N 单独数组的方法:

import numpy as np
import itertools as IT


def gendata():
    # You, of course, have a different gendata...
    N = 100
    for i in xrange(N):
        yield (np.random.random(), str(i))


def fromiter(iterable, dtype, chunksize=7):
    chunk = np.fromiter(IT.islice(iterable, chunksize), dtype=dtype)
    result = [chunk[name].copy() for name in chunk.dtype.names]
    size = len(chunk)
    while True:
        chunk = np.fromiter(IT.islice(iterable, chunksize), dtype=dtype)
        N = len(chunk)
        if N == 0:
            break
        newsize = size + N
        for arr, name in zip(result, chunk.dtype.names):
            col = chunk[name]
            arr.resize(newsize, refcheck=0)
            arr[size:] = col
        size = newsize
    return result

x, y = fromiter(gendata(), '<f8,|S20')

order = np.argsort(x)
x = x[order]
y = y[order]

# Some pseudo-random value in x
N = 10
val = x[N]
print(x[N], y[N])
# (0.049875262239617246, '46')

idx = x.searchsorted(val)
print(x[idx], y[idx])
# (0.049875262239617246, '46')

上面的fromiter 函数以块的形式读取迭代(大小为chunksize)。它调用 NumPy 数组方法 resize 来根据需要扩展结果数组。

我使用了一个小的默认值chunksize,因为我是在小数据上测试这段代码。当然,您可能希望更改默认块大小或传递具有更大值的chunksize 参数。

【讨论】:

是的,我也想分块阅读,谢谢你的好例子。我们不能在这里将chunksize 传递给np.fromiter 以加快速度吗? 不幸的是,我没有办法。如果我们使用count=chunksize,如果迭代包含少于chunksize 项,则对np.fromiter 的调用可能会失败。如果我们试图在 try..except 块中捕获它,那么我们将丢失数据,因为可迭代对象仅适用于一次传递。

以上是关于如何从一个可迭代的元组中填充两个(或更多)numpy 数组?的主要内容,如果未能解决你的问题,请参考以下文章

Swift flatMap:如何从数组中仅删除元组中特定元素为零的元组?

元组--补充

元组--补充

Python 元组拆包

Python中的元组(Tuple)

从列表中的元组中删除空字符串