1. Lighthouse

Level 9 · Turn a messy price export into numbers

The request

From Thomas Whitaker, Head of Merchandising

“Our new supplier sent their price list as a spreadsheet export and it's a mess: dollar signs, commas, blank lines, 'n/a', 'TBD'. I need the prices as real numbers so I can compare them with ours. Leave a gap where there's no real price.”

Your goal

Using supplier (given), drop the rows where every column is empty with dropna(how='all'), keeping rows that have only a sku. Clean price into a float column price_usd: strip spaces, remove '$' and ',', then convert with pd.to_numeric(errors='coerce') so text like 'n/a' becomes NaN. Set result to a DataFrame with columns sku and price_usd, in the original row order, index reset.

  • Show exactly sku price_usdnot done yet
  • 9 rows, sorted as askednot done yet

Your code

Python

Loading the editor…

    ResultPress Run to see yours

    Mote checks your result

    Press Run. Each row that matches the goal becomes a firefly in your lantern.

    The lesson

    Blank rows: dropna(how=“all”)

    Spreadsheet exports often carry empty lines between blocks. In the plain Python lists that build supplier, those cells are None; once they’re in a DataFrame, pandas treats them as missing, just like NaN. dropna removes rows with missing values, but by default it removes a row if any value is missing:

    len(supplier)                   # 11
    len(supplier.dropna())          # 8, HP-111 is gone too
    len(supplier.dropna(how="all")) # 9, only the fully blank rows go

    HP-111 has a sku but no price yet. That’s information Thomas wants to see (“no price yet”), so how="all" is the right choice: drop a row only when every column is empty. subset=["sku"] is the other common option, dropping rows that have no sku whatever else they hold.

    From text to numbers

    pd.to_numeric(supplier["price"]) fails straight away with ValueError: Unable to parse string "$1,299.00". Clean the text first so only digits and a decimal point remain:

    text = (
        clean["price"].str.strip()
        .str.replace("$", "", regex=False)
        .str.replace(",", "", regex=False)
    )

    regex=False says “replace this exact character”. In patterns, $ has a special meaning, so being explicit avoids surprises. After this, “$1,299.00” is “1299.00” and ” 45 ” is “45”, but “n/a” and “TBD” are still words.

    errors="coerce" tells to_numeric to turn anything it can’t read into NaN instead of stopping:

    clean["price_usd"] = pd.to_numeric(text, errors="coerce")
    clean["price_usd"].isna().sum()   # 3: n/a, TBD and HP-111's missing price

    Coercing is a decision, not a fix: the bad values become gaps. Always count the NaNs it created and look at what they were, so a typo like “12,99” (a decimal comma) isn’t silently turned into 1299.

    The cleaned list

    Nine products remain, six with a real price, from $12.99 (HP-108) to $1,299.00 (HP-101). The three gaps tell Thomas exactly which skus to chase the supplier about. Because price_usd is now a real float column, he can sort it, average it or compare it with products["unit_price"], none of which works on the original text. Keep the raw price column next to it while you check the result, then drop it from the version you share.

    Common mistakes

    • dropna() without how="all", which also drops products that just lack a price.
    • astype(float) on the raw text, which raises a ValueError on “$1,299.00”.
    • Forgetting to remove the comma, so “1,050” becomes NaN instead of 1050.
    • Coercing without counting the NaNs afterwards.

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