【问题标题】:save numpy array in append mode以追加模式保存numpy数组
【发布时间】:2015-08-03 06:56:11
【问题描述】:

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

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

【问题讨论】:

    标签: python numpy save


    【解决方案1】:

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

    但是,当您开始拥有大量数据时,最好使用专门用于处理此类数据集的文件格式(例如 HDF5)[1]

    例如,下面是使用PyTablesnumpy 数组保存在HDF5 中的解决方案,

    第 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
    

    【讨论】:

    • 感谢您将我指向 PyTables。使用 Array 类的更简单的方法足以满足我的目的。我很好奇为什么np.save 没有附加模式。如果它是明智的,我想它已经实现了。
    • 这还是2018年最好的方法吗?
    • HDF5 是优于 npy 的文件格式是一个有争议的论点。越来越多的论文表明 HDF5 实际上是一种非常麻烦的文件格式,例如exdir 正在转向将数据保存在 numpy 文件中。
    • 是的,这个答案有点过时了。例如,现在 zarr 也可能是一种可能性。随意编辑答案。
    【解决方案2】:

    要使用 numpy.save 将数据附加到现有文件,我们应该使用:

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

    我已检查它在 python 2.7 和 numpy 1.10.4 中是否有效

    我已经改编了来自here 的代码,其中谈到了 savetxt 方法。

    【讨论】:

    • 我刚刚检查过,它在python 2.7.12numpy 1.12.1 中不起作用。数组保持不变,没有附加任何内容。另请注意,您提供的链接谈论的是savetxt 方法,而不是np.save
    • 我已经能够在 python 3.5 和 numpy 1.11.3 中成功使用这种类型的堆叠模式。虽然必须以二进制模式打开文件。
    • @PaxRomana99:这就是我得到的:with Path('/tmp/npy').open('wb') as f: np.save(f, np.zeros(2))with Path('/tmp/npy').open('ab') as f: np.save(f, np.ones(2))np.load('/tmp/npy')Out: array([0., 0.])希望array([[0., 0.], [1., 1.]])
    • @ethanabrooks:我添加了一个显示示例模式的答案
    • 应该是open 而不是file
    【解决方案3】:

    .npy 文件包含标头,其中包含数组的形状和 dtype。如果您知道生成的数组是什么样子,您可以自己编写标头,然后以块的形式编写数据。例如,这里是连接二维矩阵的代码:

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

    如果您需要更通用的解决方案(在追加时编辑标题),您将不得不使用 [1] 中的 fseek 技巧。

    灵感来自
    [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

    【讨论】:

      【解决方案4】:

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

      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]
      

      【讨论】:

      • 这非常无效,因为您必须加载numpy文件,它甚至可能无法放入内存。
      【解决方案5】:

      这是对 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.]])

      【讨论】:

      • 谢谢!请注意:对于具有很多行的文件,这种加载方式将太慢。而不是使用 vstack (每次有效地创建一个新的完整矩阵),创建一次完整矩阵然后填充行会快得多。例如:size = (&lt;num_rows&gt;, &lt;num_cols) # the shape of your matrix for i in range(size[0]): data[i,:] = np.load(f)
      【解决方案6】:

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

      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)
      
      

      【讨论】:

        【解决方案7】:

        我创建了一个库来创建大于机器主内存的 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 总标题字节数需要被 64 整除。因此,可能有一些空闲空间来增加形状条目数组描述符。但是,这不能保证,并且可能会随机失败。直接使用 NpyAppendArray(filename) 初始化数组(见上文),因此将使用 64 字节的备用头空间创建头以供增长。

        64 字节的额外标头空间能否满足我的需求?

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

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

        因此,这种方法应该可以覆盖广泛的用例。

        【讨论】:

          猜你喜欢
          • 2018-12-08
          • 1970-01-01
          • 1970-01-01
          • 2016-06-15
          • 1970-01-01
          • 2021-04-23
          • 2018-01-14
          • 2016-04-08
          • 2015-02-21
          相关资源
          最近更新 更多