2015-03-25 19 views
14

Ho un dataframe panda con 3 livelli di un MultiIndex. Sto cercando di estrarre righe di questo dataframe in base a un elenco di valori che corrispondono a due dei livelli.Come dividere un MultiIndex DataFrame con il MultiIndex di un altro

ho qualcosa di simile a questo:

ix = pd.MultiIndex.from_product([[1, 2, 3], ['foo', 'bar'], ['baz', 'can']], names=['a', 'b', 'c']) 
data = np.arange(len(ix)) 
df = pd.DataFrame(data, index=ix, columns=['hi']) 
print(df) 

      hi 
a b c  
1 foo baz 0 
     can 1 
    bar baz 2 
     can 3 
2 foo baz 4 
     can 5 
    bar baz 6 
     can 7 
3 foo baz 8 
     can 9 
    bar baz 10 
     can 11 

Ora voglio prendere tutte le righe in cui i livelli di indice 'b' e 'c' sono in questo indice:

ix_use = pd.MultiIndex.from_tuples([('foo', 'can'), ('bar', 'baz')], names=['b', 'c']) 

cioè valori di hi avere ('foo', 'can') o ('bar', 'baz') nei livelli b e c rispettivamente: (1, 2, 5, 6, 9, 10).

Quindi mi piacerebbe prendere uno slice(None) al primo livello e tirare fuori tuple specifiche sul secondo e terzo livello.

Inizialmente pensavo che passare un oggetto multi-indice a .loc avrebbe tirato fuori i valori/i livelli che volevo, ma questo non funziona. Qual è il modo migliore per fare qualcosa di simile?

+0

ho avuto un paio di diversi tentativi di ottenere questo al lavoro. Penso di aver trovato una soluzione decente al fatto che questo al momento sembra essere un po 'difficile. Provalo! – LondonRob

risposta

19

Ecco un modo per ottenere questa fetta:

df.sort_index(inplace=True) 
idx = pd.IndexSlice 
df.loc[idx[:, ('foo','bar'), 'can'], :] 

cedendo

  hi 
a b c  
1 bar can 3 
    foo can 1 
2 bar can 7 
    foo can 5 
3 bar can 11 
    foo can 9 

Nota che potrebbe essere necessario per ordinare MultiIndex prima di poter affettarlo. Bene panda è così gentile da mettere in guardia se hai bisogno di farlo:

KeyError: 'MultiIndex Slicing requires the index to be fully lexsorted tuple len (3), lexsort depth (1)' 

Si può leggere di più su come utilizzare affettatrici nel docs

Se per qualche motivo usando affettatrici non è un'opzione: ecco un modo per ottenere la stessa fetta con .isin() metodo:

df[df.index.get_level_values('b').isin(ix_use.get_level_values(0)) & df.index.get_level_values('c').isin(ix_use.get_level_values(1))] 

che non è chiaramente più concise.

UPDATE:

Per le condizioni che si è aggiornato: ecco un modo per farlo:

cond1 = (df.index.get_level_values('b').isin(['foo'])) & (df.index.get_level_values('c').isin(['can'])) 
cond2 = (df.index.get_level_values('b').isin(['bar'])) & (df.index.get_level_values('c').isin(['baz'])) 
df[cond1 | cond2] 

produzione:

  hi 
a b c  
1 foo can 1 
    bar baz 2 
2 foo can 5 
    bar baz 6 
3 foo can 9 
    bar baz 10 
+0

Questo è davvero vicino a quello che sto cercando, ma avrei dovuto formulare la domanda più chiaramente. In realtà, ciò di cui ho bisogno è un valore dal livello "c" che dipende dal valore nel livello "b". Ad esempio, ogni volta che il livello "b" è "pippo", voglio il valore dove il livello "c" è "può", e ogni volta che il livello "b" è "bar", voglio il valore dove livello " c "is 'baz' – choldgraf

+0

Aggiornato la risposta con queste due condizioni, che dovrebbe dare un'idea su come gestirlo. – Primer

0

Trovo interessante che questo doesn funziona:

In [45]: df.loc[(idx[:, 'foo', 'can'], idx[:, 'bar', 'baz']), ] 
Out[45]: 
      hi 
a b c  
1 bar baz 2 
     can 3 
    foo baz 0 
     can 1 
2 bar baz 6 
     can 7 
    foo baz 4 
     can 5 
3 bar baz 10 
     can 11 
    foo baz 8 
     can 9 

In un certo senso sembra "dovrebbe", in qualche modo. In ogni caso, ecco una soluzione ragionevole:

Supponiamo le tuple che si desidera tagliare si trovano nell'indice di un altro DataFrame (dal momento che suona come probabilmente sono nel tuo caso!).

In [53]: ix_use = pd.MultiIndex.from_tuples([('foo', 'can'), ('bar', 'baz')], names=['b', 'c']) 
In [55]: other = pd.DataFrame(dict(a=1), index=ix_use) 
In [56]: other 
Out[56]: 
     a 
b c  
foo can 1 
bar baz 1 

Ora per affettare df dall'indice di other possiamo utilizzare il fatto che .loc/.ix consentono di dare una lista di tuple (si veda l'ultimo esempio here).

Prima cerchiamo di creare l'elenco di tuple che vogliamo:

In [13]: idx = [(x,) + y for x in df.index.levels[0] for y in other.index.values] 
In [14]: idx 
Out[14]: 
[(1, 'foo', 'can'), 
(1, 'bar', 'baz'), 
(2, 'foo', 'can'), 
(2, 'bar', 'baz'), 
(3, 'foo', 'can'), 
(3, 'bar', 'baz')] 

Ora possiamo passare questo elenco per .ix o .loc:

In [17]: df.ix[idx] 
Out[17]: 
      hi 
a b c  
1 foo can 1 
    bar baz 2 
2 foo can 5 
    bar baz 6 
3 foo can 9 
    bar baz 10 
2

lo consiglio the query() method proprio come in this Q&A.

Semplicemente usando questa, che credo sia un modo più naturale di esprimere:

In [27]: df.query("(b == 'foo' and c == 'can') or (b == 'bar' and c == 'baz')") 
Out[27]: 
      hi 
a b c  
1 foo can 1 
    bar baz 2 
2 foo can 5 
    bar baz 6 
3 foo can 9 
    bar baz 10