Seleccionar columnas y filas: por nombre o por posición
Apuntar a una parte de una tabla: columnas con corchetes, filas por etiqueta con loc y por posición con iloc, celdas individuales con at; y saber cuál de las dos formas estás usando, porque tras un recorte o una ordenación dejan de coincidir.
¿Cómo quieres aprender?
El problema que vamos a resolver
Has leído el archivo de pedidos de la tienda y lo has comprobado, tal como se explicó en el capítulo tres. Ahora la gente empieza a hacerte preguntas sobre él.
El equipo de almacén dice: «Envíanos solo el producto y la cantidad de cada pedido, nada más». El contable dice: «¿Qué fue exactamente el pedido 1006 y cuánto costó?». Y tu responsable dice: «Muéstrame los tres últimos pedidos que llegaron».
Ninguna de esas peticiones es un cálculo sobre toda la tabla. Cada una de ellas consiste en apuntar: a algunas columnas, a una fila, a las últimas filas, a una sola celda. Imprimirlo todo y seguir la lectura con el dedo funciona para ocho filas. No funciona para ocho mil, y no funciona dentro de un programa, que no tiene dedos.
Pandas dispone de varias formas de apuntar, y la razón de que existan varias es el verdadero tema de este capítulo. A una fila se le puede apuntar de dos formas distintas: por su nombre (su etiqueta en el índice) o por su posición (primera, segunda, última). Cuando una tabla acaba de ser leída, casualmente ambas cosas coinciden con los mismos números, por lo que los principiantes nunca notan que hay dos. En el momento en que tomas las últimas tres filas, o las ordenas, o las filtras, dejan de ser lo mismo; y el código que las confunde empieza a devolver la fila equivocada sin mostrar ningún error.
Por tanto, este capítulo trata sobre cómo seleccionar, pero sobre todo trata de saber siempre cuál de las dos formas estás usando.
Al terminar este capítulo podrás
- Seleccionar una columna y obtener una
Series, o varias y obtener unDataFrame, y saber cuál de los dos tienes en tus manos - Seleccionar filas por etiqueta con
.locy por posición con.iloc, y explicar por qué sus cortes terminan de forma diferente - Seleccionar filas y columnas a la vez con
.loc[rows, columns]y predecir las dimensiones del resultado, hasta llegar a un solo valor con.loco.at - Asignar a las filas etiquetas significativas con
set_index(), y comprobar cómo eso cambia lo que significa.loc - Explicar por qué modificar una selección no modifica la tabla original en pandas 3, y cambiar el original correctamente; interpretar y solucionar los mensajes de error
KeyErroreIndexErrorque produce la selección
Requisitos previos: Leer un CSV y comprobar qué llegó.
Crea el archivo primero
En la carpeta de tu proyecto, crea un archivo llamado orders.csv con exactamente estas nueve líneas. Este archivo se utilizará durante el resto del curso, así que guárdalo en un lugar donde lo puedas encontrar de nuevo:
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0Cada línea es un pedido: un número de pedido, el día, la sucursal de la tienda que lo vendió, qué se vendió, la categoría del producto, cuántas unidades y el precio de un artículo.
Antes de escribir el código
Seleccionar parece algo tan simple que la gente empieza a escribir corchetes de inmediato. Dedica primero un minuto a estos pasos, porque todos los errores de selección de este capítulo provienen de saltarse alguno de ellos.
1. Formula la pregunta en una sola frase y subraya los sustantivos. Los sustantivos te indican a qué estás apuntando:
- «el producto y la cantidad de cada pedido» — dos columnas, todas las filas
- «el pedido 1006» — una fila, nombrada por su número de pedido
- «los últimos tres pedidos» — filas por posición, contadas desde el final
- «el precio del pedido 1006» — una celda: una fila y una columna juntas
2. Mira la tabla antes de apuntar hacia ella. Solo puedes apuntar a nombres que existan. Tres aspectos importan para la selección: el tamaño, los nombres exactos de las columnas y el índice (las etiquetas a la izquierda).
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(list(orders.columns))
print(orders.index)(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)Los nombres de las columnas se devuelven como una lista, entre comillas; de este modo puedes ver que 'price' está en minúsculas y no tiene espacios sueltos. El índice es un RangeIndex de 0 a 7: las etiquetas son simplemente un recuento. Recuerda esto, porque es la coincidencia sobre la que gira todo el capítulo. En este momento, la fila con etiqueta 2 es también la fila en posición 2. Eso no siempre será así.
3. Esboza la respuesta antes de generarla. ¿Qué debería salir: un solo número, una columna o una tabla más pequeña? ¿Cuántas filas y columnas? Para la petición del equipo de almacén, la respuesta es una tabla de 8 filas y 2 columnas. Para «el pedido 1006» es una fila. Para «el precio del pedido 1006» es un solo número, 850.0. Si conoces las dimensiones que esperas, una selección incorrecta resulta evidente en cuanto ves el resultado.
4. Elige la herramienta según lo que conoces. Esta lista resume el capítulo en una sola imagen:
- Conoces nombres de columna → corchetes:
orders["quantity"],orders[["product", "quantity"]] - Conoces la etiqueta de una fila, su nombre en el índice →
.loc:orders.loc[2] - Conoces la posición de una fila (primera, última, quinta) →
.iloc:orders.iloc[-3:] - Necesitas filas y columnas juntas →
.loc[rows, cols]o.iloc[rows, cols]:orders.loc[2:4, ["product", "price"]] - Necesitas exactamente una celda →
.at[row, col]o.loc[row, col]:orders.at[5, "price"]
5. Decide cómo vas a comprobarlo. Después de cualquier selección, imprime .shape o type() del resultado y compáralo con tu boceto. Es una sola línea y detecta la mayoría de los errores que veremos a continuación antes de que se conviertan en números incorrectos.
El resto del capítulo recorre estas herramientas una a una, cada una con su propio ejemplo resuelto.
Una columna: un nombre entre corchetes
Coloca el nombre de una columna entre corchetes y obtendrás esa columna:
quantity = orders["quantity"]
print(type(quantity))
print(quantity)<class 'pandas.Series'>
0 12
1 5
2 2
3 7
4 30
5 1
6 20
7 4
Name: quantity, dtype: int64Lo que se devuelve es una Series, exactamente el objeto del capítulo dos: valores, un índice a la izquierda, un Name (la columna de la que provino) y un solo dtype. Fíjate en que el índice vino con ella. Los números del 0 al 7 no son un adorno: siguen indicando a qué fila pertenece cada número.
Al ser una Series, todos los métodos de Series funcionan en ella directamente:
print(orders["quantity"].sum())81Esa es la razón habitual para seleccionar una sola columna: hacerle una pregunta.
Varias columnas: una lista dentro de los corchetes
Para el equipo de almacén, necesitas dos columnas. Coloca una lista de nombres dentro de los corchetes:
stock = orders[["product", "quantity"]]
print(type(stock))
print(stock)<class 'pandas.DataFrame'>
product quantity
0 pen 12
1 notebook 5
2 bag 2
3 bottle 7
4 eraser 30
5 bag 1
6 pen 20
7 bottle 4Los corchetes dobles no son una sintaxis especial, y vale la pena verlo con claridad. El par exterior es la selección; el par interior es una lista ordinaria de Python: ["product", "quantity"]. Podrías construir la lista primero y pasarla después —wanted = ["product", "quantity"] y luego orders[wanted]— y significaría exactamente lo mismo. Las columnas se devuelven en el orden de la lista, por lo que así es también como se reordenan columnas.
El resultado es un DataFrame, porque una tabla con dos columnas es una tabla. Y aquí hay una distinción que desconcierta a la gente durante meses: una lista con un solo nombre dentro sigue devolviendo un DataFrame.
print(orders["product"].shape)
print(orders[["product"]].shape)(8,)
(8, 1)(8,) es una Series: una dimensión, ocho valores. (8, 1) es un DataFrame con ocho filas y una columna. Se imprimen de forma distinta, tienen métodos distintos y, cuando los guardas en un archivo, el DataFrame conserva el encabezado de su columna. La regla es simple: un nombre da una Series; una lista da un DataFrame, sin importar cuántos nombres contenga la lista.
¿Por qué no orders.quantity?
Verás el acceso a columnas escrito con un punto en muchos tutoriales:
print(orders.branch.head(2))0 north
1 south
Name: branch, dtype: strAquí funciona y es más corto. Pero solo funciona cuando el nombre de la columna resulta ser un identificador válido de Python que no esté ya utilizado para otra cosa, y un DataFrame tiene cientos de métodos y atributos. He aquí una pequeña tabla cuyas columnas se llaman count y size, dos nombres perfectamente razonables para una tienda:
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})
print(d.size)
print(d["size"])4
0 3
1 4
Name: size, dtype: int64d.size no te devolvió la columna en absoluto. Te devolvió el propio atributo size del DataFrame —el número de celdas, 2 × 2 = 4— en silencio. Sin errores, simplemente una respuesta incorrecta. Un nombre con un espacio, como unit price, ni siquiera se puede escribir con un punto. Los corchetes no presentan ninguno de estos problemas, por lo que la regla para el código definitivo es: usa df["col"]. El punto está bien para un vistazo rápido en la terminal, nada más.
Filas por etiqueta: .loc
Las filas no se seleccionan con corchetes simples. (Los corchetes simples con un nombre significan «columna», y pandas no adivinará a cuál te referías). Las filas tienen sus propias dos herramientas, y la primera es .loc: localización basada en etiquetas (label-based location). Le indicas la etiqueta del índice:
print(orders.loc[2])order_id 1003
date 2024-03-02
branch north
product bag
category accessories
quantity 2
price 850.0
Name: 2, dtype: objectUna fila se devuelve como una Series colocada de lado: los nombres de las columnas se han convertido en su índice y su Name es la etiqueta de la fila, 2.
Fíjate en dtype: object. Una columna tiene un solo tipo, pero una fila atraviesa distintas columnas —un número, algo de texto, un decimal—, por lo que el único tipo capaz de contenerlos a todos es el genérico object. Esa es una de las razones por las que pandas piensa en columnas: una fila mezcla tipos, una columna no.
Ahora la parte importante. Pide a .loc un rango de etiquetas:
print(orders.loc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0
4 1005 2024-03-03 north eraser stationery 30 8.0Tres filas: 2, 3 y 4. Un corte (slice) con .loc incluye su final. Eso sorprende a cualquiera que conozca Python, donde range(2, 4) y list[2:4] se detienen antes del 4. No es una incoherencia; hay un motivo.
Las etiquetas no siempre son números. Si las etiquetas fueran nombres de productos, escribirías algo como loc["bag":"eraser"]. Para excluir el final, tendrías que indicar la etiqueta posterior a "eraser", y con nombres no hay un «siguiente» que puedas conocer sin mirar. Así que con etiquetas indicas la primera que quieres y la última que quieres, y obtienes ambas. Con las posiciones es distinto: la posición siguiente después de 4 es siempre 5, por lo que la regla de «detenerse antes» de Python no cuesta nada allí.
Filas por posición: .iloc
La segunda herramienta es .iloc: localización por enteros (integer location). Ignora por completo las etiquetas y cuenta posiciones, desde 0, exactamente igual que una lista de Python:
print(orders.iloc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0Dos filas, las posiciones 2 y 3; la posición 4 queda excluida, como en todo Python. Pon las dos salidas una junto a otra: loc[2:4] dio tres filas e iloc[2:4] dio dos, sobre la misma tabla, con los mismos números entre los corchetes.
Dado que .iloc es posicional, los números negativos cuentan desde el final, lo que responde directamente a la pregunta de tu responsable:
print(orders.iloc[-3:])order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0«Los tres últimos» es una pregunta sobre la posición, de modo que .iloc es la herramienta que encaja con ella. .loc no tiene ni idea de qué significa «último»; solo entiende de nombres.
Las etiquetas y las posiciones no son lo mismo
Hasta ahora, la etiqueta 2 y la posición 2 correspondían a la misma fila, por lo que no podías distinguir ambas herramientas por sus resultados. Toma los tres últimos pedidos como una tabla propia y verás cómo se separan:
last3 = orders.tail(3)
print(last3)order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0Las filas conservaron sus etiquetas: 5, 6, 7. Pandas no las renumera, y lo hace a propósito: la etiqueta es la forma en que una fila sigue siendo la misma fila después de recortar la tabla. Ahora pide «la primera fila» de ambas formas:
print(last3.iloc[0])order_id 1006
date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 5, dtype: objectlast3.loc[0]KeyError: 0.iloc[0] significa «la fila en primer lugar», que es el pedido con la etiqueta 5. .loc[0] significa «la fila cuya etiqueta es 0», y no existe tal fila en esta tabla, por lo que pandas lanza un KeyError. Este es el error que te atrapará más adelante en el curso: después de filtrar filas u ordenarlas —ambas cosas llegarán más adelante—, las posiciones cambian y las etiquetas no. La regla que debes recordar:
- ¿Preguntas por la posición (primera, última, las tres primeras tras ordenar)? Usa
.iloc. - ¿Preguntas por la identidad (este pedido, este producto)? Usa
.loc.
Y el peor caso no es el KeyError. El peor caso es cuando la etiqueta 0 sí existe en otra parte de la tabla: entonces .loc[0] devuelve silenciosamente una fila distinta de la que pretendías.
.locpide un nombre,.ilocpide una posición. En una tabla recién leída ambos coinciden por casualidad, por lo que el código que los confunde supera todas las pruebas del primer día... y devuelve la fila equivocada, sin ningún error, la primera vez que la tabla se recorta, filtra u ordena.
Asignar a las filas etiquetas significativas: set_index
El contable preguntó por el pedido 1006. Con el índice por defecto tendrías que saber que el pedido 1006 se encuentra bajo la etiqueta 5: un dato relativo al archivo, no al pedido. El nombre real de una fila es su número de pedido, así que convirtamos el número de pedido en el índice:
by_id = orders.set_index("order_id")
print(by_id.head(3))
print(by_id.shape)date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0
1003 2024-03-02 north bag accessories 2 850.0
(8, 6)Tres cosas han cambiado. Los números de pedido se han desplazado a la izquierda, al índice. El índice tiene ahora un nombre, order_id, impreso en su propia línea sobre él. Y las dimensiones son (8, 6) en lugar de (8, 7): order_id ya no es una columna, es el índice. Observa también que set_index devolvió una nueva tabla, a la que llamamos by_id; orders permanece intacta.
Ahora .loc habla el idioma de la pregunta:
print(by_id.loc[1006])date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 1006, dtype: objectY un corte por etiquetas es ahora un rango de números de pedido, con ambos extremos incluidos:
print(by_id.loc[1003:1005, ["product", "quantity"]])product quantity
order_id
1003 bag 2
1004 bottle 7
1005 eraser 30.iloc no ha cambiado en absoluto: las posiciones son posiciones, sean cuales sean las etiquetas:
print(by_id.iloc[0:2])date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0Aquí las dos herramientas se han separado con claridad: by_id.loc[0] daría un KeyError ahora, porque 0 no es un número de pedido, mientras que by_id.iloc[0] es el pedido 1001. Si alguna vez necesitas que el número de pedido vuelva a ser una columna, by_id.reset_index() lo devuelve a su sitio.
Elige una columna cuyos valores sean únicos. Un índice tiene como propósito nombrar filas, y un nombre compartido por dos filas no apunta a una sola. Observa qué ocurre con product, que se repite:
by_product = orders.set_index("product")
print(by_product.index.is_unique)
print(by_product.loc["bag"])False
order_id date branch category quantity price
product
bag 1003 2024-03-02 north accessories 2 850.0
bag 1006 2024-03-03 south accessories 1 850.0Ningún error, pero loc["bag"] devolvió un DataFrame de dos filas en lugar de una sola fila como Series. El código que esperaba una sola fila (por ejemplo, para leer su price como un número suelto) ahora recibe dos. Por eso merece la pena dedicar una línea a comprobar index.is_unique cada vez que establezcas un índice mediante el cual pretendas buscar filas.
Filas y columnas juntas
Tanto .loc como .iloc aceptan dos elementos separados por una coma: las filas primero, las columnas segundo. Cada parte puede ser una sola etiqueta, una lista o un corte (slice):
print(orders.loc[2:4, ["product", "price"]])product price
2 bag 850.0
3 bottle 120.0
4 eraser 8.0print(orders.loc[[0, 3], ["branch", "product"]])branch product
0 north pen
3 east bottleUn corte también funciona para las columnas, con la regla inclusiva de .loc: «todas las columnas desde branch hasta category». Dos puntos solos : significan «todas las filas»:
print(orders.loc[:, "branch":"category"])branch product category
0 north pen stationery
1 south notebook stationery
2 north bag accessories
3 east bottle accessories
4 north eraser stationery
5 south bag accessories
6 east pen stationery
7 north bottle accessories.iloc hace lo mismo por posición. Las columnas también se cuentan desde 0, de modo que branch es la columna 2 y product es la columna 3:
print(orders.iloc[[0, 3], [2, 3]])branch product
0 north pen
3 east bottleEl mismo resultado que la versión con .loc de dos bloques más arriba, pero fíjate en cuánto más difícil resulta de leer. [2, 3] no dice nada; ["branch", "product"] lo dice todo y sigue funcionando si alguien añade una columna al principio del archivo. Prefiere nombres cuando conozcas los nombres.
¿Qué dimensiones tendrá el resultado?
Que obtengas un solo valor, una Series o un DataFrame depende exclusivamente de lo que pongas a cada lado de la coma. Una sola etiqueta elimina esa dimensión; una lista o corte la conserva:
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)float64
Series
Series
DataFrame- una etiqueta, una etiqueta → un solo valor
- una etiqueta, una lista o corte → una
Series(una fila) - una lista o corte, una etiqueta → una
Series(una columna) - una lista o corte, una lista o corte → un
DataFrame
Este es el paso 3 de la planificación —esboza la respuesta— convertido en regla. Si quieres recuperar una tabla, incluso de una sola fila, pon una lista a ambos lados: by_id.loc[[1006], ["price"]].
Un solo valor: .loc y .at
La pregunta del contable —¿cuánto costó el pedido 1006?— es una sola celda: una etiqueta de fila y un nombre de columna.
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])850.0
850.0Ambos dan el mismo número. .at solo acepta una única fila y una única columna, nada más, lo que lo hace ligeramente más rápido y deja clara tu intención: esto es una sola celda. (.iat es su gemelo posicional, by_id.iat[5, 5]). Usa el que se lea mejor; .loc funciona perfectamente.
Un único valor es un número común y corriente, así que puedes operar con él de inmediato. El importe del pedido 1004 (cantidad multiplicada por precio):
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])840.0Una selección es su propio objeto
Hay una cosa más que debes comprender antes de usar selecciones en programas reales: qué sucede cuando modificas una de ellas.
import pandas as pd
orders = pd.read_csv("orders.csv")
prices = orders["price"]
prices[0] = 999.0
print(prices.head(2))
print(orders.loc[0, "price"])0 999.0
1 60.0
Name: price, dtype: float64
15.0prices cambió. orders no. En pandas 3, toda selección actúa como una copia independiente: puedes modificarla libremente y la tabla original jamás se verá alterada. (Internamente, pandas evita copiar realmente los datos hasta que escribes en ellos —ese es el nombre del mecanismo, Copy-on-Write—, pero la regla que necesitas es el comportamiento, no el mecanismo).
Lo mismo ocurre con una selección de varias columnas:
stock = orders[["product", "price"]]
stock["price"] = 0
print(orders["price"].head(2))0 15.0
1 60.0
Name: price, dtype: float64Este es el comportamiento seguro: una función que toma una selección y la edita no puede dañar tu tabla principal a tus espaldas. Pero tiene una consecuencia que debes conocer. Si pretendes modificar el original, debes hacerlo sobre el original, en un solo paso, con .loc:
orders.loc[0, "price"] = 16.0
print(orders.loc[0, "price"])16.0La tentadora versión en dos pasos —seleccionar una columna y luego asignar una celda en ella— no funciona, y pandas 3 te lo advierte:
orders["price"][0] = 1.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(orders.loc[0, "price"])16.0Sigue siendo 16.0: la asignación se aplicó sobre una copia temporal y se descartó. orders["price"] produjo una selección y [0] = 1.0 modificó eso, no orders. Eso se denomina asignación encadenada (chained assignment): dos pares de corchetes consecutivos a la izquierda de =. La solución siempre es la que indica la advertencia: un solo .loc tanto con la fila como con la columna, orders.loc[0, "price"] = 1.0.
Si lees tutoriales antiguos, verás este terreno descrito con un SettingWithCopyWarning y consejos de que a veces el original cambiaba y a veces no. Eso ocurría en pandas 1 y 2. En pandas 3 la regla es simple y siempre la misma: una selección nunca modifica el original; .loc sobre el original sí.
Un ejemplo completo
Las tres peticiones del inicio del capítulo, resueltas en un solo programa. select_orders.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check what arrived before pointing into it
print("Shape :", orders.shape)
print("Columns:", list(orders.columns))
# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()
# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()
# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()
# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])Shape : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True
Stock view: (8, 2)
product quantity
0 pen 12
1 notebook 5
2 bag 2
product bag
quantity 1
price 850.0
Name: 1006, dtype: object
Order 1006 total: 850.0
branch product quantity
order_id
1006 south bag 1
1007 east pen 20
1008 north bottle 4Por qué está escrito de esta manera:
- Mira antes de apuntar. Se imprimen primero
shapeylist(columns), de modo que un nombre de columna mal escrito o con espacios quedaría a la vista antes de que cualquier selección falle. - Comprueba que el índice sea único antes de usarlo para buscar pedidos, garantizando así que
by_id.loc[1006]sea una sola fila yby_id.at[...]un solo número. - Cada petición utiliza la herramienta que corresponde a su pregunta. Columnas por nombre con corchetes; un pedido por su identidad con
.locy.at; «los tres últimos» por posición con.iloc. - Imprime las dimensiones de la vista de almacén:
(8, 2)coincide exactamente con el boceto: cada pedido, dos columnas. - La última selección es la que conviene mejorar.
[1, 2, 4]casualmente representabranch,product,quantityenby_id; ten en cuenta que las posiciones se desplazaron en una unidad porqueorder_idpasó a ser el índice. Las posiciones son frágiles. En tu propio código,by_id.iloc[-3:][["branch", "product", "quantity"]]oby_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]]expresarían lo mismo con nombres.
Cuando algo falla
KeyError: 'Price' No hay ninguna columna con exactamente ese nombre. Los nombres de columna distinguen entre mayúsculas y minúsculas: price y Price son nombres distintos. Imprime list(df.columns) y copia el nombre desde allí. Con una lista de nombres, orders[["product", "Price"]], el mensaje pasa a ser KeyError: "['Price'] not in index" e indica con precisión cuál es el que falta; con un punto, orders.Price, es AttributeError: 'DataFrame' object has no attribute 'Price'. Misma causa, misma solución.
KeyError: 'price' — aunque price está claramente en el archivo El encabezado probablemente tiene espacios después de las comas, order_id, product, price, y esos espacios pasaron a formar parte de los nombres:
from io import StringIO
import pandas as pd
raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))
print(list(padded.columns))
padded["price"]['order_id', ' product', ' price']
KeyError: 'price'La lista muestra el espacio inicial en ' price', que es exactamente la razón por la que en el capítulo tres se recomendó imprimir las columnas como lista. Soluciónalo al leer con pd.read_csv("file.csv", skipinitialspace=True) o después con df.columns = df.columns.str.strip().
KeyError: ('product', 'price') Escribiste orders["product", "price"]: un solo par de corchetes. Sin la lista interior, Python pasa los dos nombres como una sola tupla, y pandas busca una columna llamada ('product', 'price'). Escribe orders[["product", "price"]].
KeyError: 0 al usar .loc Usaste una posición con .loc. O bien la tabla se recortó (tail, un filtro, una ordenación) de modo que la etiqueta 0 ya no existe, o bien estableciste un índice y las etiquetas son ahora números de pedido. Si te refieres a la «primera fila», escribe .iloc[0]. En una tabla cuyo índice es de texto, el mismo error cometido con un corte —orders.set_index("product").loc[0:2]— lanza en su lugar TypeError: cannot do slice indexing on Index with these indexers [0] of type int; la solución es la misma: .iloc[0:2].
IndexError: single positional indexer is out-of-bounds Se proporcionó a .iloc una posición que no existe: orders.iloc[8] en una tabla de ocho filas, cuyas posiciones van de 0 a 7. Comprueba df.shape; la última fila es siempre df.iloc[-1]. El mismo mensaje aparece para una posición de columna demasiado grande, orders.iloc[:, 7].
ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types Le diste a .iloc un nombre de columna: orders.iloc[0, "branch"]. .iloc solo admite posiciones. Usa .loc con una etiqueta —orders.loc[0, "branch"]— o, si realmente necesitas la posición, búscala: orders.columns.get_loc("branch") devuelve 2.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
Misma tabla, mismos números entre corchetes. ¿Qué se imprime?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
print(len(orders.loc[2:5]), len(orders.iloc[2:5]))- A4 4
- B3 4
- C3 3
- D4 3
El objetivo es imprimir el producto del primero de los tres últimos pedidos. ¿Qué ocurre realmente y por qué?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
last3 = orders.tail(3)
# Wanted: the product of the first of these three orders
print(last3.loc[0, "product"])- AImprime `bag`, el producto de la primera de las tres filas
- B`IndexError` — la tabla solo tiene tres filas
- C`KeyError: 0` — `tail(3)` conservó las etiquetas 5, 6 y 7, y `.loc` busca una etiqueta 0
- DImprime `pen`, el producto en la primera fila de la tabla original
Necesitas un DataFrame que contenga solo la columna price, no una Series. ¿Qué línea te da eso?
- Aorders.loc[:, "price"]
- Borders.price
- Corders["price"]
- Dorders[["price"]]
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Utilizando el mismo archivo orders.csv, escribe select.py. Antes de escribir código, indica en un comentario qué debería devolver cada selección (un solo valor, una Series o un DataFrame) y cuántas filas. El programa debe cumplir cuatro condiciones:
- Las columnas se seleccionan siempre por nombre: imprime el
shapededate,branchyquantityjuntas. - Los pedidos se buscan por identidad en una tabla indexada por
order_id, después de comprobar que el índice sea único: imprime la fila completa del pedido 1007, y luegoproductybranchpara los pedidos del 1002 al 1005 (los cuatro). - «Primero» y «último» se responden por posición: imprime
productypricepara los dos primeros y para los dos últimos pedidos; luego, con.at, la cantidad del pedido 1005 y el importe total del pedido 1003 (cantidad × precio). - Modifica el precio del pedido 1001 a
16.0en la tabla indexada con un solo.loc, e imprímelo para demostrar que el cambio se llevó a cabo.
Tu salida debería ser exactamente esta:
(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0A continuación predice, escribe tu predicción y solo entonces ejecuta cada una de estas tres líneas:
by_id.loc[0]orders.iloc[8]by_id["price"][1001] = 99.0— y después, imprime de nuevo el precio del pedido 1001
Solución
Planifica primero. El bloque de comentarios es el boceto del paso 3 de «Antes de escribir el código»: lo que debería devolver cada selección:
- Tres columnas →
orders[[...]]→ unDataFramede dimensiones(8, 3) - Un pedido, por identidad →
by_id.loc[1007]→ unaSeries(una fila) - Cuatro pedidos por identidad, dos columnas →
by_id.loc[1002:1005, [...]]→ unDataFramede 4 × 2, porque.locincluye 1005 - Los dos primeros y los dos últimos, por posición →
.iloc[:2]e.iloc[-2:]→ dosDataFrames de 2 × 2 - Celdas individuales →
.at→ números simples - Modificar el original → un solo
.locsobreby_id→ la propia tabla cambia
select.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# Columns by name -> a DataFrame of 8 rows, 3 columns
print(orders[["date", "branch", "quantity"]].shape)
# Rows named by their order number; check the names are unique first
by_id = orders.set_index("order_id")
print("unique:", by_id.index.is_unique)
# One order by identity -> a Series; four orders -> 4 x 2 (loc includes 1005)
print(by_id.loc[1007])
print(by_id.loc[1002:1005, ["product", "branch"]])
# First two and last two by place -> .iloc for rows, names for columns
print(by_id.iloc[:2][["product", "price"]])
print(by_id.iloc[-2:][["product", "price"]])
# Single cells -> plain numbers
print("qty of 1005:", by_id.at[1005, "quantity"])
print("total of 1003:", by_id.at[1003, "quantity"] * by_id.at[1003, "price"])
# Change the original, in one step
by_id.loc[1001, "price"] = 16.0
print("price of 1001:", by_id.at[1001, "price"])(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Cada resultado coincide con lo planificado: (8, 3) para tres columnas, cuatro pedidos para 1002:1005 porque .loc incluye su final, y dos filas para cada una de las selecciones posicionales. En la condición 3 las filas se extraen por posición y las columnas por nombre —.iloc para las filas y luego una lista de nombres—, de modo que el código expresa exactamente lo que pretende.
Ahora las tres predicciones:
by_id.loc[0]KeyError: 0Las etiquetas son ahora números de pedido; no existe ningún pedido 0. La primera fila por posición sería by_id.iloc[0].
orders.iloc[8]IndexError: single positional indexer is out-of-boundsOcho filas significa posiciones del 0 al 7. La última fila es orders.iloc[7], o mejor aún, orders.iloc[-1].
by_id["price"][1001] = 99.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(by_id.at[1001, "price"])16.0Sigue siendo 16.0: by_id["price"] generó una selección independiente y el 99.0 fue a parar a ella, no a by_id. La condición 4 funcionó y esta no, y la diferencia reside en usar un solo .loc frente a dos corchetes seguidos.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz