La solicitud

De Megan Collins, Directora de Ventas
Tu objetivo
Paso 1 de 3. Asigna a result un DataFrame con las columnas month (texto 'YYYY-MM'), orders (pedidos completados distintos), net_revenue (redondeado a 2) y avg_order_value (ingreso neto / pedidos, redondeado a 2) de septiembre a diciembre de 2025, ordenado por month y con índice por defecto.
- Usa el DataFrame
orderspendiente - Usa el DataFrame
order_itemspendiente - Muestra exactamente
monthordersnet_revenueavg_order_valuependiente
Paso 2 de 3. Asigna a result un DataFrame con las columnas category, rev_2024, rev_2025 (ingreso neto del 1 de noviembre al 31 de diciembre de cada año, redondeado a 2) y yoy_change_pct ((rev_2025 - rev_2024) * 100 / rev_2024, con sumas sin redondear, redondeado a 2), ordenado por yoy_change_pct de forma descendente y con índice por defecto.
- Usa el DataFrame
productspendiente - Usa el DataFrame
orderspendiente - Muestra exactamente
categoryrev_2024rev_2025yoy_change_pctpendiente
Paso 3 de 3. Asigna a result un DataFrame con las columnas channel, nov_revenue y dec_revenue (ingreso neto de noviembre y diciembre de 2025, redondeado a 2) y mom_change_pct ((dec - nov) * 100 / nov, con sumas sin redondear, redondeado a 2), ordenado por channel de forma ascendente y con índice por defecto.
- Usa el DataFrame
orderspendiente - Usa el DataFrame
order_itemspendiente - Muestra exactamente
channelnov_revenuedec_revenuemom_change_pctpendiente
Este caso se desbloquea cuando terminas:
La solicitud y la lección ya se pueden leer. Resuelve todos los niveles de esos capítulos, en cualquier orden, y este caso se abre.
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.
Paso 3 · 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.
Lo que recibe Megan, 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
La misma pregunta, las mismas definiciones
Esta es la misma revisión del nivel de SQL de este caso, construida en pandas. Mantén las
definiciones idénticas o los números no coincidirán: pedidos completados, líneas con quantity > 0,
ingreso quantity * unit_price * (1 - discount_pct / 100), sin envío. Cuando una versión en SQL y
otra en pandas del mismo informe no coinciden, nueve de cada diez veces es porque una de las dos
filtró distinto.
Construye la tabla base una sola vez
En SQL repites el JOIN en cada consulta. En pandas construyes una tabla a nivel de línea y la reutilizas:
completed = orders[orders["status"] == "completed"]
lines = order_items[order_items["quantity"] > 0].merge(
completed[["order_id", "order_date", "channel"]],
on="order_id", how="inner", validate="many_to_one",
)
lines["revenue"] = lines["quantity"] * lines["unit_price"] * (1 - lines["discount_pct"] / 100)
Filtrar orders antes de la unión es la versión en pandas de WHERE o.status = 'completed': la
unión interna descarta toda línea cuyo pedido no esté en completed. validate="many_to_one" hace
que pandas lance un error si alguna vez se duplica un id de pedido, que es como detectarías una
duplicación antes de que llegue a una diapositiva.
Tres herramientas de fechas y etiquetas que usa este caso
Etiquetas de mes con .dt.strftime. En una columna datetime, .dt.strftime("%Y-%m") devuelve
texto como "2025-11". %Y es el año de cuatro dígitos y %m el mes de dos dígitos. El texto con
esa forma se ordena en orden de calendario, así que es una buena clave para groupby.
orders["order_date"].dt.strftime("%Y-%m").head(2)
# 0 2023-01
# 1 2023-01
Números de mes con .dt.month. .dt.month da el mes como número, de 1 a 12, así que
lines["order_date"].dt.month == 11 es True en las filas de noviembre.
Etiquetas a partir de una condición con np.where. np.where(condition, value_if_true, value_if_false)
construye una columna fila por fila, como un if de dos ramas:
orders["shipping_type"] = np.where(orders["shipping_fee"] == 0, "free shipping", "paid shipping")
Cada fila cuya tarifa es 0 recibe "free shipping", y cada una de las demás "paid shipping". En
este caso lo usas para etiquetar cada línea con su temporada o mes, y luego pivotar sobre esa
etiqueta.
Por último, sort_index() ordena un DataFrame por su índice, que después de un pivote es la clave
de las filas (channel en el paso 3), así que equivale a ordenar por canal antes de reset_index().
Paso 1: groupby con agregación con nombre
COUNT(DISTINCT order_id) se convierte en ("order_id", "nunique"). Usar "count" en su lugar
contaría líneas, el mismo error que COUNT(*) en SQL. El valor medio del pedido es un cociente de
dos agregados, así que calcúlalo después del groupby, a partir del ingreso sin redondear, y redondea
las columnas al final.
Los filtros de fecha comparan una columna datetime64 con un texto: pandas interpreta
"2026-01-01" como la medianoche, así que < "2026-01-01" conserva todas las marcas de tiempo del
31 de diciembre.
Pasos 2 y 3: dos periodos como columnas
El SUM(CASE WHEN period THEN revenue END) de SQL tiene un equivalente directo en pandas: etiqueta
cada fila con su periodo y luego pivota.
holiday = lines[in_24 | in_25].copy()
holiday["season"] = np.where(holiday["order_date"] < "2025-01-01", "rev_2024", "rev_2025")
wide = holiday.pivot_table(index="category", columns="season", values="revenue", aggfunc="sum")
El resultado tiene category como índice y una columna por temporada. Dos líneas de limpieza lo
hacen coincidir con la salida de SQL: wide.columns.name = None quita la etiqueta «season» sobrante
de las columnas, y reset_index() vuelve a convertir el índice de categorías en una columna.
Ponerles a las etiquetas de periodo los nombres finales de las columnas (rev_2024, nov_revenue)
te ahorra un renombrado.
Una alternativa es groupby(["category", "season"])["revenue"].sum().unstack(). Produce la misma
tabla ancha; pivot_table simplemente lo dice en una sola llamada.
SQL y pandas lado a lado
| Paso | SQL | pandas |
|---|---|---|
| filtrar filas | WHERE antes de GROUP BY |
máscara booleana antes de groupby |
| conteo distinto | COUNT(DISTINCT ...) |
nunique |
| periodos lado a lado | SUM(CASE WHEN ...) |
pivot_table(columns=...) |
| ordenar | ORDER BY ... DESC |
sort_values(..., ascending=False) |
Errores comunes
- Redondear antes de dividir. Redondea las columnas de ingresos después de calcular el crecimiento, exactamente como lo hace la versión en SQL en su SELECT exterior.
- Orden de las categorías.
pivot_tableordena el índice alfabéticamente; el ejercicio pide el orden por crecimiento, así que ordena de forma explícita. - Olvidar
reset_index. Un resultado concategorycomo índice se ve igual en un cuaderno, pero no es la tabla que pide el ejercicio. - Periodos ausentes. Si una categoría no tuvo ventas en una temporada,
pivot_tabledejaría un valor faltante (NaN) en esa celda. Aquí todas las categorías venden en ambas temporadas, pero compruébalo antes de dividir.
¡Linterna encendida!
Practicaste: mergegroupbynamed aggregationnuniquepivot tableyoymom
- Resuelto
- Sin pistas
- Al primer intento
