The request

From Megan Collins, Head of Sales
Your goal
Set result to a DataFrame with one row per calendar month from January 2023 to December 2025 (36 rows) and columns order_date (the first day of the month, e.g. 2023-01-01) and n_orders (number of orders placed that month, all statuses), sorted by order_date ascending, index reset.
- Use the
ordersDataFramenot done yet - Show exactly
order_daten_ordersnot done yet - 36 rows, sorted as askednot done yet
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 Megan 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 |
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 |
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 |
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
Grouping by time
groupby groups rows that share a value. Time needs something slightly different: you want every
order from 3 March 2025 at 14:07 and every order from 28 March at 21:50 to land in the same
bucket, “March 2025”. resample does exactly that. It is a groupby for time: it cuts the
timeline into regular periods (days, weeks, months, quarters, years) and groups the rows by the
period they fall into.
It needs the timestamps in the index, so the pattern always starts with set_index:
monthly = orders.set_index("order_date").resample("MS").size()
monthly.head(3)
# order_date
# 2023-01-01 192
# 2023-02-01 157
# 2023-03-01 191
After resample you call an aggregation, exactly as after groupby: .size() counts rows,
["shipping_fee"].sum() adds a column, .mean(), .max() and friends all work.
The frequency codes
The string you pass is a frequency alias. The ones analysts use most:
| alias | period | label |
|---|---|---|
"D" |
day | the day |
"W-SUN" (or "W") |
week ending Sunday | the Sunday |
"MS" |
calendar month | first day of the month |
"ME" |
calendar month | last day of the month |
"QS" / "QE" |
quarter | first / last day |
"YS" / "YE" |
year | first / last day |
"MS" and "ME" produce the same counts; only the label differs. January 2023 is 2023-01-01
with "MS" and 2023-01-31 with "ME". Pick the one that matches whatever you’ll join or compare
against. Here Megan’s targets use the first of the month, so "MS" it is. (Older tutorials write
"M"; recent pandas versions have renamed it "ME" and warn about the old name.)
What the result looks like
Harbor & Pine’s orders span 36 months. The quietest was February 2023 with 157 orders; the busiest was December 2025 with 777. Every November and December is roughly double a normal month, which you’ll see straight away when you scan the column.
The result is a Series indexed by month. To share it as a table, name the values and move the index back into a column:
monthly.rename("n_orders").reset_index()
# order_date n_orders
# 0 2023-01-01 192
# 1 2023-02-01 157
Why not group by month text?
You could build a "2025-03" text column with dt.strftime and groupby it. That works, but
resample has two advantages. The result keeps real dates, so you can plot it, shift it and
compute growth on it. And a month with no orders still appears, with a count of 0, instead of
silently vanishing from the table, which matters as soon as you compute “change since last month”.
Common mistakes
- Calling
orders.resample("MS")withoutset_index. pandas raises aTypeErrorasking for a DatetimeIndex (or passon="order_date"instead). - Using
"ME"when the other table is keyed by month starts, or the reverse. The numbers are right but nothing lines up when you merge. - Using
.count()on the whole frame, which returns one count per column..size()returns one number per month. - Writing the deprecated
"M"alias from older code.
Lantern lit!
Practised: datetimeindexresampleset indexmonth startsize
- Solved
- No hints
- First run
