python - iloc - pandas: cortar un MultiIndex por rango de índice secundario
pandas select columns (4)
Tengo una serie con un MultiIndex como este:
import numpy as np
import pandas as pd
buckets = np.repeat([''a'',''b'',''c''], [3,5,1])
sequence = [0,1,5,0,1,2,4,50,0]
s = pd.Series(
np.random.randn(len(sequence)),
index=pd.MultiIndex.from_tuples(zip(buckets, sequence))
)
# In [6]: s
# Out[6]:
# a 0 -1.106047
# 1 1.665214
# 5 0.279190
# b 0 0.326364
# 1 0.900439
# 2 -0.653940
# 4 0.082270
# 50 -0.255482
# c 0 -0.091730
Me gustaría obtener los valores s [''b''] donde el segundo índice ('' sequence
'') está entre 2 y 10.
Cortar en el primer índice funciona bien:
s[''a'':''b'']
# Out[109]:
# bucket value
# a 0 1.828176
# 1 0.160496
# 5 0.401985
# b 0 -1.514268
# 1 -0.973915
# 2 1.285553
# 4 -0.194625
# 5 -0.144112
Pero no en el segundo, al menos por lo que parecen ser las dos formas más obvias:
1) Esto devuelve los elementos 1 a 4, sin nada que ver con los valores del índice
s[''b''][1:10]
# In [61]: s[''b''][1:10]
# Out[61]:
# 1 0.900439
# 2 -0.653940
# 4 0.082270
# 50 -0.255482
Sin embargo, si invierto el índice y el primer índice es entero y el segundo índice es una cadena, funciona:
In [26]: s
Out[26]:
0 a -0.126299
1 a 1.810928
5 a 0.571873
0 b -0.116108
1 b -0.712184
2 b -1.771264
4 b 0.148961
50 b 0.089683
0 c -0.582578
In [25]: s[0][''a'':''b'']
Out[25]:
a -0.126299
b -0.116108
A partir de pandas 0.14.0, es posible dividir objetos de .loc
proporcionando .loc
una tupla que contiene objetos de .loc
:
In [2]: s.loc[(''b'', slice(2, 10))]
Out[2]:
b 2 -1.206052
4 -0.735682
dtype: float64
Como responde Robbie-Clarken , desde 0.14 puede pasar una porción de la tupla que pasa a loc :
In [11]: s.loc[(''b'', slice(2, 10))]
Out[11]:
b 2 -0.65394
4 0.08227
dtype: float64
De hecho, puedes pasar una porción para cada nivel:
In [12]: s.loc[(slice(''a'', ''b''), slice(2, 10))]
Out[12]:
a 5 0.27919
b 2 -0.65394
4 0.08227
dtype: float64
Nota: la rebanada es inclusiva.
Respuesta antigua:
También puedes hacer esto usando:
s.ix[1:10, "b"]
(Es una buena práctica hacer en un solo ix / loc / iloc ya que esta versión permite la asignación).
Esta respuesta se escribió antes de la introducción de iloc a principios de 2013, es decir, la ubicación de posición / entero, que puede preferirse en este caso. La razón por la que se creó fue para eliminar la ambigüedad de los objetos pandas indexados a enteros, y ser más descriptivo: "Estoy cortando en posición".
s["b"].iloc[1:10]
Dicho esto, estoy un poco en desacuerdo con los documentos que ix es:
La forma más robusta y consistente.
no lo es, la forma más consistente es describir lo que estás haciendo:
- usar loc para etiquetas
- usar iloc para posicionar
- Usa ix para ambos (si realmente tienes que hacerlo)
Recuerda el zen de python :
explícito es mejor que implícito
La mejor manera en que puedo pensar es usar "seleccionar" en este caso. Aunque incluso dice en la documentación que "este método debe usarse solo cuando no hay una forma más directa".
Indexación y selección de datos.
In [116]: s
Out[116]:
a 0 1.724372
1 0.305923
5 1.780811
b 0 -0.556650
1 0.207783
4 -0.177901
50 0.289365
0 1.168115
In [117]: s.select(lambda x: x[0] == ''b'' and 2 <= x[1] <= 10)
Out[117]: b 4 -0.177901
No estoy seguro si esto es ideal pero funciona creando una máscara.
In [59]: s.index
Out[59]:
MultiIndex
[(''a'', 0) (''a'', 1) (''a'', 5) (''b'', 0) (''b'', 1) (''b'', 2) (''b'', 4)
(''b'', 50) (''c'', 0)]
In [77]: s[(tpl for tpl in s.index if 2<=tpl[1]<=10 and tpl[0]==''b'')]
Out[77]:
b 2 -0.586568
4 1.559988
EDITAR: la solución de Hayden es el camino a seguir