Cómo dividir un MultiIndex DataFrame con el MultiIndex de otro

Tengo un dataframe de pandas con 3 niveles de un MultiIndex. Estoy tratando de sacar filas de este dataframe de acuerdo con una lista de valores que corresponden a dos de los niveles.

Tengo algo como esto:

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 abc 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 

Ahora quiero tomar todas las filas donde los niveles de índice ‘b’ y ‘c’ están en este índice:

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

es decir, los valores de hi tienen ('foo', 'can') o ('bar', 'baz') en los niveles c respectivamente: (1, 2, 5, 6, 9, 10) .

Así que me gustaría tomar un slice(None) en el primer nivel y extraer tuplas específicas en el segundo y tercer nivel.

Inicialmente pensé que pasar un objeto de múltiples índices a .loc sacaría los valores / niveles que quería, pero esto no está funcionando. ¿Cuál es la mejor manera de hacer algo como esto?

Aquí hay una manera de obtener esta rebanada:

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

flexible

  hi abc 1 bar can 3 foo can 1 2 bar can 7 foo can 5 3 bar can 11 foo can 9 

Tenga en cuenta que es posible que deba ordenar MultiIndex antes de poder dividirlo. Bueno, los pandas tienen la amabilidad de advertirte si necesitas hacerlo:

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

Puedes leer más sobre cómo usar los rebanadores en los documentos

Si, por algún motivo, utilizar segmentaciones no es una opción, aquí hay una manera de obtener la misma porción utilizando el método .isin() :

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

Lo que claramente no es tan conciso.

ACTUALIZAR:

Para las condiciones que has actualizado aquí es una forma de hacerlo:

 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] 

productor:

  hi abc 1 foo can 1 bar baz 2 2 foo can 5 bar baz 6 3 foo can 9 bar baz 10 

Recomendaría el método de query() como en esta P&R .

Simplemente utilizando esto, lo que creo que es una forma más natural de express:

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

Me parece interesante que esto no funcione:

 In [45]: df.loc[(idx[:, 'foo', 'can'], idx[:, 'bar', 'baz']), ] Out[45]: hi abc 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 

Parece que debería “de alguna manera”. En cualquier caso, aquí hay una solución razonable:

Supongamos que las tuplas por las que desea DataFrame están en el índice de otro DataFrame (ya que parece que probablemente estén en su 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 bc foo can 1 bar baz 1 

Ahora, para dividir df por el índice de other , podemos usar el hecho de que .loc / .ix permite dar una lista de tuplas (vea el último ejemplo aquí ).

Primero vamos a construir la lista de tuplas que queremos:

 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')] 

Ahora podemos pasar esta lista a .ix o .loc :

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