La solicitud

De Thomas Whitaker, Director de Merchandising
Tu objetivo
Paso 1 de 2. Revisa el cambio. Para cada línea de pedido de los productos cuyo precio subió, etiqueta period ('before' si order_date < '2025-03-01', si no 'after') y price_paid ('old' si el unit_price de la línea es igual al precio más bajo del producto, si no 'new'). Asigna a result un DataFrame con las columnas period, price_paid y lines (el número de líneas de pedido), ordenado por period de forma descendente y luego por price_paid de forma descendente, con índice por defecto. Usa todas las líneas de pedido, sea cual sea el estado.
- Usa el DataFrame
orderspendiente - Usa el DataFrame
order_itemspendiente - Muestra exactamente
periodprice_paidlinespendiente
Paso 2 de 2. Compara en igualdad de condiciones. Usa pedidos completados, líneas con quantity > 0 y solo productos lanzados antes de 2024-01-01. Etiqueta cada línea con price_group 'raised' o 'unchanged'. Asigna a result un DataFrame con las columnas price_group, units_2024 y units_2025 (cantidad total del 1 de marzo al 31 de agosto de cada año), units_change_pct, revenue_2024 y revenue_2025 (ingreso neto, redondeado a 2) y revenue_change_pct. Ambas columnas de cambio son (2025 - 2024) * 100 / 2024 con sumas sin redondear, redondeadas a 1. Ordena por price_group, con índice por defecto.
- Usa el DataFrame
productspendiente - Usa el DataFrame
orderspendiente - Muestra exactamente
price_groupunits_2024units_2025units_change_pctrevenue_2024revenue_2025revenue_change_pctpendiente
Paso 1 · tu código
Cargando el editor…
Solución de referencia
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote revisa tu resultado
Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.
Paso 2 · tu código
Cargando el editor…
Solución de referencia
| product_id | name | category | subcategory | unit_price |
|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 |
Mote revisa tu resultado
Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.
Lo que recibe Thomas, y por qué
DataFrames
Cargados como DataFrames de pandas, junto con pd y np.
orders12,000 filas · 7 columnas
order_idintegercustomer_idintegerorder_datetimestampstatusvarcharchannelvarcharshipping_feedecimalcoupon_codevarchar
| order_id | customer_id | order_date | status | channel | shipping_fee | coupon_code |
|---|---|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web | 0.00 | NaN |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web | 0.00 | NaN |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace | 4.99 | WELCOME10 |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace | 4.99 | WELCOME10 |
| 1005 | 82 | 2023-01-02 11:43:36 | completed | web | 0.00 | WELCOME10 |
order_items30,338 filas · 6 columnas
order_item_idintegerorder_idintegerproduct_idintegerquantityintegerunit_pricedecimaldiscount_pctdecimal
| order_item_id | order_id | product_id | quantity | unit_price | discount_pct |
|---|---|---|---|---|---|
| 1 | 1001 | 121 | 9 | 31.99 | 0.00 |
| 2 | 1001 | 154 | 3 | 11.99 | 0.00 |
| 3 | 1002 | 154 | 4 | 11.99 | 0.00 |
| 4 | 1002 | 194 | 1 | 149.00 | 0.00 |
| 5 | 1002 | 193 | 2 | 19.99 | 0.00 |
products120 filas · 8 columnas
product_idintegernamevarcharcategoryvarcharsubcategoryvarcharunit_pricedecimalunit_costdecimallaunched_ondateis_activeboolean
| product_id | name | category | subcategory | unit_price | unit_cost | launched_on | is_active |
|---|---|---|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 | NaN | 2023-09-11 | true |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 | 503.50 | 2024-05-16 | true |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 | 429.87 | 2023-05-27 | true |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 | 214.70 | 2022-12-20 | true |
| 105 | Avery Leather Bed Frame | Furniture | Beds | 449.00 | 232.70 | 2021-08-31 | true |
customers2,018 filas · 9 columnas
customer_idintegerfirst_namevarcharlast_namevarcharemailvarcharcityvarcharstatevarcharsignup_datedatesegmentvarcharreferral_sourcevarchar
| customer_id | first_name | last_name | city | state | signup_date | segment | referral_source | |
|---|---|---|---|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco | CA | 2022-06-02 | consumer | search |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta | GA | 2022-06-02 | consumer | NaN |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles | CA | 2022-06-04 | business | NaN |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle | WA | 2022-06-06 | business | |
| 5 | Kyle | Jimenez | kjimenez27@yahoo.com | Indianapolis | IN | 2022-06-07 | consumer | NaN |
returns1,673 filas · 5 columnas
return_idintegerorder_item_idintegerreturned_ondatereasonvarcharrefund_amountdecimal
| return_id | order_item_id | returned_on | reason | refund_amount |
|---|---|---|---|---|
| 1 | 14 | 2023-01-10 | damaged | 83.69 |
| 2 | 42 | 2023-01-10 | wrong_item | 19.99 |
| 3 | 105 | 2023-01-14 | wrong_item | 10.99 |
| 4 | 24 | 2023-01-16 | changed_mind | 26.99 |
| 5 | 126 | 2023-01-16 | late | 20.99 |
employees60 filas · 8 columnas
employee_idintegerfirst_namevarcharlast_namevarchardepartmentvarchartitlevarcharmanager_idintegerhire_datedatesalaryinteger
| employee_id | first_name | last_name | department | title | manager_id | hire_date | salary |
|---|---|---|---|---|---|---|---|
| 1 | Catherine | Hale | Executive | Chief Executive Officer | NaN | 2016-03-01 | 285000 |
| 2 | Robert | Kim | Executive | Chief Operating Officer | 1 | 2017-01-09 | 235000 |
| 3 | Megan | Collins | Sales | Head of Sales | 1 | 2018-04-16 | 172000 |
| 4 | Chloe | Gutierrez | Sales | Account Executive | 3 | 2020-01-21 | 72000 |
| 5 | Frank | Bell | Sales | Account Executive | 3 | 2023-05-21 | 73000 |
web_sessions40,000 filas · 7 columnas
session_idintegercustomer_idintegerstarted_attimestampdevicevarcharsourcevarcharpages_viewedintegerconvertedboolean
| session_id | customer_id | started_at | device | source | pages_viewed | converted |
|---|---|---|---|---|---|---|
| 1 | NaN | 2023-01-01 23:02:24 | mobile | paid | 1 | false |
| 2 | 18 | 2023-01-01 15:07:49 | desktop | paid | 1 | false |
| 3 | NaN | 2023-01-01 16:55:46 | desktop | direct | 7 | false |
| 4 | NaN | 2023-01-01 12:43:28 | mobile | organic | 3 | false |
| 5 | NaN | 2023-01-01 13:06:21 | mobile | social | 2 | false |
marketing_spend3,276 filas · 3 columnas
spend_datedatechannelvarcharamountdecimal
| spend_date | channel | amount |
|---|---|---|
| 2023-01-01 | paid | 492.99 |
| 2023-01-01 | 37.38 | |
| 2023-01-01 | social | 200.53 |
| 2023-01-02 | paid | 709.13 |
| 2023-01-02 | 64.13 |
La lección
Qué estás revisando, y por qué
Antes de comparar nada, demuestra que estás mirando los productos correctos y la fecha correcta.
Thomas dice «unos 30 productos, 1 de marzo». Los datos pueden confirmar ambas cosas, porque
order_items guarda el precio pagado en cada línea, mientras que products["unit_price"] solo
guarda el precio de lista de hoy.
Un producto cuyo precio cambió tiene dos precios distintos entre sus líneas de pedido. La agregación con nombre da ambos en una sola pasada, y una máscara booleana conserva los productos donde difieren:
prices = order_items.groupby("product_id", as_index=False).agg(
old_price=("unit_price", "min"), new_price=("unit_price", "max")
)
prices = prices[prices["old_price"] != prices["new_price"]]
len(prices) # 29
El paso 1 etiqueta entonces cada una de sus líneas dos veces, por fecha y por precio, con
np.where, y cuenta las combinaciones. Si el cambio ocurrió limpiamente el 1 de marzo, solo existen
dos combinaciones: antes con el precio viejo, después con el nuevo.
En igualdad de condiciones, con un grupo de control
El paso 2 responde la pregunta acordada. Tres decisiones hacen justa la comparación:
- Los mismos meses, con un año de diferencia. De marzo a agosto de 2025 frente a marzo a agosto de 2024.
- Productos en igualdad de condiciones. Solo los productos lanzados antes del 1 de enero de 2024, para que todos estuvieran a la venta durante ambas ventanas completas.
- Un grupo de control. Todos los demás productos se etiquetan como
unchanged. Su crecimiento es lo que la tienda hizo de todos modos.
isin etiqueta los grupos en una línea, y pivot_table pone cada año en su propia columna, la
versión en pandas de SUM(CASE WHEN ...):
lines["price_group"] = np.where(lines["product_id"].isin(raised), "raised", "unchanged")
u = lines.pivot_table(index="price_group", columns="yr", values="quantity", aggfunc="sum")
Las columnas de u son los años como enteros, así que u[2025] - u[2024] resta fila por fila.
Calcula los porcentajes con estos totales sin redondear y redondea al final.
Cómo leer el resultado
| price_group | units_change_pct | revenue_change_pct |
|---|---|---|
| raised | 21.0 | 44.3 |
| unchanged | 9.2 | 13.4 |
Si la subida hubiera dañado la demanda, los productos con precio más alto crecerían menos que los que no cambiaron. Crecieron más. Los ingresos crecieron más rápido que las unidades porque cada unidad dejó cerca de 6% más y porque las ventas se desplazaron hacia los artículos más caros del grupo.
Errores comunes
- Filtra con
<sobre el día siguiente al final de la ventana:d < "2025-09-01"conserva todo el 31 de agosto. - Olvidar
.copy()después de filtrar y luego añadir columnas produce unSettingWithCopyWarning. pivot_tableordena su índice, así que el resultado ya está en el orden deprice_group; elsort_valuesexplícito convierte eso en una promesa en lugar de una coincidencia.
¡Linterna encendida!
Practicaste: data validationgroupbynamed aggregationpivot tablelike for likecontrol groupyoy
- Resuelto
- Sin pistas
- Al primer intento
