如何从一个可迭代的元组中填充两个(或更多)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.ndarray
s。
数据源是 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 定义为 '<f8,|S20'
我未能提供
列名,因此 NumPy 将第一列命名为 'f0'
,第二列
'f1'
。如果我们使用了
dtype='[('fval','<f8'), ('text','|S20')]
那么结构化数组arr
将具有列名'fval'
和
'text'
.
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.fromiter
在count
为
指定的。通过知道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 数组?的主要内容,如果未能解决你的问题,请参考以下文章