как сжать списки/вложенные списки в hdf5

Недавно я узнал о сжатии hdf5 и работе с ним. Что у него есть некоторые преимущества перед .npz/npy при работе с гигантскими файлами. Мне удалось попробовать небольшой список, так как я иногда работаю со списками, которые имеют следующие строки:

def write():
    test_array = ['a1','a2','a1','a2','a1','a2', 'a1','a2', 'a1','a2','a1','a2','a1','a2', 'a1','a2', 'a1','a2','a1','a2','a1','a2', 'a1','a2']
    

    with  h5py.File('example_file.h5', 'w') as f:
        f.create_dataset('test3', data=repr(test_array), dtype='S', compression='gzip', compression_opts=9) 
        f.close()
    

Однако я получил эту ошибку:

f.create_dataset('test3', data=repr(test_array), dtype='S', compression='gzip', compression_opts=9)
  File "/usr/local/lib/python3.6/dist-packages/h5py/_hl/group.py", line 136, in create_dataset
    dsid = dataset.make_new_dset(self, shape, dtype, data, **kwds)
  File "/usr/local/lib/python3.6/dist-packages/h5py/_hl/dataset.py", line 118, in make_new_dset
    tid = h5t.py_create(dtype, logical=1)
  File "h5py/h5t.pyx", line 1634, in h5py.h5t.py_create
  File "h5py/h5t.pyx", line 1656, in h5py.h5t.py_create
  File "h5py/h5t.pyx", line 1689, in h5py.h5t.py_create
  File "h5py/h5t.pyx", line 1508, in h5py.h5t._c_string
ValueError: Size must be positive (size must be positive)

После нескольких часов поиска в сети лучших способов сделать это я не смог найти. Есть ли лучший способ сжатия списков с помощью H5?


person lobjc    schedule 05.03.2021    source источник


Ответы (2)


Это более общий ответ для вложенных списков, где каждый вложенный список имеет разную длину. Это также работает для более простого случая, когда вложенные списки имеют одинаковую длину. Есть 2 решения: одно с h5py и одно с PyTables.

Пример h5py
h5py не поддерживает неоднородные массивы, поэтому вам нужно создать набор данных на основе самой длинной подстроки и добавить элементы в короткие подстроки. Вы получите 'None' (или подстроку) в каждой позиции массива, которая не имеет соответствующего значения во вложенном списке. Будьте осторожны с записью dtype=. Здесь показано, как найти самую длинную строку в списке (как slen=##) и использовать ее для создания dtype='S##'

import h5py
import numpy as np

test_list = [['a01','a02','a03','a04','a05','a06'], 
             ['a11','a12','a13','a14','a15','a16','a17'], 
             ['a21','a22','a23','a24','a25','a26','a27','a28']]

# arrlen and test_array from answer to SO #10346336 - Option 3:
# Ref: https://stackoverflow.com/a/26224619/10462884    
slen = max(len(item) for sublist in test_list for item in sublist)
arrlen = max(map(len, test_list))
test_array = np.array([tl+[None]*(arrlen-len(tl)) for tl in test_list], dtype='S'+str(slen))
  
with h5py.File('example_nested.h5', 'w') as f:
     f.create_dataset('test3', data=test_array, compression='gzip')

Пример PyTables
PyTables поддерживает рваные двумерные массивы как VLArrays (переменной длины). Это позволяет избежать сложности добавления значений «Нет» для коротких подстрок. Также не нужно заранее определять длину массива, так как количество строк не определяется при создании VLArray (ряды добавляются после создания). Опять же, будьте осторожны с записью dtype=. Здесь используется тот же метод, что и выше.

import tables as tb
import numpy as np

test_list = [['a01','a02','a03','a04','a05','a06'], 
             ['a11','a12','a13','a14','a15','a16','a17'], 
             ['a21','a22','a23','a24','a25','a26','a27','a28']]
   
slen = max(len(item) for sublist in test_list for item in sublist)

with tb.File('example_nested_tb.h5', 'w') as h5f:        
    vlarray = h5f.create_vlarray('/','vla_test', tb.StringAtom(slen) ) 
    for slist in test_list:
        arr = np.array(slist,dtype='S'+str(slen))
        vlarray.append(arr)

    print('-->', vlarray.name)
    for row in vlarray:
        print('%s[%d]--> %s' % (vlarray.name, vlarray.nrow, row))
person kcw78    schedule 05.03.2021
comment
Большое спасибо за помощь!! - person lobjc; 05.03.2021

Вы близки. Аргумент data= предназначен для работы с существующим массивом NumPy. Когда вы используете список, за кулисами он преобразуется в массив. Он работает для списка чисел. (Обратите внимание, что списки и массивы — это разные классы объектов Python.)

Вы столкнулись с проблемой преобразования списка строк. По умолчанию для dtype задан тип Unicode NumPy (в вашем случае «U2»). Это проблема для h5py (и HDF5). Согласно документации h5py: HDF5 не поддерживает широкие символы. Вместо того, чтобы пытаться обойти это и «притворяться», что поддерживает его, h5py выдаст ошибку, если вы попытаетесь сохранить данные этого типа. Полная информация о NumPy и строках доступна по этой ссылке: документ h5py: Строки в HDF5

Я немного изменил ваш пример, чтобы показать, как вы можете заставить его работать. Обратите внимание, что я явно создал массив строк NumPy и объявил dtype='S2', чтобы получить желаемый тип строки. Я добавил пример со списком целых чисел, чтобы показать, как список работает с числами. Однако массивы NumPy являются предпочтительным объектом данных.

Я удалил оператор f.close(), так как он не требуется при использовании менеджера контекста (структура with / as:)

Также будьте осторожны с уровнем сжатия. Вы получите (немного) большее сжатие с compression_opts=9 по сравнению с compression_opts=1, но вы будете платить за время обработки ввода-вывода каждый раз, когда вы получаете доступ к набору данных. Предлагаю начать с 1.

import h5py
import numpy as np

test_array = np.array(['a1','a2','a1','a2','a1','a2', 'a1','a2', 
                       'a1','a2','a1','a2','a1','a2', 'a1','a2', 
                       'a1','a2','a1','a2','a1','a2', 'a1','a2'], dtype='S2')

data_list = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

with h5py.File('example_file.h5', 'w') as f:
     f.create_dataset('test3', data=test_array, compression='gzip', compression_opts=9) 

     f.create_dataset('test4', data=data_list, compression='gzip', compression_opts=1) 
person kcw78    schedule 05.03.2021
comment
Ух ты. Мне это нравится. Вы тоже пробовали с вложенными списками? Я приму это, хотя меня также интересуют вложенные списки. Спасибо за попытку!!!! - person lobjc; 05.03.2021
comment
HDF5 поддерживает вложенные массивы. Это сложнее, но можно сделать. Можете ли вы привести пример? Предупреждение: самая большая сложность — проверка размеров вложенных списков. Как правило, в наборах данных HDF5 используются фиксированные формы (размеры), поэтому перед их созданием необходимо знать форму каждого из них. - person kcw78; 05.03.2021
comment
Я сделал списки, что все они равны len(): pp =my_list, chunks = [pp[x:x+700] for x in range(0, len(pp), 700)], тогда data_list будет моим «кусочки» - person lobjc; 05.03.2021
comment
Я заметил, что при чтении файла h5; hf = h5py.File('drugrx_nested.h5', 'r') data = hf['test3'][:], списки получаются с байтовыми строками: [b'a1',b'a2',b'a1 ',b'a2',b'a1',b...']. Любой способ создать строки вместо этого? - person lobjc; 06.03.2021
comment
У вас есть массив строк байтов. Вы должны расшифровать их. Есть несколько способов сделать это. Самый простой способ — добавить .astype('U') к оператору данных (это преобразует их в Unicode) — например, data = hf['test3'][:].astype('U'). См. этот ответ SO для других методов: Как декодировать пустой массив закодированных строк - person kcw78; 06.03.2021