1. Faro

Nivel 10 · Desafío final: el informe limpio de 2025

La solicitud

De Catherine Hale, Directora General

“Para el dossier de la junta necesito los ingresos de 2025 mes a mes y nuestro recuento real de clientes por segmento. La última vez las cifras no coincidían con finanzas. Esta vez, primero limpia.”

Tu objetivo

Paso 1 de 3. Asigna a result el número de filas de order_items (un int) con una cantidad de 0 o menos.

  • Usa el DataFrame order_itemspendiente
  • Guarda un número en resultpendiente

Paso 1 · tu código

Python

Cargando el editor…

    ResultadoVista previa de order_items · presiona Ejecutar para ver el tuyo
    Vista previa de order_items
    order_item_idorder_idproduct_idquantityunit_price
    11001121931.99
    21001154311.99
    31002154411.99
    410021941149.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

    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.

      Paso 3 · tu código

      Python

      Cargando el editor…

        ResultadoVista previa de customers · presiona Ejecutar para ver el tuyo
        Vista previa de customers
        customer_idfirst_namelast_nameemailcity
        1OwenHudsonowen.hudson@icloud.comSan Francisco
        2DouglasMendozadmendoza69@gmail.comAtlanta
        3HenryBrookshbrooks@lakesidecafe.comLos Angeles
        4BrianGrahambgraham@bluecedarhomes.comSeattle

        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.

        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
        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
        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
        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
        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

        Limpia y luego cuenta

        Las cifras de la junta se comparan con las de finanzas, con la presentación del trimestre pasado y con la memoria del CEO. Cuando discrepan, la primera pregunta siempre es “¿qué incluiste?”. Un informe limpio responde esa pregunta antes de que nadie la haga: elimina las filas incorrectas conocidas, usa una definición declarada de ingresos y cuenta clientes reales, no registros.

        Paso 1: dimensiona el problema

        Antes de excluir nada, mídelo:

        (order_items["quantity"] <= 0).sum()
        order_items[order_items["quantity"] <= 0].head()

        Las cantidades cero y negativas son errores de entrada de datos aquí (nunca son artículos devueltos). Saber que son 24 líneas de unas 30.000 te dice que el efecto es pequeño pero real, y te permite escribir una nota al pie de una línea: “24 líneas de pedido con cantidades inválidas excluidas”.

        Nunca arregles filas incorrectas adivinando (pasar los negativos a positivo, por ejemplo) a menos que alguien que es dueño de los datos confirme que esa es la corrección correcta.

        Paso 2: una serie limpia de ingresos mensuales

        La definición, escrita primero: ingresos de línea tras descuento, solo cantidades válidas, solo pedidos completados, por mes de la fecha del pedido, 2025.

        good = order_items[order_items["quantity"] > 0]
        good = good.assign(line_revenue=good["quantity"] * good["unit_price"] * (1 - good["discount_pct"] / 100))
        
        completed = orders.loc[orders["status"] == "completed", ["order_id", "order_date"]]
        lines = good.merge(completed, on="order_id", how="inner", validate="many_to_one")

        El merge inner hace dos trabajos: trae la fecha del pedido y descarta las líneas de pedidos cancelados, reembolsados y pendientes. Luego filtra a 2025 y agrupa por una etiqueta de mes:

        lines_2025 = lines[lines["order_date"].dt.year == 2025]
        lines_2025.assign(month=lines_2025["order_date"].dt.strftime("%Y-%m")).groupby("month", as_index=False)...

        strftime("%Y-%m") da texto como '2025-11', que se ordena bien y se lee bien. Espera que noviembre y diciembre destaquen: el Black Friday, el Cyber Monday y las fiestas los elevan muy por encima de cualquier otro mes, mientras que febrero es el más tranquilo.

        order_date incluye una hora, y eso está bien para agrupar por mes. Si alguna vez filtras con <= "2025-12-31", recuerda que eso significa la medianoche al inicio del 31 de diciembre, lo que descarta todo ese día. Usa < "2026-01-01" o .dt.year == 2025 en su lugar.

        Paso 3: clientes reales

        deduped = (
            customers.assign(email_key=customers["email"].str.strip().str.lower())
            .sort_values("customer_id")
            .drop_duplicates(subset="email_key", keep="first")
        )
        deduped.groupby("segment", as_index=False).agg(n_customers=("customer_id", "count"))

        Ordenar por customer_id antes de eliminar convierte “conservar el registro original” en una regla explícita en lugar de un accidente del orden del archivo. El total sin duplicados debería ser exactamente 18 menos que len(customers); compruébalo.

        Ten en cuenta que esto cuenta a todos los que se registraron. Si la junta quiere clientes activos, esa es otra definición (clientes con un pedido completado en 2025) y otra cifra. Di cuál de las dos muestra la diapositiva.

        Cómo es un buen paso de limpieza

        • Está medido: sabes cuántas filas toca.
        • Está explicado: una frase sobre el porqué.
        • Es reproducible: código, no ediciones manuales en una hoja de cálculo.
        • Está comprobado: los totales antes y después concilian.

        Errores comunes

        • Filtrar la cantidad después de agregar, cuando las líneas incorrectas ya están dentro de los totales.
        • Agrupar solo por dt.month, lo que mezcla meses de años distintos si falta el filtro de año.
        • Eliminar duplicados sin ordenar, de modo que un registro duplicado podría sobrevivir en lugar del original.

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