Efficient, correct pandas patterns: vectorized ops over loops, groupby aggregations, merge/join semantics with validate, datetime handling, read_csv dtype/parse_dates/chunksize, categorical memory savings, avoiding SettingWithCopyWarning, and when pandas is the wrong tool. Triggers on: pandas dataframe, vectorize a loop, groupby aggregation, merge dataframes, SettingWithCopyWarning, read_csv memory, too big for pandas.
./install.sh pandas-patternsname: "pandas_patterns" description: "Efficient, correct pandas patterns: vectorized ops over loops, groupby aggregations, merge/join semantics with validate, datetime handling, read_csv dtype/parse_dates/chunksize, categorical memory savings, avoiding SettingWithCopyWarning, and when pandas is the wrong tool. Triggers on: pandas dataframe, vectorize a loop, groupby aggregation, merge dataframes, SettingWithCopyWarning, read_csv memory, too big for pandas." metadata: { "includeInPrompt": true }
Write pandas code that is fast and correct: vectorized column ops instead of row loops, one-pass groupby aggregations, merges with explicit cardinality checks, timezone-naive-safe datetime handling, memory-aware CSV reads, categorical columns for low-cardinality text, and SettingWithCopy-safe assignment. Scoped to the transformation/analysis step — dirty-file repair is a different skill; extraction from nested JSON is jq-yq territory. All patterns below were run against pandas 2.1.4 on Linux; behavior may differ on other versions.
for over rows is ~100x slower than the equivalent column op (measured: 0.80 ms vs 110 ms on 100k rows). Reach for df["a"] * 2 + df["b"], .str / .dt accessors, .where, and np.where before apply — apply(lambda) was 48x slower than the same vectorized op in testing. apply is still the right escape hatch for genuinely row-local logic that can't be vectorized..groupby(...).agg(...) with named aggregations beats chained groupbys or per-group loops. Use agg(mean_a=("a", "mean"), total_b=("b", "sum")) — names the output columns directly, no post-hoc rename. For multi-key groups pass a list; for weighted or rolling stats use .transform to broadcast back to row shape.how (inner/left/right/outer) deliberately — the default inner silently drops non-matching rows. Use validate= to assert cardinality: validate="many_to_one" raised MergeError in testing when the right side had duplicate keys, which is exactly when you want the loud failure instead of a fan-out. For index-aligned joins, .join on set_index is the idiom; for lookups by value, merge is clearer.pd.to_datetime(col, format="%Y-%m-%d %H:%M:%S") when you know the format — inference silently parses "01/02/2026" as January 2nd (month-first default), so format= (or dayfirst=True) is about correctness, not just speed. Access components via .dt (dt.date, dt.day_name()). Compare and subtract as datetimes; never hand-roll date math on strings.pd.read_csv(path, dtype={"a": "float32", "grp": "category"}, parse_dates=["ts"]) cut memory 13.4 MB → 1.7 MB on a 100k-row test (the parse pass costs some time, the memory win is the point). For files that don't fit RAM, iterate pd.read_csv(path, chunksize=250_000) and aggregate per chunk — never read the whole file and hope.category (54x). Rule of thumb: fewer than ~100 distinct values on a large frame → .astype("category"); watch observed=False in groupby if unobserved categories showing up matters.df[df["a"] > 500]["flag"] = 1 raises SettingWithCopyWarning — it may or may not write. Two safe patterns: write on the frame with .loc (df.loc[df["a"] > 500, "flag"] = 1), or take an explicit .copy() of the slice first and assign freely (plus .assign() for chained, warning-free column creation).csv module than a full read_csv (99 ms vs 243 ms) and use O(1) memory — reach for csv/itertools when you never need a DataFrame. Files far beyond RAM need chunking or a distributed frame (dask/polars out-of-core); pandas eagerly materializes everything.After applying this skill, an agent should produce:
apply use justified as non-vectorizable..agg(...); merges with how stated and validate= where cardinality is assumed.read_csv calls with explicit dtype/parse_dates (or a stated reason to skip), and chunksize for large files.format= or dayfirst when strings are ambiguous; no string date math..loc/.copy() assignments only — no chained assignment.apply is the wrong choice — measured 48x slower.how on every merge; validate= whenever you assume a cardinality. Silent fan-outs are data corruption.format= or dayfirst; .dt for components; strings never carry date math.category; oversized reads become chunksize iteration. The frame you don't materialize can't OOM you.df[mask][col] = ..., rewrite with .loc or .copy().publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.