python: каковы эффективные методы для гибкой работы с глубоко вложенными данными? - PullRequest
8 голосов
/ 30 марта 2010

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

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

Я использую 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)

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

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

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

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

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

Ответы [ 5 ]

12 голосов
/ 30 марта 2010

«Я хранил это в глубоко вложенном словаре»

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

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

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

    { ('AS091209M02', '100113', 'R16', '1263399103', 'Responses', 'N01', 'Sequential', 'Ch1' ): array, 
    ...
    

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

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

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

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

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

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

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

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

Фильтрация:

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

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

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

КСТАТИ. Это может быть сопоставлено с реляционной базой данных тривиально. Это будет медленнее, но вы можете иметь несколько одновременных операций обновления. За исключением нескольких одновременных обновлений, реляционная база данных не предлагает никаких функций выше этого.

2 голосов
/ 30 марта 2010

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

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)]
1 голос
/ 30 марта 2010

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

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 может автоматически определять глубину из именованного кортежа и возвращать названный кортеж.

0 голосов
/ 30 марта 2010

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

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 голосов
/ 30 марта 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...