apply(..., axis=1), iterrows() and for loops call a Python function once per row. Column operations hand the whole column to compiled code instead, which on a large table is often tens to hundreds of times faster. Most row-by-row code has a vectorized version:
| Row by row |
Vectorized |
df.apply(lambda r: r["a"] * r["b"], axis=1) |
df["a"] * df["b"] |
apply with if/else |
np.where(condition, if_true, if_false) |
if/elif/elif |
np.select([cond1, cond2], [label1, label2], default=...) |
| a loop that adds up per key |
groupby(...).sum() |
apply(lambda s: s.upper()) on text |
.str.upper() |
np.select uses the first condition that's True, so list the strictest one first. iterrows has a second trap: each row comes back as one Series, so in a table with whole numbers and decimals, units arrives as a decimal.