What is the difference between loc and iloc in pandas?
Short answer
loc selects by label (the index and column names), while iloc selects by integer position. They differ most when your index is not a simple 0-based range.
The short version
loc selects by label, the actual values in the index and the column names. iloc selects by integer position, counting from zero regardless of what the labels are. When your index happens to be 0, 1, 2, ... the two look identical, which is exactly why the difference trips people up later.
A worked example
import pandas as pd
df = pd.DataFrame(
{"name": ["a", "b", "c"], "score": [10, 20, 30]},
index=[5, 6, 7],
)
print(df.loc[5, "score"]) # 10, the row labelled 5
print(df.iloc[0]["score"]) # 10, the first row by position
Here df.loc[5] and df.iloc[0] point at the same row, but only because label 5 sits in position 0. Ask for df.loc[0] and you get a KeyError, because there is no label 0.
Why this matters after filtering
The classic bug appears after you filter or sort. The index keeps its original labels, so positions and labels no longer line up.
big = pd.DataFrame({"value": range(10)})
filtered = big[big["value"] > 6] # labels 7, 8, 9
filtered.iloc[0] # works, first surviving row
filtered.loc[0] # KeyError, label 0 was filtered out
If you truly want the first surviving row by position, use iloc. If you want a specific original row, use loc.
Slicing behaves differently too
With loc, slices include both ends because they are labels. With iloc, slices exclude the end, like ordinary Python.
df.loc[5:6] # rows labelled 5 and 6, both included
df.iloc[0:2] # positions 0 and 1, position 2 excluded
A practical rule
Reach for loc by default, because named access is easier to read and survives reordering. Use iloc only when position genuinely is what you mean, for example "the last row" with df.iloc[-1]. If you ever mix them up, remember the mnemonic that the "i" in iloc stands for integer position.