Чтение и запись массивов numpy в файлы HDF5 и из них

Я создаю программное обеспечение для моделирования, и мне нужно записать (тысячи) двумерных массивов numpy в таблицы в файле HDF5, где одно измерение массива является переменным. Входящий array имеет тип float32; для экономии места на диске каждый массив хранится в виде таблицы с соответствующими типами данных для столбцов (следовательно, массивы не используются). Когда я читаю таблицы, я хотел бы получить numpy.ndarray типа float32, чтобы я мог делать хорошие вычисления для анализа. Ниже приведен пример кода с массивом видов A, B и C плюс время.

То, как я сейчас читаю и пишу, «работает», но очень медленно. Таким образом, возникает вопрос: как правильно быстро сохранить array в table, а также снова прочитать его в ndarrays? Я экспериментировал с numpy.recarray, но не могу заставить это работать (ошибки типов, ошибки размеров, совершенно неправильные числа и т. д.)?

Код:

import tables as pt
import numpy as np

# Variable dimension
var_dim=100

# Example array, rows 0 and 3 should be stored as float32, rows 1 and 2 as uint16
array=(np.random.random((4, var_dim)) * 100).astype(dtype=np.float32)

filename='test.hdf5'
hdf=pt.open_file(filename=filename,mode='w')
group=hdf.create_group(hdf.root,"group")

particle={
    'A':pt.Float32Col(),
    'B':pt.UInt16Col(),
    'C':pt.UInt16Col(),
    'time':pt.Float32Col(),
    }
dtypes=np.array([
    np.float32,
    np.uint16,
    np.uint16,
    np.float32
    ])

# This is the table to be stored in
table=hdf.create_table(group,'trajectory', description=particle, expectedrows=var_dim)

# My current way of storing
for i, row in enumerate(array.T):
    table.append([tuple([t(x) for t, x in zip(dtypes, row)])])
table.flush()
hdf.close()


hdf=pt.open_file(filename=filename,mode='r')
array_table=hdf.root.group._f_iter_nodes().__next__()

# My current way of reading
row_list = []
for i, row in enumerate(array_table.read()):
    row_list.append(np.array(list(row)))

#The retreived array
array=np.asarray(row_list).T


# I've tried something with a recarray
rec_array=array_table.read().view(type=np.recarray)

# This gives me errors, or wrong results
rec_array.view(dtype=np.float64)
hdf.close()

Ошибка, которую я получаю:

Traceback (most recent call last):
  File "/home/thomas/anaconda3/lib/python3.6/site-packages/numpy/core/records.py", line 475, in __setattr__
    ret = object.__setattr__(self, attr, val)
ValueError: new type not compatible with array.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/thomas/Documents/Thesis/SO.py", line 53, in <module>
    rec_array.view(dtype=np.float64)
  File "/home/thomas/anaconda3/lib/python3.6/site-packages/numpy/core/records.py", line 480, in __setattr__
    raise exctype(value)
ValueError: new type not compatible with array.
Closing remaining open files:test.hdf5...done

person Patrickens    schedule 26.04.2017    source источник
comment
Это может помочь увидеть форму и тип array (из первого asarray). Я предполагаю, что это уже структурированный массив. Или аналогичная информация для recarray версии.   -  person hpaulj    schedule 26.04.2017
comment
Является ли использование таблиц единственным возможным решением для вас? Как вы впоследствии получаете доступ к своим данным (только ко всем 2D-массивам или их подмножествам)?   -  person max9111    schedule 27.04.2017
comment
В ваших реальных данных также всего несколько столбцов (в вашем примере только четыре)? Ваши данные сжимаемы? Возможно ли для вас даже сжатие с потерями? computation.llnl.gov/projects/floating- точечное сжатие/   -  person max9111    schedule 27.04.2017


Ответы (2)


В качестве быстрого и грязного решения можно избежать циклов, временно преобразовав массивы в списки (если вы можете сэкономить память). По какой-то причине массивы записей легко конвертируются в/из списков, но не в/из обычных массивов.

Хранение:

table.append(array.T.tolist())

Загрузка:

loaded_array = np.array(array_table.read().tolist(), dtype=np.float64).T

Должен быть более "Numpythonic" подход для преобразования между массивами записей и обычными массивами, но я недостаточно знаком с первым, чтобы знать, как это сделать.

person kazemakase    schedule 26.04.2017
comment
Это уже делает код более читабельным! Я все еще задаюсь вопросом, каким был бы путь Numpythonic. Все равно спасибо! - person Patrickens; 26.04.2017
comment
@Patrickens Я только что узнал о np.core .records.fromarrays, но я не думаю, что в данном случае это дает какую-то пользу. Внутри он преобразует массив в список массивов, что менее эффективно, чем .tolist(), и требует больше аргументов. Может быть, мой подход не такой уж и ненумпифонический :) - person kazemakase; 26.04.2017
comment
В ограниченных случаях view или astype можно использовать для преобразования структурированных массивов в числовые, но этот посредник tolist является наиболее общим средством. Обратите внимание, что для обратного пути требуется преобразовать список списков в список кортежей. Альтернативой является копирование полей по имени. Поскольку количество записей обычно велико по сравнению с количеством полей, вы не много потеряете на этой итерации. - person hpaulj; 26.04.2017
comment
stackoverflow.com/questions/39502461 / — это пример, когда даже tolist не является достаточно общим. from numpy.lib import recfunctions — это еще один набор структурированных/переназначенных функций. Они предпочитают рекурсивное копирование полей по имени. - person hpaulj; 26.04.2017

Я не работал с tables, но смотрел его файлы с h5py. Я предполагаю, что ваш array или recarray представляет собой структурированный массив с типом dtype, например:

In [131]: dt=np.dtype('f4,u2,u2,f4')
In [132]: np.array(arr.tolist(), float)
Out[132]: 
array([[ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.]])
In [133]: arr
Out[133]: 
array([( 1., 1, 1,  1.), ( 1., 1, 1,  1.), ( 1., 1, 1,  1.)], 
      dtype=[('f0', '<f4'), ('f1', '<u2'), ('f2', '<u2'), ('f3', '<f4')])

Используя подход @kazemakase's tolist (который я рекомендовал в других постах):

In [134]: np.array(arr.tolist(), float)
Out[134]: 
array([[ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.]])

astype неправильно принимает форму

In [135]: arr.astype(np.float32)
Out[135]: array([ 1.,  1.,  1.], dtype=float32)

view работает, когда типы компонентов одинаковы, например, с двумя полями с плавающей запятой.

In [136]: arr[['f0','f3']].copy().view(np.float32)
Out[136]: array([ 1.,  1.,  1.,  1.,  1.,  1.], dtype=float32)

Но требует переделки. view использует байты буфера данных, просто переинтерпретируя.

Многие функции recfunctions используют копирование поля за полем. Здесь эквивалент будет

In [138]: res = np.empty((3,4),'float32')
In [139]: for i in range(4):
     ...:     res[:,i] = arr[arr.dtype.names[i]]
     ...:     
In [140]: res
Out[140]: 
array([[ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.]], dtype=float32)

Если количество полей мало по сравнению с количеством записей, эта итерация не требует больших затрат.


def foo(arr):
    res = np.empty((arr.shape[0],4), np.float32)
    for i in range(4):
        res[:,i] = arr[arr.dtype.names[i]]
    return res

С большим массивом из 4 полей копия по полю явно быстрее:

In [143]: arr = np.ones(10000, dtype=dt)
In [149]: timeit x1 = foo(arr)
10000 loops, best of 3: 73.5 µs per loop
In [150]: timeit x2 = np.array(arr.tolist(), np.float32)
100 loops, best of 3: 11.9 ms per loop
person hpaulj    schedule 26.04.2017