Indice / pandas: fundamentos / 4.4
loc vs iloc
Dos formas de pedir una fila: por etiqueta o por posición. Al principio dan lo mismo, y esa es exactamente la razón de que el error aparezca tarde, cuando el programa ya andaba.
Para qué sirve
loc pide por etiqueta del índice.
iloc pide por posición, contando desde 0 como una lista
de Python. Con el índice por defecto 0, 1, 2… las dos cosas coinciden y
todo parece intercambiable.
Deja de coincidir en cuanto filtrás una fila, ordenás la tabla o le ponés un índice propio - o sea, siempre. Y el error no da error: devuelve otra fila, sin avisar.
ELI5Explicado facil
En un edificio, el 7º piso y el séptimo piso desde abajo suelen ser el mismo. Hasta que descubrís que no hay piso 13, o que existe un entrepiso.
loc es «llevame al piso que dice 7 en el botón».
iloc es «subí siete pisos». El ascensor te lleva igual a los dos lados
y no te avisa que llegaste a otro lado.
El ejemplo mínimo
import pandas as pd
catalogo = pd.DataFrame({
"producto": ["Teclado", "Monitor", "Webcam", "Auriculares"],
"precio": [4.53, 872.23, 713.42, 542.80],
}, index=["P001", "P003", "P005", "P006"])
print(catalogo)
print()
print("iloc[1] -> la segunda fila, contando desde 0:")
print(catalogo.iloc[1].to_string())
print()
print("loc['P003'] -> la fila que se LLAMA P003:")
print(catalogo.loc["P003"].to_string())
print()
print("Aca dan lo mismo. Eso es una casualidad de este ejemplo,")
print("no una regla: pasa porque P003 esta en la posicion 1.")
producto precio
P001 Teclado 4.53
P003 Monitor 872.23
P005 Webcam 713.42
P006 Auriculares 542.80
iloc[1] -> la segunda fila, contando desde 0:
producto Monitor
precio 872.23
loc['P003'] -> la fila que se LLAMA P003:
producto Monitor
precio 872.23
Aca dan lo mismo. Eso es una casualidad de este ejemplo,
no una regla: pasa porque P003 esta en la posicion 1.
El corte, que es la trampa que más se cobra
import pandas as pd
catalogo = pd.DataFrame({
"producto": ["Teclado", "Monitor", "Webcam", "Auriculares", "Mouse"],
"precio": [4.53, 872.23, 713.42, 542.80, 781.78],
})
print(catalogo)
print()
print("iloc[1:3] ->", len(catalogo.iloc[1:3]), "filas")
print(catalogo.iloc[1:3])
print()
print("loc[1:3] ->", len(catalogo.loc[1:3]), "filas")
print(catalogo.loc[1:3])
print()
print("El mismo 1:3 escrito igual, y uno trae una fila mas.")
print("iloc corta como Python: para ANTES de la posicion 3.")
print("loc corta por etiqueta, y la etiqueta 3 esta INCLUIDA.")
print()
print("Es la unica excepcion a la regla de que en Python el final no entra,")
print("y por eso se olvida.")
producto precio
0 Teclado 4.53
1 Monitor 872.23
2 Webcam 713.42
3 Auriculares 542.80
4 Mouse 781.78
iloc[1:3] -> 2 filas
producto precio
1 Monitor 872.23
2 Webcam 713.42
loc[1:3] -> 3 filas
producto precio
1 Monitor 872.23
2 Webcam 713.42
3 Auriculares 542.80
El mismo 1:3 escrito igual, y uno trae una fila mas.
iloc corta como Python: para ANTES de la posicion 3.
loc corta por etiqueta, y la etiqueta 3 esta INCLUIDA.
Es la unica excepcion a la regla de que en Python el final no entra,
y por eso se olvida.
loc incluye el final del corte
Es la única excepción a la regla de Python de que el final no entra, y por eso se
olvida siempre. iloc[1:3] da 2 filas; loc[1:3] da 3. El
mismo 1:3 escrito igual.
Tiene sentido si lo pensás así: loc trabaja con etiquetas, y
«desde la etiqueta 1 hasta la etiqueta 3» naturalmente incluye la 3. Con
posiciones, en cambio, la convención de Python es que el final se excluye.
Lo mismo, paso a paso
- Punto de partidaCinco filas que sobrevivieron a un filtro. Mira la primera columna: el indice quedo [2, 7, 8, 9, 12], con agujeros. Ya no coincide con la posicion, y de eso se trata la leccion.
caros = inv[inv["precio_unitario"] > 800] - ilociloc[0] pide POSICION: la primera fila de la tabla, sea cual sea su etiqueta.
caros.iloc[0] - locloc[2] pide ETIQUETA: la fila que se llama 2. Da la misma que antes, y esa coincidencia es justamente la que hace que el error tarde en aparecer.
caros.loc[2] - iloc contra locAca se separan. iloc[2] es la tercera fila (en naranja). loc[2] sigue siendo la que se llama 2 (en verde). El mismo numero, dos filas distintas.
caros.iloc[2] # vs caros.loc[2] - Corte con ilociloc[1:3] corta como Python: arranca en la posicion 1 y para ANTES de la 3. Dos filas.
caros.iloc[1:3] - Corte con locloc[7:9] corta por etiqueta, y el final SI entra. Tres filas. Es la unica excepcion a la regla de que en Python el final no se incluye, y por eso se olvida.
caros.loc[7:9] - loc en dos dimensionesY la forma que conviene aprender: loc[que filas, que columnas]. Una mascara para las filas y un nombre para la columna, en una sola expresion.
caros.loc[caros["precio_unitario"] > 850, "producto"]
Sobre datos de verdad
import pandas as pd
inventario = pd.read_csv("inventario_productos.csv")
caros = inventario[inventario["precio_unitario"] > 800]
print(caros[["codigo", "producto", "precio_unitario"]].to_string())
print()
print("indice:", list(caros.index))
print()
print("iloc[2] -> la TERCERA fila de esta tabla:")
print(caros.iloc[2][["codigo", "producto"]].to_string())
print()
print("loc[2] -> la fila que se llama 2, o sea la primera:")
print(caros.loc[2][["codigo", "producto"]].to_string())
print()
print("Son filas distintas. Y con la tabla sin filtrar habrian sido la misma.")
print("Por eso el error aparece cuando el programa ya andaba.")
codigo producto precio_unitario
2 P003 Monitor 24 pulgadas 872.23
7 P008 Disco SSD 1TB 810.40
8 P009 Disco HDD 2TB 848.60
9 P010 Memoria RAM 16GB 876.30
12 P013 Gabinete ATX 835.84
17 P018 Alfombrilla 817.57
27 P028 Notebook 15 pulgadas 907.42
32 P033 Zapatilla eléctrica 918.40
indice: [2, 7, 8, 9, 12, 17, 27, 32]
iloc[2] -> la TERCERA fila de esta tabla:
codigo P009
producto Disco HDD 2TB
loc[2] -> la fila que se llama 2, o sea la primera:
codigo P003
producto Monitor 24 pulgadas
Son filas distintas. Y con la tabla sin filtrar habrian sido la misma.
Por eso el error aparece cuando el programa ya andaba.
Ahí está el problema completo en dos líneas: loc[2] devuelve
P003 y iloc[2] devuelve P009. Sobre la tabla
sin filtrar habrían devuelto la misma fila. Es un error que se planta el día que
alguien agrega un filtro tres funciones más arriba.
La forma que conviene aprender
import pandas as pd
inventario = pd.read_csv("inventario_productos.csv")
# La forma que conviene aprender: loc[que filas, que columnas].
caros = inventario.loc[
inventario["precio_unitario"] > 850,
["codigo", "producto", "precio_unitario"],
]
print(caros.to_string(index=False))
print()
# Una sola columna con loc devuelve Series.
print("solo la columna producto de esas filas:")
print(inventario.loc[inventario["precio_unitario"] > 850, "producto"].to_string())
print()
# Y todas las columnas, con dos puntos.
print("todas las columnas de una fila, por etiqueta:")
print(inventario.loc[9, :].to_string())
codigo producto precio_unitario
P003 Monitor 24 pulgadas 872.23
P010 Memoria RAM 16GB 876.30
P028 Notebook 15 pulgadas 907.42
P033 Zapatilla eléctrica 918.40
solo la columna producto de esas filas:
2 Monitor 24 pulgadas
9 Memoria RAM 16GB
27 Notebook 15 pulgadas
32 Zapatilla eléctrica
todas las columnas de una fila, por etiqueta:
codigo P010
producto Memoria RAM 16GB
categoria Componentes
cantidad 26.0
precio_unitario 876.3
df.loc[filas, columnas] es la que más rinde: dice en una sola
expresión qué filas y qué columnas, y es la que hay que usar cuando además vas a
escribir (lección 4.7). Escribir con corchetes encadenados no
funciona en pandas 3, y loc es la forma correcta.
Una sola celda
import pandas as pd
inventario = pd.read_csv("inventario_productos.csv")
print("at (etiqueta, columna):", inventario.at[9, "producto"])
print("iat (posicion, posicion):", inventario.iat[9, 1])
print("los dos leen UNA celda, y son mas rapidos que loc/iloc para eso.")
print()
try:
inventario[0]
except KeyError as error:
print("inventario[0] -> KeyError:", error)
print("Con un DataFrame, el corchete simple busca una COLUMNA llamada 0.")
print("Para la primera fila hay que pedir posicion: inventario.iloc[0].")
print()
try:
inventario.loc[0, "precio"]
except KeyError as error:
print("inventario.loc[0, 'precio'] -> KeyError:", error)
print("La columna se llama precio_unitario. loc pide etiquetas exactas,")
print("de fila y de columna.")
at (etiqueta, columna): Memoria RAM 16GB
iat (posicion, posicion): Memoria RAM 16GB
los dos leen UNA celda, y son mas rapidos que loc/iloc para eso.
inventario[0] -> KeyError: 0
Con un DataFrame, el corchete simple busca una COLUMNA llamada 0.
Para la primera fila hay que pedir posicion: inventario.iloc[0].
inventario.loc[0, 'precio'] -> KeyError: 'precio'
La columna se llama precio_unitario. loc pide etiquetas exactas,
de fila y de columna.
loc y at trabajan con etiquetas;
iloc y iat con posiciones. Los de nombre
corto (at, iat) leen o escriben una sola
celda y son más rápidos para eso; los otros dos hacen todo lo demás.
Dónde se rompe
Causa: se escribió df[0] esperando la primera fila. Con un
DataFrame, el corchete simple busca una columna llamada 0.
Arreglo: df.iloc[0] para la primera fila por posición, o
df.loc[0] si de verdad querés la fila etiquetada 0.
Causa: se usó loc[3] pensando en la cuarta fila, pero el
índice está filtrado y la etiqueta 3 ya no existe.
Arreglo: si querés posición, pedí posición: iloc[3]. Y si
querés etiquetas limpias después de filtrar,
reset_index(drop=True).
Causa: iloc con una posición que no existe: el filtro dejó
menos filas de las que esperabas.
Arreglo: mirá len(df) antes. Este error, al menos, es
ruidoso: el silencioso es el otro.
Causa: es loc, que incluye el final del corte.
Arreglo: ninguno, es lo que hace. O usás iloc, o contás una
menos. Lo importante es saber cuál estás usando.
Causa: el índice tiene la misma etiqueta repetida. loc
devuelve todas las que coincidan: una Series si hay una, un DataFrame si
hay varias.
Arreglo: assert df.index.is_unique después de
set_index. Es la lección 4.6.
Acá hay que elegir
loc, casi siempre. Si el índice significa algo (un legajo, un SKU), pedir por etiqueta dice lo que querés y sobrevive a que alguien filtre u ordene la tabla más arriba.iloc, cuando la posición es la pregunta. «Las primeras 10 filas», «la última». Ahí la etiqueta no importa y la posición sí.- Ninguno de los dos para filtrar. Para eso está la máscara booleana, que es la lección 4.5.
Y una regla que ahorra discusiones: si escribiste un número entero adentro de
loc, parate un segundo y preguntate si es una etiqueta o una posición.
Nueve de cada diez veces querías iloc.
Recetario
| Expresión | Qué devuelve | Cuándo |
|---|---|---|
df.loc[e] | La fila con esa etiqueta, como Series | El índice significa algo |
df.iloc[p] | La fila en esa posición | «La primera», «la última» |
df.loc[e1:e2] | Corte por etiqueta, e2 incluida | Rango de etiquetas ordenadas |
df.iloc[p1:p2] | Corte por posición, p2 excluida | Como un slice de Python |
df.loc[filas, cols] | Selección 2D por etiqueta | La forma que conviene aprender |
df.loc[mascara, "c"] | Una columna de las filas que cumplen | Filtrar y proyectar de una |
df.loc[mascara, "c"] = v | Escribe en esas celdas | La única forma correcta de asignar filtrando |
df.iloc[-1] | La última fila | Cuando el índice no ayuda |
df.at[e, "c"] | Una celda, por etiqueta | Leer o escribir un valor suelto |
df.iat[p, k] | Una celda, por posición | Lo mismo, contando |
df.reset_index(drop=True) | Renumera 0..n−1 | Después de filtrar, si la etiqueta vieja ya no sirve |