1. Lighthouse

Level 9 · Pick a welcome offer per customer

The request

From Jordan Reyes, Head of Marketing

“We're sending a welcome-back offer to everyone. Business accounts get a call from their account manager. Consumers who found us on social get a creator code, consumers we can't trace get a short survey, and the rest get the newsletter. How many of each, by source group?”

Your goal

Define source_group(src) returning 'Unknown' when src is missing (NaN), 'Social' for instagram, tiktok or pinterest, 'Search' for search or google_ads, and 'Other' for anything else; apply it to customers.referral_source to make a column source_group. Then use customers.apply(..., axis=1) to make a column offer: 'account call' when segment is 'business', otherwise 'creator code' when source_group is 'Social', 'survey' when it is 'Unknown', else 'newsletter'. Set result to a DataFrame with columns source_group, offer, n_customers (customers in that combination), sorted by source_group then offer A to Z, index reset.

  • Use the customers DataFramenot done yet
  • Show exactly source_group offer n_customersnot done yet
  • 8 rows, sorted as askednot done yet

Your code

Python

Loading the editor…

    ResultPreview of customers · press Run to see yours
    Preview of customers
    customer_idfirst_namelast_nameemailcity
    1OwenHudsonowen.hudson@icloud.comSan Francisco
    2DouglasMendozadmendoza69@gmail.comAtlanta
    3HenryBrookshbrooks@lakesidecafe.comLos Angeles
    4BrianGrahambgraham@bluecedarhomes.comSeattle

    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.

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

    apply on a Series: one value at a time

    Series.apply(fn) calls your function once per value and collects the answers into a new Series. A def is the right shape when the rule has several branches, and it’s where you deal with missing values:

    def source_group(src):
        if pd.isna(src):
            return "Unknown"
        if src in ("instagram", "tiktok", "pinterest"):
            return "Social"
        if src in ("search", "google_ads"):
            return "Search"
        return "Other"
    
    customers["referral_source"].apply(source_group).value_counts()
    # Unknown 666, Search 548, Social 474, Other 330

    A missing referral source shows up in pandas as NaN. NaN isn’t equal to anything, not even itself, so src == None never catches it. pd.isna(src) does, and it works on single values as well as on whole columns. Put that check first, before any test that assumes text.

    For a one-line rule, a lambda saves the def:

    products["unit_price"].apply(lambda p: round(p * 1.03, 2))   # a 3% price rise

    That one is really vectorised maths in disguise: (products["unit_price"] * 1.03).round(2) does the same and runs far faster. Reach for apply when the logic needs if.

    apply on rows: axis=1

    Jordan’s offer depends on two columns, segment and the new source_group. With axis=1, DataFrame.apply passes your function one whole row at a time, and you read any column from it by name:

    def offer(row):
        if row["segment"] == "business":
            return "account call"
        if row["source_group"] == "Social":
            return "creator code"
        if row["source_group"] == "Unknown":
            return "survey"
        return "newsletter"
    
    customers["offer"] = customers.apply(offer, axis=1)

    The order of the ifs is the business rule: business accounts get a call whatever their source, so that test comes first. 286 business customers get a call, 746 consumers the newsletter, 575 the survey and 411 a creator code.

    Which group is biggest? idxmax

    max() tells you the largest value; idxmax() tells you where it is, meaning its index label:

    counts = customers["source_group"].value_counts()
    counts.max()      # 666
    counts.idxmax()   # 'Unknown'
    counts.drop("Unknown").idxmax()   # 'Search'

    That’s a useful reality check: the biggest “source” is not knowing the source at all. Among known sources, search brings in the most customers. On a DataFrame, idxmax() returns the label of the top row for each column.

    Common mistakes

    • Checking src == None instead of pd.isna(src), so missing values fall through to ‘Other’.
    • Forgetting axis=1, so the function receives columns instead of rows and fails.
    • Calling the function yourself, apply(source_group()), instead of passing it by name.
    • Putting the business test last, so business customers from social get a creator code.
    • Using max() when you need the label of the biggest group: that’s idxmax().

    Pick what may run on this site. You can change this any time from "Cookie settings" at the bottom of every page.