All Cheat Sheets
Pandas Cheat Sheet
Python pandas cheat sheet for pandas 2.x: read_csv and Parquet IO, loc/iloc selection, cleaning, groupby/agg, merge, pivot_table, melt, datetime, .str and performance tips.
Import & IO
Import
import pandas as pd
import numpy as npRead CSV
df = pd.read_csv("data.csv")Read CSV, typed
df = pd.read_csv(
"data.csv",
usecols=["id", "price", "created_at"],
dtype={"id": "int64"},
parse_dates=["created_at"],
)Selecting columns and dtypes up front saves memory and parsing timeArrow-backed dtypes
df = pd.read_csv("data.csv", dtype_backend="pyarrow")pandas 2.0+; faster strings and nullable types (requires pyarrow)Read in chunks
for chunk in pd.read_csv("big.csv", chunksize=100_000):
process(chunk)Stream files larger than memoryRead Parquet
df = pd.read_parquet("data.parquet", columns=["id", "price"])Needs pyarrow (or fastparquet); keeps dtypes, much faster than CSVRead Excel
df = pd.read_excel("report.xlsx", sheet_name="Sales")Needs openpyxl for .xlsxRead JSON lines
df = pd.read_json("events.jsonl", lines=True)Read SQL
df = pd.read_sql("SELECT * FROM orders", con=engine)engine = SQLAlchemy engine or DBAPI connectionFrom dict
df = pd.DataFrame({"name": ["Ana", "Ben"], "age": [31, 27]})Write CSV
df.to_csv("out.csv", index=False)index=False avoids an extra unnamed columnWrite Parquet
df.to_parquet("out.parquet", index=False)Inspecting Data
First / last rows
df.head() # 5 rows
df.tail(3)Shape
df.shape # (rows, columns)Summary
df.info()Columns, non-null counts, dtypes, memoryStatistics
df.describe(include="all")Column dtypes
df.dtypesUnique counts
df.nunique()Value counts
df["city"].value_counts(dropna=False, normalize=True)normalize=True gives proportionsMemory usage
df.memory_usage(deep=True).sum()deep=True measures object/string columns accuratelySelecting & Filtering
One column (Series)
df["price"]Several columns
df[["name", "price"]]By label (loc)
df.loc[10, "price"]
df.loc[:, "name":"price"]Label slices include the end labelBy position (iloc)
df.iloc[0:5, 0:2]Position slices exclude the end, like Python listsBoolean filter
df[df["age"] > 30]Combine conditions
df[(df["age"] > 30) & (df["city"] == "Pune")]Use & | ~ with parentheses, not and / orFilter + columns
df.loc[df["age"] > 30, ["name", "age"]]isin
df[df["city"].isin(["Pune", "Delhi"])]query()
min_age = 30
df.query("age > @min_age and city == 'Pune'")@ references local variablesSet a value safely
df.loc[df["qty"] < 0, "qty"] = 0One .loc call; avoid chained df[mask][col] = ...Cleaning
Count missing
df.isna().sum()Drop missing
df = df.dropna(subset=["email"])Fill missing
df = df.fillna({"qty": 0, "note": ""})Forward fill
df["price"] = df["price"].ffill()fillna(method=...) is deprecated; use ffill()/bfill()Find duplicates
df.duplicated(subset=["id"]).sum()Drop duplicates
df = df.drop_duplicates(subset=["id"], keep="last")Change dtypes
df = df.astype({"qty": "int64", "status": "category"})Parse numbers
df["price"] = pd.to_numeric(df["price"], errors="coerce")Unparseable values become NaNNullable dtypes
df = df.convert_dtypes()Int64, string, boolean types that support missing valuesRename columns
df = df.rename(columns={"Order ID": "order_id"})Normalize headers
df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")Drop columns
df = df.drop(columns=["tmp", "debug"])Transforming Columns
Add column (chainable)
df = df.assign(total=lambda d: d["price"] * d["qty"])Vectorized arithmetic
df["total"] = df["price"] * df["qty"]Far faster than apply()Map values
df["gender"] = df["g"].map({"M": "Male", "F": "Female"})Unmapped values become NaNConditional column
df["tier"] = np.where(df["spend"] > 1000, "gold", "std")Many conditions
df["band"] = np.select(
[df.age < 18, df.age < 65],
["minor", "adult"],
default="senior",
)Bin values
df["age_band"] = pd.cut(df["age"], bins=[0, 18, 65, 120], labels=["minor", "adult", "senior"])Series.apply
df["slug"] = df["title"].apply(slugify)Python-level loop; use only when no vectorized method existsRow-wise apply
df.apply(lambda r: r.a + r.b, axis=1)Slow on large frames; prefer column mathElement-wise map
df[["a", "b"]].map(round)DataFrame.map replaces applymap (pandas 2.1+)Sort
df.sort_values(["city", "sales"], ascending=[True, False])GroupBy & Aggregation
Group and sum
df.groupby("city")["sales"].sum()Several keys, flat result
df.groupby(["city", "year"], as_index=False)["sales"].sum()Named aggregation
df.groupby("city").agg(
total=("sales", "sum"),
avg=("sales", "mean"),
orders=("order_id", "nunique"),
)Group sizes
df.groupby("city").size()Transform (same length)
df["share"] = df["sales"] / df.groupby("city")["sales"].transform("sum")Broadcasts group results back to every rowRank within group
df["rank"] = df.groupby("city")["sales"].rank(ascending=False)Running total per group
df["cum"] = df.groupby("user_id")["amount"].cumsum()Filter groups
df.groupby("city").filter(lambda g: len(g) >= 10)Categorical keys
df.groupby("status", observed=True)["id"].count()observed=True skips unused categories (the future default)Crosstab
pd.crosstab(df["city"], df["plan"], margins=True)Merge, Join & Concat
Left join
out = orders.merge(users, on="user_id", how="left")how: inner (default), left, right, outer, crossDifferent key names
orders.merge(users, left_on="uid", right_on="id")Validate keys
orders.merge(users, on="user_id", validate="many_to_one")Raises if users has duplicate keysTrack match source
m = a.merge(b, on="id", how="outer", indicator=True)
m["_merge"].value_counts()Suffixes
a.merge(b, on="id", suffixes=("_old", "_new"))Stack rows
df = pd.concat([df1, df2], ignore_index=True)DataFrame.append was removed in pandas 2.0Add one row
df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True)Collect rows in a list and concat once inside loopsSide by side
pd.concat([left, right], axis=1)Aligns on the indexJoin on index
df.join(other, on="key", how="left")Reshaping
Pivot table
df.pivot_table(
index="city", columns="year",
values="sales", aggfunc="sum", fill_value=0,
)Pivot (no aggregation)
df.pivot(index="date", columns="metric", values="value")Fails on duplicate index/column pairs; use pivot_tableWide to long (melt)
df.melt(id_vars=["id"], var_name="month", value_name="sales")Stack / unstack
df.stack()
df.unstack()Move column level to index and backExplode lists
df.explode("tags")One row per list elementIndex in/out
df.set_index("id")
df.reset_index()Dates & Times
Parse dates
df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")An explicit format is faster and avoids ambiguityDate parts
df["date"].dt.year
df["date"].dt.month
df["date"].dt.day_name()Time zones
df["ts"].dt.tz_localize("UTC").dt.tz_convert("Asia/Kolkata")Date range
pd.date_range("2026-01-01", periods=7, freq="D")Resample
df.set_index("date")["sales"].resample("ME").sum()'ME' = month end; 'M' is deprecated since pandas 2.2Rolling window
df.set_index("date")["sales"].rolling("7D").mean()Shift / change
df["prev"] = df["sales"].shift(1)
df["pct"] = df["sales"].pct_change()Round timestamps
df["ts"].dt.floor("h")Lowercase 'h' since pandas 2.2String Methods (.str)
Case & trim
df["name"].str.strip().str.lower()Contains
df[df["email"].str.contains("@gmail", case=False, na=False)]na=False treats missing values as no matchReplace (regex)
df["txt"].str.replace(r"\s+", " ", regex=True)Split to columns
df[["first", "last"]] = df["name"].str.split(" ", n=1, expand=True)Extract
df["num"] = df["code"].str.extract(r"(\d+)", expand=False)String dtype
df["name"] = df["name"].astype("string[pyarrow]")Performance & Copy-on-Write
Vectorize firstColumn arithmetic, .str, .dt and np.where beat apply(), iterrows() and Python loops by 10-100x
Iterate if you must
for row in df.itertuples(index=False):
print(row.name)itertuples is much faster than iterrowsCategory dtype
df["country"] = df["country"].astype("category")Big memory savings for low-cardinality textLoad lessUse usecols, dtype and Parquet with columns= to read only what you need
Enable Copy-on-Write
pd.options.mode.copy_on_write = Truepandas 2.x opt-in; the default behaviour in pandas 3.0CoW in practice
sub = df[df["a"] > 0]
sub["b"] = 1 # never modifies dfWith CoW, derived objects act as copies; no SettingWithCopyWarningExplicit copy
sub = df[mask].copy()Without CoW, copy subsets you plan to modifyAvoid growing in loops
rows = []
for x in data:
rows.append({...})
df = pd.DataFrame(rows)Build once instead of concat in every iterationKeep the references coming
One email a week: new and trending developer tools, fresh comparisons, and what shipped. Unsubscribe in one click.

