The request

From Laura Bennett, Head of Finance
Your goal
Step 1 of 4. Set result to a DataFrame with columns channel, 2023, 2024, 2025: the sum of completed line revenue per channel and year of order_date, rounded to 2. One row per channel, sorted A to Z.
- Use the
ordersDataFramenot done yet - Use the
order_itemsDataFramenot done yet - Show exactly
channel202320242025not done yet
Step 2 of 4. Add a column growth_2025 = 2025 revenue / 2024 revenue - 1, rounded to 3. Set result to a DataFrame with columns channel, 2023, 2024, 2025, growth_2025 (revenue rounded to 2), one row per channel, sorted by growth_2025 from highest to lowest.
- Use the
ordersDataFramenot done yet - Use the
order_itemsDataFramenot done yet - Show exactly
channel202320242025growth_2025not done yet
Step 3 of 4. Using all orders (any status) whose order_date is in 2025, set result to a DataFrame with columns month, app, marketplace, web: the number of orders per month name (e.g. 'January', from dt.month_name()) and channel. Rows in calendar order, January to December.
- Use the
ordersDataFramenot done yet - Show exactly
monthappmarketplacewebnot done yet - 12 rows, sorted as askednot done yet
Step 4 of 4. From the 2025 month by channel grid of step 3, set result to a dict {'january_leader':
- Use the
ordersDataFramenot done yet - Store a dict in
resultnot done yet
Step 1 · your code
Loading the editor…
Reference solution
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
Step 2 · your code
Loading the editor…
Reference solution
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
Step 3 · your code
Loading the editor…
Reference solution
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
Step 4 · your code
Loading the editor…
Reference solution
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
What Laura gets, and why
DataFrames
Loaded for you as pandas DataFrames, plus pd and np.
orders12,000 rows · 7 columns
order_idintegercustomer_idintegerorder_datetimestampstatusvarcharchannelvarcharshipping_feedecimalcoupon_codevarchar
| order_id | customer_id | order_date | status | channel | shipping_fee | coupon_code |
|---|---|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web | 0.00 | NaN |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web | 0.00 | NaN |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace | 4.99 | WELCOME10 |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace | 4.99 | WELCOME10 |
| 1005 | 82 | 2023-01-02 11:43:36 | completed | web | 0.00 | WELCOME10 |
order_items30,338 rows · 6 columns
order_item_idintegerorder_idintegerproduct_idintegerquantityintegerunit_pricedecimaldiscount_pctdecimal
| order_item_id | order_id | product_id | quantity | unit_price | discount_pct |
|---|---|---|---|---|---|
| 1 | 1001 | 121 | 9 | 31.99 | 0.00 |
| 2 | 1001 | 154 | 3 | 11.99 | 0.00 |
| 3 | 1002 | 154 | 4 | 11.99 | 0.00 |
| 4 | 1002 | 194 | 1 | 149.00 | 0.00 |
| 5 | 1002 | 193 | 2 | 19.99 | 0.00 |
customers2,018 rows · 9 columns
customer_idintegerfirst_namevarcharlast_namevarcharemailvarcharcityvarcharstatevarcharsignup_datedatesegmentvarcharreferral_sourcevarchar
| customer_id | first_name | last_name | city | state | signup_date | segment | referral_source | |
|---|---|---|---|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco | CA | 2022-06-02 | consumer | search |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta | GA | 2022-06-02 | consumer | NaN |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles | CA | 2022-06-04 | business | NaN |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle | WA | 2022-06-06 | business | |
| 5 | Kyle | Jimenez | kjimenez27@yahoo.com | Indianapolis | IN | 2022-06-07 | consumer | NaN |
products120 rows · 8 columns
product_idintegernamevarcharcategoryvarcharsubcategoryvarcharunit_pricedecimalunit_costdecimallaunched_ondateis_activeboolean
| product_id | name | category | subcategory | unit_price | unit_cost | launched_on | is_active |
|---|---|---|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 | NaN | 2023-09-11 | true |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 | 503.50 | 2024-05-16 | true |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 | 429.87 | 2023-05-27 | true |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 | 214.70 | 2022-12-20 | true |
| 105 | Avery Leather Bed Frame | Furniture | Beds | 449.00 | 232.70 | 2021-08-31 | true |
returns1,673 rows · 5 columns
return_idintegerorder_item_idintegerreturned_ondatereasonvarcharrefund_amountdecimal
| return_id | order_item_id | returned_on | reason | refund_amount |
|---|---|---|---|---|
| 1 | 14 | 2023-01-10 | damaged | 83.69 |
| 2 | 42 | 2023-01-10 | wrong_item | 19.99 |
| 3 | 105 | 2023-01-14 | wrong_item | 10.99 |
| 4 | 24 | 2023-01-16 | changed_mind | 26.99 |
| 5 | 126 | 2023-01-16 | late | 20.99 |
employees60 rows · 8 columns
employee_idintegerfirst_namevarcharlast_namevarchardepartmentvarchartitlevarcharmanager_idintegerhire_datedatesalaryinteger
| employee_id | first_name | last_name | department | title | manager_id | hire_date | salary |
|---|---|---|---|---|---|---|---|
| 1 | Catherine | Hale | Executive | Chief Executive Officer | NaN | 2016-03-01 | 285000 |
| 2 | Robert | Kim | Executive | Chief Operating Officer | 1 | 2017-01-09 | 235000 |
| 3 | Megan | Collins | Sales | Head of Sales | 1 | 2018-04-16 | 172000 |
| 4 | Chloe | Gutierrez | Sales | Account Executive | 3 | 2020-01-21 | 72000 |
| 5 | Frank | Bell | Sales | Account Executive | 3 | 2023-05-21 | 73000 |
web_sessions40,000 rows · 7 columns
session_idintegercustomer_idintegerstarted_attimestampdevicevarcharsourcevarcharpages_viewedintegerconvertedboolean
| session_id | customer_id | started_at | device | source | pages_viewed | converted |
|---|---|---|---|---|---|---|
| 1 | NaN | 2023-01-01 23:02:24 | mobile | paid | 1 | false |
| 2 | 18 | 2023-01-01 15:07:49 | desktop | paid | 1 | false |
| 3 | NaN | 2023-01-01 16:55:46 | desktop | direct | 7 | false |
| 4 | NaN | 2023-01-01 12:43:28 | mobile | organic | 3 | false |
| 5 | NaN | 2023-01-01 13:06:21 | mobile | social | 2 | false |
marketing_spend3,276 rows · 3 columns
spend_datedatechannelvarcharamountdecimal
| spend_date | channel | amount |
|---|---|---|
| 2023-01-01 | paid | 492.99 |
| 2023-01-01 | 37.38 | |
| 2023-01-01 | social | 200.53 |
| 2023-01-02 | paid | 709.13 |
| 2023-01-02 | 64.13 |
The lesson
Two grids, one meeting
This boss uses the chapter’s grid tools on a real board request. The data starts long: one row per order line or per order. The board reads grids, and asks questions about them.
Each step runs on a fresh copy of the tables, so each one builds what it needs again:
items = order_items.merge(orders[["order_id", "order_date", "status", "channel"]], on="order_id")
items = items[items["status"] == "completed"].copy()
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount_pct"] / 100)
items["year"] = items["order_date"].dt.year
Growth in wide form
With channel down the side and year across, completed revenue totals 1,600,511.41 for 2023, 1,993,605.32 for 2024 and 2,300,170.84 for 2025. Each year is a column, so growth is column arithmetic, computed for every channel at once:
grid["growth_2025"] = grid[2025] / grid[2024] - 1
The labels came from dt.year, so they’re integers. Compute growth on the unrounded totals, then
round. Sorted by growth, the story is plain: the app grew 53.1% and became the biggest channel, the
marketplace grew 13.3%, web fell 7.2%.
Months in calendar order
dt.month_name() turns a date into “January”, “February” and so on, which is what a reader wants
to see. The catch: pandas sorts text alphabetically, so a pivot on month names comes out April,
August, December, February… Two fixes:
months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"]
grid.reindex(months) # rows in the order of the list
or keep the month number (dt.month) next to the name, sort by the number, and then set the name as
the index. reindex is shorter when you already have the list; it also adds a row of NaN for any
month that’s missing, which makes gaps visible instead of silently dropping them.
Reading the grid: idxmax and sum(axis=1)
Two short methods answer most “which one?” questions:
.idxmax()on a Series returns the label of its largest value (.max()returns the value).grid.loc["January"].idxmax()is the channel that sold most in January.grid.sum(axis=1)adds across each row (one total per month);grid.sum()adds down each column (one total per channel). Chain.idxmax()to name the biggest.
grid.idxmax(axis=1) # the leading channel in every month
In 2025 the app led nine months, but web led January, June and August. A headline like “the app leads every month” would be wrong, and these two lines are how you check it.
Common mistakes
- Pricing lines with
products["unit_price"]instead of the price on the order line. - Writing
grid["2025"]for an integer column label. - Leaving months in alphabetical order.
- Using
.max()when the question asks which month or channel: that’s.idxmax(). - Summing with
axis=0when you want one total per row.
Lantern lit!
Practised: pivot tablegrowth ratemonth namereindexidxmaxsum axis
- Solved
- No hints
- First run
