1. Lighthouse

Level 1 · Compute line revenue

The request

From Laura Bennett, Head of Finance

“The order lines have quantity, price and discount, but no revenue. Can you add a line revenue column? Send me the first 20 lines so I can check the maths against our ledger.”

Your goal

Add a column line_revenue = quantity * unit_price * (1 - discount_pct / 100), rounded to 2 decimals, to order_items. Set result to the first 20 rows of that table: all original columns plus line_revenue as the last column, in the original row order.

  • Use the order_items DataFramenot done yet
  • Show exactly order_item_id order_id product_id quantity unit_price discount_pct line_revenuenot done yet
  • 20 rows, sorted as askednot done yet

Your code

Python

Loading the editor…

    ResultPreview of order_items · press Run to see yours
    Preview of order_items
    order_item_idorder_idproduct_idquantityunit_price
    11001121931.99
    21001154311.99
    31002154411.99
    410021941149.00

    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.

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

    Arithmetic on whole columns

    When you multiply two columns, pandas multiplies them row by row and returns a new Series:

    order_items["quantity"] * order_items["unit_price"]

    No loop needed. This is called vectorised arithmetic, and it’s both shorter and far faster than looping in Python. You can mix columns and plain numbers freely: order_items["unit_price"] * 1.08 applies the same number to every row.

    Know your units

    Look at discount_pct before using it:

    order_items["discount_pct"].value_counts()
    # 0.0, 10.0, 15.0, 20.0, 25.0

    It’s stored in percentage points: 10.0 means 10% off. So the share the customer pays is 1 - discount_pct / 100. If you wrote 1 - discount_pct, a 10% discount would make revenue negative. Other datasets store the same idea as a fraction (0.10); always check a few values.

    Line revenue is therefore:

    order_items["quantity"] * order_items["unit_price"] * (1 - order_items["discount_pct"] / 100)

    Two ways to add the column

    The direct way assigns to a new column name, which modifies the DataFrame in place:

    items = order_items.copy()
    items["line_revenue"] = (
        items["quantity"] * items["unit_price"] * (1 - items["discount_pct"] / 100)
    ).round(2)

    assign returns a new DataFrame with the column added and leaves the original alone:

    items = order_items.assign(
        line_revenue=(
            order_items["quantity"] * order_items["unit_price"]
            * (1 - order_items["discount_pct"] / 100)
        ).round(2)
    )

    assign fits nicely into method chains. Inside a chain, where the intermediate DataFrame has no name, pass a function that receives it:

    order_items.assign(gross=lambda d: d["quantity"] * d["unit_price"])

    Both styles are common. The important part is to avoid chained assignment, such as order_items[order_items["quantity"] > 0]["line_revenue"] = .... That writes to a temporary copy, not to order_items. To change only some rows, use one .loc call: items.loc[items["quantity"] <= 0, "line_revenue"] = 0.

    Rounding

    .round(2) on a Series rounds every value. For money, round the column you report, and keep in mind that the sum of rounded lines can differ by a cent or two from the rounded sum of unrounded lines. Finance teams usually define which one is official; when in doubt, ask.

    Other new-column patterns

    orders["has_coupon"] = orders["coupon_code"].notna()            # boolean flag
    orders["order_year"] = orders["order_date"].dt.year             # part of a date
    products["margin"] = products["unit_price"] - products["unit_cost"]   # NaN where cost is missing

    Arithmetic with a missing value gives a missing value, so margin is NaN for the nine products without a cost. That’s correct: you don’t know their margin.

    Common mistakes

    • Treating discount_pct as a fraction.
    • Using products.unit_price (today’s list price) instead of the line’s unit_price (price actually paid). Prices rose on some products in March 2025, so the two differ on older lines.
    • Expecting order_items.assign(...) to change order_items without storing the result.