Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

pandas User Guide

Generated June 2026 from pandas-dev/pandas at b5c64e2

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

pandas is a fast, flexible, and expressive Python library for working with labeled, tabular, and time-series data. Its two core objects — the one-dimensional Series and the two-dimensional DataFrame — give you a single, consistent toolkit for loading data from almost any source, cleaning and reshaping it, grouping and aggregating it, joining it with other data, and writing the result back out.

This guide shows you how to use pandas: how to install it, the everyday tasks that make up real data work, and the recipes you reach for again and again. It is organized around what you want to do with your data — not around the library's internal module layout. If you need pandas' architecture, build system, or internals, see the pandas Admin Guide.

Note

Throughout this guide, pandas is imported under its conventional alias and NumPy alongside it:

import pandas as pd
import numpy as np

Every example assumes these two imports are already in place.


1. Quick Start

The fastest way to understand pandas is to build a DataFrame, look at it, and ask it a question.

import pandas as pd

# Build a DataFrame from a dict of columns
df = pd.DataFrame({
    "name": ["Ada", "Bram", "Cleo", "Dao"],
    "team": ["A", "B", "A", "B"],
    "score": [91, 72, 88, 64],
})

print(df)
   name team  score
0   Ada    A     91
1  Bram    B     72
2  Cleo    A     88
3   Dao    B     64

Now answer a real question — what is the average score per team? — in one line:

print(df.groupby("team")["score"].mean())
team
A    89.5
B    68.0
Name: score, dtype: float64

That single expression captures the split-apply-combine pattern that pandas is built around: split the rows into groups, apply a calculation, and combine the results back into a labeled object. The rest of this guide expands on every step.

Tip

The two objects you will use constantly are Series (a single labeled column) and DataFrame (a table of Series that share one row index). Almost every method exists on both, and selecting a single column from a DataFrame gives you a Series.


2. Installation

pandas ships as a pre-built binary wheel for all common platforms, so a normal install requires no compiler.

pip (PyPI)

python -m pip install pandas

conda (conda-forge)

conda install -c conda-forge pandas

With optional extras

# Excel, Parquet, plotting, and HTML support in one go
python -m pip install "pandas[excel,parquet,plot,html]"

Verify the install and inspect your environment:

import pandas as pd

print(pd.__version__)
pd.show_versions()      # full dependency + system report, useful for bug reports
3.0.0
INSTALLED VERSIONS
------------------
python           : 3.13.x
pandas           : 3.0.0
numpy            : 2.x
...

2.1 Requirements

pandas requires Python 3.11 or newer and these runtime dependencies:

Dependency Role Notes
numpy Underlying numeric arrays >=1.26, <3
python-dateutil Flexible date parsing >=2.8.2
tzdata IANA time-zone database Required on Windows and Emscripten only

Important

numpy and python-dateutil are hard dependencies — pandas refuses to import without them and raises an ImportError naming the missing package.

2.2 Optional dependencies

Many pandas features activate only when an optional package is installed. You do not need any of these to start, but a feature will raise an ImportError telling you exactly which package to install when you first use it.

Feature you want Install
Read/write Excel (.xlsx) openpyxl, or xlsxwriter (write), python-calamine/xlrd (read), odfpy (.ods)
Read/write Parquet pyarrow (recommended) or fastparquet
Read/write HDF5 (.h5) pytables
Read/write Feather / ORC pyarrow
SQL databases sqlalchemy plus a driver (psycopg2, pymysql, adbc-driver-*)
HTML tables (read_html) lxml, html5lib, or beautifulsoup4
XML (read_xml / to_xml) lxml
Plotting matplotlib
Styler HTML/Excel export jinja2
Numba-accelerated apply / windows numba
Faster evaluation numexpr, bottleneck
Arrow-backed dtypes pyarrow (>=13)
Cloud storage paths (s3://, gs://) fsspec + s3fs / gcsfs
SAS / SPSS / Stata via reader pyreadstat
Clipboard I/O pyqt/qtpy (Linux)

Tip

When in doubt, install pyarrow and matplotlib. Between them they unlock Parquet, Feather, ORC, Arrow-backed dtypes, and all charting — the features most users reach for first.


3. The Two Core Objects

3.1 Series — a labeled column

A Series is a one-dimensional array of values paired with an index (the labels).

s = pd.Series([10, 20, 30], index=["a", "b", "c"], name="counts")
print(s)
a    10
b    20
c    30
Name: counts, dtype: int64
print(s["b"])        # label-based access -> 20
print(s.mean())      # 20.0
print(s * 2)         # vectorized: every element doubled

3.2 DataFrame — a labeled table

A DataFrame is a collection of Series that share a single row index. You can build one from a dict of columns, a list of rows, NumPy arrays, or by reading a file.

From a dict of columns

df = pd.DataFrame({
    "city": ["Phoenix", "Tucson", "Mesa"],
    "pop": [1_650_000, 543_000, 504_000],
})

From a list of row dicts

df = pd.DataFrame([
    {"city": "Phoenix", "pop": 1_650_000},
    {"city": "Tucson", "pop": 543_000},
])

From a NumPy array

import numpy as np
df = pd.DataFrame(
    np.arange(6).reshape(3, 2),
    columns=["x", "y"],
)

3.3 First look at any DataFrame

When you receive an unfamiliar table, these methods orient you instantly:

Method What it tells you
df.head(n) / df.tail(n) First / last n rows (default 5)
df.shape (rows, columns) tuple
df.info() Column names, non-null counts, dtypes, memory use
df.describe() Summary statistics for numeric columns
df.dtypes The dtype of each column
df.columns / df.index The column and row labels
df.sample(n) A random n-row sample
df.info()
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   city    3 non-null      object
 1   pop     3 non-null      int64

Note

A column of text shows as dtype object by default. To get pandas' dedicated, more memory-efficient string type, pass dtype="string" when constructing, or call df["city"] = df["city"].astype("string"). See §11 on dtypes.


4. Reading and Writing Data (I/O)

pandas' I/O layer is one of its biggest draws: a family of pd.read_* functions that pull data in and matching .to_* methods that write it back out. Every reader returns a DataFrame (or Series); every writer is a method on the object you want to save.

4.1 The reader / writer matrix

Format Read Write Optional dependency
CSV / delimited text pd.read_csv df.to_csv built-in
Fixed-width text pd.read_fwf — built-in
Excel pd.read_excel df.to_excel openpyxl / xlsxwriter
JSON pd.read_json df.to_json built-in
HTML tables pd.read_html df.to_html lxml / bs4
XML pd.read_xml df.to_xml lxml
Parquet pd.read_parquet df.to_parquet pyarrow
Feather pd.read_feather df.to_feather pyarrow
ORC pd.read_orc df.to_orc pyarrow
HDF5 pd.read_hdf df.to_hdf pytables
Stata pd.read_stata df.to_stata built-in
SAS pd.read_sas — built-in / pyreadstat
SPSS pd.read_spss — pyreadstat
Pickle pd.read_pickle df.to_pickle built-in
Clipboard pd.read_clipboard df.to_clipboard platform
SQL pd.read_sql / read_sql_query / read_sql_table df.to_sql sqlalchemy + driver
Iceberg pd.read_iceberg df.to_iceberg pyiceberg

4.2 CSV — the workhorse

df = pd.read_csv("sales.csv")

read_csv has the deepest option set of any reader. The ones you will use most:

Parameter Purpose Example
sep / delimiter Field separator sep="\t" for TSV
header Row number(s) to use as column names header=None
names Supply column names yourself names=["a","b"]
index_col Column to use as the row index index_col=0
usecols Read only some columns usecols=["date","total"]
dtype Force column dtypes dtype={"id": "int32"}
parse_dates Parse columns as datetimes parse_dates=["date"]
nrows Read only the first N rows nrows=1000
skiprows Skip leading rows skiprows=3
na_values Extra strings to treat as missing na_values=["N/A", "-"]
encoding File text encoding encoding="latin-1"
chunksize Iterate in chunks (for big files) chunksize=100_000
engine Parser backend engine="pyarrow"
compression De/compress on the fly compression="gzip"

Tip

Choose the right CSV engine for the job. The valid values are "c" (the default, fast), "python" (most flexible, supports regex separators), "pyarrow" (multi-threaded, fastest for large files — requires pyarrow), and "python-fwf" (fixed-width). Pass it as engine=....

Reading a large file without exhausting memory:

total = 0
for chunk in pd.read_csv("huge.csv", chunksize=100_000):
    total += chunk["amount"].sum()
print(total)

Writing back out — note index=False to avoid dumping the row labels as a column:

df.to_csv("clean.csv", index=False)

4.3 Excel

# Read one sheet
df = pd.read_excel("report.xlsx", sheet_name="Q1")

# Read every sheet into a dict of DataFrames
sheets = pd.read_excel("report.xlsx", sheet_name=None)

# Write multiple DataFrames to separate sheets
with pd.ExcelWriter("out.xlsx") as writer:
    q1.to_excel(writer, sheet_name="Q1", index=False)
    q2.to_excel(writer, sheet_name="Q2", index=False)

Note

When you give an existing workbook to ExcelWriter, the if_sheet_exists argument controls collisions. Valid values are "error" (the default), "new", "replace", and "overlay".

4.4 Parquet, Feather, and ORC — fast columnar formats

These binary formats preserve dtypes exactly, compress well, and load far faster than CSV. They require pyarrow.

df.to_parquet("data.parquet")              # write
df = pd.read_parquet("data.parquet")       # read

# Read just two columns from a large file — columnar formats make this cheap
df = pd.read_parquet("data.parquet", columns=["date", "total"])

Tip

For any dataset you will load more than once, save it as Parquet rather than CSV. Round-trips are faster, files are smaller, and — unlike CSV — dtypes survive the trip.

4.5 JSON

df = pd.read_json("records.json")

# Flatten nested JSON into a flat table
records = [{"id": 1, "user": {"name": "Ada", "city": "Phoenix"}}]
flat = pd.json_normalize(records)
#    id  user.name  user.city
# 0   1        Ada    Phoenix

to_json and read_json share an orient argument that controls the JSON shape: "records", "columns", "index", "split", "table", and "values".

4.6 SQL databases

from sqlalchemy import create_engine
engine = create_engine("postgresql+psycopg2://user:pw@host/db")

# Full table
df = pd.read_sql_table("customers", engine)

# Arbitrary query (use parameters, never string-format user input)
df = pd.read_sql_query(
    "SELECT * FROM orders WHERE total > %(min)s",
    engine,
    params={"min": 100},
)

# Write a DataFrame to a table
df.to_sql("orders_clean", engine, if_exists="replace", index=False)

Warning

Build SQL with parameters (params=...), not Python string formatting. Interpolating user input directly into a query string exposes you to SQL injection.


5. Selecting and Indexing Data

Getting the rows and columns you want is the most common pandas task. There are three doors into a DataFrame, and choosing the right one prevents most beginner confusion.

5.1 The three accessors

Accessor Selects by Use for
[] Column name (or boolean mask) Quick column or filter
.loc[] Labels (row label, column name) Almost everything
.iloc[] Integer position (0-based) Position-based slicing
df["score"]                 # one column -> Series
df[["name", "score"]]       # several columns -> DataFrame

df.loc[0, "score"]          # value at row label 0, column "score"
df.loc[0:2, ["name", "score"]]   # label slice (END-INCLUSIVE)
df.iloc[0:2, [0, 2]]        # position slice (END-EXCLUSIVE)

Important

.loc label slices include the endpoint (df.loc[0:2] returns rows 0, 1, and 2), while .iloc position slices exclude it (df.iloc[0:2] returns rows 0 and 1) — matching normal Python slicing. This is the single most common source of off-by-one surprises in pandas.

5.2 Boolean filtering (the workhorse)

Build a boolean Series and pass it inside [] or .loc[]:

df[df["score"] >= 80]                      # rows where score >= 80
df[(df["score"] >= 80) & (df["team"] == "A")]   # AND
df[(df["team"] == "A") | (df["team"] == "B")]   # OR
df[~(df["team"] == "A")]                    # NOT

Caution

Use &, |, and ~ — not the Python keywords and, or, not — for element-wise boolean logic, and wrap each condition in parentheses. The bitwise operators bind tighter than comparisons, so df["a"] > 1 & df["b"] < 2 is parsed as df["a"] > (1 & df["b"]) < 2 and raises ValueError: The truth value of a Series is ambiguous.

Other handy filters:

df[df["team"].isin(["A", "C"])]            # membership
df[df["score"].between(70, 90)]            # inclusive range
df[df["name"].str.startswith("A")]         # string predicate
df.query("score >= 80 and team == 'A'")    # string expression form

5.3 Adding, renaming, and dropping

df["passed"] = df["score"] >= 70           # new column from a vector
df = df.assign(bonus=df["score"] * 0.1)    # functional, returns a new frame

df = df.rename(columns={"score": "points"})
df = df.drop(columns=["bonus"])            # drop columns
df = df.drop(index=[0, 1])                 # drop rows by label

5.4 Setting and resetting the index

df = df.set_index("name")     # promote a column to the row index
df = df.reset_index()         # push the index back to a column

6. Cleaning Data and Handling Missing Values

Real data is messy. pandas represents "missing" with NaN (for floats), NaT (for datetimes), and the dtype-agnostic pd.NA. A consistent set of methods detects and fills it.

6.1 Detecting missing data

df.isna()                 # element-wise boolean mask of missing values
df.isna().sum()           # count of missing values per column
df.notna()                # the inverse
df["col"].isna().mean()   # fraction missing in one column

Note

isnull / notnull are exact aliases of isna / notna. Use whichever reads better — they behave identically.

6.2 Dropping vs. filling

df.dropna()                       # drop any row containing a missing value
df.dropna(how="all")              # drop only rows that are entirely missing
df.dropna(subset=["score"])       # consider only specific columns
df.dropna(axis=1)                 # drop columns (instead of rows)

df.fillna(0)                      # replace missing with a constant
df.fillna({"score": 0, "city": "unknown"})   # per-column fills
df["score"].ffill()               # forward-fill (carry last value forward)
df["score"].bfill()               # back-fill
df["value"].interpolate()         # numeric/temporal interpolation

Tip

how accepts "any" (default — drop if any value is missing) or "all" (drop only if every value is missing).

6.3 Removing duplicates

df.duplicated()                   # boolean mask of duplicate rows
df.drop_duplicates()              # keep the first occurrence of each row
df.drop_duplicates(subset=["id"], keep="last")

Note

The keep argument chooses which duplicate survives: "first" (default), "last", or False (drop all duplicated rows).

6.4 Replacing and converting values

df["grade"] = df["grade"].replace({"A+": "A", "B-": "B"})
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")   # bad -> NaN
df["when"] = pd.to_datetime(df["when"], errors="coerce")

Important

The errors argument on pd.to_numeric, pd.to_datetime, and pd.to_timedelta accepts "raise" (default — stop on the first bad value) or "coerce" (turn unparseable values into NaN/NaT). Use "coerce" when you expect dirty input and want to find the bad rows afterward with .isna().

6.5 Working with text columns — the .str accessor

Vectorized string methods live under .str and operate on a whole column at once:

df["name"].str.lower()
df["name"].str.strip()
df["name"].str.contains("a", case=False)
df["email"].str.split("@").str[1]          # domain part
df["phone"].str.replace(r"\D", "", regex=True)   # keep digits only
df["name"].str.len()

7. Transforming and Computing

7.1 Vectorized arithmetic

Operations apply to whole columns at once — no Python loop required, and far faster than one.

df["total"] = df["price"] * df["qty"]
df["pct"]   = df["score"] / df["score"].sum() * 100
df["z"]     = (df["x"] - df["x"].mean()) / df["x"].std()

7.2 Summary statistics

Every numeric Series and DataFrame exposes the standard reductions:

Method Result
.sum() .mean() .median() Totals and central tendency
.min() .max() Extremes
.std() .var() Spread
.count() Non-missing count
.nunique() Number of distinct values
.value_counts() Frequency table of a column
.quantile(q) The q-th quantile
.cumsum() .cumprod() Running totals
.describe() All of the above at once
df["team"].value_counts()
A    2
B    2
Name: count, dtype: int64

Note

By default reductions skip missing values (skipna=True). Pass skipna=False to make any NaN propagate into the result.

7.3 Applying your own functions

When a built-in method does not exist, map a function over the data:

df["score"].map(lambda v: "pass" if v >= 70 else "fail")   # element-wise on a Series
df.apply(np.mean)                       # reduce each column
df.apply(lambda row: row["a"] + row["b"], axis=1)   # across columns, per row

Tip

Reach for apply only when a vectorized method genuinely does not exist. df["x"] * 2 is dramatically faster than df["x"].apply(lambda v: v * 2) because it runs in compiled code rather than calling Python once per row.

7.4 Binning continuous values

pd.cut(df["age"], bins=[0, 18, 65, 120], labels=["child", "adult", "senior"])
pd.qcut(df["income"], q=4)              # four equal-sized quantile buckets

8. Group By: Split–Apply–Combine

groupby is the heart of analytical pandas. It splits rows into groups by one or more keys, applies a function to each group, and combines the answers into a labeled result.

df.groupby("team")["score"].mean()                 # one stat, one column
df.groupby("team").agg({"score": "mean", "name": "count"})
df.groupby(["team", "passed"])["score"].sum()      # multiple keys -> MultiIndex

8.1 Named aggregations

To control the output column names, use pd.NamedAgg (or the (column, func) tuple form):

df.groupby("team").agg(
    avg_score=pd.NamedAgg(column="score", aggfunc="mean"),
    n_players=pd.NamedAgg(column="name", aggfunc="count"),
)
      avg_score  n_players
team
A          89.5          2
B          68.0          2

8.2 transform vs. aggregate vs. filter

Operation Returns Use when
.agg(...) One row per group You want a summary table
.transform(...) Same shape as input You want to broadcast a group stat back onto every row
.filter(...) A subset of rows You want to keep/drop entire groups by a condition
# Add a column holding each row's group mean (transform keeps the original shape)
df["team_avg"] = df.groupby("team")["score"].transform("mean")

# Keep only groups with more than one member
df.groupby("team").filter(lambda g: len(g) > 1)

Tip

transform is the tool for "compare each value to its group" — for example computing a within-group z-score or the percent of a group total. It returns a result aligned to the original rows so you can assign it straight back as a new column.


9. Combining DataFrames

9.1 Concatenating (stacking)

pd.concat([df_jan, df_feb])                 # stack rows (same columns)
pd.concat([df_left, df_right], axis=1)      # stack columns (same index)

9.2 Merging / joining (database-style)

pd.merge aligns two frames on one or more key columns, just like a SQL join.

pd.merge(orders, customers, on="customer_id", how="left")
pd.merge(a, b, left_on="uid", right_on="id", how="inner")

The how argument selects the join type:

how Keeps
"inner" Only keys present in both frames (default)
"left" All keys from the left frame
"right" All keys from the right frame
"outer" The union of keys from both
"cross" The Cartesian product (every row paired with every row)
"left_anti" Left rows whose key is not in the right frame
"right_anti" Right rows whose key is not in the left frame

Tip

Pass validate="one_to_one" (or "one_to_many", "many_to_one") to have pandas assert the relationship between your keys. If the data violates it — e.g. a "unique" key that has duplicates — the merge raises immediately instead of silently producing a row explosion.

9.3 Specialized merges

# As-of (nearest-key) merge — essential for time-series alignment
pd.merge_asof(trades, quotes, on="time", by="symbol")

# Ordered merge with optional group-wise fill
pd.merge_ordered(a, b, on="date", fill_method="ffill")

10. Reshaping and Pivoting

Function / method Turns Into
df.pivot_table(...) Long rows A wide summary grid (with aggregation)
df.pivot(...) Long rows Wide (no aggregation; keys must be unique)
pd.melt(df, ...) Wide columns Long key/value rows
df.stack() / df.unstack() Move levels between rows and columns
pd.crosstab(a, b) Two vectors A frequency cross-tabulation
pd.get_dummies(df) Categorical column One-hot indicator columns
pd.pivot_table(
    df, values="score", index="team", columns="passed", aggfunc="mean"
)

pd.melt(df, id_vars=["name"], value_vars=["math", "science"],
        var_name="subject", value_name="grade")

pd.get_dummies(df["team"])        # -> columns team_A, team_B (0/1)

Note

pivot requires the index/column combination to be unique and only reshapes; pivot_table aggregates duplicate combinations with aggfunc (default "mean"). If pivot raises ValueError: Index contains duplicate entries, switch to pivot_table.


11. Data Types (dtypes)

Every column has a single dtype that determines how it is stored and what operations are valid. Picking good dtypes saves memory and prevents surprises.

11.1 The dtype families

Family Examples Notes
NumPy numeric int64, float64, bool Default; cannot hold missing values in integer columns
Nullable (masked) Int64, Float64, boolean Capital first letter; support pd.NA
String string Dedicated text type (vs. generic object)
Categorical category Repeated labels stored once — big memory win
Datetime / timedelta datetime64[ns], timedelta64[ns] Time-series native types
Period / Interval period[M], interval Spans and ranges
Arrow-backed int64[pyarrow], string[pyarrow] Backed by Apache Arrow (needs pyarrow)

11.2 Converting dtypes

df["id"]     = df["id"].astype("int32")
df["team"]   = df["team"].astype("category")        # memory + speed for repeated text
df["name"]   = df["name"].astype("string")
df = df.convert_dtypes()                             # auto-pick best nullable dtypes
df = df.astype({"a": "Int64", "b": "float64"})      # several at once

Tip

Convert any column with few distinct repeated values (status codes, country names, categories) to "category". On large frames this can cut memory use by an order of magnitude and speeds up groupby on that column.

11.3 Nullable integers and pd.NA

A plain NumPy int64 column cannot store missing values — pandas would silently promote it to float64. The nullable Int64 dtype (capital I) fixes this:

s = pd.Series([1, 2, None], dtype="Int64")
# 0       1
# 1       2
# 2    <NA>
# dtype: Int64

12. Working with Time Series

Time-series support is a flagship pandas feature: a rich timestamp type, calendar-aware date ranges, frequency conversion, and rolling-window statistics.

12.1 Timestamps and date ranges

pd.Timestamp("2026-06-04 09:30")
pd.to_datetime(df["when"])                         # parse a column
pd.date_range("2026-01-01", periods=12, freq="MS") # 12 month-start dates
pd.bdate_range("2026-01-01", "2026-01-31")         # business days only

Common frequency aliases for freq=: "D" (day), "B" (business day), "W" (week), "MS"/"ME" (month start/end), "QS"/"QE" (quarter), "YS"/"YE" (year), "h", "min", "s".

12.2 Datetime components — the .dt accessor

df["when"].dt.year
df["when"].dt.month_name()
df["when"].dt.dayofweek            # Monday=0
df["when"].dt.normalize()          # strip the time part
df["when"].dt.tz_localize("UTC").dt.tz_convert("US/Arizona")

12.3 Resampling (time-based group by)

When the index is a datetime, resample groups rows into time buckets:

ts = ts.set_index("when")
ts["sales"].resample("ME").sum()       # monthly totals
ts["price"].resample("W").mean()       # weekly average

12.4 Rolling and expanding windows

df["roll7"]  = df["value"].rolling(window=7).mean()    # 7-row moving average
df["cumavg"] = df["value"].expanding().mean()          # cumulative average
df["ewm"]    = df["value"].ewm(span=10).mean()         # exponentially weighted

Tip

For an irregular time index, give rolling a time-based window string such as rolling("30D") instead of an integer count — pandas then uses a true 30-day window regardless of how many rows fall inside it.


13. Plotting

With matplotlib installed, every Series and DataFrame gains a .plot accessor.

df["score"].plot(kind="line")
df.plot(x="date", y="total", kind="line")
df["team"].value_counts().plot(kind="bar")
df.plot.scatter(x="height", y="weight")
df["score"].plot(kind="hist", bins=20)

Valid kind values: "line", "bar", "barh", "hist", "box", "kde"/"density", "area", "pie", "scatter", and "hexbin".

Note

kind="scatter", "hexbin", and "pie" apply to DataFrame objects and need their axis columns specified. Some kinds raise ValueError: pie requires either y column or 'subplots=True' if you omit the column.


14. Displaying and Configuring Output

pandas truncates wide and tall output by default. Control this with the options system:

pd.set_option("display.max_rows", 100)
pd.set_option("display.max_columns", 50)
pd.set_option("display.precision", 3)         # decimal places shown
pd.set_option("display.width", 120)

pd.get_option("display.max_rows")             # read a setting
pd.reset_option("display.max_rows")           # restore the default
pd.describe_option("display.max_rows")        # see docs + current value

Scope a setting to a single block with a context manager so it reverts automatically:

with pd.option_context("display.max_rows", None):
    print(df)          # show every row, just here

Tip

Frequently useful display options: display.max_rows, display.max_columns, display.precision, display.float_format (e.g. pd.set_option("display.float_format", "{:,.2f}".format)), display.max_colwidth, and display.expand_frame_repr.


15. Common Recipes

Top N rows by a column

df.nlargest(5, "score")
df.nsmallest(3, "price")

Conditional new column (vectorized if/else)

df["band"] = np.where(df["score"] >= 70, "pass", "fail")

Multi-condition mapping

df["band"] = pd.cut(df["score"], [0, 60, 80, 100],
                    labels=["low", "mid", "high"])

Sort by one or more columns

df.sort_values(["team", "score"], ascending=[True, False])
df.sort_index()

Count and percentage in one table

counts = df["team"].value_counts()
pct    = df["team"].value_counts(normalize=True).mul(100).round(1)
pd.DataFrame({"count": counts, "pct": pct})

Chained, readable pipeline

result = (
    df
    .query("score >= 70")
    .assign(bonus=lambda d: d["score"] * 0.1)
    .groupby("team", as_index=False)
    .agg(total=("score", "sum"))
    .sort_values("total", ascending=False)
)

Tip

Wrap a multi-step transformation in parentheses and put one method per line. This "method chaining" style reads top-to-bottom like a recipe and avoids littering your code with throwaway intermediate variables.


16. Troubleshooting

Error: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()

Cause: You combined boolean conditions with Python's and/or, or forgot parentheses around comparisons. Solution: Use &, |, ~ and wrap each condition: df[(df.a > 1) & (df.b < 2)]. Prevention: Treat parentheses around every comparison as mandatory in filters.

Error: KeyError: 'columnname'

Cause: The column or index label does not exist — often a stray space or a case mismatch. Solution: Check df.columns.tolist(). Strip whitespace with df.columns = df.columns.str.strip(). Prevention: Inspect df.info() right after loading so you know the exact labels.

Error: SettingWithCopyWarning / changes that don't "stick"

Cause: Assigning into the result of a chained selection (df[df.a > 1]["b"] = 0) writes to a temporary copy. Solution: Use a single .loc assignment: df.loc[df["a"] > 1, "b"] = 0. Prevention: Whenever you both filter and assign, do it in one .loc[rows, cols] = value statement.

Error: ValueError: Index contains duplicate entries, cannot reshape

Cause: pivot (or unstack) found duplicate index/column key combinations. Solution: Switch to pivot_table, which aggregates duplicates via aggfunc. Prevention: Confirm key uniqueness with df.duplicated(subset=keys).any() before pivoting.

Error: ImportError: Missing optional dependency 'openpyxl' (or pyarrow, lxml, matplotlib, …)

Cause: The format/feature you used needs an optional package that is not installed. Solution: pip install openpyxl (or whichever package the message names). Prevention: Install pyarrow and matplotlib up front; they cover most optional features.

Error: MergeError / unexpected row explosion after merge

Cause: A key you assumed was unique actually has duplicates, so rows multiply (a many-to-many join). Solution: Add validate="one_to_many" (or the right relationship) to make pandas check; deduplicate the key side first. Prevention: Run df["key"].is_unique on the side you expect to be unique before merging.

Symptom: An integer column became float64 after introducing missing values

Cause: NumPy int64 cannot hold NaN, so pandas promotes the column to float64. Solution: Use the nullable Int64 dtype: s.astype("Int64"). Prevention: Construct integer columns that may contain gaps with dtype="Int64" from the start.

Error: TypeError: only list-like objects are allowed to be passed to isin(), you passed a 'str'

Cause: You passed a single string to .isin(), which expects a collection. Solution: Wrap it in a list: df[df["team"].isin(["A"])]. Prevention: Always give .isin() a list/set, even for one value.

Symptom: read_csv is slow or runs out of memory on a huge file

Cause: The whole file is being read into memory at once. Solution: Read in chunks with chunksize=..., select only needed columns with usecols=..., or switch to engine="pyarrow". Prevention: For repeated loads, convert the source to Parquet once and read that instead.

Symptom: Dates loaded as plain strings, so .dt accessor fails

Cause: read_csv does not parse dates unless told to. Solution: Pass parse_dates=["col"], or convert afterward with pd.to_datetime(df["col"]). Prevention: Specify parse_dates at read time.


17. Frequently Asked Questions

Q: What is the difference between a Series and a DataFrame? A Series is one labeled column (1-D); a DataFrame is a table of Series sharing one row index (2-D). Selecting a single column from a DataFrame returns a Series.

Q: When should I use .loc vs .iloc? Use .loc for label-based access (row/column names) and .iloc for integer position. .loc is what you want the vast majority of the time. Remember .loc slices include the endpoint; .iloc slices do not.

Q: How do I avoid the SettingWithCopyWarning? Make filtered assignments in one statement with .loc: df.loc[mask, "col"] = value. Do not chain df[mask]["col"] = value.

Q: My numbers turned into floats with .0 everywhere — why? A NumPy integer column was promoted to float64 because a missing value was introduced. Use the nullable Int64 dtype to keep integers and still allow pd.NA.

Q: How do I apply a function to every row? df.apply(func, axis=1). But first check whether a vectorized expression can do the same thing far faster — apply calls Python once per row.

Q: How do I get the unique values / their counts in a column? df["col"].unique() for the distinct values, df["col"].nunique() for the count, and df["col"].value_counts() for a frequency table.

Q: How do I rename columns? df.rename(columns={"old": "new"}), or assign a fresh list to df.columns.

Q: How do I select rows where a column is in a set of values? df[df["col"].isin(["a", "b", "c"])].

Q: Why is my filter raising "truth value of a Series is ambiguous"? You used and/or instead of &/|, or omitted parentheses. Write df[(cond1) & (cond2)].

Q: How do I read just part of a giant CSV? Use nrows= to cap rows, usecols= to limit columns, or chunksize= to iterate. For repeated work, convert to Parquet.

Q: What's the fastest on-disk format for round-tripping a DataFrame? Parquet (via pyarrow). It is compact, fast, and — unlike CSV — preserves dtypes exactly.

Q: How do I see all rows/columns instead of the truncated view? Raise or remove the display limits: pd.set_option("display.max_rows", None), or scope it with pd.option_context.

Q: Does pandas have a command-line tool? No. pandas is a library you import (import pandas as pd) and use from Python, notebooks, or scripts. It ships no end-user CLI.

Q: Which Python versions are supported? Python 3.11 and newer. Free-threaded (no-GIL) CPython 3.13 is supported on an experimental basis.