1. Lighthouse

Level 6 · Boss: write up the spring test

The request

From Jordan Reyes, Head of Marketing

“Before the holiday test we ran a smaller banner test in the spring, March and April 2025, same setup. Nobody ever analysed it properly. Can you write it up, with a clear verdict I can forward to Catherine?”

Your goal

Step 1 of 4. Set result to a DataFrame with one row per arm (A, then B) and columns arm, sessions (count), conversions (sessions with converted True) and rate (conversions / sessions, rounded to 4).

  • Use the web_sessions DataFramenot done yet
  • Show exactly arm sessions conversions ratenot done yet
  • 2 rows, sorted as askednot done yet
Spring banner test, conversion rate per arm
Spring banner test, conversion rate per arm0%1%2%3%4%A: no banner: 3.8%3.8%A: no bannerB: banner: 3%3%B: bannerArmSessions that converted
Spring banner test, conversion rate per arm0%1%2%3%4%A: no banner: 3.8%3.8%A: no bannerB: banner: 3%3%B: bannerArmSessions that converted

1,068 sessions in each arm. The banner arm converted less often. Your job is to say whether that gap means anything.

Show the numbers
ArmSessions that converted
A: no banner3.8%
B: banner3%

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.

        Step 4 · 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

          The four numbers of a write-up

          A test write-up answers four questions, in this order:

          1. Was the test healthy? The arm table: sessions per arm (the sample ratio check) and conversions.
          2. What happened? The lift, absolute and relative.
          3. How sure are we? The 95% confidence interval and the p-value.
          4. What could the test have seen? The minimum detectable effect, and the verdict.

          You built the first three for the holiday test. The spring test is smaller: 1,068 sessions per arm against 2,604, which is exactly why the fourth question matters.

          Minimum detectable effect

          Power is the chance a test detects an effect that is really there. The minimum detectable effect (MDE) is the smallest effect the test detects with a chosen power, usually 80%, at the usual 5% level:

          nd = NormalDist()
          mde_abs = (nd.inv_cdf(0.975) + nd.inv_cdf(0.8)) * math.sqrt(2 * p_a * (1 - p_a) / n_a)
          mde_rel = mde_abs / p_a      # about 0.61

          On the spring sample, only a change of about 61% in conversion had a fair chance of showing up. Banners, button colours and copy changes typically move conversion by a few percent. This test was too small for its question before it started, and it is better to say so than to let a flat result read as “the banner does nothing”.

          The same formula answers the planning question in reverse: fix the lift you care about and solve for the sessions you need. That is how you avoid running the next underpowered test.

          Writing the verdict

          Fix the decision rule before you look: ship on a significant gain, stop on a significant loss, otherwise call it inconclusive. Then the verdict is computed, not argued, and the write-up for Jordan fits in three sentences:

          “The banner arm converted 3.00% versus 3.84% (−0.84 points, −22%); the 95% interval runs from −2.38 to +0.70 points, p = 0.28. Verdict: inconclusive. With 1,068 sessions per arm the test could only detect changes of about 61%, so it cannot rule out a typical banner effect either way.”

          Common mistakes

          • Leading with the relative lift. “−22%” alone sounds like a disaster. With its interval it is a shrug.
          • Treating “inconclusive” as “no effect”. An underpowered test that finds nothing has learned very little. Say how big an effect it could have found.
          • Typing the verdict. If the rule is written in code, anyone can rerun it and get the same call, even when they would have preferred a different one.
          • Reusing the holiday conclusion. Season, traffic mix and sample size differ. Each test gets its own write-up.

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