1. Faro

Nivel 5 · ¿Cuánto tarda en llegar una devolución?

La solicitud

De Angela Rivera, Directora de Atención al Cliente

“Estamos pensando en una ventana de devolución de 30 días. Para cada motivo de devolución, ¿cuántos días después del pedido llegan las devoluciones de media, y qué proporción aceptaría todavía una ventana de 30 días?”

Tu objetivo

Enlaza cada devolución con su pedido (returns → order_items por order_item_id → orders por order_id). Días hasta la devolución = returned_on menos el día del calendario de order_date (sin la hora del día), en días enteros. Asigna a result un DataFrame con las columnas reason, n_returns (número de devoluciones), avg_days (media de días hasta la devolución, redondeada a 1) y share_30d (proporción de devoluciones con returned_on en el día del pedido + 30 días o antes, entre 0 y 1, redondeada a 3), una fila por motivo, ordenado por avg_days ascendente, con el índice reiniciado.

  • Usa el DataFrame orderspendiente
  • Usa el DataFrame order_itemspendiente
  • Muestra exactamente reason n_returns avg_days share_30dpendiente

Tu código

Python

Cargando el editor…

    ResultadoVista previa de orders · presiona Ejecutar para ver el tuyo
    Vista previa de orders
    order_idcustomer_idorder_datestatuschannel
    10011252023-01-01 20:51:19completedweb
    10021562023-01-02 07:58:46completedweb
    1003222023-01-02 10:25:27completedmarketplace
    1004482023-01-02 11:21:51completedmarketplace

    Mote revisa tu resultado

    Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.

    DataFrames

    Cargados como DataFrames de pandas, junto con pd y np.

    orders12,000 filas · 7 columnas
    • order_idinteger
    • customer_idinteger
    • order_datetimestamp
    • statusvarchar
    • channelvarchar
    • shipping_feedecimal
    • coupon_codevarchar
    Primeras 5 filas de orders
    order_idcustomer_idorder_datestatuschannelshipping_feecoupon_code
    10011252023-01-01 20:51:19completedweb0.00NaN
    10021562023-01-02 07:58:46completedweb0.00NaN
    1003222023-01-02 10:25:27completedmarketplace4.99WELCOME10
    1004482023-01-02 11:21:51completedmarketplace4.99WELCOME10
    1005822023-01-02 11:43:36completedweb0.00WELCOME10
    order_items30,338 filas · 6 columnas
    • order_item_idinteger
    • order_idinteger
    • product_idinteger
    • quantityinteger
    • unit_pricedecimal
    • discount_pctdecimal
    Primeras 5 filas de order_items
    order_item_idorder_idproduct_idquantityunit_pricediscount_pct
    11001121931.990.00
    21001154311.990.00
    31002154411.990.00
    410021941149.000.00
    51002193219.990.00
    returns1,673 filas · 5 columnas
    • return_idinteger
    • order_item_idinteger
    • returned_ondate
    • reasonvarchar
    • refund_amountdecimal
    Primeras 5 filas de returns
    return_idorder_item_idreturned_onreasonrefund_amount
    1142023-01-10damaged83.69
    2422023-01-10wrong_item19.99
    31052023-01-14wrong_item10.99
    4242023-01-16changed_mind26.99
    51262023-01-16late20.99
    customers2,018 filas · 9 columnas
    • customer_idinteger
    • first_namevarchar
    • last_namevarchar
    • emailvarchar
    • cityvarchar
    • statevarchar
    • signup_datedate
    • segmentvarchar
    • referral_sourcevarchar
    Primeras 5 filas de customers
    customer_idfirst_namelast_nameemailcitystatesignup_datesegmentreferral_source
    1OwenHudsonowen.hudson@icloud.comSan FranciscoCA2022-06-02consumersearch
    2DouglasMendozadmendoza69@gmail.comAtlantaGA2022-06-02consumerNaN
    3HenryBrookshbrooks@lakesidecafe.comLos AngelesCA2022-06-04businessNaN
    4BrianGrahambgraham@bluecedarhomes.comSeattleWA2022-06-06businesspinterest
    5KyleJimenezkjimenez27@yahoo.comIndianapolisIN2022-06-07consumerNaN
    products120 filas · 8 columnas
    • product_idinteger
    • namevarchar
    • categoryvarchar
    • subcategoryvarchar
    • unit_pricedecimal
    • unit_costdecimal
    • launched_ondate
    • is_activeboolean
    Primeras 5 filas de products
    product_idnamecategorysubcategoryunit_priceunit_costlaunched_onis_active
    101Vale Velvet LoveseatFurnitureSofas1659.00NaN2023-09-11true
    102Alder Oak Coffee TableFurnitureTables919.00503.502024-05-16true
    103Orchard Rattan Dining ChairFurnitureChairs789.00429.872023-05-27true
    104Upland Oak BookcaseFurnitureStorage429.00214.702022-12-20true
    105Avery Leather Bed FrameFurnitureBeds449.00232.702021-08-31true
    employees60 filas · 8 columnas
    • employee_idinteger
    • first_namevarchar
    • last_namevarchar
    • departmentvarchar
    • titlevarchar
    • manager_idinteger
    • hire_datedate
    • salaryinteger
    Primeras 5 filas de employees
    employee_idfirst_namelast_namedepartmenttitlemanager_idhire_datesalary
    1CatherineHaleExecutiveChief Executive OfficerNaN2016-03-01285000
    2RobertKimExecutiveChief Operating Officer12017-01-09235000
    3MeganCollinsSalesHead of Sales12018-04-16172000
    4ChloeGutierrezSalesAccount Executive32020-01-2172000
    5FrankBellSalesAccount Executive32023-05-2173000
    web_sessions40,000 filas · 7 columnas
    • session_idinteger
    • customer_idinteger
    • started_attimestamp
    • devicevarchar
    • sourcevarchar
    • pages_viewedinteger
    • convertedboolean
    Primeras 5 filas de web_sessions
    session_idcustomer_idstarted_atdevicesourcepages_viewedconverted
    1NaN2023-01-01 23:02:24mobilepaid1false
    2182023-01-01 15:07:49desktoppaid1false
    3NaN2023-01-01 16:55:46desktopdirect7false
    4NaN2023-01-01 12:43:28mobileorganic3false
    5NaN2023-01-01 13:06:21mobilesocial2false
    marketing_spend3,276 filas · 3 columnas
    • spend_datedate
    • channelvarchar
    • amountdecimal
    Primeras 5 filas de marketing_spend
    spend_datechannelamount
    2023-01-01paid492.99
    2023-01-01email37.38
    2023-01-01social200.53
    2023-01-02paid709.13
    2023-01-02email64.13

    La lección

    Restar fechas

    Cuando restas una columna datetime de otra, pandas te da una columna Timedelta: una duración, no una fecha.

    r = (
        returns.merge(order_items[["order_item_id", "order_id"]], on="order_item_id")
        .merge(orders[["order_id", "order_date"]], on="order_id")
    )
    (r["returned_on"] - r["order_date"]).head(2)
    # 0   7 days 12:16:24
    # 1   5 days 12:47:48

    El accesor .dt también funciona con Timedeltas: .dt.days da los días enteros, y .dt.total_seconds() la duración exacta en segundos (divide entre 86.400 para obtener días fraccionarios).

    Cuidado con la hora del día

    returned_on es una fecha (medianoche), pero order_date es una marca de tiempo con hora. La primera devolución de arriba pertenece a un pedido hecho a las 11:43 del 2 de enero de 2023 y volvió el 10 de enero: “7 days 12:16” después. .dt.days descarta el día parcial y dice 7, pero en el calendario la devolución llegó 8 días después del día del pedido. Para contar días de calendario, pon primero el pedido a medianoche con .dt.normalize():

    order_day = r["order_date"].dt.normalize()
    r["days"] = (r["returned_on"] - order_day).dt.days
    r["days"].mean()   # 16.29 (15.29 without normalize)

    Un día entero de diferencia en la media por una llamada a método que falta: comprueba siempre qué tipo de fecha contiene cada columna.

    Mover fechas: Timedelta y DateOffset

    Para desplazar una fecha una duración fija, suma un pd.Timedelta:

    order_day + pd.Timedelta(days=30)    # 30 days later
    orders["order_date"] - pd.Timedelta(hours=6)

    Para movimientos conscientes del calendario, usa pd.DateOffset: pd.DateOffset(months=1) mueve el 31 de enero al 28 o 29 de febrero, y pd.offsets.MonthEnd(0) lleva una fecha hacia delante hasta el final de su mes. Timedelta es un número fijo de días; DateOffset sigue el calendario. Una “ventana de 30 días” es un Timedelta; “hasta el final del mes que viene” es un DateOffset.

    La ventana de devolución por motivo

    Cuando cada fila tiene days y un within_30 True/False, una agregación con nombre responde a Angela. La media de una columna booleana es la proporción de valores True:

    #          reason  n_returns  avg_days  share_30d
    #      wrong_item        255       7.9      1.000
    #         damaged        477       8.0      1.000
    #    changed_mind        698      22.7      0.711
    #            late        243      22.9      0.741

    Los artículos dañados y equivocados vuelven en aproximadamente una semana, todos dentro de los 30 días. Las devoluciones por cambio de opinión y por retraso tardan tres semanas de media, y aproximadamente una cuarta parte llega después del día 30. En conjunto, el 84,2 % de las devoluciones cabría en la ventana, así que la política afecta justo a los clientes que cambiaron de opinión.

    Errores comunes

    • Restar una marca de tiempo con hora del día y leer .dt.days como días de calendario.
    • Llamar a .days sobre la columna en lugar de .dt.days (funciona con un Timedelta suelto, no con una Series).
    • Sumar un número simple a una fecha (order_day + 30), lo que lanza un TypeError. Envuélvelo en pd.Timedelta(days=30).
    • Usar pd.DateOffset(months=1) cuando la regla es realmente un número fijo de días, o al revés.

    Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.