1. Lighthouse

Level 4 · Which weekday sells most?

The request

From Jordan Reyes, Head of Marketing

“I'm scheduling next year's email sends. In 2025, how many orders did we get on each day of the week? Keep the days in calendar order, Monday first.”

Your goal

Using orders placed in 2025 (all statuses), set result to a DataFrame with columns weekday (the English day name, e.g. 'Monday') and n_orders, one row per day, ordered Monday to Sunday, index reset.

  • Use the orders DataFramenot done yet
  • Show exactly weekday n_ordersnot done yet
  • 7 rows, sorted as askednot done yet

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

    Dates are not text

    A date stored as text, '2025-03-14 19:02:11', can’t tell you its weekday, can’t be compared safely, and sorts correctly only by luck of the format. Convert it to a real datetime:

    pd.to_datetime(orders["order_date"])

    In this game the date columns were parsed when the tables were loaded, so order_date already has dtype datetime64[ns], and pd.to_datetime simply returns it unchanged. On the job, a plain pd.read_csv gives you text, so converting is usually the first line of any date work. You can do it at load time with pd.read_csv(path, parse_dates=["order_date"]).

    When some values are malformed, errors="coerce" turns them into NaT instead of failing, and a format= string makes parsing strict and fast:

    pd.to_datetime(raw_dates, format="%Y-%m-%d", errors="coerce")

    Then count the NaT values to see how many dates didn’t parse. Coercing silently and moving on hides data problems.

    The .dt accessor

    Datetime columns have a .dt accessor, like .str for text:

    d = orders["order_date"]
    d.dt.year             # 2025
    d.dt.month            # 3
    d.dt.day_name()       # 'Friday'
    d.dt.dayofweek        # 0 = Monday ... 6 = Sunday
    d.dt.hour             # 19
    d.dt.date             # the date part only
    d.dt.to_period("M")   # 2025-03, a monthly period
    d.dt.strftime("%Y-%m")   # '2025-03' as text

    Filtering by date works with comparisons and between, and pandas accepts date strings on the other side:

    orders[orders["order_date"] >= "2025-01-01"]
    orders[orders["order_date"].dt.year == 2025]

    Sorting days in calendar order

    Day names sort alphabetically (Friday, Monday, Saturday…), which nobody wants on a chart. Group by the day number as well and sort by it:

    (
        in_2025.assign(day_num=d.dt.dayofweek, weekday=d.dt.day_name())
        .groupby(["day_num", "weekday"], as_index=False)
        .agg(n_orders=("order_id", "count"))
        .sort_values("day_num")[["weekday", "n_orders"]]
    )

    An alternative is an ordered categorical: pd.Categorical(names, categories=["Monday", ..., "Sunday"], ordered=True), which also sorts correctly in charts.

    Other type fixes

    Dates are the most common type problem, but not the only one:

    pd.to_numeric(raw_prices, errors="coerce")          # text to numbers, bad values to NaN
    web_sessions["customer_id"].astype("Int64")         # float IDs back to integers, keeping missing
    products["is_active"].astype(int)                   # True/False to 1/0

    Int64 (capital I) is pandas’ nullable integer type. Plain astype(int) fails on a column with missing values, which is why web_sessions.customer_id loads as float in the first place.

    Reading the result

    Weekend days come out ahead for Harbor & Pine, which fits a home-goods store: people browse furniture and decor when they have time. Before acting on it, check whether the pattern holds in 2023 and 2024 too, and whether it’s driven by a few big sale weekends like Black Friday. One year of weekday counts is a hint, not a law.

    Common mistakes

    • Sorting by the day name and getting Friday first.
    • Comparing dates stored as text with dates as datetimes.
    • Using errors="coerce" and never counting how many values became NaT.