The request

From Jordan Reyes, Head of Marketing
Your goal
Set result to a DataFrame of products with category 'Lighting' and unit_price between 50 and 200 inclusive, columns name and unit_price, sorted by unit_price ascending then name ascending, with the index reset.
- Use the
productsDataFramenot done yet - Show exactly
nameunit_pricenot done yet - 10 rows, sorted as askednot done yet
Your code
Loading the editor…
Reference solution
| product_id | name | category | subcategory | unit_price |
|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 |
Mote checks your result
Press Run. Each row that matches the goal becomes a firefly in your lantern.
What Jordan gets, and why
DataFrames
Loaded for you as pandas DataFrames, plus pd and np.
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 |
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 |
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 |
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
Ranges with between
“From $50 to $200” is a range. You could write two comparisons:
(products["unit_price"] >= 50) & (products["unit_price"] <= 200)
between says the same thing more clearly:
products["unit_price"].between(50, 200)
By default both ends are included, matching how people usually speak about ranges. Change it
with inclusive="left", "right" or "neither" when the brief says “under $200” or “over $50”.
Always check which one the brief means. The Harbor & Pine catalog has products at exactly $189 and
$159, and you’ll find edge cases like a $200 product in real catalogs all the time.
between works on dates too, which you’ll use constantly:
orders[orders["order_date"].between("2025-11-01", "2025-11-30 23:59:59")]
Filtering on text
Text columns have a .str accessor with vectorised string methods that return masks:
products["name"].str.contains("Lamp") # name includes 'Lamp'
products["name"].str.contains("lamp", case=False) # ignore case
products["name"].str.startswith("Oak")
products["subcategory"].isin(["Pendants", "Chandeliers"])
So “every product with Brass in the name” is
products[products["name"].str.contains("Brass")].
Two cautions with str.contains:
- It treats the pattern as a regular expression by default, so characters like
(,.and+have special meanings. Passregex=Falseto match text literally. - On a column with missing values, it returns NaN for those rows, and using that as a mask fails.
Pass
na=Falseto treat missing text as “doesn’t contain”.
Building the answer
mask = (products["category"] == "Lighting") & products["unit_price"].between(50, 200)
(
products.loc[mask, ["name", "unit_price"]]
.sort_values(["unit_price", "name"])
.reset_index(drop=True)
)
sort_values with a list sorts ascending on every column by default, which is what this brief wants.
The price is a snapshot
products.unit_price is the current list price. Some products had a price rise in March 2025,
so older order lines in order_items show a lower price paid. For a campaign list, the current
price is right. For revenue, always use the price on the order line. Picking the right column for
the question is part of the job.
Check the edges
After filtering a range, look at the smallest and largest values that made it through:
result["unit_price"].agg(["min", "max"]). If the brief says $50 to $200 and the minimum is $54.99,
check whether a product at exactly $50 exists and was wrongly excluded. Edge checks like this take
seconds and catch the inclusive/exclusive mistakes that reviewers love to find.
Common mistakes
- Using
betweenwhen the brief says “under” and including the upper bound. - Forgetting
na=Falseonstr.containsfor a column with gaps. - Searching with the wrong case:
str.contains("lamp")misses “Floor Lamp”.
Lantern lit!
Practised: betweenstr containsfilteringsorting
- Solved
- No hints
- First run
