You "fix" some values — df[df["status"] == "FAILED"]["amount"] = 0 — and the code
runs. Maybe a long warning scrolls past: SettingWithCopyWarning on older pandas, or
ChainedAssignmentError: A value is being set on a copy... on pandas 3. Then you print
the column and the old values are still there. No crash, no traceback — the table is
unchanged, and downstream numbers are quietly wrong.
First or second week of pandas, during cleaning tasks: zeroing failed transactions,
capping outliers, fixing labels for a filtered subset. Also appears when you carved a
sub-table earlier (sub = df[mask]) and later assign into sub, expecting df to
change too. Linked module: 0.5 (Pandas).
df[mask]["amount"] = 0 is TWO operations chained. First df[mask] builds a new,
temporary DataFrame — a copy of the matching rows. Then ["amount"] = 0 writes into
that temporary object, which nothing references, so it is discarded a moment later.
The original df never saw the write. Pandas 3 makes this deterministic: under
copy-on-write, chained assignment NEVER updates the original, and the
ChainedAssignmentError warning says so. Older pandas was worse — sometimes the write
landed, sometimes not, which is why the old warning said "may". Confirm in two lines:
print the target values before and after your assignment; if they match, you have this
bug. The tell in code review is two indexing operations back to back with = at the
end: df[...][...] = value.
Collapse the two steps into one loc call that names rows and columns together:
df.loc[df["status"] == "FAILED", "amount"] = 0. One indexing operation, applied
directly to df, so the write lands — verify with the same before/after print. If you
genuinely want an independent sub-table to work on, say so explicitly:
sub = df[mask].copy(), then operate on sub knowing df stays untouched. Choose
one intent per line: mutate the original via loc, or fork a copy via .copy() —
never something in between.
One rule catches it all: never chain square brackets and then assign. Any mutation of
selected rows is written as df.loc[row_condition, column] = value in a single call.
Take every SettingWithCopyWarning or ChainedAssignmentError as a build failure, not
background noise. After each mutation step in a pipeline, print a one-line checksum —
df["amount"].sum() or value_counts() — so a silent no-op becomes loud immediately.
Q: "You set some values on a filtered DataFrame, got no error, but the data did not
change. What happened?" Strong answer: explain that chained indexing created a
temporary copy and the assignment wrote to it, name the single-loc fix, and add the
version detail — pandas 3 copy-on-write makes the no-op guaranteed, older versions
were unpredictable. Mentioning the before/after print as proof shows you debug with
evidence, not folklore.