Skip to content
elevatedevco

Cheat sheet · Data & MLOps

Advanced Pandas cheat sheet

The key ideas on one page — plus common pitfalls and interview tips. Skim it before an interview, or print it for revision.


Cheat sheet

Pandas is the de-facto Python library for tabular, labeled data, built on NumPy (with optional Apache Arrow backends) and a columnar memory model. This cheat sheet covers the data model, vectorized manipulation, joins/reshaping, groupby internals, time series, and performance — the areas interviewers actually probe — and reflects pandas 2.x/3.0 (2026) behavior.

Core Data Model & Selection

  • Two primary objects: Series (1-D, labeled) and DataFrame (2-D, dict-of-Series sharing one Index). The Index is a first-class, possibly non-unique, hashable axis — not just row numbers.
  • Selection: use .loc for label-based and .iloc for positional access. Avoid chained indexing (df[mask][col] = ...) — it may write to a temporary copy; under Copy-on-Write (default in pandas 3.0) such writes never propagate, so always assign via a single .loc[mask, col] = ...
  • Boolean masking is the idiomatic filter: df[df['x'] > 0]; combine conditions with & | ~ and parenthesize each one. Use .query('x > 0 and y < 5') for readability on large expressions.
  • dtypes matter: prefer nullable dtypes (Int64, Float64, boolean, string) and category for low-cardinality columns to cut memory and enable proper NA handling.
  • object dtype usually means boxed Python objects (slow); diagnose with df.info(memory_usage='deep') and df.dtypes. Pandas 2.x+ adds first-class Arrow-backed dtypes via dtype_backend='pyarrow' (read_* functions or .convert_dtypes()).

Missing Data, Cleaning & Vectorization

  • Missing markers: legacy float NaN, NaT for datetimes, and pd.NA for nullable/Arrow dtypes. NaN != NaN — use .isna()/.notna(), never == np.nan.
  • Handle NA with dropna(subset=...), fillna(value=...), or interpolate(); use the ffill()/bfill() methods directly — fillna(method='pad'/'backfill') is removed in pandas 3.0.
  • Vectorize: operate on whole columns/NumPy arrays instead of iterrows/itertuples or apply. A vectorized op is often 10-100x faster than a Python-level loop.
  • Use .map (Series, elementwise via callable/dict), .apply (along an axis), and DataFrame.map (elementwise; .applymap is deprecated since 2.1). Prefer np.where / np.select / .clip for conditional logic.
  • String ops via the .str accessor (df['s'].str.lower().str.contains(...)) and datetimes via .dt; both are vectorized. duplicated()/drop_duplicates(subset=, keep=) for dedup.

GroupBy: Split-Apply-Combine

  • Model: split rows by key(s) -> apply a function per group -> combine results. df.groupby('k')['v'].agg(...) is the workhorse.
  • agg accepts named aggregations for clean output columns: .agg(avg=('v', 'mean'), n=('v', 'size')). It also takes a list of funcs or a per-column dict.
  • transform returns a result aligned to the original index (same shape) — ideal for group-wise normalization or filling: df['z'] = g.transform(lambda s: (s - s.mean()) / s.std()).
  • filter keeps/drops whole groups by a predicate; apply is the most flexible but slowest — reach for built-in agg/transform first.
  • observed=True is important when grouping on categoricals (avoids the Cartesian explosion of unused category combinations); in pandas 3.0 observed defaults to True. Use as_index=False or .reset_index() to flatten keys back to columns.

Merge, Join & Reshape

  • merge is SQL-style: how in {inner, left, right, outer, cross}; specify on/left_on/right_on. Use validate='m:1' (etc.) to assert key cardinality and catch silent fan-out duplicates.
  • Row-count surprises usually mean duplicate keys on one side causing a many-to-many blowup. Use indicator=True to audit which side rows came from.
  • join aligns on index (convenient for index-keyed frames); concat stacks along axis=0 (rows) or axis=1 (columns) and aligns on the other axis.
  • Long<->wide: pivot_table(index, columns, values, aggfunc) aggregates (handles duplicate keys); pivot does not (it raises on duplicates). melt goes wide->long; stack/unstack move between index and column levels.
  • MultiIndex enables hierarchical reshaping; select with .loc[(a, b), :], pd.IndexSlice, or .xs(). Flatten afterwards if downstream code expects single-level columns.

Time Series

  • Use a DatetimeIndex (pd.to_datetime, set as index) to unlock slicing by partial strings (df.loc['2026-01']) and time-aware ops.
  • resample is groupby-over-time: df.resample('D').sum() / .ohlc() / .agg(...). Distinguish downsample (aggregate) from upsample (introduces NA -> ffill/interpolate). Note pandas 2.2+ aliases: use 'ME'/'YE'/'h'/'min' ('M'/'Y'/'H'/'T' are deprecated).
  • Rolling/expanding windows: df.rolling(window=7, min_periods=1).mean(), .ewm(span=...) for exponential weighting; window can be an offset like '7D' for time-based windows (requires a sorted DatetimeIndex).
  • shift/diff/pct_change for lags and period-over-period change; combine with groupby for per-entity lags (g['v'].shift(1)).
  • Timezones: tz_localize to attach, tz_convert to translate; store/compute in UTC and convert for display to avoid DST bugs.

Performance & Scale

  • Memory: downcast numerics, use category for repeated strings, and Arrow-backed string dtype; df.memory_usage(deep=True) to measure.
  • Avoid growing a DataFrame in a loop (repeated concat is O(n^2)); collect rows in a list and build once, or use vectorized construction.
  • I/O: read only needed columns (usecols), set dtype on read, and prefer columnar formats (Parquet/Feather) over CSV for speed and type fidelity. read_csv(chunksize=) for out-of-core streaming.
  • Copy-on-Write (CoW) is the default in pandas 3.0 — it removes most SettingWithCopy ambiguity and avoids defensive copies via lazy copy semantics. On pandas 2.x enable it early with pd.set_option('mode.copy_on_write', True) to match 3.0 behavior.
  • When data exceeds memory or a single core: scale out with Polars, DuckDB (SQL over DataFrames/Parquet), or Dask rather than forcing pandas. eval()/query() can speed large arithmetic via numexpr.