1. Lighthouse

Level 8 · Boss: the holiday season review

The request

From Catherine Hale, Chief Executive Officer

“Before we plan next November I want the holiday season in numbers: our best week of 2025, how a typical month compares through the year, how this Q4 did against last year, and how much of each year's revenue lands in each quarter.”

Your goal

Step 1 of 4. Build daily revenue (every calendar day) and its rolling 7-day sum (the day and the 6 days before it), computed on the full history. Set result to a DataFrame with the 3 days of 2025 with the highest rolling 7-day sum: columns order_date (the last day of the window), weekday (its English day name, e.g. 'Thursday') and revenue_7d (rounded to 2), sorted by revenue_7d descending, index reset.

  • Use the orders DataFramenot done yet
  • Use the order_items DataFramenot done yet
  • Show exactly order_date weekday revenue_7dnot done yet

Step 1 · your code

Python

Loading the editor…

    ResultPreview of orders · press Run to see yours
    Preview of orders
    order_idcustomer_idorder_datestatuschannel
    10011252023-01-01 20:51:19completedweb
    10021562023-01-02 07:58:46completedweb
    1003222023-01-02 10:25:27completedmarketplace
    1004482023-01-02 11:21:51completedmarketplace

    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 orders · press Run to see yours
      Preview of orders
      order_idcustomer_idorder_datestatuschannel
      10011252023-01-01 20:51:19completedweb
      10021562023-01-02 07:58:46completedweb
      1003222023-01-02 10:25:27completedmarketplace
      1004482023-01-02 11:21:51completedmarketplace

      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 orders · press Run to see yours
        Preview of orders
        order_idcustomer_idorder_datestatuschannel
        10011252023-01-01 20:51:19completedweb
        10021562023-01-02 07:58:46completedweb
        1003222023-01-02 10:25:27completedmarketplace
        1004482023-01-02 11:21:51completedmarketplace

        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 orders · press Run to see yours
          Preview of orders
          order_idcustomer_idorder_datestatuschannel
          10011252023-01-01 20:51:19completedweb
          10021562023-01-02 07:58:46completedweb
          1003222023-01-02 10:25:27completedmarketplace
          1004482023-01-02 11:21:51completedmarketplace

          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.

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

          One table, four views of time

          Every step starts from lines: one row per order line of a completed order, with its line_revenue and the order’s order_date. From there, time can be cut in two ways. Resampling puts rows into consecutive periods on a timeline (2 December 2025, the week ending on it, November 2025). Date parts pull one piece out of each date (its weekday, its month of the year, its quarter) so rows from different years can be grouped together. The boss needs both.

          Date parts with .dt

          The .dt accessor extracts parts of a datetime column:

          d = lines["order_date"]
          d.dt.year           # 2025
          d.dt.quarter        # 1 to 4
          d.dt.month          # 1 to 12
          d.dt.month_name()   # 'November'
          d.dt.day_name()     # 'Friday'

          year, quarter and month are attributes (no brackets) and return numbers; month_name() and day_name() are methods and return text. For any other label, strftime formats a date with codes: "%Y-%m" gives '2025-11', "%b" gives 'Nov', "%B" the full name, "%a" a short weekday. On a DatetimeIndex the same methods are available directly, without .dt: table.index.strftime("%b").

          Months in calendar order

          Month names are text, and text sorts alphabetically: April, August, December… To keep calendar order, carry the month number next to the name and sort by the number:

          parts = lines.assign(month_num=lines["order_date"].dt.month, month=lines["order_date"].dt.month_name())
          parts.groupby(["month_num", "month"], as_index=False)["line_revenue"].sum().sort_values("month_num")

          Grouping by both keys costs nothing (each number has one name) and gives you a column to sort on. Another option is an ordered categorical, pd.Categorical(names, categories=[...], ordered=True), but a month number is simpler and survives every merge.

          Averaging a month over three years needs two steps: first sum per year and month, then average those totals. Averaging lines directly would give the average order line, not the average month.

          Rolling windows and growth, history first

          The best week of 2025 is a rolling 7-day sum over a daily series, built on the full history and filtered to 2025 afterwards. It ends on Thursday 4 December 2025 at $104,267.39, when the window holds the whole Black Friday to Cyber Monday run. In 2024 the best window was bigger, $117,208.44.

          The same rule applies to year-over-year growth: pct_change(12) needs the 2024 months in the series, so select Q4 2025 only at the end. The answer is uncomfortable: November 2025 revenue fell 8.4% on November 2024 although order counts grew 13.7%, the kind of result you report plainly, with the order count next to it.

          Shares of each year

          transform("sum") returns the group total on every row of the group, so a quarter’s share of its year is one division. Q4 brought 0.375 of 2023 revenue, 0.381 of 2024 and 0.328 of 2025.

          Common mistakes

          • Sorting month names alphabetically instead of by month number.
          • Writing dt.month_name without brackets, which gives a method, not names.
          • Filtering to 2025 before rolling or pct_change(12), which turns the start of the period into NaN.
          • Dividing by the grand total of all years instead of each year’s own total.

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