The request

From Jordan Reyes, Head of Marketing
Your goal
Cut completed["order_value"] into the bands given in the starter with pd.cut(..., bins=bins, labels=labels). Set result to the share of orders in each band, as returned by .value_counts(sort=False, normalize=True), rounded to 3 decimals (a Series indexed by band, bands in order).
- Use the
ordersDataFramenot done yet - Use the
order_itemsDataFramenot done yet - Show exactly
order_valueproportionnot done yet
Small baskets are a quarter of all orders and 2% of revenue. Orders above $1,000 are 15% of orders and more than half of revenue.
Show the numbers
| Order value band | Orders | Revenue |
|---|---|---|
| under $100 | 26.2% | 2.3% |
| $100-250 | 21.8% | 6.9% |
| $250-500 | 19.8% | 13.2% |
| $500-1,000 | 16.7% | 21.8% |
| $1,000-2,500 | 12.4% | 34.5% |
| $2,500+ | 3% | 21.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.
What Jordan 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
Shape before summary
Last chapter you saw that the mean order ($543) is twice the median ($269). That gap tells you the column is right-skewed, but not what the shape looks like. A distribution answers “how many values are there at each size?”, and to see it you group the values into ranges, called bins or bands, and count each one.
pandas measures the lopsidedness directly:
completed["order_value"].skew() # 3.95
A skew of 0 means symmetric. Above 1 is strongly right-skewed: most values are small, with a long tail of big ones. Order values sit at almost 4.
Cutting values into bands
pd.cut assigns each value to a band you define:
bins = [0, 100, 250, 500, 1000, 2500, float("inf")]
labels = ["under $100", "$100-250", "$250-500", "$500-1,000", "$1,000-2,500", "$2,500+"]
bands = pd.cut(completed["order_value"], bins=bins, labels=labels)
bands.value_counts(sort=False)
Each band includes its right edge: an order of exactly $100.00 lands in “under $100”. A value
outside every band (here the 4 orders worth $0 or less) gets NaN and is left out of the counts.
sort=False keeps the bands in their natural order instead of sorting by count.
Uneven bands on purpose
The bands here get wider as values grow: $100, $150, $250, $500, $1,500 wide. On a skewed column that is the point. Equal $500 bands would put two thirds of orders in the first band and leave the top bands with a handful each. Wider bands where the data is thin keep every band meaningful.
Orders versus revenue
The chart puts two distributions side by side. Orders pile up on the left: a quarter of them are under $100. Revenue piles up on the right: those small baskets bring in 2% of sales, while orders above $1,000 (15% of orders) bring in more than half. Both are true, and they answer different questions. “Where are most customers?” is the orders view. “Where is the money?” is the revenue view. A good analyst shows Jordan both before he sets offer thresholds.
Common mistakes
- Forgetting the edges.
pd.cutincludes the right edge and excludes the left. A first edge of 0 drops zero and negative values; check how manyNaNyou get withbands.isna().sum(). - Sorting by count.
value_counts()sorts the biggest band first by default. On order values the bands happen to shrink in order, so you wouldn’t notice; on most columns it scrambles the distribution. Saysort=Falseand the bands stay in their own order. - Equal-width bands on skewed data. Most bands end up nearly empty and one band holds most of
the data. Use bands that match how the business talks, or quantiles with
pd.qcut.
Lantern lit!
Practised: distributionbinningpd cutskew
- Solved
- No hints
- First run
