Índices y selección#

Ya hemos visto los diferentes métodos de indexación para arrays de numpy. Hay métodos similares para acceder y modificar valores en Series y DataFrame, aunque es conveniente estudiarlos con detalle para ver las diferencias.

Selección de datos en Series#

Vamos a proseguir con la analogía con los arrays y los diccionarios para estudiar el acceso a los datos.

Como diccionario#

import pandas as pd
data = pd.Series([-1.0, -0.5, 0, 0.5, 1.0],
                 index=['a', 'b', 'c', 'd', 'e'])
data
a   -1.0
b   -0.5
c    0.0
d    0.5
e    1.0
dtype: float64
data['b']
np.float64(-0.5)

Podemos acceder también a más funcionalidades de los diccionarios:

'd' in data
True
data.keys()
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

Hay algunas diferencias, values() no funciona, pero values sí (y devuelve un ndarray). Igual que en un diccionario, items() devuelve un iterador:

print(data.items())
list(data.items())
<zip object at 0x7ffb27d5fd80>
[('a', -1.0), ('b', -0.5), ('c', 0.0), ('d', 0.5), ('e', 1.0)]

Los objetos Series se pueden modificar sobre la marcha, igual que los diccionarios, usando la asignación para ello:

data['f'] = 1.5
data
a   -1.0
b   -0.5
c    0.0
d    0.5
e    1.0
f    1.5
dtype: float64

Como array#

Aquí podemos utilizar los mismos mecanismos que con los arrays de numpy (con una pequeña sorpresa)

# Usando el operador : (slicing)
data['a':'c']
a   -1.0
b   -0.5
c    0.0
dtype: float64
# Usando el operador : (slicing)
# pero con un índice entero implícito
data[0:2]
a   -1.0
b   -0.5
dtype: float64
# máscaras
data[ (data > -1.0) & (data < 0)]
b   -0.5
dtype: float64
# arrays de índices (fancy indexing)
data[['e', 'a']]
e    1.0
a   -1.0
dtype: float64

Como vemos, hay una fuente potencial de confusión con el uso de :. Puede utilizarse con los objetos de index, en este caso cadenas, y entonces incluye el último elemento.

También puede utilizar con el índice entero implícito (el que tendría si fuera un array de numpy) en cuyo caso no incluye el último elemento.

¿Qué sucede si estamos usando un índice entero? En ese caso, pandas usa el índice explícito para indexar y el implícito para recortar:

data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
data
1    a
3    b
5    c
dtype: object
# Índice explícito para indexar
data[1]
'a'
# Índice implícito con slice
data[1:3]
3    b
5    c
dtype: object

Como este comportamiento puede inducir a confusión, pandas proporciona unos atributos de los objetos Series que nos permiten exponer cuál de los dos mecanismos queremos.

El atributo loc permite acceder con los índices explícitos

# Índice explícito para indexar
data.loc[1]
'a'
# Índice explícito con slice
data.loc[1:3]
1    a
3    b
dtype: object

El atributo iloc permite acceder con los índices implícitos

# Índice implícito para indexar
data.loc[1]
'a'
# Índice implícito con slice
data.loc[1:3]
1    a
3    b
dtype: object

Existe un tercer atributo, ix, pero ha sido marcado para eliminación a partir de pandas 0.20.1 y se prefiere utilizar iloc y loc.

Selección de datos en DataFrame#

Recordemos que un DataFrame se comporta como un array bididimensional o como un diccionario de Series con un índice común.

Como un diccionario#

nombre = ['Mercurio', 'Venus', 'Tierra', 'Marte', 'Júpiter', 'Saturno', 'Urano', 'Neptuno']
semieje = [0.39, 0.72, 1.0, 1.52, 5.2, 9.54, 19.19, 30.06]
nlunas = [0, 0, 1, 2, 79, 82, 27, 14]

dis = pd.Series(semieje, index=nombre)
nmoon = pd.Series(nlunas, index=nombre)
data = pd.DataFrame({'a': dis, '#moon':nmoon})
data
a #moon
Mercurio 0.39 0
Venus 0.72 0
Tierra 1.00 1
Marte 1.52 2
Júpiter 5.20 79
Saturno 9.54 82
Urano 19.19 27
Neptuno 30.06 14

Podemos acceder a las columnas (Series) utilizando [] como en un diccionario y de la misma manera podemos añadir columnas

import numpy as np

# Periodo sidéreo (en años), vía tercera ley de Kepler
data['P'] = np.sqrt(data['a']**3)
data
a #moon P
Mercurio 0.39 0 0.243555
Venus 0.72 0 0.610940
Tierra 1.00 1 1.000000
Marte 1.52 2 1.873982
Júpiter 5.20 79 11.857824
Saturno 9.54 82 29.466093
Urano 19.19 27 84.064467
Neptuno 30.06 14 164.809964

Como un array#

Podemos acceder al array de datos internos usando values

data.values
array([[  0.39      ,   0.        ,   0.24355492],
       [  0.72      ,   0.        ,   0.61094026],
       [  1.        ,   1.        ,   1.        ],
       [  1.52      ,   2.        ,   1.87398186],
       [  5.2       ,  79.        ,  11.85782442],
       [  9.54      ,  82.        ,  29.46609346],
       [ 19.19      ,  27.        ,  84.06446668],
       [ 30.06      ,  14.        , 164.80996395]])

Y hay casos en los que nos gustaría operar el DataFrame como si fuera un array, por ejemplo:

data.T
Mercurio Venus Tierra Marte Júpiter Saturno Urano Neptuno
a 0.390000 0.72000 1.0 1.520000 5.200000 9.540000 19.190000 30.060000
#moon 0.000000 0.00000 1.0 2.000000 79.000000 82.000000 27.000000 14.000000
P 0.243555 0.61094 1.0 1.873982 11.857824 29.466093 84.064467 164.809964

Ahora bien, igual que sucedía en Series, tenemos el operador [] para acceder a los elementos. Y mientras que un solo índice en un array accede a las filas:

data.values[0]
array([0.39      , 0.        , 0.24355492])

un solo índice en DataFrame accede a las columnas:

data['#moon']
Mercurio     0
Venus        0
Tierra       1
Marte        2
Júpiter     79
Saturno     82
Urano       27
Neptuno     14
Name: #moon, dtype: int64

Pandas ofrece de nuevo los atributos de índice que aparecían en Series.

Utilizando iloc, podemos utilizar los índices como si tuviéramos un ndarray:

data.iloc[0, 2]
np.float64(0.24355492193753753)

y también:

data.iloc[0:4, [0,2]]
a P
Mercurio 0.39 0.243555
Venus 0.72 0.610940
Tierra 1.00 1.000000
Marte 1.52 1.873982

o por ejemplo:

data.iloc[4:, 1:]
#moon P
Júpiter 79 11.857824
Saturno 82 29.466093
Urano 27 84.064467
Neptuno 14 164.809964

Utilizando loc, utilizamos los índices explícitos: index para las filas y columns para las filas:

data.loc['Mercurio', 'P']
np.float64(0.24355492193753753)

y también:

data.loc['Mercurio':'Marte', ['a','P']]
a P
Mercurio 0.39 0.243555
Venus 0.72 0.610940
Tierra 1.00 1.000000
Marte 1.52 1.873982

o por ejemplo:

data.loc['Júpiter':, '#moon':]
#moon P
Júpiter 79 11.857824
Saturno 82 29.466093
Urano 27 84.064467
Neptuno 14 164.809964

Para ambos atributos, podemos utilizar los mecanismo que vimos en ndarray.

Por ejemplo, con una máscara:

data.loc[data['a'] > 2, ['a', 'P']]
a P
Júpiter 5.20 11.857824
Saturno 9.54 29.466093
Urano 19.19 84.064467
Neptuno 30.06 164.809964

Recordemos, finalmente, que todas estas operaciones también se puede usar en asignaciones:

data2 = data.copy()
data2.loc[data['a'] < 2, 'P'] = 1.0
data2
a #moon P
Mercurio 0.39 0 1.000000
Venus 0.72 0 1.000000
Tierra 1.00 1 1.000000
Marte 1.52 2 1.000000
Júpiter 5.20 79 11.857824
Saturno 9.54 82 29.466093
Urano 19.19 27 84.064467
Neptuno 30.06 14 164.809964

Convenciones sobre el uso de []#

Veamos ahora los últimos detalles que debemos conocer si utilizamos directamente [].

Hemos visto que si pasamos un solo índice a [] accedemos a las columnas.

data['a']
Mercurio     0.39
Venus        0.72
Tierra       1.00
Marte        1.52
Júpiter      5.20
Saturno      9.54
Urano       19.19
Neptuno     30.06
Name: a, dtype: float64

Pero si pasamos una sección con : accedemos a las filas:

# ¡No funciona!
# data['a':'P']
# ¡Funciona!
data['Tierra':'Júpiter']
a #moon P
Tierra 1.00 1 1.000000
Marte 1.52 2 1.873982
Júpiter 5.20 79 11.857824

Y también podemos pasar los índices de las columnas (estilo array, el último no incluído).

data[2:5]
a #moon P
Tierra 1.00 1 1.000000
Marte 1.52 2 1.873982
Júpiter 5.20 79 11.857824

De la misma manera, las máscaras booleanas se aplican por columnas, no por filas.

data[data["P"] > 10]
a #moon P
Júpiter 5.20 79 11.857824
Saturno 9.54 82 29.466093
Urano 19.19 27 84.064467
Neptuno 30.06 14 164.809964