2014-07-23 4 views
5

Diciamo che si dispone di un DataFrame di regioni (inizio, fine) coordinate e un altro DataFrame di posizioni che possono o meno rientrare in una determinata regione. Ad esempio:Unione di dataframe pandas in base alla relazione in più colonne

region = pd.DataFrame({'chromosome': [1, 1, 1, 1, 2, 2, 2, 2], 'start': [1000, 2000, 3000, 4000, 1000, 2000, 3000, 4000], 'end': [2000, 3000, 4000, 5000, 2000, 3000, 4000, 5000]}) 
position = pd.DataFrame({'chromosome': [1, 2, 1, 3, 2, 1, 1], 'BP': [1500, 1100, 10000, 2200, 3300, 400, 5000]}) 
print region 
print position 


    chromosome end start 
0   1 2000 1000 
1   1 3000 2000 
2   1 4000 3000 
3   1 5000 4000 
4   2 2000 1000 
5   2 3000 2000 
6   2 4000 3000 
7   2 5000 4000 

     BP chromosome 
0 1500   1 
1 1100   2 
2 10000   1 
3 2200   3 
4 3300   2 
5 400   1 
6 5000   1 

Una posizione rientra un'area se:

position['BP'] >= region['start'] & 
position['BP'] <= region['end'] & 
position['chromosome'] == region['chromosome'] 

Ogni posizione è garantita a cadere entro un massimo di una regione anche se potrebbe non cadere in nessuna.

Qual è il modo migliore per unire questi due dataframe in modo tale da accodare colonne aggiuntive per posizionarsi con la regione in cui ricade se cade in qualsiasi regione. Dando in questo caso più o meno il seguente output:

 BP chromosome start end 
0 1500   1 1000 2000 
1 1100   2 1000 2000 
2 10000   1 NA  NA 
3 2200   3 NA  NA 
4 3300   2 3000 4000 
5 400   1 NA  NA 
6 5000   1 4000 5000 

Un approccio è quello di scrivere una funzione per calcolare il rapporto che voglio e quindi utilizzare il metodo DataFrame.apply come segue:

def within(pos, regs): 
    istrue = (pos.loc['chromosome'] == regs['chromosome']) & (pos.loc['BP'] >= regs['start']) & (pos.loc['BP'] <= regs['end']) 
    if istrue.any(): 
     ind = regs.index[istrue].values[0] 
     return(regs.loc[ind ,['start', 'end']]) 
    else: 
     return(pd.Series([None, None], index=['start', 'end'])) 

position[['start', 'end']] = position.apply(lambda x: within(x, region), axis=1) 
print position 

     BP chromosome start end 
0 1500   1 1000 2000 
1 1100   2 1000 2000 
2 10000   1 NaN NaN 
3 2200   3 NaN NaN 
4 3300   2 3000 4000 
5 400   1 NaN NaN 
6 5000   1 4000 5000 

Ma io spero che ci sia un modo più ottimizzato rispetto a fare ogni confronto in tempo O (N). Grazie!

risposta

5

Una soluzione sarebbe quella di fare un inner-join su chromosome, escludere le righe che violano, e poi fare sinistra-join con position:

>>> df = pd.merge(position, region, on='chromosome', how='inner') 
>>> idx = (df['BP'] < df['start']) | (df['end'] < df['BP']) # violating rows 
>>> pd.merge(position, df[~idx], on=['BP', 'chromosome'], how='left') 
     BP chromosome end start 
0 1500   1 2000 1000 
1 1100   2 2000 1000 
2 10000   1 NaN NaN 
3 2200   3 NaN NaN 
4 3300   2 4000 3000 
5 400   1 NaN NaN 
6 5000   1 5000 4000 
+0

Questo migliora decisamente le prestazioni. Grazie! – dylkot

+0

Sfortunatamente, i miei file di dati sono troppo grandi per contenere df in memoria. Sto cercando di trovare una soluzione alternativa in cui carico la posizione e la regione in dataframes con un multiindice con cromosoma come indice esterno e quindi eseguo l'unione in modo indipendente per ciascun cromosoma. Sto elaborando questo codice ora, ma se qualcuno vede un modo migliore per farlo, per favore fatemelo sapere. Grazie. – dylkot

0

Il modo migliore che ho trovato per risolvere questo problema da solo grandi set di dati utilizzavano il metodo intersect di bedestools che era stato avvolto in python da pybedtools (http://pythonhosted.org/pybedtools/) poiché il problema si riduceva davvero all'intercettazione di due serie di regioni (una delle quali in questo caso è solo di lunghezza 1).