pandas·Lesson 8·11 min·0/4 exercises

Adding and changing columns

Creating columns, and the chained assignment that silently does nothing.

df["new"] =.assign()np.wherepd.cut.loc[mask, col] =Copy-on-Write

Watch it happen

Play it through, or step back and forth yourself.

orders
city
cups
revenue
price
0
Delhi
120
2400.0
20.0
1
Mumbai
80
2000.0
25.0
2
Pune
150
3000.0
20.0
df["price"] = df["revenue"] / df["cups"]

df["price"] = df["revenue"] / df["cups"]. Assigning to a name that doesn't exist creates it; assigning to one that does replaces it. The right-hand side is an ordinary vectorised expression — no loop.

The idea

Assigning to a column name that doesn't exist creates it; assigning to one that does replaces it. The right-hand side is an ordinary vectorised expression — no loop:

orders["price"] = orders["revenue"] / orders["cups"]
orders["cups"] = orders["cups"] * 2

Conditional columns

orders["band"] = np.where(orders["cups"] > 100, "busy", "quiet")

orders["band"] = pd.cut(
    orders["cups"], bins=[0, 90, 120, 999], labels=["quiet", "steady", "busy"]
)

np.where is the two-outcome case you already know. pd.cut bands a continuous column into labelled ranges — and returns a category, so it sorts in band order rather than alphabetically. pd.qcut does the same by quantile, giving equal-sized groups.

assign returns a new frame

orders.assign(
    price=lambda d: d["revenue"] / d["cups"],
    busy=lambda d: d["cups"] > 100,
)

Same job, but it returns a copy rather than editing in place — which is what makes it chainable. Use a lambda when a new column depends on another one you're defining in the same call, since d refers to the frame as it is at that point.

Hand-drawn notes contrasting assigning a column in place with assign, which returns a new frame and chains without naming a temporary.

The trap: chained assignment

This looks reasonable and does not work:

orders[orders["cups"] > 100]["price"] = 0     # writes nowhere

There are two bracket operations. The first produces a temporary frame; the second writes into that temporary; then it's discarded. Your original is untouched.

Historically pandas emitted a SettingWithCopyWarning here — a warning famous for being ignored, partly because it sometimes appeared when nothing was wrong, and the write sometimes worked anyway depending on memory layout.

The fix is to do it in one indexing operation:

orders.loc[orders["cups"] > 100, "price"] = 0      # one bracket — works

One bracket, not two. That's the whole rule, and it's the same instinct as the NumPy view-versus-copy lesson.

What changed in pandas 3

Copy-on-Write is now the default, and it makes the behaviour consistent: an operation never modifies its parent. So chained assignment now reliably does nothing, rather than working by accident on some layouts and not others.

That's a real improvement — a silent failure that happens every time is far easier to find than one that happens sometimes. The habit doesn't change: select once, then assign.

Hand-drawn notes showing chained assignment writing into a temporary copy so the original frame never changes, against a single loc call that lands.

Renaming and dropping

orders.rename(columns={"cups": "cups_sold"})
orders.drop(columns=["rating"])
orders.insert(1, "price", values)      # at a specific position

These all return new frames by default. You'll see inplace=True in older code; it's discouraged now — it doesn't save memory, it breaks chaining, and it makes code harder to reason about.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

tryCompare the two "changed anything?" lines — that is the whole lesson.

Press Run — the output appears here.

Your turn

4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Add a price column to a copy of orders revenue / cups — and return the frame.

your answer

Do the same with .assign() instead, returning a new frame with a price column.

your answer

Add a band column that is "busy" where cups is over 100 and "quiet" otherwise, and return the frame.

your answer

Set cups to 0 for every row over 100, and return the frame. The starter uses chained assignment and silently does nothing — fix it.

your answer