В PyTables, как создать вложенный массив переменной длины? - PullRequest
11 голосов
/ 20 марта 2011

Я использую PyTables 2.2.1 с Python 2.6, и я хотел бы создать таблицу, которая содержит вложенные массивы переменной длины.

Я искал документацию по PyTables и пример учебного пособия ( PyTables Tutorial 3.8 ) показывает, как создать вложенный массив длины = 1. Но для этого примера, как бы я добавил переменное число строк в данные 'info2 / info3 / x' и 'info2 / info3/ y '?

Для более простого понимания структуры таблицы, вот мой доморощенный пример:

"""Desired Pytable output:

DIEM    TEMPUS  Temperature             Data
5       0       100         Category1 <--||-->  Category2
                         x <--| |--> y          z <--|
                        0           0           0
                        2           1           1
                        4           1.33        2.67
                        6           1.5         4.5
                        8           1.6         6.4
5       1       99
                        2           2           0   
                        4           2           2
                        6           2           4
                        8           2           6
5       2       96
                        4           4           0
                        6           3           3
                        8           2.67        5.33


Note that nested arrays have variable length.
"""

import tables as ts

tableDef =      {'DIEM': ts.Int32Col(pos=0),
                'TEMPUS': ts.Int32Col(pos=1), 
                'Temperature' : ts.Float32Col(pos=2),
                'Data': 
                    {'Category1': 
                        {
                        'x': ts.Float32Col(), 
                        'y': ts.Float32Col()
                        }, 
                    'Category2': 
                        {
                        'z': ts.Float32Col(), 
                        }
                    }
                }

# create output file
fpath = 'TestDb.h5'
fh = ts.openFile(fpath, 'w')
# define my table
tableName = 'MyData'
fh.createTable('/', tableName, tableDef)
tablePath = '/'+tableName
table = fh.getNode(tablePath)

# get row iterator
row = table.row
for i in xrange(3):
    print '\ni=', i
    # calc some fake data
    row['DIEM'] = 5
    row['TEMPUS'] = i
    row['Temperature'] = 100-i**2

    for j in xrange(5-i):
        # Note that nested array has variable number of rows
        print 'j=', j,
        # calc some fake nested data
        val1 = 2.0*(i+j)
        val2 = val1/(j+1.0)
        val3 = val1 - val2

        ''' Magic happens here...
        How do I write 'j' rows of data to the elements of 
        Category1 and/or Category2?

        In bastardized pseudo-code, I want to do:

        row['Data/Category1/x'][j] = val1
        row['Data/Category1/y'][j] = val2
        row['Data/Category2/z'][j] = val3
        '''

    row.append()
table.flush()

fh.close()

Я не нашел никаких указаний в документации PyTables, что такая структура невозможна... но если такая структура на самом деле невозможна, каковы мои альтернативы вложенным столбцам переменной длины?

  • EArray?VLArray?Если да, то как интегрировать эти типы данных в вышеописанную структуру?
  • какая-то другая идея?

Любая помощь будет принята с благодарностью!

РЕДАКТИРОВАТЬ с дополнительнымиинформация: Похоже, что гуру PyTables уже обратились к вопросу "возможна ли такая структура":

Почтовый форум PyTables - Иерархические наборы данных

Так что кто-нибудь понялспособ создать аналогичную структуру данных PyTable?

Еще раз спасибо!

Ответы [ 3 ]

9 голосов
/ 26 марта 2012

У меня похожая задача: сбросить данные фиксированного размера с массивами переменной длины.

Сначала я попытался использовать поля StringCol фиксированного размера (64 * 1024) для хранения моих данных переменной длины (они всегда <64 КБ). Но он был довольно медленным и занимал много места на диске, несмотря на сжатие blosc. </p>

После нескольких дней расследования я закончил со следующим решением:

(спойлер: мы храним поля массива в отдельных экземплярах EArray, по одному EArray на одно поле массива)

  1. Я храню данные фиксированного размера в обычной таблице pytables.
  2. Я добавил в эти таблицы 2 дополнительных поля: arrFieldName_Offset и arrFieldName_Length :

    class Particle(IsDescription):
       idnumber  = Int64Col()
       ADCcount  = UInt16Col()
       TDCcount  = UInt8Col()
       grid_i    = Int32Col()
       grid_j    = Int32Col()
       pressure  = Float32Col()
       energy    = FloatCol()
       buffer_Offset = UInt32() # note this field!
       buffer_Length = UInt32() # and this one too!
    
  3. Я также создаю один экземпляр EArray для каждого поля массива:

    datatype = StringAtom(1)
    buffer = h5file.createEArray('/detector', 'arr', datatype, (0,), "")
    
  4. Затем я добавляю строки, соответствующие данным фиксированного размера:

    row['idnumber'] = ...
    ...
    row['energy'] = ...
    row['buffer_Offset'] = buffer.nrows
    # my_buf is a string (I get it from a stream)
    row['buffer_Length'] = len(my_buf)
    table.append(row)
    
  5. Ta-д! Добавьте буфер в массив.

    buffer.append(np.ndarray((len(my_buf),), buffer=my_buf, dtype=datatype))
    
  6. Это хитрость. В моих экспериментах этот подход в 2-10 раз быстрее, чем хранение рваных массивов фиксированного размера (например, StringAtom (HUGE_NUMBER)), а результирующая БД в несколько раз меньше (2-5x)

  7. Получить данные буфера очень просто. Предположим, что строка - это одна строка, которую вы читаете из своей БД:

    # Open array for reading
    buffer = h5file.createEArray('/detector', 'Particle.buffer', datatype, (0,), "")
    ...
    row = ...
    ...
    bufferDataYouNeed = buffer[ row['buffer_Offset'] : row['buffer_Offset'] + row['buffer_Length']]
    
4 голосов
/ 23 июня 2011

Это обычная вещь, которую люди, начинающие с PyTables, хотят делать. Конечно, это было первое, что I попытался сделать. По состоянию на 2009 год, я не думаю, что эта функциональность была поддержана. Вы можете посмотреть здесь одно решение "Я всегда рекомендую":

http://www.mail-archive.com/pytables-users@lists.sourceforge.net/msg01207.html

Короче, просто поместите каждый VLArray в отдельное место. Если вы сделаете это, возможно, вам не понадобятся VLArrays. Если вы храните отдельные VLArrays для каждой пробной версии (или любой другой), вы можете хранить метаданные в этих VLArrays (гарантированно оставаться синхронизированными с массивом при переименованиях, перемещениях и т. Д.) Или помещать их в таблицу (легче искать).

Но вы также можете выбрать любую временную точку для атома вашего столбца, а затем просто добавить другой столбец для отметки времени. Это позволило бы создать «рваный» массив, который все еще имеет регулярную, повторяющуюся (табличную) структуру в памяти. Например:

Trial Data
1     0.4, 0.5, 0.45
2     0.3, 0.4, 0.45, 0.56

становится

Trial Timepoint Data
1     1         0.4
1     2         0.5
...
2     4         0.56

Данные выше представляют собой одно число, но это может быть, например, атом 4x5x3.

Если сейчас в PyTables поддерживаются вложенные VLArrays, я бы, конечно, хотел бы знать!

В качестве альтернативы, я думаю, что h5py поддерживает полный набор функций HDF5, поэтому, если вы действительно привержены макету вложенных данных, вам может повезти больше. Вы будете терять много приятных функций, хотя! И, по моему опыту, наивные нейробиологи в конечном итоге имеют довольно низкую производительность, поскольку у них нет интеллектуального выбора в pytables для размещения данных, разбиения на фрагменты и т. Д. Пожалуйста, сообщите, если вы идете по этому пути!

0 голосов
/ 16 марта 2012

Я тоже столкнулся с этим, и я прекратил использовать фиксированный размер массива. Массивы, которые я пытался сохранить, имели переменную len, поэтому я создал новые из них с правильной фиксированной длиной

Я сделал что-то вроде

def filled_list(src_list, targ_len):
    """takes a varible len() list and creates a new one with a fixed len()"""
    for i in range(targ_len):
        try:
            yield src_list[i]
        except IndexError:
            yield 0

src_list = [1,2,3,4,5,6,7,8,9,10,11]
new_list = [x for x in filled_list(src_list, 100)]

Это помогло мне.

...