Python: что эффективные методы состоят в том, чтобы иметь дело с глубоко вложенными данными гибким способом?

Мой вопрос не об определенном фрагменте кода, но более общий, поэтому терпите меня:

Как я должен организовать данные, которые я анализирую, и какие инструменты я должен использовать для управления им?

Я использую Python и numpy для анализа данных. Поскольку документация Python указывает, что словари очень оптимизированы в Python, и также вследствие того, что сами данные очень структурированы, я сохранил его в глубоко вложенном словаре.

Вот скелет словаря: положение в иерархии определяет природу элемента, и каждая новая строка определяет содержание ключа в уровне прецедента:

[AS091209M02] [AS091209M01] [AS090901M06] ... 
[100113] [100211] [100128] [100121] 
[R16] [R17] [R03] [R15] [R05] [R04] [R07] ... 
[1263399103] ... 
[ImageSize] [FilePath] [Trials] [Depth] [Frames] [Responses] ... 
[N01] [N04] ... 
[Sequential] [Randomized] 
[Ch1] [Ch2]

Править: Объяснить немного лучше мой набор данных:

[individual] ex: [AS091209M02]
[imaging session (date string)] ex: [100113]
[Region imaged] ex: [R16]
[timestamp of file] ex [1263399103]  
[properties of file] ex: [Responses]
[regions of interest in image ] ex [N01]
[format of data] ex [Sequential]
[channel of acquisition: this key indexes an array of values] ex [Ch1]

Тип операций, которые я выполняю, должен, например, вычислить свойства массивов (перечисленный под Ch1, Ch2), массивы погрузки, чтобы сделать новый набор, например, проанализировать ответы N01 от региона 16 (R16) данного человека в различных моментах времени, и т.д.

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

Моя проблема возникает из громоздкого способа, которым я должен программировать операции словаря. У меня часто есть фрагменты кода, которые идут как это:

for mk in dic.keys():
    for rgk in dic[mk].keys():
        for nk in dic[mk][rgk].keys():
            for ik in dic[mk][rgk][nk].keys():
                for ek in dic[mk][rgk][nk][ik].keys():
                    #do something

то, которое является ужасным, громоздким, одноразового использования, и хрупким (должно повторно кодировать его для любого варианта словаря).

Я пытался использовать рекурсивные функции, но кроме самых простых приложений, я столкнулся с некоторыми очень противными ошибками и причудливыми поведениями, которые вызвали большую пустую трату времени (не помогает, что мне не удается отладить с pdb в ipython, когда я имею дело с глубоко вложенными рекурсивными функциями). В конце единственная рекурсивная функция, которую я использую регулярно, следующая:

def dicExplorer(dic, depth = -1, stp = 0):
    '''prints the hierarchy of a dictionary.
    if depth not specified, will explore all the dictionary
    '''
    if depth - stp == 0: return
    try : list_keys = dic.keys()
    except AttributeError: return
    stp += 1
    for key in list_keys:
        else: print '+%s> [\'%s\']' %(stp * '---', key)
        dicExplorer(dic[key], depth, stp)

Я знаю, что делаю эту несправедливость, потому что мой код длинен, noodly и одноразового использования. Я должен или использовать лучшие методы, чтобы гибко управлять словарями или поместить данные в некоторый формат базы данных (sqlite?). Моя проблема состоит в том, что, так как я (плохо) выучился самостоятельно в отношении программирования, я испытываю недостаток в практическом опыте и фоновом знании для понимания доступных опций. Я готов изучить новые инструменты (SQL, объектно-ориентированное программирование), любой ценой сделать задание, но я отказываюсь инвестировать свое время и усилия во что-то, что будет тупиком для моих потребностей.

Таким образом, что Ваши предложения состоят в том, чтобы заняться этой проблемой и смочь кодировать мои инструменты более кратким, гибким и допускающим повторное использование способом?

Приложение: независимо выполнения чего-то с конкретным подсловарем словаря данных, вот некоторые примеры операций, которые я реализовал для набора данных dic или sub словаря его:

на самом деле у меня есть некоторые рекурсивные функции, которые работали хорошо:

def normalizeSeqDic(dic, norm_dic = {}, legend = ()):
    '''returns a normalized dictionary from a seq_amp_dic. Normalization is performed using the first time point as reference
    '''
    try : 
        list_keys = dic.keys()
        for key in list_keys:
            next_legend = legend + (key,) 
            normalizeSeqDic(dic[key], norm_dic, next_legend)
    except AttributeError:
        # normalization
        # unpack list
        mk, ek, nk, tpk = legend
        #assign values to amplitude dict
        if mk not in norm_dic: norm_dic[mk] = {}
        if ek not in norm_dic[mk]: norm_dic[mk][ek] = {}
        if nk not in norm_dic[mk][ek]: norm_dic[mk][ek][nk] = {}
        if tpk not in norm_dic[mk][ek][nk]: norm_dic[mk][ek][nk][tpk] = {}
        new_array = []
        for x in range(dic.shape[0]):
            new_array.append(dic[x][1:]/dic[x][0])
        new_array = asarray(new_array)
        norm_dic[mk][ek][nk][tpk] = new_array
    return norm_dic

def poolDic(dic):
    '''returns a dic in which all the values are pooled, and root (mk) keys are fused
    these pooled dics can later be combined into another dic
    '''
    pooled_dic = {}
    for mk in dic.keys():
        for ek in dic[mk].keys():
            for nk in dic[mk][ek].keys():
                for tpk in dic[mk][ek][nk].keys():
                    #assign values to amplitude dict
                    if ek not in pooled_dic: pooled_dic[ek] = {}
                    if nk not in pooled_dic[ek]: pooled_dic[ek][nk] = {}
                    if tpk not in pooled_dic[ek][nk]:
                        pooled_dic[ek][nk][tpk] = dic[mk][ek][nk][tpk]
                    else: pooled_dic[ek][nk][tpk]= vstack((pooled_dic[ek][nk][tpk], dic[mk][ek][nk][tpk]))
    return pooled_dic

def timePointsDic(dic):
    '''Determines the timepoints for each individual key at root
    '''
    tp_dic = {}
    for mk in dic.keys():
        tp_list = []
        for rgk in dic[mk].keys():
            tp_list.extend(dic[mk][rgk]['Neuropil'].keys())
        tp_dic[mk]=tuple(sorted(list(set(tp_list))))
    return tp_dic

для некоторых операций я не нашел никакой другой путь, чем сгладить словарь:

def flattenDic(dic, label):
    '''flattens a dic to produce a list of of tuples containing keys and 'label' values
    '''
    flat_list = []
    for mk in dic.keys():
        for rgk in dic[mk].keys():
            for nk in dic[mk][rgk].keys():
                for ik in dic[mk][rgk][nk].keys():
                    for ek in dic[mk][rgk][nk][ik].keys():
                        flat_list.append((mk, rgk, nk, ik, ek, dic[mk][rgk][nk][ik][ek][label])
    return flat_list

def extractDataSequencePoints(flat_list, mk, nk, tp_list):
        '''produces a list containing arrays of time point values
        time_points is a list of the time points wished (can have 2 or 3 elements)
        '''
        nb_tp = len(tp_list)
        # build tp_seq list
        tp_seq = []
        tp1, tp2, tp3 = [], [], []
        if nk == 'Neuropil':
            tp1.extend(x for x in flat_list if x[0]==mk and x[2] == 'Neuropil' and x[3] == tp_list[0])
            tp2.extend(x for x in flat_list if x[0]==mk and x[2] == 'Neuropil'and  x[3] == tp_list[1])
        else:
            tp1.extend(x for x in flat_list if x[0]==mk and x[2] != 'Neuropil'and x[3] == tp_list[0])
            tp2.extend(x for x in flat_list if x[0]==mk and x[2] != 'Neuropil'and x[3] == tp_list[1])
        if nb_tp == 3:
            if nk == 'Neuropil':
                tp3.extend(x for x in flat_list if x[0]==mk and x[2] == 'Neuropil'and x[3] == tp_list[2])
            else:
                tp3.extend(x for x in flat_list if x[0]==mk and x[2] != 'Neuropil'and x[3] == tp_list[2])
        for x in tp1:
            for y in tp2:
                if x[0:3] == y[0:3] :
                    if nb_tp == 3:
                        for z in tp3:
                            if x[0:3] == z[0:3] :
                                tp_seq.append(asarray([x[4],y[4],z[4]]))
                    else:
                        tp_seq.append(asarray([x[4],y[4]]))
        return tp_seq
9
задан AlexandreS 30 March 2010 в 12:16
поделиться

5 ответов

"Я хранил его в глубоко вложенном словаре"

И, как вы видели, это не очень хорошо работает.

Какова альтернатива?

  1. Составные ключи и неглубоко вложенный словарь. У вас есть ключ из 8 частей: ( индивидуум, сеанс визуализации, область изображения, временная метка файла, свойства файла, области интереса на изображении, формат данных, канал получения), который сопоставляет в массив значений.

    { ('AS091209M02', '100113', 'R16', '1263399103', 'Responses', 'N01', 'Sequential', 'Ch1' ): массив, 
    ...
    

    Проблема с этим - поиск.

  2. Правильные структуры классов. На самом деле, полное определение класса может быть излишним.

"Тип операций, которые я выполняю, это, например, вычисление свойств массивов (перечисленных в Ch1, Ch2), собирать массивы для создания новой коллекции, например, анализировать ответы N01 из области 16 (R16) данного индивидуума в разные моменты времени и т.д."

Рекомендации

Во-первых, используйте namedtuple для вашего конечного объекта.

Array = namedtuple( 'Array', 'individual, session, region, timestamp, properties, roi, format, channel, data' )

Или что-то в этом роде. Создайте простой список этих именованных кортежей. Затем вы можете просто выполнять итерации над ними.

Во-вторых, используйте множество простых операций map-reduce над этим основным списком объектов массива.

Фильтрация:

for a in theMasterArrrayList:
    if a.region = 'R16' and interest = 'N01':
        # do something on these items only.

Сокращение по общему ключу:

individual_dict = defaultdict(list)
for a in theMasterArrayList:
    individual_dict[ a.individual ].append( a )

Это создаст подмножество в карте, которое содержит именно те элементы, которые вам нужны.

Затем вы можете сделать indiidual_dict['AS091209M02'] и получить все их данные. Вы можете сделать это для любого (или всех) доступных ключей.

region_dict = defaultdict(list)
for a in theMasterArrayList:
    region_dict[ a.region ].append( a )

При этом не копируются никакие данные. Это быстро и относительно компактно в памяти.

Сопоставление (или преобразование) массива:

for a in theMasterArrayList:
    someTransformationFunction( a.data )

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

def region_filter( array_list, region_set ):
    for a in array_list:
        if a.region in region_set:
            yield a

def array_map( array_list, someConstant ):
    for a in array_list:
        yield Array( *(a[:8] + (someTranformation( a.data, someConstant ),) )

def some_result( array_list, region, someConstant ):
    for a in array_map( region_filter( array_list, region ), someConstant ):
        yield a

Вы можете строить преобразования, сокращения, отображения в более сложные вещи.

Самое главное - создавать только те словари, которые вам нужны из основного списка, чтобы не делать больше фильтрации, чем это минимально необходимо.

BTW. Это может быть тривиально отображено на реляционную базу данных. Это будет медленнее, но вы сможете выполнять несколько одновременных операций обновления. Кроме нескольких одновременных обновлений, реляционная база данных не предлагает никаких возможностей сверх этого.

12
ответ дан 4 December 2019 в 13:46
поделиться

Поделюсь некоторыми мыслями по этому поводу. Вместо этой функции:

for mk in dic.keys():
    for rgk in dic[mk].keys():
        for nk in dic[mk][rgk].keys():
            for ik in dic[mk][rgk][nk].keys():
                for ek in dic[mk][rgk][nk][ik].keys():
                    #do something

Которую хочется просто написать как:

for ek in deep_loop(dic):
    do_something

Есть 2 способа. Первый - функциональный, второй - генератороподобный. Второй:

def deep_loop(dic):
    for mk in dic.keys():
        for rgk in dic[mk].keys():
            for nk in dic[mk][rgk].keys():
                for ik in dic[mk][rgk][nk].keys():
                    for ek in dic[mk][rgk][nk][ik].keys():
                        yield ek

Это позволяет передать логику прохождения по словарю. Очень легко модифицировать эту функцию для поддержки различных способов прохождения через структуру. Это зависит от того, как изменяется ваша структура, является ли это просто глубиной цикла или чем-то другим. Не могли бы вы опубликовать несколько более продвинутых примеров того, какие требования к прохождению по дереву у вас есть? Например, фильтрация, поиск и т.д.? Глубина будет выглядеть следующим образом (не проверено) - это даст пару (кортеж ключей), (значение):

def deep_loop(dic, depth):
    if depth == 0:
        yield (), dic
    for subkey, subval in dic.items():
        for ktuple, value in deep_loop(subval, depth-1):
            yield (subkey,)+ktuple, value

Теперь все проще:

for (k1,k2,k3,k4), value in deep_loop(dic, 4):
    # do something

Есть и другие способы настроить это, вы можете добавить именованный тип кортежа в качестве параметра deep_loop. Deep_loop может автоматически определять глубину по именованному кортежу и возвращать именованный кортеж.

1
ответ дан 4 December 2019 в 13:46
поделиться

Вы можете написать функцию генератора, которая позволяет вам перебирать все элементы определенного уровня:

def elementsAt(dic, level):
    if not hasattr(dic, 'itervalues'):
        return
    for element in dic.itervalues():
        if level == 0:
            yield element
        else:
            for subelement in elementsAt(element, level - 1):
                yield subelement

Которая затем может использоваться следующим образом:

for element in elementsAt(dic, 4):
    # Do something with element

Если вам также нужно фильтровать элементы, вы можете сначала получить все элементы, которые нужно отфильтровать (скажем, уровень 'rgk'):

for rgk in getElementsAt(dic, 1):
    if isValid(rgk):
        for ek in getElementsAt(rgk, 2):
            # Do something with ek

По крайней мере, это упростит работу с иерархией словарей . Также поможет использование более описательных имен.

0
ответ дан 4 December 2019 в 13:46
поделиться

Вы можете улучшить вид ваших циклов, заменив:

for mk in dic.keys():
    for rgk in dic[mk].keys():
        for nk in dic[mk][rgk].keys():
            for ik in dic[mk][rgk][nk].keys():
                for ek in dic[mk][rgk][nk][ik].keys():
                    #do something

на

for mv in dic.values():
    for rgv in mv.values():
        for nv in rgv.values():
            for iv in nv.values():
                for ev in iv.values():
                    #do something

Таким образом, вы получите доступ ко всем значениям с помощью относительно короткого кода. Если вам также нужны некоторые ключи, вы можете сделать что-то вроде:

for (mk, mv) in dic.items():
    # etc.

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

dic[(mk, rgk, nv, ik, ek)]
2
ответ дан 4 December 2019 в 13:46
поделиться

Вы спросите: Как мне организовать данные, которые я анализирую, и какие инструменты мне следует использовать для управления ими?

Я подозреваю, что словарь, несмотря на всю его оптимизацию, не является правильным ответом на этот вопрос. Я думаю, вам лучше использовать XML или, если для него есть привязка Python, HDF5, даже NetCDF. Или, как вы сами предлагаете, базу данных.

Если ваш проект имеет достаточную продолжительность и полезность, чтобы оправдать изучение того, как использовать такие технологии, то я думаю, вы обнаружите, что изучение их сейчас и создание правильных структур данных - лучший путь к успеху, чем борьба с неправильными данными. конструкции для всего проекта. Изучение XML, HDF5, SQL или чего-то еще, что вы выберете, расширяет ваши общие знания и дает вам возможность лучше взяться за следующий проект. Использование неудобных, специфичных для конкретной проблемы и своеобразных структур данных приводит к тому же набору проблем в следующий раз.

0
ответ дан 4 December 2019 в 13:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: