1. Lighthouse

Level 6 · Boss: the channel that looks worse

The request

From Jordan Reyes, Head of Marketing

“Customers who find us on Pinterest spend about $100 more per order than Google Ads customers, so I want to move Google Ads budget to Pinterest. Before I do, can you check that the comparison is fair?”

Your goal

Step 1 of 3. Set result to a dict with the mean order_value per referral source, rounded to 2 decimals: keys "google_ads" and "pinterest".

  • Use the customers DataFramenot done yet
  • Use the orders DataFramenot done yet
  • Store a dict in resultnot done yet
Average completed-order value by referral source
Average completed-order value by referral source$0$250$500$750$1,000$1,250All orders, google_ads: $535.22All orders, pinterest: $632.14All ordersBusiness orders, google_ads: $1,172Business orders, pinterest: $1,027Business ordersConsumer orders, google_ads: $374.4Consumer orders, pinterest: $350.71Consumer ordersgoogle_adspinterest
Average completed-order value by referral source$0$250$500$750$1,000$1,250All orders, google_ads: $535.22All orders, pinterest: $632.14All ordersBusiness orders, google_ads: $1,172Business orders, pinterest: $1,027Consumer orders, google_ads: $374.4Consumer orders, pinterest: $350.71Consumer ordersgoogle_adspinterest

Overall, pinterest is ahead. Inside each segment, google_ads is ahead. Both are true at the same time.

Show the numbers
google_adspinterest
All orders$535.22$632.14
Business orders$1,172$1,027
Consumer orders$374.4$350.71

Step 1 · your code

Python

Loading the editor…

    ResultPreview of customers · press Run to see yours
    Preview of customers
    customer_idfirst_namelast_nameemailcity
    1OwenHudsonowen.hudson@icloud.comSan Francisco
    2DouglasMendozadmendoza69@gmail.comAtlanta
    3HenryBrookshbrooks@lakesidecafe.comLos Angeles
    4BrianGrahambgraham@bluecedarhomes.comSeattle

    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 customers · press Run to see yours
      Preview of customers
      customer_idfirst_namelast_nameemailcity
      1OwenHudsonowen.hudson@icloud.comSan Francisco
      2DouglasMendozadmendoza69@gmail.comAtlanta
      3HenryBrookshbrooks@lakesidecafe.comLos Angeles
      4BrianGrahambgraham@bluecedarhomes.comSeattle

      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 customers · press Run to see yours
        Preview of customers
        customer_idfirst_namelast_nameemailcity
        1OwenHudsonowen.hudson@icloud.comSan Francisco
        2DouglasMendozadmendoza69@gmail.comAtlanta
        3HenryBrookshbrooks@lakesidecafe.comLos Angeles
        4BrianGrahambgraham@bluecedarhomes.comSeattle

        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.

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

        Simpson’s paradox

        Simpson’s paradox is when a comparison points one way in every subgroup and the other way when the subgroups are pooled. It sounds like a trick. It is ordinary, and it happens whenever two groups contain different mixes of something that strongly affects the outcome.

        At Harbor & Pine that something is the segment. Business customers’ completed orders average about $1,050; consumer orders about $350. Any group with more business customers in it will have a higher average order value, whoever those customers are.

        The mix behind the overall gap

        pd.crosstab(two["referral_source"], two["segment"], normalize="index")
        referral_source business consumer
        google_ads 20% 80%
        pinterest 42% 58%

        Pinterest brings twice the share of business customers. Its overall average is pulled up by that mix, not by its customers spending more than comparable customers from Google Ads. Inside each segment, the comparison runs the other way.

        Holding the mix equal

        To compare fairly, give both sources the same mix and recompute. Take one common set of weights (the segment shares of all orders from both sources, 31% business and 69% consumer), and average each source’s segment means with those weights:

        mix = two["segment"].value_counts(normalize=True)
        by_segment.mul(mix, axis=0).sum()

        This is called standardization. It answers: “what would each source’s average be if both had the same customers by segment?”

        How sure is the reversal?

        Not very, and saying so is part of the job. Order values vary a lot (a standard deviation around $1,150 for business orders), and there are only 155 google_ads business orders. The within-segment gaps of $145 and $24 are well inside what chance produces with groups this size. The solid finding is the negative one: the $97 overall gap is a mix effect, so it is no reason to move budget.

        Common mistakes

        • Comparing pooled averages of groups with different compositions. Always ask what drives the outcome, then check whether the groups differ on it.
        • Declaring the reversed result the winner. The reversal can be noise; the mix effect is the finding.
        • Standardizing with one group’s own mix. Use a common mix for both, or the comparison is still tilted.
        • Splitting by every column until something flips. Choose the variable that drives the outcome for a reason you can explain, before you look.

        Pick what may run on this site. You can change this any time from "Cookie settings" at the bottom of every page.