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

时间:2013-02-25 20:18:16

标签: python arrays numpy iteration

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

数据源是可重复的2元组。 numpy具有fromiter功能,但我该如何使用它?迭代中的项目数是未知的。由于内存限制,我不能首先将它消耗到列表中。我想到了itertools.tee,但它似乎在这里增加了很多内存开销。

我想我能做的就是以块的形式使用迭代器并将它们添加到数组中。然后我的问题是,如何有效地做到这一点?我应该制作2个2D阵列并为它们添加行吗? (然后我需要将它们转换为1D)。

或许还有更好的方法?我真正需要的是通过对数时间内相应数字的值来搜索字符串数组(这就是为什么我要按浮点值排序)并保持尽可能紧凑。

P.S。迭代没有排序。

2 个答案:

答案 0 :(得分:8)

也许使用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'])

在O(log N)时间内,可以使用searchsorted按第一列中的值查找行:

# 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')

你在评论中提出了许多重要问题;让我试着在这里回答:

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

    也许在online documentation进行更彻底的讨论。对于您提到的示例here,这是一个很好的补充。

  • Dtypes可用于定义具有列名称的结构化数组,或 使用默认列名称。 'f0''f1'等是默认列 名。由于我将dtype定义为'<f8,|S20',因此无法提供 列名,因此NumPy命名第一列'f0',第二列 'f1'。如果我们使用过

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

    然后结构化数组arr将具有列名'fval''text'

  • 不幸的是,必须在调用np.fromiter时修复dtype。您 可以想象,可以迭代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.fromiter具有更好的效果count 指定。通过了解count(行数)和。{ dtype(以及每行的大小)NumPy可以预先分配 为结果数组准确的内存。如果你没有指定 count,然后NumPy将猜测初始大小 数组,如果太小,它会尝试调整数组的大小。如果 原始的记忆块可以延长你的运气。但如果 NumPy必须分配一个全新的内存块,然后才能分配旧内存 必须将数据复制到新位置,这将减慢速度 性能显着。

答案 1 :(得分:1)

这是一种从N的生成器构建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参数。