# How to Select Rows in pandas: Mastering Boolean Indexing and Conditional Data Manipulation

> Learn efficient pandas select rows with boolean indexing and conditional data manipulation. Master df.loc and df.iloc with & and | operators to filter DataFrames effectively.

- Repository: [pandas/pandas](https://github.com/pandas-dev/pandas)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Use `df.loc[boolean_condition]` for label-based selection or `df.iloc[boolean_array]` for position-based selection**, combining multiple conditions with `&` (and) and `|` (or) operators wrapped in parentheses to filter DataFrames efficiently without explicit loops.

The pandas library provides powerful mechanisms to select rows based on complex conditions through its sophisticated indexing architecture. According to the pandas-dev/pandas source code, the `IndexingMixin` class in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py) orchestrates how boolean masks and label-based keys translate into high-performance NumPy operations. Understanding how to leverage boolean indexing with `.loc` and `.iloc` enables you to write readable, vectorized data manipulation code that scales to millions of rows.

## Architecture of Row Selection in pandas

The row selection pipeline in pandas relies on a hierarchy of indexer classes defined in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py). The **`IndexingMixin`** class (line 151) attaches the four primary accessor properties—`.loc`, `.iloc`, `.at`, and `.iat`—to every DataFrame and Series.

When you write `df.loc[condition]`, the following sequence executes:

1. **`IndexingMixin.loc`** returns a `_LocIndexer` instance (line 1590).
2. **`_LocationIndexer.__getitem__`** (line 889) normalizes the key, expanding callables and checking for tuple-style indexing.
3. **`_LocIndexer._validate_key`** (lines 636-682) ensures keys exist in the axis or represent valid slices.
4. **`_maybe_mask_setitem_value`** (lines 708-735) converts boolean arrays into integer positions via `indexer.nonzero()[0]`.
5. The final positions are passed to `NDFrame._get_slice_axis`, which extracts data without copying when possible.

For position-based selection, **`_iLocIndexer`** (line 1700) bypasses label validation and works directly with integer positions, offering faster access when you know the exact row numbers.

## How to Select Rows with Boolean Conditions

### Single Condition Selection

The most common pattern for pandas select rows operations uses a boolean Series generated by comparison operators. In [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py), the docstring (lines 410-415) documents how `_LocIndexer` accepts boolean masks:

```python
import pandas as pd

df = pd.DataFrame(
    {"max_speed": [1, 4, 7], "shield": [2, 5, 8]},
    index=["cobra", "viper", "sidewinder"],
)

# Select rows where shield is greater than 6

result = df.loc[df["shield"] > 6]

```

The boolean Series `df["shield"] > 6` aligns with the DataFrame's index before `_maybe_mask_setitem_value` converts the mask to positional indices.

### Combining Multiple Conditions

Complex filtering requires combining boolean expressions using the `&` (and) and `|` (or) operators. Because these bitwise operators have lower precedence than comparison operators, you must wrap each condition in parentheses:

```python

# Select rows where max_speed > 1 AND shield < 8

condition = (df["max_speed"] > 1) & (df["shield"] < 8)
result = df.loc[condition]

# Select rows where max_speed <= 1 OR shield >= 8

result = df.loc[(df["max_speed"] <= 1) | (df["shield"] >= 8)]

```

Python evaluates the combined expression first, yielding a single boolean Series that follows the same indexing path as single conditions.

### Callable Selectors for Method Chains

For fluent method chaining, pandas supports callable selectors that receive the DataFrame as an argument. The `__getitem__` method (lines 892-896) expands callables via `com.apply_if_callable`:

```python
result = (df
          .loc[lambda d: d["shield"] == 8]
          .assign(max_speed=lambda d: d.max_speed * 2))

```

This pattern keeps data transformations readable and avoids creating intermediate variables.

### Alignment of Boolean Series

When supplying a boolean Series with a different index, **`_LocIndexer._validate_key`** (lines 650-658) automatically aligns the mask to the target DataFrame's axis. This ensures that row selection works correctly even when the boolean mask originates from another DataFrame or a reindexed operation.

## MultiIndex Row Selection with IndexSlice

Working with hierarchical indexes requires special syntax. The **`IndexSlice`** helper (lines 99-108) enables readable slicing of MultiIndex levels without verbose tuple construction:

```python
import numpy as np

idx = pd.MultiIndex.from_product(
    [["cobra", "viper"], ["A", "B", "C"]], 
    names=["snake", "letter"]
)
mdf = pd.DataFrame(
    np.arange(12).reshape(6, 2),
    index=idx,
    columns=["x", "y"]
)

# Select all rows for "cobra" with letters "A" through "B"

sl = pd.IndexSlice
result = mdf.loc[sl["cobra", "A":"B"], :]

```

The **`_is_nested_tuple_indexer`** method (lines 998-1005) detects tuple-style selectors for MultiIndex levels, while `_handle_lowerdim_multi_index_axis0` resolves these tuples to appropriate sub-slices.

## When to Use .loc vs .iloc for Row Selection

Choosing the correct accessor impacts both performance and correctness:

- **`df.loc[labels]`**: Use for **label-based** selection including strings, datetimes, or categorical indices. Validates that labels exist in the index.
- **`df.iloc[positions]`**: Use for **integer-position** based selection when you need the fastest possible access without label lookup overhead.
- **`pd.IndexSlice`**: Use for complex **MultiIndex** slicing where readability matters.

For mixed label and position requirements, combine indexers sequentially: `df.loc[row_labels].iloc[:, col_pos]`.

## Summary

- **`IndexingMixin`** in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py) provides the `.loc` and `.iloc` properties that power all row selection operations.
- **Boolean indexing** with `.loc` converts boolean masks to integer positions via `_maybe_mask_setitem_value`, enabling vectorized filtering.
- **Multiple conditions** require parentheses around each expression when combining with `&` or `|` operators.
- **Callable selectors** support method chaining by deferring evaluation until the DataFrame is available.
- **`IndexSlice`** simplifies MultiIndex row selection syntax, automatically handling level alignment.

## Frequently Asked Questions

### What's the difference between using .loc and .iloc for boolean indexing?

**`.loc`** accepts boolean arrays aligned with the index labels, while **`.iloc`** accepts boolean arrays aligned with integer positions (0-based). According to the source code in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py), `.loc` validates labels through `_LocIndexer._validate_key` (lines 636-682), whereas `.iloc` uses `_iLocIndexer` (line 1700) to work directly with positional indices, offering faster access when you don't need label alignment.

### Why do I need parentheses when combining multiple boolean conditions?

Python's operator precedence places bitwise `&` and `|` lower than comparison operators like `>` and `==`. Without parentheses, Python evaluates comparisons before the bitwise operations, causing a TypeError. The pandas documentation (lines 35-38) enforces this rule, requiring expressions like `(df["A"] > 1) & (df["B"] < 2)` to ensure correct boolean logic.

### How does pandas handle boolean masks with different indexes than the DataFrame?

The `_LocIndexer._validate_key` method (lines 650-658) automatically aligns the boolean Series index to the target DataFrame's index before applying the mask. This alignment ensures that True/False values match the correct rows even when the boolean Series was constructed from a different data source or filtered subset.

### Can I use boolean indexing with MultiIndex DataFrames?

Yes. Boolean indexing works with MultiIndex DataFrames by applying the mask to the primary index level. For complex slicing across levels, use **`pd.IndexSlice`** (defined in lines 99-108) within `.loc` to specify partial selections like `df.loc[pd.IndexSlice[:, "level2_value"], :]`. The `_is_nested_tuple_indexer` method (lines 998-1005) handles the underlying tuple resolution.