Pandas Indexing & Selection

Almost every pandas workflow starts with selecting data: specific columns, rows matching a condition, a date range, a single cell to fix. Pandas offers several ways to do it ([], .loc, .iloc, boolean masks, .query()), and choosing the wrong one produces some of the most confusing behavior in the library: selecting by label when you meant position, silent no-op assignments, and the infamous SettingWithCopyWarning.

Modern pandas (2.x with Copy-on-Write, the default in 3.0) makes the rules simpler and more predictable. This page covers how DataFrames are structured, how to select precisely, and how to modify data safely.

TL;DR

Quick Example

Core Concepts

DataFrame Anatomy

The Selection Methods

Rule of thumb: use .loc for labels and conditions, .iloc for positions, and be explicit.

Boolean Masks

A comparison on a Series produces a boolean Series; indexing with it keeps True rows:

Use &, |, and ~ (not and/or/not), and wrap each condition in parentheses because of operator precedence. Useful helpers: .isin(), .between(), .isna()/.notna(), and the .str and .dt accessors.

Setting Values and Copy-on-Write

With Copy-on-Write (CoW) (optional in 2.x, the default in pandas 3.0), any DataFrame or Series derived from another behaves as an independent copy. Modifying it never changes the original, and memory is shared lazily until a write occurs. Consequences:

Index and MultiIndex

Missing Data

Pandas represents missing values as NaN, None, NaT (datetimes), or pd.NA (nullable dtypes). Tools: isna(), notna(), dropna(subset=…), fillna(value or method), and interpolate(). Nullable dtypes (Int64, boolean, string, or Arrow-backed ones) keep integers as integers when values are missing, instead of silently converting them to float.

Best Practices

Be Explicit With loc and iloc

Plain df[...] changes meaning depending on the argument type. .loc and .iloc make intent clear, and avoid label-vs-position bugs, especially with integer indexes.

Use Method Chaining

df.query(...).assign(...).loc[:, cols].sort_values(...) builds readable transformation pipelines without temporary variables or in-place mutation, and it works well with Copy-on-Write.

Set Correct dtypes Early

Parse dates on load, convert categorical strings to category, and use nullable or Arrow dtypes. Correct types make selection faster and prevent comparisons of strings against numbers. See pandas performance.

Check Shapes and Samples

After filtering or joining, check shape, head(), and value_counts() to confirm you selected what you intended. Silent over- or under-filtering is a common analysis bug.

Common Mistakes

Chained Assignment

Using and/or With Series

df[(df.a > 1) and (df.b < 5)] raises "The truth value of a Series is ambiguous". Use & and |, with parentheses.

Confusing Inclusive and Exclusive Slices

df.loc["a":"c"] includes "c"; df.iloc[0:3] excludes position 3. Mixing them up drops or adds a row at the boundary.

FAQ

What's the difference between loc and iloc?

.loc selects by labels (index values and column names), or boolean arrays, and label slices include the end label. .iloc selects by integer positions, like Python lists, and position slices exclude the end. Use .loc for meaningful keys and conditions, and .iloc when you truly mean "the first N rows".

How do I fix SettingWithCopyWarning?

Don't chain indexing when assigning. Use a single .loc[row_selector, column] = value on the DataFrame you want to modify, or create an explicit copy with .copy() if you intend to work on a separate subset. With Copy-on-Write (default in pandas 3), the warning disappears, but chained assignment still doesn't modify the original.

How do I filter rows by multiple conditions?

Combine boolean Series with & (and), | (or), and ~ (not), wrapping each condition in parentheses: df.loc[(df.a > 1) & (df.b == "x")]. Or use df.query("a > 1 and b == 'x'") for readability.

When should I set an index?

When you frequently look up rows by a key, work with time series, or want automatic alignment between datasets. Keep a plain RangeIndex when rows have no natural key, or when you mostly filter by column conditions.

Related Topics

References