# iloc pandas vs loc pandas: The Definitive Guide to Label vs Position Indexing

> Master pandas iloc vs loc for label vs position indexing. Learn how to select data efficiently using inclusive and exclusive slicing, avoiding common errors.

- Repository: [pandas/pandas](https://github.com/pandas-dev/pandas)
- Tags: deep-dive
- Published: 2026-02-19

---

**The primary difference is that `df.loc` selects data by label (index and column names) with inclusive slicing, while `df.iloc` selects by integer position (0-based) with exclusive slicing, raising `KeyError` and `IndexError` respectively for invalid selections.**

The `pandas-dev/pandas` library provides two distinct indexers for data selection that are often confused: `loc` and `iloc`. Understanding the distinction between **iloc pandas** and **loc pandas** indexing is fundamental for efficient data manipulation, as choosing the wrong indexer can lead to unexpected `KeyError` exceptions or incorrect data subsets.

## Core Architectural Difference: Labels vs Integer Positions

The fundamental distinction between these indexers is implemented in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py). According to the pandas source code, the `_LocIndexer` class (defined at line 1225) handles label-based selection, while the `_iLocIndexer` class (defined at line 1918) manages position-based selection.

| Feature | `df.loc` | `df.iloc` |
|---------|----------|-----------|
| **Indexing mode** | Label-based (index/column names) | Position-based (integer locations) |
| **Slice behavior** | Inclusive of stop label | Exclusive of stop position (Python standard) |
| **Error type** | `KeyError` for missing labels | `IndexError` for out-of-range positions |
| **Typical use** | Selecting by meaningful identifiers (dates, IDs) | Selecting by physical row/column order |

## Practical Usage Examples

### Selecting with loc (Label-Based)

Use `df.loc` when you need to select data by the actual index labels or column names. In [`pandas/core/frame.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/frame.py) (line 4162), the `DataFrame.__getitem__` method delegates to these indexers when you use bracket notation.

```python
import pandas as pd

df = pd.DataFrame(
    {"A": [10, 20, 30], "B": [40, 50, 60]},
    index=["x", "y", "z"]
)

# Select rows "x" through "y" (inclusive) and column "A"

result = df.loc["x":"y", "A"]
print(result)

# Output:

# x    10

# y    20

# Name: A, dtype: int64

```

Notice that `"x":"y"` includes the row labeled `'y'` because `loc` uses inclusive slicing.

### Selecting with iloc (Position-Based)

Use `df.iloc` when you need to select by integer position, regardless of what the index labels contain. This is implemented in the `_iLocIndexer` class and follows standard Python slicing conventions.

```python

# Select the first two rows (positions 0 and 1) and first column (position 0)

result = df.iloc[0:2, 0]
print(result)

# Output:

# 0    10

# 1    20

# Name: A, dtype: int64

```

Here, `0:2` includes positions `0` and `1` but excludes position `2`, following Python's exclusive slicing convention.

## Critical Behavioral Differences

### Slice Inclusivity

The most common source of confusion between **iloc pandas** and **loc pandas** selection is slice endpoint behavior. As shown in the examples above, `df.loc["x":"y"]` includes the `'y'` label, while `df.iloc[0:2]` excludes the final index (2). This aligns with Python's standard slicing for integers but requires careful attention when mixing label-based selection.

### Error Handling

The error types differ based on the indexing mode, as implemented in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py):

- **`df.loc`**: Raises `KeyError` when a label is not found in the index or columns, similar to dictionary key lookup.
- **`df.iloc`**: Raises `IndexError` when an integer position is out of bounds (e.g., trying to access row 100 in a 10-row DataFrame).

This distinction is crucial for debugging: if you see `KeyError`, check your index labels; if you see `IndexError`, check your integer positions.

### Integration with __getitem__

According to [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) (line 4281), the `__getitem__` method provides a shortcut that attempts to use `loc` for label-based selection in certain contexts, but explicit use of `.loc` or `.iloc` is recommended for clarity and predictable behavior.

## Summary

- **`df.loc`** selects by **label** (index/column names) with **inclusive** slicing and raises **KeyError** for missing labels.
- **`df.iloc`** selects by **integer position** (0-based) with **exclusive** slicing and raises **IndexError** for out-of-bounds positions.
- Both indexers are implemented in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py) (`_LocIndexer` at line 1225 and `_iLocIndexer` at line 1918).
- Use **loc pandas** indexing when working with meaningful identifiers; use **iloc pandas** indexing when you need physical row/column order.

## Frequently Asked Questions

### Can I use negative indices with iloc?

Yes, **iloc pandas** indexing supports negative integers, similar to standard Python sequences. For example, `df.iloc[-1]` selects the last row, and `df.iloc[-2:]` selects the last two rows. This works because `_iLocIndexer` translates negative integers to their corresponding positive positions before accessing the underlying data.

### Why does loc include the stop index but iloc doesn't?

**Loc pandas** selection treats the index as a set of labels with potential semantic meaning (like dates or names), where the boundary labels themselves carry information worth including. **Iloc pandas** selection follows standard Python slicing conventions where the stop index is exclusive, consistent with how Python lists and NumPy arrays behave. This design choice in [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py) maintains consistency with Python's indexing semantics for integers while providing intuitive label-based inclusion.

### Which is faster, loc or iloc?

Performance is generally comparable for simple selections, but **iloc pandas** indexing can be slightly faster in some cases because it accesses data directly by integer position without needing to resolve label-to-position mappings. However, the difference is usually negligible unless working with extremely large DataFrames or performing operations in tight loops. For optimal performance in either case, ensure you are not chaining indexers (e.g., `df.loc[...].iloc[...]`) as this creates intermediate copies.

### Can I mix labels and positions in a single selection?

No, you cannot mix label-based and position-based indexing in a single call to either **loc pandas** or **iloc pandas**. You must use one or the other consistently for each dimension. If you need to select columns by position and rows by label (or vice versa), use chained indexing or convert positions to labels using `df.columns[pos]` or `df.index[pos]` before passing them to `.loc`. The [`pandas/core/indexing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py) implementation enforces this separation to prevent ambiguity in selection logic.