Pandas loc vs iloc: Key Operational Differences
Understanding the operational differences between .loc
and .iloc is fundamental to effective data manipulation in
Python's Pandas library. While both indexers are used to extract,
filter, and modify subsets of data in a DataFrame or Series, they rely
on entirely different indexing mechanisms. This guide explains how
.loc operates as a label-based indexer, how
.iloc functions as an integer-position indexer, and how
their slicing, filtering, and indexing rules diverge in practice.
The Fundamental Rule: Labels vs. Positions
The core distinction between the two indexers comes down to how they identify elements:
.locis label-based: It selects rows and columns using their explicitly defined index names or column labels..ilocis integer position-based: It selects rows and columns using their 0-indexed numerical positions, regardless of what the actual index labels are.
import pandas as pd
df = pd.DataFrame(
{"City": ["New York", "Paris", "Tokyo"], "Population": [8.4, 2.1, 14.0]},
index=["a", "b", "c"],
)In this DataFrame:
df.loc['a', 'City']retrieves'New York'by specifying the row label'a'and column label'City'.df.iloc[0, 0]retrieves'New York'by specifying the 0th row position and 0th column position.
Key Operational Differences
1. Slicing Endpoints (Inclusion vs. Exclusion)
A major operational difference lies in how Python range slicing behaves:
.locincludes the stop bound: When using label ranges like'a':'c', the stop label'c'is included in the result..ilocexcludes the stop bound: Following standard Python list-slicing conventions,0:2includes positions0and1, while excluding position2.
# Returns rows 'a' and 'b' (inclusive of 'b')
df.loc["a":"b"]
# Returns rows at index positions 0 and 1 (excludes position 2)
df.iloc[0:2]2. Handling Numeric Indices
Confusion often arises when a DataFrame uses integer labels that do not match their default order (e.g., after sorting, filtering, or custom assignment).
df_num = pd.DataFrame({"Score": [10, 20, 30]}, index=[2, 0, 1])df_num.loc[0]looks for the label0and returns20.df_num.iloc[0]looks for the 0th physical row in memory and returns10.
Using .loc with integers queries the explicit label
values; using .iloc always queries the row's physical
offset from 0 to n - 1.
3. Boolean Masking
Both accessors support conditional selections, but with different constraints:
.locaccepts aligned Boolean Series: You can pass conditional expressions directly because.localigns the Boolean Series by index labels:df.loc[df["Population"] > 5].ilocrequires positional Boolean arrays:.iloccannot automatically align an indexed Series. It requires an array-like sequence of booleans or a list with the exact length of the axis:df.iloc[(df["Population"] > 5).to_numpy()]
4. Callable Functions
Both .loc and .iloc accept callable
functions (such as lambda functions) that take the calling DataFrame or
Series as their sole argument:
df.loc[lambda x: x['Population'] > 5]filters rows based on a label-oriented condition.df.iloc[lambda x: [0, 2]]selects specific rows by numerical position.
Quick Reference Comparison
| Feature | .loc |
.iloc |
|---|---|---|
| Input Type | Labels, names, or Boolean Series | Integers, integer lists, or slices |
| Slicing Behavior | Closed interval: start:stop
includes stop |
Half-open interval:
start:stop excludes stop |
| Out-of-Bounds Handling | Raises KeyError if label is
not found |
Raises IndexError if integer
position is invalid |
| Primary Use Case | Querying by meaningful IDs, dates, or column names | Positional operations, splitting data (e.g., train/test splits) |