1. Lighthouse

Level 6 · Boss: who converts on the website?

The request

From Jordan Reyes, Head of Marketing

“Before we move budget around I want three facts from the session data: how many paid mobile visits converted, how well our engaged visitors convert, and what our most engaged email visits look like.”

Your goal

Step 1 of 3. Set result to the number of sessions (an int) where device is 'mobile', source is 'paid' and converted is True.

  • Use the web_sessions DataFramenot done yet
  • Store a number in resultnot done yet

Step 1 · your code

Python

Loading the editor…

    ResultPreview of web_sessions · press Run to see yours
    Preview of 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 checks your result

    Press Run. Each row that matches the goal becomes a firefly in your lantern.

    Step 2 · your code

    Python

    Loading the editor…

      ResultPreview of web_sessions · press Run to see yours
      Preview of 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 checks your result

      Press Run. Each row that matches the goal becomes a firefly in your lantern.

      Step 3 · your code

      Python

      Loading the editor…

        ResultPreview of web_sessions · press Run to see yours
        Preview of 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 checks your result

        Press Run. Each row that matches the goal becomes a firefly in your lantern.

        DataFrames

        Loaded for you as pandas DataFrames, plus pd and np.

        web_sessions40,000 rows · 7 columns
        • session_idinteger
        • customer_idinteger
        • started_attimestamp
        • devicevarchar
        • sourcevarchar
        • pages_viewedinteger
        • convertedboolean
        First 5 rows of 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
        customers2,018 rows · 9 columns
        • customer_idinteger
        • first_namevarchar
        • last_namevarchar
        • emailvarchar
        • cityvarchar
        • statevarchar
        • signup_datedate
        • segmentvarchar
        • referral_sourcevarchar
        First 5 rows of 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 rows · 8 columns
        • product_idinteger
        • namevarchar
        • categoryvarchar
        • subcategoryvarchar
        • unit_pricedecimal
        • unit_costdecimal
        • launched_ondate
        • is_activeboolean
        First 5 rows of 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 rows · 7 columns
        • order_idinteger
        • customer_idinteger
        • order_datetimestamp
        • statusvarchar
        • channelvarchar
        • shipping_feedecimal
        • coupon_codevarchar
        First 5 rows of 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 rows · 6 columns
        • order_item_idinteger
        • order_idinteger
        • product_idinteger
        • quantityinteger
        • unit_pricedecimal
        • discount_pctdecimal
        First 5 rows of 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 rows · 5 columns
        • return_idinteger
        • order_item_idinteger
        • returned_ondate
        • reasonvarchar
        • refund_amountdecimal
        First 5 rows of 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 rows · 8 columns
        • employee_idinteger
        • first_namevarchar
        • last_namevarchar
        • departmentvarchar
        • titlevarchar
        • manager_idinteger
        • hire_datedate
        • salaryinteger
        First 5 rows of 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
        marketing_spend3,276 rows · 3 columns
        • spend_datedate
        • channelvarchar
        • amountdecimal
        First 5 rows of marketing_spend
        spend_datechannelamount
        2023-01-01paid492.99
        2023-01-01email37.38
        2023-01-01social200.53
        2023-01-02paid709.13
        2023-01-02email64.13

        The lesson

        A funnel dataset

        web_sessions has one row per visit to the website: when it started, the device, the traffic source, how many pages the visitor viewed and whether the visit ended in a purchase. It’s 40,000 rows, and every question in this level is a filter followed by a count, a share or a sort.

        Step 1: count with a mask

        You don’t need to filter rows to count them. A mask is a column of True and False, and True counts as 1:

        mask = (
            (web_sessions["device"] == "mobile")
            & (web_sessions["source"] == "paid")
            & web_sessions["converted"]
        )
        mask.sum()

        Notice the last condition has no == True. converted is already boolean, so it’s a mask on its own. Writing web_sessions["converted"] == True works too, but it’s redundant. .sum() on a mask returns a NumPy integer; wrapping it in int() gives a plain Python number.

        Step 2: a rate is a mean

        The share of sessions that converted is the number of True values divided by the number of rows, which is exactly the mean of a boolean column:

        engaged = web_sessions[web_sessions["pages_viewed"] >= 10]
        engaged["converted"].mean()

        This is one of the most useful tricks in pandas. Conversion rate, return rate, share of orders with a coupon: each is .mean() of the right mask, on the right set of rows. The “right set of rows” is the important part. Conversion “among engaged sessions” has a different denominator from conversion over all sessions, and mixing them up is one of the most common errors in funnel reporting.

        Compare it with the overall rate, web_sessions["converted"].mean(). The engaged rate is much higher, which is expected: someone who browses ten pages is far more likely to buy. That doesn’t mean making people view more pages would make them buy; engagement and buying intent move together. Keep correlation and cause separate when you present this.

        Step 3: a ranked list

        email = web_sessions[web_sessions["source"] == "email"]
        (
            email.sort_values(["pages_viewed", "session_id"], ascending=[False, True])
            .head(15)[["session_id", "device", "pages_viewed", "converted"]]
            .reset_index(drop=True)
        )

        Many sessions share the same page count, so session_id as the tie-breaker makes the list reproducible. Filter first, then sort: sorting 40,000 rows and filtering later gives the same answer but does more work, and it’s easier to make mistakes in the order of steps.

        Reading the results like a marketer

        • A conversion count without a denominator is hard to judge. “51 paid mobile conversions” means little until you know how many paid mobile sessions there were.
        • Sessions without a customer_id are anonymous visitors. In this dataset, only known customers convert, which mirrors how many stores track purchases.

        Common mistakes

        • Using and between masks instead of &.
        • Computing the step 2 share over all sessions instead of the engaged ones.
        • Sorting by pages_viewed only, so the 15th row changes between runs.