Мой вопрос не об определенном фрагменте кода, но более общий, поэтому терпите меня:
Как я должен организовать данные, которые я анализирую, и какие инструменты я должен использовать для управления им?
Я использую 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
"Я хранил его в глубоко вложенном словаре"
И, как вы видели, это не очень хорошо работает.
Какова альтернатива?
Составные ключи и неглубоко вложенный словарь. У вас есть ключ из 8 частей: ( индивидуум, сеанс визуализации, область изображения, временная метка файла, свойства файла, области интереса на изображении, формат данных, канал получения), который сопоставляет в массив значений.
{ ('AS091209M02', '100113', 'R16', '1263399103', 'Responses', 'N01', 'Sequential', 'Ch1' ): массив,
...
Проблема с этим - поиск.
Правильные структуры классов. На самом деле, полное определение класса может быть излишним.
"Тип операций, которые я выполняю, это, например, вычисление свойств массивов (перечисленных в 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. Это может быть тривиально отображено на реляционную базу данных. Это будет медленнее, но вы сможете выполнять несколько одновременных операций обновления. Кроме нескольких одновременных обновлений, реляционная база данных не предлагает никаких возможностей сверх этого.
Поделюсь некоторыми мыслями по этому поводу. Вместо этой функции:
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 может автоматически определять глубину по именованному кортежу и возвращать именованный кортеж.
Вы можете написать функцию генератора, которая позволяет вам перебирать все элементы определенного уровня:
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
По крайней мере, это упростит работу с иерархией словарей . Также поможет использование более описательных имен.
Вы можете улучшить вид ваших циклов, заменив:
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)]
Вы спросите: Как мне организовать данные, которые я анализирую, и какие инструменты мне следует использовать для управления ими?
Я подозреваю, что словарь, несмотря на всю его оптимизацию, не является правильным ответом на этот вопрос. Я думаю, вам лучше использовать XML или, если для него есть привязка Python, HDF5, даже NetCDF. Или, как вы сами предлагаете, базу данных.
Если ваш проект имеет достаточную продолжительность и полезность, чтобы оправдать изучение того, как использовать такие технологии, то я думаю, вы обнаружите, что изучение их сейчас и создание правильных структур данных - лучший путь к успеху, чем борьба с неправильными данными. конструкции для всего проекта. Изучение XML, HDF5, SQL или чего-то еще, что вы выберете, расширяет ваши общие знания и дает вам возможность лучше взяться за следующий проект. Использование неудобных, специфичных для конкретной проблемы и своеобразных структур данных приводит к тому же набору проблем в следующий раз.