Módulo 08 — Seleccionar y filtrar: loc, iloc y máscaras booleanas
Tener los datos cargados sirve de poco si no sabes extraer justo la parte que te interesa: las filas de Madrid, los productos caros, las tres primeras columnas. Este módulo es el que más vas a usar en tu día a día con Pandas. Tómatelo en serio.
El dataset de trabajo
Todos los ejemplos usan esta tabla:
import pandas as pd
df = pd.DataFrame({
"nombre": ["Ada", "Luis", "Marta", "Juan", "Eva", "Iván"],
"edad": [20, 22, 21, 23, 19, 25],
"ciclo": ["DAW", "DAW", "ASIR", "DAW", "ASIR", "DAM"],
"nota": [9, 5, 7, 4, 8, 6],
"ciudad": ["Madrid", "Sevilla", "Madrid", "Bilbao", "Sevilla", "Madrid"],
})
df = df.set_index("nombre") # usamos el nombre como índice
set_index convierte una columna en el índice de la tabla. Útil cuando una columna identifica cada fila, como aquí el nombre.
Dos formas de seleccionar filas: loc e iloc
Pandas separa seleccionar por etiqueta (el nombre del índice) de seleccionar por posición (el número de fila). Confundirlos es el error número uno de quien empieza.
loctrabaja con etiquetas:df.loc["Ada"].iloctrabaja con posiciones:df.iloc[0](laies de integer).
df.loc["Ada"] # la fila cuya etiqueta es "Ada"
df.iloc[0] # la primera fila, sea cual sea su etiqueta
df.loc["Marta", "nota"] # valor concreto: nota de Marta -> 7
df.iloc[2, 2] # fila 2, columna 2 por posición -> 7
Seleccionar rangos con loc e iloc
# Por etiqueta (loc INCLUYE el final):
df.loc["Ada":"Marta"] # de Ada a Marta, ambas incluidas
df.loc["Ada":"Marta", "edad":"nota"] # subtabla de filas y columnas
# Por posición (iloc EXCLUYE el final, como el slicing normal):
df.iloc[0:3] # filas 0, 1, 2 (la 3 no)
df.iloc[0:3, 1:3] # filas 0-2, columnas 1-2
# Columnas concretas:
df.loc[:, ["edad", "nota"]] # todas las filas, dos columnas
df.iloc[:, [0, 2]] # todas las filas, columnas 0 y 2
Recuerda la diferencia que más despista: con loc el final del rango se incluye; con iloc no, igual que el slicing normal de Python. Tiene sentido: con etiquetas, “de Ada a Marta” significa hasta Marta inclusive.
Filtrado booleano: seleccionar filas que cumplen una condición
Esto es lo que hace a Pandas tan potente, y es exactamente la máscara booleana de NumPy del módulo 06. Comparas una columna contra un valor y obtienes una Series de True/False. Metes esa máscara entre corchetes y te quedas con las filas True:
df[df["nota"] >= 7]
# edad ciclo nota ciudad
# nombre
# Ada 20 DAW 9 Madrid
# Marta 21 ASIR 7 Madrid
# Eva 19 ASIR 8 Sevilla
Léelo así: df["nota"] >= 7 produce la máscara; df[...] se queda con las filas verdaderas. Otros ejemplos:
df[df["ciudad"] == "Madrid"] # solo los de Madrid
df[df["edad"] < 21] # menores de 21
df[df["ciclo"] != "DAW"] # los que no son de DAW
Combinar varias condiciones
Igual que en NumPy: & para “y”, | para “o”, y cada condición entre paréntesis. Olvidar los paréntesis es un error clásico que da mensajes crípticos.
# Aprobados de Madrid:
df[(df["nota"] >= 5) & (df["ciudad"] == "Madrid")]
# De DAW o de DAM:
df[(df["ciclo"] == "DAW") | (df["ciclo"] == "DAM")]
# Negación con ~ (suspensos = NO aprobados):
df[~(df["nota"] >= 5)]
Para comprobar si un valor está en una lista, isin() es más limpio que encadenar muchos |:
df[df["ciudad"].isin(["Madrid", "Bilbao"])]
df[df["ciclo"].isin(["DAW", "DAM"])]
Filtrar por texto: el accesor .str
Para condiciones sobre cadenas, .str da acceso a los métodos de texto aplicados a toda la columna:
df[df["ciclo"].str.startswith("DA")] # ciclos que empiezan por "DA"
df[df["ciudad"].str.contains("a")] # ciudades que contienen una "a"
df["ciudad"].str.upper() # toda la columna en mayúsculas
Esto lo profundizamos en el módulo 09 (limpieza), pero ténlo en el radar: .str te deja aplicar lo del módulo 01 a una columna entera.
Combinar selección de filas y columnas con loc
loc acepta a la vez una condición de filas y una lista de columnas. Es la forma más expresiva de pedir “estas columnas, de las filas que cumplan esto”:
df.loc[df["nota"] >= 7, ["edad", "nota"]]
# edad nota
# nombre
# Ada 20 9
# Marta 21 7
# Eva 19 8
Modificar valores con loc
loc no solo lee: también escribe. Para cambiar valores que cumplen una condición:
# Subir 1 punto a todos los de DAW:
df.loc[df["ciclo"] == "DAW", "nota"] = df.loc[df["ciclo"] == "DAW", "nota"] + 1
# Marcar una categoría:
df.loc[df["nota"] >= 9, "mencion"] = "Honor"
Hazlo siempre con loc. Si intentas modificar sobre una selección encadenada (df[df["x"] > 0]["y"] = ...), Pandas te avisa con un SettingWithCopyWarning y puede que el cambio no se aplique. La regla: para asignar, usa loc.
Ordenar los resultados
df.sort_values("nota") # de menor a mayor nota
df.sort_values("nota", ascending=False) # de mayor a menor
df.sort_values(["ciclo", "nota"]) # primero por ciclo, dentro por nota
df.sort_index() # ordena por el índice (los nombres)
Combinado con filtrado, respondes preguntas reales en una línea: “los tres mejores aprobados” es filtrar por nota >= 5, ordenar descendente y coger head(3).
Ejercicios
Usa el df de alumnos del principio (antes de las modificaciones de la sección “Modificar valores”).
08.1 — Selecciona la fila de “Juan” con loc, y la cuarta fila (posición 3) con iloc. Comprueba que son la misma.
08.2 — Muestra solo las columnas edad y nota de todos los alumnos.
08.3 — Filtra los alumnos que han aprobado (nota >= 5).
08.4 — Filtra los alumnos de Madrid que además tengan más de 20 años.
08.5 — Muestra los alumnos que estudian DAW o ASIR, ordenados por nota de mayor a menor.
08.6 — Muestra el nombre y la nota de los dos alumnos con mejor nota. (Pista: ordena descendente y usa head(2).)
08.7 — Usando isin, filtra los alumnos cuyas ciudades sean Madrid o Sevilla.
08.8 — ¿Cuál es la nota media de los alumnos de DAW? (Filtra y luego .mean() sobre la columna nota.)
08.9 — Con loc, cambia la nota de un alumno concreto por su nombre. Luego usa iloc para seleccionar las filas 1 a 3 y las columnas en posición 0 y 2.
08.10 — Filtra los alumnos cuyo nombre empiece por vocal. Usa .str.startswith() pasándole una tupla de vocales en mayúsculas y minúsculas.
08.11 — Crea dos masks: (a) alumnos con nota >= 7 y de ciclo DAW; (b) alumnos con nota < 5 o de otro ciclo. Muestra cada grupo en un DataFrame separado.
Soluciones
08.1
print(df.loc["Juan"])
print(df.iloc[3])
08.2
print(df[["edad", "nota"]])
# o equivalente:
print(df.loc[:, ["edad", "nota"]])
08.3
print(df[df["nota"] >= 5])
08.4
print(df[(df["ciudad"] == "Madrid") & (df["edad"] > 20)])
08.5
seleccion = df[df["ciclo"].isin(["DAW", "ASIR"])]
print(seleccion.sort_values("nota", ascending=False))
08.6
print(df.sort_values("nota", ascending=False).head(2)[["nota"]])
# (el nombre es el índice, así que sale solo)
08.7
print(df[df["ciudad"].isin(["Madrid", "Sevilla"])])
08.8
print(df[df["ciclo"] == "DAW"]["nota"].mean()) # 6.0
08.9
# Cambiar nota con loc:
df.loc[df["nombre"] == "Ada", "nota"] = 9.5
print(df.loc[df["nombre"] == "Ada", ["nombre", "nota"]])
# Filas 1-3, columnas 0 y 2 con iloc:
print(df.iloc[1:4, [0, 2]])
08.10
vocales = ("A", "E", "I", "O", "U", "a", "e", "i", "o", "u")
filtrado = df[df["nombre"].str.startswith(vocales)]
print(filtrado)
08.11
buenos = df[(df["nota"] >= 7) & (df["ciclo"] == "DAW")]
print("Nota alta en DAW:\n", buenos)
otros = df[(df["nota"] < 5) | (df["ciclo"] != "DAW")]
print("Nota baja o fuera de DAW:\n", otros)