Columnas nuevas
En resumen: Una columna nueva en pandas se calcula a partir de las otras columnas de una vez: aritmética, np.where o map; assign devuelve una tabla nueva, y apply es la forma más lenta.
Problema
Lo que vale cada línea
order_items.csv te dice, para cada línea de un pedido, qué producto es, cuántas unidades y a qué precio. Lo que valía la línea entera no está escrito en ningún sitio:
El gerente quiere los ingresos, pero sin los pedidos cancelados. También quiere saber qué líneas son grandes, y el informe debe tener nombres de categorías, no números. Para todo esto necesitas columnas que no están en el archivo. Las calcularás a partir de las que sí están.
La idea
Una columna a partir de aritmética
Una columna es una Serie, y la aritmética con Series está vectorizada, como con los arreglos de NumPy. Unidades por precio da el valor de las 152 líneas de una vez. Guarda el resultado con un nombre nuevo y la tabla recibe una columna nueva:
total apareció a la derecha: la primera línea son 4 unidades a 7,90 €, es decir, 31,60 €. Si el nombre ya existe, la columna se reemplaza por los valores nuevos.
La suma de todas las líneas es 1963,50 €. Eso todavía no son los ingresos: entre estas líneas hay también pedidos que los clientes cancelaron.
Míralo
Ingresos sin los cancelados
Un pedido cancelado no se paga, así que no cuenta en los ingresos. El estado está en orders.csv, y las líneas en order_items.csv. Tomas los números de los pedidos cancelados con loc, y luego isin de la lección anterior encuentra sus líneas:
7 pedidos fueron cancelados. Sin sus líneas quedan 131 de 152, y los ingresos son 1697,00 €. La regla vale para todo el curso: cuando la pregunta es sobre ingresos, los pedidos cancelados no cuentan.
Pruébalo
Dinero en los estantes
Añade a products una columna stock_value: cuánto dinero hay parado en stock de cada producto (precio por stock). Luego pon en top3 los tres productos con mayor stock_value, sin la tarjeta de regalo (id 25).
Solución
import pandas as pd
products = pd.read_csv('products.csv')
products['stock_value'] = products['price'] * products['stock']
shelf = products[products['id'] != 25]
top3 = shelf.nlargest(3, 'stock_value')
top3[['name', 'price', 'stock', 'stock_value']]products['stock_value'] = products['price'] * products['stock'], luego products[products['id'] != 25].nlargest(3, 'stock_value'). Donde más dinero hay parado es en el café (223,50 €), la sartén (199,20 €) y el juego de cuchillos (195,00 €). Sin el filtro la tarjeta de regalo va primera con 19 980 €, pero 999 es un marcador, no tarjetas en un estante.
La idea
Una tabla nueva con assign
items['total'] = … cambia la tabla misma. assign hace lo contrario: devuelve una tabla nueva con una columna más, y la vieja se queda igual. Así puedes seguir directamente con un filtro:
6 líneas valen 50 € o más. La última línea muestra que items no tiene total: assign no la tocó. Esto es Copy-on-Write de la lección sobre seleccionar: un cambio da una tabla nueva, y el original no cambia.
Dentro de assign puedes pasar una función en lugar de una columna. lambda d: … recibe la tabla en ese momento, así que el segundo assign ve el total del primero:
Una secuencia de métodos así, cada uno trabajando con el resultado del anterior, se llama una cadena. Los paréntesis alrededor te permiten escribirla en varias líneas.
La idea
Una cosa o la otra: np.where
Una línea «grande» vale 20 € o más. np.where de la lección de NumPy también funciona con columnas: para cada fila toma un valor o el otro:
Hay 35 líneas grandes y 117 pequeñas. El resultado tiene un valor para cada fila, así que se convierte en una columna.
La idea
De número a nombre: map
En products la categoría es un número, y el gerente quiere un nombre. map con un diccionario reemplaza cada valor por el que le corresponde en el diccionario:
3 pasa a ser «Pan y bollería», 8 pasa a ser «Cocina». La tarjeta de regalo no tiene categoría, no hay clave para ella en el diccionario, y map deja NaN.
No hace falta teclear el diccionario a mano. categories.set_index('id')['name'] es una Serie con los números como índice y los nombres como valores, y map también funciona con una Serie así: busca el valor en su índice.
La idea
Tu propia función: apply
La tienda da descuento en una línea grande: 10 % desde 50 € y 5 % desde 20 €. La regla es una función de Python con if. apply la ejecuta para cada valor de la columna:
117 líneas no reciben descuento, 29 reciben un 5 % y 6 reciben un 10 %. Todos los descuentos juntos suman 84,14 €.
apply es cómodo cuando la regla ya es una función que trabaja con un valor. Esta regla también se podría escribir vectorizada, con dos np.where uno dentro del otro. Con 152 líneas no notarás la diferencia. Con muchas líneas, sí.
Míralo
Lo que cuesta apply
Aquí está la misma aritmética hecha de dos maneras sobre las 26 619 líneas de los pedidos del año. apply(…, axis=1) ejecuta la función para cada fila, y time.perf_counter() mide cuánto tiempo pasa:
El resultado es el mismo (True en la penúltima línea). En nuestras pruebas apply fue más de cien veces más lento que la aritmética vectorizada. Los números exactos dependen de la máquina, pero la diferencia se mantiene.
La regla: primero buscas aritmética vectorizada, np.where o map. apply queda para una regla que no se puede escribir de otra manera.
Más precisamente, para curiosos
apply con axis=1 convierte cada fila en una Serie pequeña y llama a tu función de Python 26 619 veces. La aritmética vectorizada recorre la columna una vez, en un bucle de C dentro de NumPy. Por eso la diferencia crece con el número de filas: con 152 filas es invisible, y con un millón es la diferencia entre un segundo y un minuto.
La idea
El informe: rename y drop
Antes de mandar la tabla al gerente, quitas las columnas que no necesita y pones nombres que entienda. drop(columns=[…]) quita columnas, y rename(columns={viejo: nuevo}) las renombra:
Las dos devuelven una tabla nueva, como assign. Las columnas que no están en el diccionario de rename conservan su nombre viejo.
Ejercicio
Los ingresos del año
¿Cuáles son los ingresos de todo el año? Añade una columna total a year_items (unidades por precio), quita las líneas de los pedidos cancelados y pon la suma, redondeada a 2 decimales, en revenue.
Solución
import pandas as pd
year = pd.read_csv('year_orders.csv')
year_items = pd.read_csv('year_items.csv')
year_items['total'] = year_items['quantity'] * year_items['unit_price']
cancelled = year.loc[year['status'] == 'cancelled', 'id']
kept = year_items[~year_items['order_id'].isin(cancelled)]
revenue = round(kept['total'].sum(), 2)
print(revenue)year_items['total'] = year_items['quantity'] * year_items['unit_price'], luego kept = year_items[~year_items['order_id'].isin(cancelled)]. Los ingresos son 111 186,20 €. Con los pedidos cancelados salen 117 902,70 €: 6716,50 € en 1550 líneas que nadie pagó. Sin ~ obtienes solo esos 6716,50 €.
¿Cuánto aportó el café en grano (producto 17) en el año, sin los pedidos cancelados? Pon la suma, redondeada a 2 decimales, en coffee_revenue.
Solución
import pandas as pd
year = pd.read_csv('year_orders.csv')
year_items = pd.read_csv('year_items.csv')
cancelled = year.loc[year['status'] == 'cancelled', 'id']
coffee = year_items[(year_items['product_id'] == 17) & ~year_items['order_id'].isin(cancelled)]
coffee = coffee.assign(total=coffee['quantity'] * coffee['unit_price'])
coffee_revenue = round(coffee['total'].sum(), 2)
print(coffee['unit_price'].value_counts())
print(coffee_revenue)coffee_revenue es 9585,10 €. value_counts sobre unit_price muestra dos precios: 13,90 € en 192 líneas (hasta el 1 de marzo) y 14,90 € en 345 líneas. Si tomas el precio de hoy de products con map, obtienes 9819,10 €: 234 € más de lo que los clientes pagaron en realidad. Con los pedidos cancelados salen 10 021,10 €.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Resúmenes y valores faltantes · Series y DataFrame · Seleccionar: [], loc e iloc · Filtrar y ordenar
- Continúa en: Tipos y valores faltantes · Grupos: groupby · Unir tablas: merge · Ancho y largo: pivot_table y melt
- Ejercicios sobre el tema: Tablas con pandas