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