1. Lighthouse

Level 9 · Boss: the marketing dashboard

The request

From Jordan Reyes, Head of Marketing

“Budget season. I need one figure for my slide: what we spent per channel each month of 2025, which traffic sources convert, and what a conversion costs us per channel. Four charts, one picture.”

Your goal

Step 1 of 3. Set result to a DataFrame of 2025 marketing spend with one row per month and columns month (first day of the month, a date), email, paid and social (sum of amount, rounded to 2), sorted by month. Draw it as a line chart.

  • Use the marketing_spend DataFramenot done yet
  • Show exactly month email paid socialnot done yet
  • 12 rows, sorted as askednot done yet

Step 1 · your code

Python

Loading the editor…

    ResultPreview of marketing_spend · press Run to see yours
    Preview of marketing_spend
    spend_datechannelamount
    2023-01-01paid492.99
    2023-01-01email37.38
    2023-01-01social200.53
    2023-01-02paid709.13

    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.

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

        The lesson

        A dashboard is a grid of Axes

        plt.subplots(rows, cols) creates one Figure with a grid of Axes and returns them as a 2-D array:

        fig, axs = plt.subplots(2, 2, figsize=(12, 8))
        axs[0, 0].plot(...)     # top left
        axs[0, 1].barh(...)     # top right
        axs[1, 0].bar(...)      # bottom left
        axs[1, 1].hist(...)     # bottom right
        fig.suptitle("One title for the whole figure")
        plt.tight_layout()      # stops titles and labels from overlapping

        Rows and columns count from 0. pandas plots can go into a chosen cell with ax=: monthly.plot(ax=axs[0, 0]). When two charts should be compared by height, create the grid with sharey=True so they use the same scale.

        The three tables

        Spend per month. marketing_spend has one row per day and channel. A month column and a pivot give months down and channels across. In 2025 paid spend ran at about $21,000 to $28,000 a month from January to October and jumped to $47,475.61 in November.

        Conversion by source. Every session has a source and a boolean converted. Summing a boolean counts the True values, and the mean of a boolean is the share that’s True. In 2025, 9.6% of email sessions converted, 5.4% of direct, 1.8% of organic, 1.5% of paid and 0.7% of social.

        Cost per conversion. Divide each channel’s spend by the conversions its sessions produced. The channel names in marketing_spend (email, paid, social) match the source values in web_sessions, so after renaming source to channel the two small tables join one to one. Organic and direct traffic have no spend, and the inner merge leaves them out.

        cpc = spent.merge(conv, on="channel")
        cpc["cost_per_conversion"] = cpc["spend"] / cpc["conversions"]

        Email: $38,322.22 for 187 conversions, $204.93 each. Paid: $340,778.33 for 55, $6,195.97 each. Social: $131,540.77 for 19, $6,923.20 each. Sessions aren’t linked to orders in this data, so a conversion here is a converted session, not revenue. A careful slide says that in a footnote.

        Saving the figure

        In a notebook you’d save the dashboard for the slide with fig.savefig("marketing_2025.png", dpi=150, bbox_inches="tight"). Call it before plt.show(): after show, the figure is closed and the saved file comes out blank. Here the page shows your figure automatically, so you don’t need either call.

        Common mistakes

        • Comparing spend and conversions from different periods (2025 spend against all-years sessions).
        • Dividing spend by all sessions: that’s cost per visit, a different and much smaller number.
        • Forgetting plt.tight_layout(), so titles of the lower charts overlap the upper axis labels.
        • Writing axs[0][0] and axs[0, 0] interchangeably is fine, but axs[1] alone is a whole row.

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