iloc & loc vs iloc
Use .iloc[rows, columns] to select by integer position (end-exclusive, negative indexes allowed), and master the full loc vs iloc comparison.
Watch the lesson
1 video on CodeWithMunnaX
Sometimes you don't care what a row is called — you want "the first 3 rows", "the last row", "columns 2 to 4", "every other row". That is position thinking, exactly like indexing a Python list or a NumPy array, and the tool for it is .iloc (the i stands for integer). Munna's video 04 puts loc and iloc side by side, because confusing them is the #1 indexing bug in pandas.
Definition
DataFrame.ilocis an integer-position-based indexer:df.iloc[row_positions, column_positions]selects rows and columns by their 0-based position in the table, ignoring labels completely, with end-exclusive slices and support for negative positions.
- 0-based position — the first row is position 0, the second is 1, no matter what the index labels say.
- Ignoring labels — after sorting or filtering, row labels get jumbled (e.g. 6, 1, 0) but positions are always 0, 1, 2… from top to bottom.
- End-exclusive —
iloc[1:4]returns positions 1, 2, 3 (not 4), exactly like Pythonlist[1:4]. - Negative positions —
-1is the last row/column,-2the second-last.
Syntax
python
df.iloc[i] # row at position i -> Series
df.iloc[[i, j]] # several rows -> DataFrame
df.iloc[start:stop] # rows start..stop-1 (stop EXCLUDED)
df.iloc[start:stop:step] # with a step
df.iloc[rows, cols] # rows + columns, both by position
df.iloc[i, j] # one scalar value
df.iloc[-1], df.iloc[:, -1] # last row, last column| Selector | Example | Result |
|---|---|---|
| integer | players.iloc[0] | first row as Series |
| negative integer | players.iloc[-1] | last row |
| list of integers | players.iloc[[0, -1], [0, 3]] | first & last rows, columns 0 and 3 |
| slice | players.iloc[1:4, 0:2] | rows 1–3, columns 0–1 (end excluded) |
| slice with step | players.iloc[::2, :2] | every 2nd row, first 2 columns |
| boolean array | players.iloc[mask.values] | rows where True |
Example — positions, negatives and slices
python
print(players.iloc[-1])
print(players.iloc[[0, -1], [0, 3]])
print(players.iloc[1:4, 0:2])
print(players.iloc[::2, :2])text
player Rashid Khan
team GT
role Bowler
runs 102
wickets 10
strike_rate 120.0
Name: 7, dtype: object
player runs
0 Virat Kohli 741
7 Rashid Khan 102
player team
1 Rohit Sharma MI
2 MS Dhoni CSK
3 Jasprit Bumrah MI
player team
0 Virat Kohli RCB
2 MS Dhoni CSK
4 Hardik Pandya MI
6 Shubman Gill GTHow it works: iloc[-1] is the last row (labelled 7). [[0, -1], [0, 3]] takes the first and last rows and the columns at positions 0 (player) and 3 (runs). iloc[1:4, 0:2] gives rows 1, 2, 3 — position 4 is excluded — and columns 0 and 1. Compare with loc[2:4] in the previous topic, which included 4.
Example — the moment loc and iloc disagree
With the default index, labels and positions are the same numbers, so loc and iloc seem identical. Sort the table and they split apart:
python
by_runs = players.sort_values("runs", ascending=False)
print(by_runs[["player", "runs"]].head(3))
print("loc[1] ->", by_runs.loc[1, "player"]) # the row LABELLED 1
print("iloc[1] ->", by_runs.iloc[1]["player"]) # the row in POSITION 1
print(by_runs.loc[0:2, ["player", "runs"]]) # from label 0 down to label 2!text
player runs
0 Virat Kohli 741
6 Shubman Gill 426
1 Rohit Sharma 417
loc[1] -> Rohit Sharma
iloc[1] -> Shubman Gill
player runs
0 Virat Kohli 741
6 Shubman Gill 426
1 Rohit Sharma 417
5 Ravindra Jadeja 267
4 Hardik Pandya 216
2 MS Dhoni 161How it works: after sorting, the row labelled 1 (Rohit) sits at position 2, while position 1 holds Shubman Gill (label 6). loc[0:2] walks from label 0 down the table until it reaches label 2 — that's 6 rows, not 3! If you want "the top 3", use iloc[0:3] or head(3).
Example — errors that tell you which one to use
python
players.iloc[0, "player"]
# ValueError: Location based indexing can only have [integer, integer slice
# (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types
players.iloc[20]
# IndexError: single positional indexer is out-of-bounds
print(players.iloc[5:20]) # slices never raise - they just stop at the endtext
player team role runs wickets strike_rate
5 Ravindra Jadeja CSK All-rounder 267 8 157.1
6 Shubman Gill GT Batter 426 0 147.4
7 Rashid Khan GT Bowler 102 10 120.0How it works: iloc refuses labels like "player" (the error message even reminds you that the end point is excluded). A single position past the end raises IndexError, but an out-of-range slice is forgiven, just like Python lists.
Comparison — loc vs iloc (the full table)
.loc | .iloc | |
|---|---|---|
| Selects by | labels (index labels, column names) | integer positions (0, 1, 2…) |
| Row selector | label, list of labels, label slice, boolean mask | int, list of ints, int slice, boolean array |
| Column selector | column names: "runs", ["a", "b"], "team":"runs" | column positions: 3, [0, 3], 1:4 |
| Slice end | included — loc[2:4] → 2, 3, 4 | excluded — iloc[2:4] → 2, 3 |
| Negative numbers | only if -1 is an actual label | yes — -1 = last |
| Out-of-range single item | KeyError | IndexError |
| Out-of-range slice | returns what exists | returns what exists |
| After sorting / filtering | follows labels (they move with the rows) | always top-to-bottom order |
| Boolean mask | yes — loc[df["runs"] > 400] | use a plain array — iloc[mask.values] (a boolean Series raises ValueError on a non-integer index) |
| Assigning values | df.loc[mask, "col"] = v ✅ most common | df.iloc[0, 3] = v |
| Typical question | "Dhoni's row", "rows where runs > 400" | "first 5 rows", "last column" |
And plain []? On a DataFrame, df["col"] selects columns, while df[1:3] (a slice) selects rows by position, and df[mask] filters rows. That mix of meanings is exactly why loc/iloc exist: they are explicit.
Note (pandas 3): on a Series with a non-integer index (e.g. player names),
s[2]no longer falls back to "position 2" — it raisesKeyError: 2. Uses.iloc[2]for positions ands.loc["MS Dhoni"]for labels.
Common mistakes
- ❌
iloc[0:5]expecting 6 rows. ✅ End is excluded: positions 0–4, five rows. - ❌
iloc[:, "runs"]. ✅iloctakes positions only —iloc[:, 3], or useloc[:, "runs"]. - ❌ Using
loc[0:3]for "top 3" after sorting. ✅ Labels moved; useiloc[0:3]orhead(3). - ❌ Mixing in one call:
loc[0:3, 2]. ✅ Pick one system:iloc[0:3, 2]orloc[0:3, "role"]— or combine explicitly:df.iloc[0:3]["role"].
Interview tip
Q: After df = df.sort_values("score"), what is the difference between df.loc[0] and df.iloc[0]?
A: df.loc[0] returns the row whose index label is 0 — wherever it ended up after sorting. df.iloc[0] returns whatever row is now first (the lowest score). They are the same only when labels and positions line up, e.g. right after reset_index(drop=True).
Key points to remember
- df.iloc[rows, cols] selects by 0-based integer position, ignoring labels.
- iloc slices exclude the end (iloc[1:4] → positions 1, 2, 3); loc slices include it.
- iloc supports negative positions: iloc[-1] is the last row, iloc[:, -1] the last column.
- A single out-of-range position raises IndexError; out-of-range slices are silently trimmed.
- After sorting or filtering, labels and positions differ — loc follows labels, iloc follows the visible order.
- Use iloc for 'first/last n rows' questions and loc for named rows, column names and boolean masks.
- In pandas 3, Series[int] on a non-integer index is label-only (KeyError); use .iloc for positions.
Try it yourself
iloc by position
loc vs iloc after sorting
Same selection, both ways
Practice: first, last and every other
Revision Flashcards
Tap a card to flip it and see the answer.
