Pandas Idioms
Readable pandas code exposes the sequence of table transformations and uses the most specific operation available.
Transformation style
result = (
orders.loc[lambda x: x["status"].eq("paid")]
.assign(net=lambda x: x["gross"] - x["fee"])
.groupby("customer_id", as_index=False)
.agg(total_net=("net", "sum"), orders=("net", "size"))
.sort_values("total_net", ascending=False)
)
Break a chain into named stages when it becomes difficult to inspect, reuse, or debug. Method chaining is a readability tool, not a performance guarantee.
Choose the operation
| Need | Prefer |
|---|---|
| Scalar arithmetic or typed operation | vectorized expression / accessor |
| Map each value through a lookup or function | Series.map / DataFrame.map |
| Reduce values to summaries | agg |
| Return same-shaped group/column values | transform |
| Apply a function to rows or columns | apply |
| Insert a whole-table function into a chain | pipe |
def require_positive(frame, column):
if not frame[column].ge(0).all():
raise ValueError(f"{column} must be non-negative")
return frame
clean = orders.pipe(require_positive, "gross")
Vectorize before apply
# Prefer
df["ratio"] = df["numerator"].div(df["denominator"])
# Reserve for genuinely row-dependent Python logic
df["label"] = df.apply(classify_row, axis="columns")
Row-wise apply constructs a Series per row and invokes Python repeatedly.
Benchmark only after choosing a correct, clear expression and testing on
representative data.
Mutation rule
Prefer expressions returning new objects and one-step .loc assignments. Avoid
chained assignment and avoid relying on inplace=True as an optimization.