以附加模式保存 numpy 数组

save numpy array in append mode

是否可以保存一个 numpy 数组并将其附加到一个已经存在的 npy 文件中——类似于 np.save(filename,arr,mode='a')

我有几个函数必须遍历大型数组的行。由于内存限制,我无法立即创建数组。为了避免一遍又一遍地创建行,我想创建每行一次并将其保存到文件中,并将其附加到文件中的前一行。稍后我可以在 mmap_mode 中加载 npy 文件,在需要时访问切片。

内置 .npy 文件格式非常适合处理小型数据集,无需依赖 numpy 以外的外部模块。

但是,当您开始拥有大量数据时,首选使用旨在处理此类数据集的文件格式,例如 HDF5 [1]

例如,下面是在 HDF5 中使用 PyTables

保存 numpy 数组的解决方案

第 1 步:创建可扩展 EArray 存储

import tables
import numpy as np

filename = 'outarray.h5'
ROW_SIZE = 100
NUM_COLUMNS = 200

f = tables.open_file(filename, mode='w')
atom = tables.Float64Atom()

array_c = f.create_earray(f.root, 'data', atom, (0, ROW_SIZE))

for idx in range(NUM_COLUMNS):
    x = np.random.rand(1, ROW_SIZE)
    array_c.append(x)
f.close()

步骤 2:将行附加到现有数据集(如果需要)

f = tables.open_file(filename, mode='a')
f.root.data.append(x)

第 3 步:读回数据的子集

f = tables.open_file(filename, mode='r')
print(f.root.data[1:10,2:20]) # e.g. read from disk only this part of the dataset

要使用 numpy.save 将数据附加到已存在的文件,我们应该使用:

f_handle = file(filename, 'a')
numpy.save(f_handle, arr)
f_handle.close()

我检查过它在 python 2.7 和 numpy 1.10.4

中是否有效

我改编了 here 的代码,其中讨论了 savetxt 方法。

.npy 文件包含 header,其中包含数组的形状和数据类型。如果您知道结果数组的样子,您可以自己编写 header,然后分块写入数据。例如,这里是连接二维矩阵的代码:

import numpy as np
import numpy.lib.format as fmt

def get_header(fnames):
    dtype = None
    shape_0 = 0
    shape_1 = None
    for i, fname in enumerate(fnames):
        m = np.load(fname, mmap_mode='r') # mmap so we read only header really fast
        if i == 0:
            dtype = m.dtype
            shape_1 = m.shape[1]
        else:
            assert m.dtype == dtype
            assert m.shape[1] == shape_1
        shape_0 += m.shape[0]
    return {'descr': fmt.dtype_to_descr(dtype), 'fortran_order': False, 'shape': (shape_0, shape_1)}

def concatenate(res_fname, input_fnames):
    header = get_header(input_fnames)
    with open(res_fname, 'wb') as f:
        fmt.write_array_header_2_0(f, header)
        for fname in input_fnames:
            m = np.load(fname)
            f.write(m.tostring('C'))

如果您需要更通用的解决方案(在添加时就地编辑 header),您将不得不求助于 fseek 技巧,如 [1].

灵感来自
[1]: https://mail.scipy.org/pipermail/numpy-discussion/2009-August/044570.html(开箱即用)
[2]: https://docs.scipy.org/doc/numpy/neps/npy-format.html
[3]: https://github.com/numpy/numpy/blob/master/numpy/lib/format.py

您可以尝试读取文件然后添加新数据

import numpy as np
import os.path

x = np.arange(10) #[0 1 2 3 4 5 6 7 8 9]

y = np.load("save.npy") if os.path.isfile("save.npy") else [] #get data if exist
np.save("save.npy",np.append(y,x)) #save the new

2次操作后:

print(np.load("save.npy")) #[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9]

这是对 Mohit Pandey 的回答的扩展,显示了完整的保存/加载示例。它使用 Python 3.6 和 Numpy 1.11.3.

进行了测试
from pathlib import Path
import numpy as np
import os

p = Path('temp.npy')
with p.open('ab') as f:
    np.save(f, np.zeros(2))
    np.save(f, np.ones(2))

with p.open('rb') as f:
    fsz = os.fstat(f.fileno()).st_size
    out = np.load(f)
    while f.tell() < fsz:
        out = np.vstack((out, np.load(f)))

out = array([[ 0., 0.], [ 1., 1.]])

以下内容基于PaxRomana99的回答。 它创建一个 class ,您可以使用它来保存和加载数组。 理想情况下,每次添加新数组时也会更改 npy 文件的 header 以修改形状的描述(请参阅 here 了解 header 的描述)

import numpy as np
import pickle

from pathlib import Path
import os


class npyAppendableFile():
    def __init__(self, fname, newfile=True):
        '''
        Creates a new instance of the appendable filetype
        If newfile is True, recreate the file even if already exists
        '''
        self.fname=Path(fname)
        if newfile:
            with open(self.fname, "wb") as fh:
                fh.close()
        
    def write(self, data):
        '''
        append a new array to the file
        note that this will not change the header
        '''
        with open(self.fname, "ab") as fh:
            np.save(fh, data)
            
    def load(self, axis=2):
        '''
        Load the whole file, returning all the arrays that were consecutively
        saved on top of each other
        axis defines how the arrays should be concatenated
        '''
        
        with open(self.fname, "rb") as fh:
            fsz = os.fstat(fh.fileno()).st_size
            out = np.load(fh)
            while fh.tell() < fsz:
                out = np.concatenate((out, np.load(fh)), axis=axis)
            
        return out
    
    
    def update_content(self):
        '''
        '''
        content = self.load()
        with open(self.fname, "wb") as fh:
            np.save(fh, content)

    @property
    def _dtype(self):
        return self.load().dtype

    @property
    def _actual_shape(self):
        return self.load().shape
    
    @property
    def header(self):
        '''
        Reads the header of the npy file
        '''
        with open(self.fname, "rb") as fh:
            version = np.lib.format.read_magic(fh)
            shape, fortran, dtype = np.lib.format._read_array_header(fh, version)
        
        return version, {'descr': dtype,
                         'fortran_order' : fortran,
                         'shape' : shape}
                
        
      
arr_a = np.random.rand(5,40,10)
arr_b = np.random.rand(5,40,7)    
arr_c = np.random.rand(5,40,3)    

f = npyAppendableFile("testfile.npy", True)        

f.write(arr_a)
f.write(arr_b)
f.write(arr_c)

out = f.load()

print (f.header)
print (f._actual_shape)

# after update we can load with regular np.load()
f.update_content()


new_content = np.load('testfile.npy')
print (new_content.shape)

我创建了一个库来创建比机器主内存大的 Numpy .npy 文件,方法是在零轴上追加。然后可以使用 mmap_mode="r".

读取文件

https://pypi.org/project/npy-append-array

安装

conda install -c conda-forge npy-append-array

pip install npy-append-array

例子

from npy_append_array import NpyAppendArray
import numpy as np

arr1 = np.array([[1,2],[3,4]])
arr2 = np.array([[1,2],[3,4],[5,6]])

filename = 'out.npy'

with NpyAppendArray(filename) as npaa:
    npaa.append(arr1)
    npaa.append(arr2)
    npaa.append(arr2)
    
data = np.load(filename, mmap_mode="r")

print(data)

实施细节

附加到由 np.save 创建的数组在某些情况下是可能的,因为 .npy 总 header 字节数需要被 64 整除。因此,可能有一些备用 space 以增加数组描述符中的形状条目。但是,这并不能保证,并且可能会随机失败。直接使用 NpyAppendArray(filename) 初始化数组(见上文),因此 header 将创建 64 字节的备用 header space 用于增长。

额外的 64 字节 header space 可以满足我的需求吗?

它最多允许 10^64 >= 2^212 个数组条目或数据位。事实上,这比宇宙中的原子数还少。然而,由于量子力学的限制,完全填充这样一个阵列需要的能量比煮沸海洋所需的能量还要多,比较

https://hbfs.wordpress.com/2009/02/10/to-boil-the-oceans

因此,这种方法应该涵盖范围广泛的用例。