2010-03-30 18 views
8

Mi pregunta no es sobre un fragmento de código específico sino más general, así que tenga paciencia:python: ¿cuáles son las técnicas eficientes para tratar datos profundamente anidados de manera flexible?

¿Cómo debo organizar los datos que estoy analizando y qué herramientas debo usar para gestionarlo?

Estoy usando python y numpy para analizar datos. Debido a que la documentación de Python indica que los diccionarios están muy optimizados en Python, y también debido a que los datos en sí son muy estructurados, los almacené en un diccionario profundamente anidado.

Aquí es un esqueleto del diccionario: la posición en la jerarquía define la naturaleza del elemento, y cada nueva línea define el contenido de una clave en el nivel precedente:

[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] 

Editar: Para explicar un poco mejor fijan mis datos:

[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] 

El tipo de operaciones que realizo es, por ejemplo, para calcular las propiedades de las matrices (enumerados en c1, c2), recoger las matrices para hacer una nueva colección, por ejemplo, analizar las respuestas de N01 de la región 16 (R16) de un indi dado vidual en diferentes puntos de tiempo, etc.

Esta estructura funciona bien para mí y es muy rápida, como se prometió. Puedo analizar el conjunto completo de datos con bastante rapidez (y el diccionario es demasiado pequeño para llenar el RAM de mi computadora: medio concierto).

Mi problema proviene de la manera engorrosa en la que necesito programar las operaciones del diccionario. A menudo tengo tramos de código que algo así:

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 

que es feo, engorroso no reutilizable, y quebradiza (necesidad de recodificar para cualquier variante del diccionario).

Traté de usar funciones recursivas, pero aparte de las aplicaciones más simples, me encontré con algunos errores muy desagradables y comportamientos extraños que causaron una gran pérdida de tiempo (no ayuda que no logré depurar con pdb en ipython cuando estoy tratando con funciones recursivas profundamente anidadas). Al final, la única función recursiva que utilizo con regularidad es la siguiente:

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) 

Yo sé que estoy haciendo esto mal, porque mi código es largo, noodly y no reutilizable. Necesito utilizar mejores técnicas para manipular de forma flexible los diccionarios o poner los datos en algún formato de base de datos (sqlite?). Mi problema es que dado que soy (mal) autodidacta en lo que respecta a la programación, carezco de experiencia práctica y conocimiento de fondo para apreciar las opciones disponibles. Estoy listo para aprender nuevas herramientas (SQL, programación orientada a objetos), lo que sea necesario para hacer el trabajo, pero soy reacio a invertir mi tiempo y esfuerzos en algo que será un callejón sin salida para mis necesidades.

¿Cuáles son sus sugerencias para abordar este problema y ser capaz de codificar mis herramientas de una manera más breve, flexible y reutilizable?

Adición: aparte de hacer algo con un subdiccionario particular del diccionario de datos, estos son algunos ejemplos de las operaciones que he implementado para la DIC conjunto de datos, o un diccionario secundario de la misma:

en realidad tienen algún recursiva funciones que han funcionado bien:

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 

para algunas operaciones no he encontrado ninguna otra manera que para aplanar el diccionario:

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 
+2

@AlexandreS: me temo que realmente no entiendo lo suficiente acerca de sus datos de muestra para poder dar muchos consejos. ¿Podrían ampliar los datos que están analizando y qué análisis están realizando? – MattH

+0

@MattH: edité la pregunta para proporcionar más detalles. Avíseme si no es suficiente – AlexandreS

+0

@AlexandrS: gracias por la aclaración. Puede ayudar aún más si pudiera explicar cómo se adquieren/almacenan/derivan estos datos en este momento. Creo que el camino a seguir sería hacer un diagrama abstracto de su estructura como objetos con propiedades y cómo los objetos/propiedades se relacionan entre sí. Cuando estoy decidiendo cómo codificar una estructura de datos, a menudo esbozaré estas cosas. – MattH

Respuesta

11

"Me lo almacenó en un diccionario anidada"

Y, como hemos visto, que no funciona bien.

¿Cuál es la alternativa?

  1. Teclas compuestas y un diccionario superficial. Tiene una clave de 8 partes: (sesión de imagen individual, imagen de región, marca de tiempo del archivo, propiedades de archivo, regiones de interés en imagen, formato de datos, canal de adquisición) que asigna a una matriz de valores.

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

    El problema con esto es la búsqueda.

  2. Estructuras de clase adecuadas. En realidad, una definición de Clase completa puede ser excesiva.

"El tipo de operaciones que realizo es, por ejemplo, para calcular las propiedades de las matrices (que aparece en c1, c2), recoger las matrices para hacer una nueva colección, por ejemplo analizar respuestas de N01 de la región 16 (R16) de un individuo dado en diferentes momentos, etc. "

Recomendación

En primer lugar, utilizar un namedtuple para su objetivo final.

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

O algo así. Construya una lista simple de estos objetos de tupla nombrados. A continuación, simplemente puede iterar sobre ellos.

En segundo lugar, utilice muchas operaciones simples de reducción de mapa en esta lista maestra de objetos de la matriz.

Filtrado:

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

Reducir a la clave común:

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

Esto creará un subconjunto en el mapa que tiene exactamente los elementos que desee.

Puede hacer indiidual_dict ['AS091209M02'] y tener todos sus datos. Puede hacer esto para cualquiera (o todas) las claves disponibles.

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

Esto no copia ningún dato. Es rápido y relativamente compacto en la memoria.

Mapping (o transformación) de la matriz:

for a in theMasterArrayList: 
    someTransformationFunction(a.data) 

Si la matriz es en sí misma una lista, estás pueden actualizar esa lista sin romper la tupla en su conjunto. Si necesita crear una nueva matriz a partir de una matriz existente, está creando una nueva tupla. No hay nada de malo en esto, pero es una nueva tupla. Terminas con programas como este.

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 

Puede construir transformaciones, reducciones, asignaciones en cosas más elaboradas.

Lo más importante es crear solo los diccionarios que necesita de la lista maestra para que no filtre más de lo que es mínimamente necesario.

BTW. Esto se puede asignar a una base de datos relacional trivialmente. Será más lento, pero puede tener múltiples operaciones de actualización simultáneas. Excepto por las múltiples actualizaciones concurrentes, una base de datos relacional no ofrece ninguna característica por encima de esto.

+0

Esto es muy útil. Ya había recurrido a aplanar el diccionario de datos o un (subconjuntos de él) + listas de comprensión para filtrar los datos, pero su descripción del uso de tuplas con nombre permite una sintaxis y un código mucho más limpio que el que tengo. Además, implica un aprendizaje mínimo para obtener los resultados deseados, así que creo que iré por eso, al menos por el momento. – AlexandreS

2

Usted puede hacer que sus bucles se ven mejor, mediante la sustitución:

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 

con

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 

Es por lo tanto tener acceso a todos los valores con un código relativamente breve. Si también necesita algunas teclas, se puede hacer algo como:

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

Dependiendo de sus necesidades, también puede considerar la creación y el uso de un único diccionario con claves de tupla:

dic[(mk, rgk, nv, ik, ek)] 
+1

Iterar un objeto de diccionario devuelve las claves del diccionario. No puede iterar la clave y esperar tener acceso al valor. – MattH

+0

Probé el primer bloque, pero en para rgv en mv: veo que rgv itera a través de la cadena mv, en lugar de mv.keys()? – AlexandreS

+0

@MattH: ¡tienes razón, mi mal! Fijo. ¡Gracias! – EOL

0

Usted pregunta: ¿Cómo debo organizar los datos que estoy analizando y qué herramientas debo usar para gestionarlo?

Sospecho que un diccionario, a pesar de su optimización, no es la respuesta correcta a esa pregunta. Creo que sería mejor usar XML o, si hay un enlace de Python para él, HDF5, incluso NetCDF. O, como te sugieres, una base de datos.

Si su proyecto es de suficiente duración y utilidad como para justificar el aprendizaje de cómo usar tales tecnologías, creo que encontrará que aprenderlas ahora y obtener las estructuras de datos correctas es un camino mejor para el éxito que luchar con el mal estructuras de datos para todo el proyecto. Aprender XML, o HDF5, o SQL, o lo que sea que elija, está acumulando su experiencia general y haciéndole más capaz de abordar el próximo proyecto. Cumplir con estructuras de datos incómodas, específicas de un problema e idiosincrásicas conduce a la misma serie de problemas la próxima vez.

+0

Gracias por informarme sobre HDF5 y NetCDF. Python tiene enlaces para ambos, y las licencias son utilizables. Creo que comenzaré leyendo sobre HDF5, y si parece prometedor, estudiaré cómo usarlo con mis datos. Este tipo de indicadores son exactamente lo que esperaba cuando escribí mi pregunta. – AlexandreS

0

Se podría escribir una función de generador que le permite iterar sobre todos los elementos de un cierto nivel:

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 

que luego puede ser utilizado de la siguiente manera:

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

Si también necesita elementos de filtro, primero puede obtener todos los elementos que necesitan ser filtrados (digamos, el nivel 'rgk'):

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

Al menos eso hará que sea un poco más fácil trabajar con una jerarquía de diccionarios. Usar nombres más descriptivos ayudaría también.

+0

gracias por mostrarme el uso de dic.itervalues ​​(), que no había apreciado hasta ahora. El problema es que realmente no aborda el tema de las cadenas profundamente anidadas e inflexibles. – AlexandreS

1

Voy a compartir algunas ideas sobre esto. En lugar de esta función:

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 

que le gustaría simplemente escribir como:

for ek in deep_loop(dic): 
    do_something 

Hay 2 maneras. Uno es funcional, el segundo es similar a un generador. El segundo es:

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 

Esto le permite capturar la lógica de pasar por el diccionario. Es muy fácil modificar esta función para admitir diferentes formas de pasar por la estructura. Depende de la forma en que su estructura cambie, si es solo una profundidad del ciclo o algo diferente. ¿Podría publicar algunos ejemplos más avanzados sobre qué requisitos debe cumplir al pasar por el árbol que tiene? Al igual que el filtrado, búsqueda, etc. La profundidad se vería así (no probado) - se producirá un par de (tupla de teclas), (valor):

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 

Ahora se hace más fácil:

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

Hay otras formas de personalizar esto, puede agregar un tipo de tupla con nombre como parámetro de deep_loop. Deep_loop podría autodetectar la profundidad de la tupla nombrada y devolver la tupla nombrada.

+0

Publiqué ejemplos adicionales de lo que logré implementar como un apéndice, espero que ayude. Su primera solución incluye el proceso de búsqueda en una función, pero no resuelve la mayoría de mis problemas, ya que las búsquedas a menudo son variables, y tendría que definir muchas funciones para describir cada búsqueda. Su segunda opción aborda este punto, pero usa recursividad, lo que me asusta un poco después de las malas experiencias que tuve con ella (aunque logré escribir algunas funciones recursivas). Pero gracias por mostrarme cómo acopló dic.items() con expresiones de generador, eso es útil para mí. – AlexandreS

Cuestiones relacionadas