1. Faro
  2. Faro

Nivel 2 · Costo por conversión en pandas

La solicitud

De Jordan Reyes, Director de Marketing

“Estos números van a la presentación del presupuesto. Nuestra analista de marketing trabaja en cuadernos, así que por favor construye las tres tablas en pandas, iguales a la versión en SQL de este caso, para que pueda volver a ejecutarlas cada mes.”

Tu objetivo

Paso 1 de 3. Asigna a result un DataFrame con las columnas source, sessions, conversions y conversion_rate_pct (conversions * 100 / sessions, redondeado a 2), ordenado por conversion_rate_pct de forma descendente y con índice por defecto.

  • Usa el DataFrame web_sessionspendiente
  • Muestra exactamente source sessions conversions conversion_rate_pctpendiente
  • 5 filas, ordenadas como se pidependiente

Paso 1 · tu código

Python

Cargando el editor…

    ResultadoVista previa de web_sessions · presiona Ejecutar para ver el tuyo
    Vista previa de web_sessions
    session_idcustomer_idstarted_atdevicesource
    1NaN2023-01-01 23:02:24mobilepaid
    2182023-01-01 15:07:49desktoppaid
    3NaN2023-01-01 16:55:46desktopdirect
    4NaN2023-01-01 12:43:28mobileorganic

    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 web_sessions · presiona Ejecutar para ver el tuyo
      Vista previa de web_sessions
      session_idcustomer_idstarted_atdevicesource
      1NaN2023-01-01 23:02:24mobilepaid
      2182023-01-01 15:07:49desktoppaid
      3NaN2023-01-01 16:55:46desktopdirect
      4NaN2023-01-01 12:43:28mobileorganic

      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 web_sessions · presiona Ejecutar para ver el tuyo
        Vista previa de web_sessions
        session_idcustomer_idstarted_atdevicesource
        1NaN2023-01-01 23:02:24mobilepaid
        2182023-01-01 15:07:49desktoppaid
        3NaN2023-01-01 16:55:46desktopdirect
        4NaN2023-01-01 12:43:28mobileorganic

        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.

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

        La lección

        La misma pregunta, las mismas definiciones

        Este es el análisis de presupuesto de Jordan en pandas, con las mismas definiciones que el nivel de SQL de este caso: solo 2025 en ambas tablas, una conversión es una sesión con converted == True, y el gasto se asocia a las sesiones por el nombre del canal. Organic y direct tienen sesiones pero no tienen gasto.

        Paso 1: contar valores True

        converted es una columna booleana. En pandas True se comporta como 1 y False como 0, así que ("converted", "sum") cuenta las conversiones y ("converted", "mean") daría la tasa directamente. El ejercicio pide un porcentaje redondeado a 2, así que calcúlalo a partir de los dos conteos:

        by_source = ws.groupby("source", as_index=False).agg(
            sessions=("session_id", "count"), conversions=("converted", "sum")
        )
        by_source["conversion_rate_pct"] = (by_source["conversions"] * 100 / by_source["sessions"]).round(2)

        "count" cuenta los valores no faltantes de la columna indicada. session_id nunca falta, así que cuenta todas las sesiones. Contar customer_id sería un error silencioso: las sesiones anónimas tienen NaN ahí y no se contarían.

        Paso 2: preagrega y luego une

        En SQL esto lleva dos CTE. En pandas cada una es simplemente un DataFrame:

        conv = ws.groupby("source", as_index=False).agg(conversions=("converted", "sum"))
        spend = ms.groupby("channel", as_index=False).agg(spend=("amount", "sum")).rename(columns={"channel": "source"})
        table = conv.merge(spend, on="source", how="left", validate="one_to_one")

        marketing_spend llama channel a su clave mientras que web_sessions la llama source. .rename(columns={"channel": "source"}) renombra una columna (el nombre viejo a la izquierda, el nuevo a la derecha) para que ambos DataFrames compartan la clave y on="source" funcione. left_on="source", right_on="channel" hace lo mismo sin renombrar.

        validate="one_to_one" es la forma en pandas de afirmar lo que razonaste en SQL: después del groupby, cada lado tiene una fila por clave, así que el merge no puede duplicar filas. Si más adelante alguien une las tablas en bruto por error, pandas lanza un MergeError en lugar de devolver un gasto inflado.

        how="left" desde el lado de las sesiones conserva organic y direct, con gasto NaN (la marca de pandas para un número faltante). Cualquier operación aritmética con NaN da NaN, así que su costo por conversión falta, y eso es correcto: no es cero, no aplica.

        División entre cero

        pandas devuelve inf (infinito) sin avisar cuando divides un número entre cero. La solución es convertir primero los ceros del denominador en valores faltantes. .replace(0, np.nan) cambia cada 0 de una Serie por np.nan, el número faltante de numpy, y cualquier cosa dividida entre NaN es NaN:

        pd.Series([5000, 5000]) / pd.Series([25, 0])                    # 200.0, inf
        pd.Series([5000, 5000]) / pd.Series([25, 0]).replace(0, np.nan)  # 200.0, NaN

        Ningún canal ni mes de 2025 tiene cero conversiones en estos datos, pero un informe mensual que se ejecuta cada mes tarde o temprano se topará con uno, y un inf en un gráfico es difícil de explicar.

        Paso 3: añade el mes a ambas claves

        Construye una etiqueta month en cada tabla con .dt.strftime("%Y-%m"), que convierte un datetime en texto como "2025-03" (%Y es el año y %m el mes de dos dígitos). Agrupa cada tabla por mes y haz merge por month. El grano ahora es una fila por mes en ambos lados.

        SQL y pandas lado a lado

        Paso SQL pandas
        conversiones COUNT(*) FILTER (WHERE converted) ("converted", "sum")
        CTE WITH conv AS (...) conv = ws.groupby(...)
        los nombres de la clave difieren ON s.channel = c.source rename o left_on/right_on
        afirmar que no hay duplicación razonarlo validate="one_to_one"
        NULLIF(x, 0) incluido x.replace(0, np.nan)

        Errores comunes

        • Unir sesiones en bruto con gasto en bruto. Incluso por ["source", "date"], el gasto de cada día se repite una vez por sesión de ese día.
        • Redondear el gasto antes de dividir. Calcula el costo por conversión con la suma sin redondear y redondea después.
        • Días de gasto faltantes. Faltan doce filas por caídas en marketing_spend; los totales quedan ligeramente por debajo, igual en SQL y en pandas.
        • Fechas como texto. Los filtros comparan una columna datetime64 con "2025-01-01"; pandas interpreta el texto, así que no hace falta ninguna conversión.

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