La solicitud

De Angela Rivera, Directora de Atención al Cliente
Tu objetivo
Paso 1 de 3. Asigna a result un DataFrame con las columnas category y n_returns (número de devoluciones cuya línea de pedido está en esa categoría), ordenado por n_returns descendente y luego por category ascendente, con el índice reiniciado.
- Usa el DataFrame
productspendiente - Usa el DataFrame
order_itemspendiente - Muestra exactamente
categoryn_returnspendiente
Paso 2 de 3. Asigna a result un DataFrame con las columnas category, n_lines (todas las líneas de pedido de esa categoría), n_returns (líneas devueltas de esa categoría) y return_rate (n_returns / n_lines, redondeada a 3), ordenado por return_rate descendente y luego por category ascendente, con el índice reiniciado.
- Usa el DataFrame
productspendiente - Usa el DataFrame
order_itemspendiente - Muestra exactamente
categoryn_linesn_returnsreturn_ratependiente
Paso 3 de 3. Asigna a result un DataFrame con las columnas channel y total_refund (suma de refund_amount, redondeada a 2), usando el canal del pedido al que pertenece cada línea devuelta, ordenado por total_refund descendente, con el índice reiniciado.
- Usa el DataFrame
orderspendiente - Usa el DataFrame
order_itemspendiente - Muestra exactamente
channeltotal_refundpendiente
Paso 1 · 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 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 Angela, y por qué
DataFrames
Cargados como DataFrames de pandas, junto con pd y np.
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 |
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 |
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 |
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 |
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 |
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
Primero dibuja la ruta
returns solo conoce el order_item_id que volvió. Todo lo demás está a un join de distancia:
returns --order_item_id--> order_items --product_id--> products (category)
--order_id----> orders (channel)
Esbozar esta ruta antes de escribir código es un buen hábito para cualquier pregunta con varias
tablas. Fíjate en el grano de cada salto: como máximo una devolución por línea (one-to-one), muchas
líneas por producto y por pedido (many-to-one). Esos son exactamente los valores de validate= que
debes usar.
Paso 1: recuentos por categoría
returned = (
returns
.merge(order_items[["order_item_id", "product_id"]], on="order_item_id", validate="one_to_one")
.merge(products[["product_id", "category"]], on="product_id", validate="many_to_one")
)
returned.groupby("category", as_index=False).agg(n_returns=("return_id", "count"))
len(returned) debería ser igual a len(returns). Si no lo es, algún join perdió o duplicó devoluciones.
Paso 2: tasas, no recuentos
Los recuentos brutos favorecen lo que más se vende. Una comparación justa divide por lo que vendió cada categoría. Eso necesita un denominador sacado de todas las líneas de pedido, no solo las devueltas:
lines = order_items.merge(products[["product_id", "category"]], on="product_id")
lines = lines.assign(is_returned=lines["order_item_id"].isin(returns["order_item_id"]))
La marca is_returned mantiene la tabla en el grano de línea, sin ningún merge que pueda multiplicar
filas. Luego un solo groupby da ambos recuentos, ya que la suma de una columna booleana cuenta sus
valores True:
(
lines.groupby("category", as_index=False)
.agg(n_lines=("order_item_id", "count"), n_returns=("is_returned", "sum"))
.assign(return_rate=lambda d: (d["n_returns"] / d["n_lines"]).round(3))
)
assign con una lambda te permite calcular una columna nueva a partir de la tabla agregada en la
misma cadena. El orden por tasa puede diferir del orden por recuento, y esa diferencia es la
conclusión: una categoría puede generar muchas devoluciones simplemente porque vende mucho, mientras
que otra se devuelve con una frecuencia inusual para su volumen.
Las devoluciones aquí incluyen todas las líneas de pedidos reembolsados, así que la tasa mezcla dos causas: reembolsos de pedidos completos y artículos devueltos individualmente. Un análisis cuidadoso las separaría; menciónalo si presentas esto.
Paso 3: dinero por canal
refunds = (
returns
.merge(order_items[["order_item_id", "order_id"]], on="order_item_id")
.merge(orders[["order_id", "channel"]], on="order_id")
)
refunds.groupby("channel", as_index=False).agg(total_refund=("refund_amount", "sum"))
refund_amount es un valor por devolución, así que sumarlo en una tabla con una fila por devolución
es seguro. Compara el total con returns["refund_amount"].sum() para confirmarlo.
Errores comunes
- Dividir las devoluciones entre el número de productos o de pedidos en lugar de líneas de pedido.
- Obtener tasas de devolución superiores a 1, señal segura de un fan-out en el numerador.
- Usar
countsobreis_returned, que cuenta todas las líneas, en lugar desum.
¡Linterna encendida!
Practicaste: mergemulti tablegroupbyratesvalidate
- Resuelto
- Sin pistas
- Al primer intento
