2010-03-30 4 views
8

La mia domanda non riguarda uno specifico frammento di codice ma più generale, quindi per favore portami:python: quali sono le tecniche efficienti per trattare i dati profondamente annidati in modo flessibile?

Come dovrei organizzare i dati che sto analizzando e quali strumenti dovrei usare per gestirlo?

Sto usando python e numpy per analizzare i dati. Perché la documentazione di Python indica che i dizionari sono molto ottimizzati in Python, e anche per il fatto che i dati stessi sono molto strutturati, l'ho archiviato in un dizionario profondamente annidato.

Ecco uno scheletro del dizionario: la posizione nella gerarchia definisce la natura dell'elemento, e ogni nuova linea definisce il contenuto di una chiave nel livello 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] 

Modifica: Per spiegare un po 'meglio i miei dati impostati:

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

Il tipo di operazioni che compio è, ad esempio per calcolare le proprietà delle matrici (elencati sotto Ch1, Ch2), raccogliere le matrici per fare una nuova collezione, per esempio analizzare le risposte di N01 dalla regione 16 (R16) di un dato indi in diversi punti temporali, ecc.

Questa struttura funziona bene per me ed è molto veloce, come promesso. Posso analizzare l'intero set di dati abbastanza rapidamente (e il dizionario è troppo piccolo per riempire la RAM del mio computer: mezzo concerto).

Il mio problema deriva dal modo ingombrante in cui ho bisogno di programmare le operazioni del dizionario. Ho spesso tratti di codice che vanno in questo modo:

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 

che è brutto, scomodo, non riutilizzabile, e fragili (necessità di ricodificare per qualsiasi variante del dizionario).

Ho provato a utilizzare le funzioni ricorsive, ma a parte le applicazioni più semplici, mi sono imbattuto in alcuni bug e comportamenti bizzarri che hanno causato una grande perdita di tempo (non aiuta che non riesca a eseguire il debug con pdb in ipython quando ho a che fare con funzioni ricorsive profondamente annidate). Alla fine l'unica funzione ricorsiva che uso regolarmente è la seguente:

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) 

so che sto facendo questo torto, perché il mio codice è lunga, noodly e non riutilizzabili. Devo utilizzare tecniche migliori per manipolare in modo flessibile i dizionari o per inserire i dati in qualche formato di database (sqlite?). Il mio problema è che dal momento che sono (malamente) autodidatta riguardo alla programmazione, mi manca l'esperienza pratica e le conoscenze di base per apprezzare le opzioni disponibili. Sono pronto per imparare nuovi strumenti (SQL, programmazione orientata agli oggetti), qualsiasi cosa serva per portare a termine il lavoro, ma sono riluttante a investire tempo e sforzi in qualcosa che sarà un vicolo cieco per le mie esigenze.

Quindi, quali sono i tuoi suggerimenti per affrontare questo problema ed essere in grado di codificare i miei strumenti in un modo più breve, flessibile e riutilizzabile?

Addendum: a parte di fare qualcosa con una particolare sotto-dizionario del dizionario dei dati, ecco alcuni esempi di operazioni ho implementato per la DIC dataset, o un dizionario sotto di esso:

in realtà ho un po 'ricorsivo funzioni che hanno funzionato bene:

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 

per alcune operazioni che ho trovato nessun altro modo se non per appiattire il dizionario:

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: Temo di non capire abbastanza i dati del campione per poter dare molti consigli. Per favore potresti approfondire i dati che stai analizzando e quali analisi stai eseguendo? – MattH

+0

@MattH: ho modificato la domanda per fornire maggiori dettagli. Fammi sapere se non è sufficiente – AlexandreS

+0

@AlexandrS: Grazie per il chiarimento. Potrebbe essere d'aiuto se potessi spiegare come vengono acquisiti/archiviati/derivati ​​questi dati al momento. Penso che la via da seguire sarebbe per te creare un diagramma astratto strutturato come oggetti con proprietà e come gli oggetti/proprietà si relazionano l'uno all'altro. Quando deciderò come codificare una struttura dati, abbozzerò spesso queste cose. – MattH

risposta

11

"Ho memorizzato in un dizionario nidificazione"

E, come avete visto, non funziona bene.

Qual è l'alternativa?

  1. Tasti compositi e un dizionario superficiale. Si dispone di una chiave composta da 8 parti: (individuale, sessione di imaging, immagine della regione, data e ora del file, proprietà del file, regioni di interesse nell'immagine, formato dei dati, canale di acquisizione) che mappa in una matrice di valori.

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

    Il problema con questo è la ricerca.

  2. Strutture di classe adeguate. In realtà, una definizione di Classe full-up potrebbe essere eccessiva.

"Il tipo di operazioni che si intraprendono è per esempio per calcolare le caratteristiche delle schiere (elencati sotto Ch1, Ch2), raccogliere matrici per effettuare una nuova raccolta, ad esempio analizzare risposte N01 dalla regione 16 (R16) di un dato individuo in diversi punti temporali, ecc. "

Raccomandazione

In primo luogo, utilizzare un namedtuple per l'oggetto finale.

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

O qualcosa del genere. Crea un semplice elenco di questi oggetti tupla denominati. Puoi quindi semplicemente scorrere su di loro.

In secondo luogo, utilizzare molte semplici operazioni di riduzione della mappa su questo elenco principale degli oggetti dell'array.

Filtering:

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

Ridurre del comune chiave:

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

questo creerà un sottoinsieme nella mappa che ha esattamente gli elementi che si desidera.

È quindi possibile eseguire indiidual_dict ['AS091209M02'] e disporre di tutti i relativi dati. Puoi farlo per qualsiasi (o tutte) le chiavi disponibili.

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

Questo non copia alcun dato. È veloce e relativamente compatto nella memoria.

Mapping (o trasformare) l'array:

for a in theMasterArrayList: 
    someTransformationFunction(a.data) 

Se la matrice è di per sé una lista, si è in grado di aggiornare tale elenco senza rompere la tupla nel suo complesso. Se è necessario creare un nuovo array da un array esistente, si sta creando una nuova tupla. Non c'è niente di sbagliato in questo, ma è una nuova tupla. Finisci con programmi come questo.

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 

È possibile creare trasformazioni, riduzioni, mappature in oggetti più elaborati.

La cosa più importante è creare solo i dizionari necessari dall'elenco principale in modo da non effettuare ulteriori filtraggi di quanto sia necessario.

BTW. Questo può essere mappato a un database relazionale banalmente. Sarà più lento, ma è possibile avere più operazioni di aggiornamento simultanee. Tranne che per più aggiornamenti simultanei, un database relazionale non offre alcuna funzionalità al di sopra di questo.

+0

Questo è molto utile. Ho già fatto ricorso al livellamento del dizionario dei dati oa un (sottoinsiemi di esso) + list comprehensions per filtrare i dati, ma la descrizione dell'uso della tupla denominata consente una sintassi e un codice molto più chiari di quello che ho. Inoltre, richiede un apprendimento minimo per ottenere i risultati desiderati, quindi penso che ci riuscirò, almeno per il momento. – AlexandreS

2

È può rendere i vostri cicli aspetto migliore, sostituendo:

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 

È in tal modo ottenere l'accesso a tutti i valori con un codice relativamente concisa. Se hai bisogno anche di alcuni tasti, si può fare qualcosa di simile:

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

A seconda delle esigenze, si potrebbe anche prendere in considerazione la creazione e quindi utilizzando un unico dizionario con chiavi tuple:

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

L'iterazione di un oggetto dizionario restituisce le chiavi del dizionario. Non è quindi possibile iterare la chiave e aspettarsi di accedere al valore. – MattH

+0

Ho provato il primo blocco, ma a per rgv in mv: vedo che rgv itera attraverso la stringa mv, invece di mv.keys()? – AlexandreS

+0

@MattH: hai ragione, mio ​​cattivo! Fisso. Grazie! – EOL

0

Tu chiedi: Come dovrei organizzare i dati che sto analizzando e quali strumenti devo usare per gestirli?

Sospetto che un dizionario, con tutta la sua ottimizzazione, non sia la risposta giusta a questa domanda. Penso che staresti meglio usando XML o, se c'è un binding Python per questo, HDF5, persino NetCDF. Oppure, come suggerisci tu stesso, un database.

Se il tuo progetto ha una durata e un'utilità sufficienti per garantire l'apprendimento di come utilizzare tali tecnologie, allora penso che scoprirai che impararle ora e ottenere le strutture dati corrette è una via migliore per il successo rispetto al wrestling con l'errato strutture dati per l'intero progetto. L'apprendimento di XML, o HDF5, o SQL, o qualsiasi altra cosa tu scelga, sta creando la tua esperienza generale e ti rende più capace di affrontare il prossimo progetto. Rimanere con strutture dati scomode, problematiche e idiosincratiche porta allo stesso gruppo di problemi la prossima volta.

+0

Grazie per avermi informato su HDF5 e NetCDF. Python ha collegamenti per entrambi e le licenze sono utilizzabili. Penso che inizierò leggendo su HDF5, e se sembra promettente studierò come utilizzarlo con i miei dati. Questi tipi di indicatori sono esattamente ciò che speravo quando ho scritto la mia domanda. – AlexandreS

0

Si potrebbe scrivere una funzione di generatore che permette di iterare su tutti gli elementi di un certo livello:

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 

che può quindi essere utilizzato come segue:

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

Se hai bisogno anche di elementi di filtro, è possibile prima ottenere tutti gli elementi che devono essere filtrati (ad esempio, il livello 'rgk'):

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

Almeno questo renderà un po 'più facile lavorare con una gerarchia di dizionari. Usare anche nomi più descrittivi sarebbe d'aiuto.

+0

grazie per avermi mostrato l'uso di dic.itervalues ​​(), che fino ad ora non avevo apprezzato. Il problema è che in realtà non affronta il problema dell'inflessione profonda e dell'inflessibilità per le catene. – AlexandreS

1

Condividerò alcune considerazioni a riguardo. Al posto di questa funzione:

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 

cui si desidera scrivere semplicemente come:

for ek in deep_loop(dic): 
    do_something 

Ci sono 2 modi. Uno è funzionale, il secondo è simile al generatore. Il secondo è:

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 

Ciò consente di acquisire la logica di passare attraverso il dizionario. È molto semplice modificare questa funzione per supportare diversi modi di attraversare la struttura. Dipende dal modo in cui la struttura cambia, se è solo una profondità del loop o qualcosa di diverso. Potresti postare alcuni esempi più avanzati su quali requisiti per attraversare l'albero che hai? Ti piace filtrare, cercare, ecc.? La profondità sarebbe simile a questa (non testato) - si produrrà un paio di (tupla di chiavi), (valore):

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 

Ora diventa più facile:

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

Ci sono altri modi per personalizzalo, potresti aggiungere un tipo di tupla con un nome come parametro di deep_loop. Deep_loop potrebbe rilevare automaticamente la profondità dalla tupla nominata e restituire la tupla nominata.

+0

Ho pubblicato altri esempi di ciò che sono riuscito a implementare come appendice, sperando che ciò sia d'aiuto. La tua prima soluzione racchiude il processo di ricerca in una funzione, ma non risolve molti dei miei problemi, poiché le ricerche sono spesso variabili e dovrei definire molte funzioni per descrivere ogni ricerca. La seconda opzione affronta questo punto, ma utilizza la ricorsione, che mi spaventa un po 'dopo le brutte esperienze che ho avuto con esso (anche se sono riuscito a scrivere alcune funzioni ricorsive di lavoro). Ma grazie per avermi mostrato come hai accoppiato dic.items() con le espressioni generatrici, questo è utile per me. – AlexandreS