Saltar al contenido

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

EjemploLa misma fila, pedida de dos maneras
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

EjemploEl mismo corte, dos resultados: iloc excluye el final y loc lo incluye
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

Paso a pasoloc contra iloc sobre un indice que ya no empieza en cero
  1. 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]
  2. ilociloc[0] pide POSICION: la primera fila de la tabla, sea cual sea su etiqueta.
    caros.iloc[0]
  3. 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]
  4. 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]
  5. 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]
  6. 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]
  7. 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

EjemploSobre datos de verdad: despues de filtrar dejan de coincidir
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

Ejemploloc en dos dimensiones: filas y columnas de una
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

EjemploUna sola celda, y los errores que llegan cuando falta el corchete
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.
Los cuatro, en una frase

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

KeyError: 0

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.

KeyError: 3 (y la fila 3 se ve en la tabla)

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).

IndexError: single positional indexer is out-of-bounds

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.

(sin error) el corte devuelve una fila de más

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.

(sin error) loc devuelve varias filas cuando esperabas una

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

Cuál usar
  • 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

loc, iloc, at, iat
ExpresiónQué devuelveCuándo
df.loc[e]La fila con esa etiqueta, como SeriesEl índice significa algo
df.iloc[p]La fila en esa posición«La primera», «la última»
df.loc[e1:e2]Corte por etiqueta, e2 incluidaRango de etiquetas ordenadas
df.iloc[p1:p2]Corte por posición, p2 excluidaComo un slice de Python
df.loc[filas, cols]Selección 2D por etiquetaLa forma que conviene aprender
df.loc[mascara, "c"]Una columna de las filas que cumplenFiltrar y proyectar de una
df.loc[mascara, "c"] = vEscribe en esas celdasLa única forma correcta de asignar filtrando
df.iloc[-1]La última filaCuando el índice no ayuda
df.at[e, "c"]Una celda, por etiquetaLeer o escribir un valor suelto
df.iat[p, k]Una celda, por posiciónLo mismo, contando
df.reset_index(drop=True)Renumera 0..n−1Después de filtrar, si la etiqueta vieja ya no sirve

Ponete a prueba

1. ¿Que diferencia hay entre loc e iloc?Nivel 1 · Recordar
2. ¿Que hace df[0] sobre un DataFrame?Nivel 1 · Recordar
3. df tiene 5 filas con el indice por defecto. ¿Cuantas filas devuelve df.loc[1:3]?Nivel 2 · Predecir la salida
4. Despues de filtrar, el indice de caros es [2, 7, 8, 9, 12]. ¿Que devuelve caros.loc[2]?Nivel 2 · Predecir la salida
5. ¿Cual es la diferencia entre at y loc?Nivel 2 · Predecir la salida
6. Queres las 10 primeras filas de una tabla que acabas de ordenar por precio. ¿Cual usas?Nivel 3 · Elegir entre dos casi correctas
7. Las cuatro corren sin error sobre un DataFrame de 35 filas con indice por defecto. ¿Cual escribe el valor 0 en la columna cantidad de las filas con cantidad negativa?Nivel 3 · Elegir entre dos casi correctas
8. Este codigo andaba. Alguien agrego la linea del filtro y ahora imprime otro producto, sin dar ningun error. ¿Por que?
inv = pd.read_csv("inventario_productos.csv")
inv = inv[inv["precio_unitario"] > 800]      # linea nueva
print(inv.loc[2, "producto"])
Nivel 4 · Encontrar el defecto

Prácticas de esta lección