Pandas Patterns
Overview
Most pandas pain comes from three things: chained indexing, row-wise apply, and ignoring dtypes/memory. This skill encodes the idioms that keep pandas correct and fast.
When to use
- Writing data-wrangling code.
- Code is slow, leaks memory, or throws
SettingWithCopyWarning.
- Reviewing someone's pandas for correctness.
Core rules
- Assign with
.loc, never chained.df.loc[df["age"] > 30, "segment"] = "senior" # correct
# df[df["age"] > 30]["segment"] = "senior" # WRONG: SettingWithCopyWarning, no-op risk
- Vectorize instead of
apply(axis=1). Row-wise apply is a Python loop.df["bmi"] = df["weight"] / df["height"] ** 2 # fast
# df.apply(lambda r: r.weight / r.height**2, axis=1) # 100x slower
- Use
np.select / np.where for conditional columns.import numpy as np
df["tier"] = np.select(
[df.spend > 1000, df.spend > 100],
["gold", "silver"],
default="bronze",
)
- Downcast dtypes to cut memory:
category for low-cardinality strings, int32/float32 where safe.df["country"] = df["country"].astype("category")
- Prefer
merge over loops for joins, and validate join cardinality:df = orders.merge(users, on="user_id", how="left", validate="m:1")
Performance toolkit
df.groupby(..., observed=True).agg(...) — observed=True avoids exploding categorical combinations.
pd.eval / df.query() for large boolean filters.
- Read big files in chunks (
chunksize=) or switch to Polars/DuckDB when pandas is the bottleneck.
df.pipe(fn) to compose transformations without intermediate variables.
Method chaining (readable + copy-safe)
result = (
df
.query("status == 'active'")
.assign(revenue=lambda d: d.qty * d.price)
.groupby("region", observed=True)
.agg(total=("revenue", "sum"))
.reset_index()
)
Pitfalls
inplace=True rarely saves memory and breaks chaining — avoid it.
- Iterating with
iterrows — almost always replaceable with vectorization or itertuples.
- Floating-point group keys — round or use integer/category keys.
- Silent dtype upcasts (int → float when NaN appears) — use nullable
Int64 if you must keep integers.
Hand-off
Clean, vectorized transformations that downstream skills (feature-engineering, model-evaluation) can run quickly on full datasets.
1---2name: pandas-patterns3description: Use when writing or reviewing pandas code. Covers idiomatic, vectorized, memory-efficient patterns; avoiding SettingWithCopyWarning, chained indexing, and slow apply loops.4---56# Pandas Patterns78## Overview910Most pandas pain comes from three things: chained indexing, row-wise `apply`, and ignoring dtypes/memory. This skill encodes the idioms that keep pandas correct and fast.1112## When to use1314- Writing data-wrangling code.15- Code is slow, leaks memory, or throws `SettingWithCopyWarning`.16- Reviewing someone's pandas for correctness.1718## Core rules19201. **Assign with `.loc`, never chained.**21 ```python22 df.loc[df["age"] > 30, "segment"] = "senior" # correct23 # df[df["age"] > 30]["segment"] = "senior" # WRONG: SettingWithCopyWarning, no-op risk24 ```252. **Vectorize instead of `apply(axis=1)`.** Row-wise apply is a Python loop.26 ```python27 df["bmi"] = df["weight"] / df["height"] ** 2 # fast28 # df.apply(lambda r: r.weight / r.height**2, axis=1) # 100x slower29 ```303. **Use `np.select` / `np.where` for conditional columns.**31 ```python32 import numpy as np33 df["tier"] = np.select(34 [df.spend > 1000, df.spend > 100],35 ["gold", "silver"],36 default="bronze",37 )38 ```394. **Downcast dtypes** to cut memory: `category` for low-cardinality strings, `int32`/`float32` where safe.40 ```python41 df["country"] = df["country"].astype("category")42 ```435. **Prefer `merge` over loops for joins**, and validate join cardinality:44 ```python45 df = orders.merge(users, on="user_id", how="left", validate="m:1")46 ```4748## Performance toolkit4950- `df.groupby(..., observed=True).agg(...)` — `observed=True` avoids exploding categorical combinations.51- `pd.eval` / `df.query()` for large boolean filters.52- Read big files in chunks (`chunksize=`) or switch to **Polars/DuckDB** when pandas is the bottleneck.53- `df.pipe(fn)` to compose transformations without intermediate variables.5455## Method chaining (readable + copy-safe)5657```python58result = (59 df60 .query("status == 'active'")61 .assign(revenue=lambda d: d.qty * d.price)62 .groupby("region", observed=True)63 .agg(total=("revenue", "sum"))64 .reset_index()65)66```6768## Pitfalls6970- **`inplace=True`** rarely saves memory and breaks chaining — avoid it.71- **Iterating with `iterrows`** — almost always replaceable with vectorization or `itertuples`.72- **Floating-point group keys** — round or use integer/category keys.73- **Silent dtype upcasts** (int → float when NaN appears) — use nullable `Int64` if you must keep integers.7475## Hand-off7677Clean, vectorized transformations that downstream skills (feature-engineering, model-evaluation) can run quickly on full datasets.