Select rows and columns by integer position, counting from zero.
.iloc is the positional counterpart to .loc. They answer different questions: .loc asks which label?, .iloc asks which row number? On a freshly loaded data frame those happen to coincide, because the default index runs 0, 1, 2, … and position matches label. That coincidence is temporary, and mistaking it for a rule is where the trouble starts.
Official documentation: DataFrame.iloc
The forms worth knowing
df.iloc[0] # first row
df.iloc[-1] # last row
df.iloc[:5] # first five rows
df.iloc[0, 1] # row 0, column 1 -- a single cell
df.iloc[:5, :2] # first five rows, first two columns
df.iloc[[0, 2, 4]] # rows 0, 2 and 4
Note the comma. df.iloc[0, 1] selects a cell in one operation; df.iloc[0][1] selects a row and then indexes into it — slower, harder to read, and on assignment it fails silently. Use the comma form. The same rule applies to .loc.
Unlike .loc, an .iloc slice excludes its endpoint, exactly like ordinary Python slicing: df.iloc[:3] gives rows 0, 1 and 2. The inconsistency is deliberate — .iloc counts positions, so it behaves like a list.
.iloc takes numbers, never pd.col expressions. For a condition, you want .loc.
Any index but the default breaks the correspondence
The moment your index carries meaning, position and label stop being the same idea at all. Group the Global Coal Plant Tracker by country and the index becomes country names:
import pandas as pd
url = ('https://www.bambooweekly.com/content/files/wp-content/uploads/2024/02/'
'global-coal-plant-tracker-january-2024.xlsx')
by_country = (
pd.read_excel(url, sheet_name='Units', usecols=['Country', 'Capacity (MW)'])
.groupby('Country')['Capacity (MW)']
.sum()
.sort_values(ascending=False)
)
by_country.iloc[0] # 2326616.3 -- whatever sits first
by_country.loc['China'] # 2326616.3 -- the row named China
by_country.loc[0] # KeyError: 0
.loc[0] is not merely wrong here — it is meaningless, because no label 0 exists in this index. Any set_index, groupby, resample, value_counts or pivot_table puts you in this situation, which covers most real analyses.
The subtler case: sorting
When the index is still the default, sorting produces a quieter version of the same problem. Labels stay glued to their rows, so both forms remain legal and simply disagree.
Netflix published an engagement report listing every title watched over six months. Bamboo Weekly #45 used it. Sort alphabetically, then ask for "the first row" two ways:
url = ('https://www.bambooweekly.com/content/files/4cd45et68cgf/1HyknFM84ISQpeua6TjM7A/'
'97a0a393098937a8f29c9d29c48dbfa8/'
'what_we_watched_a_netflix_engagement_report_2023jan-jun.xlsx')
df = (
pd.read_excel(url, skiprows=5, usecols=['Title', 'Hours Viewed'])
.sort_values('Title')
)
df.iloc[0, 0] # '#Alive // #살아있다' -- row now in position 0
df.loc[0, 'Title'] # 'The Night Agent: Season 1' -- the row labelled 0
Both work, neither raises, and they return different titles. Look at the index and it becomes obvious:
df.iloc[:3]
Title Hours Viewed
1927 #Alive // #살아있다 10700000
8499 #AnneFrank - Parallel Stories 800000
12506 #AtFirstSight // #Sohavégetnemérös 200000
Rows 0, 1 and 2 by position — carrying labels 1927, 8499 and 12506. Before the sort they agreed. Afterwards they have nothing to do with each other, and nothing warns you.
Three mistakes people make
Treating .iloc and .loc as interchangeable. They coincide only on an untouched default index. Set an index, group, resample or sort, and they part company — sometimes loudly with a KeyError, sometimes silently with the wrong row.
Chained brackets: df.iloc[0]['Title']. This selects a row and then indexes into the result. For reads it merely wastes time; for writes it fails silently, because the assignment lands on a temporary object that is discarded. Say df.iloc[0, 0] or df.loc[0, 'Title'] in one step.
Expecting an .iloc slice to include its endpoint. It does not. .iloc[:3] returns three rows, while .loc['a':'c'] includes c. The two follow different rules on purpose.
Watch it
How .loc and .iloc treat Pandas slices differently covers the endpoint difference and where it bites.
Practice it
Work through an .iloc exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/iloc/
Go deeper
Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.
See it on real data
Below are the 49 Bamboo Weekly exercises that use iloc on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #181: Housing costs
- Bamboo Weekly #177: European Summer
- Bamboo Weekly #175: Inflation
- Bamboo Weekly #167: Oil prices
- Bamboo Weekly #166: Income tax
- Bamboo Weekly #160: Strait of Hormuz
- Bamboo Weekly #159: State of the Union
- Bamboo Weekly #153: Venezuela
- Bamboo Weekly #152: Congestion pricing
- Bamboo Weekly #151: PyPI in 2025
- Bamboo Weekly #149: Flu season
- Bamboo Weekly #148: US Manufacturing
- Bamboo Weekly #142: Hurricanes
- Bamboo Weekly #141: Argentina
- Bamboo Weekly #138: Federal workers
- Bamboo Weekly #131: Canadian border crossings
- Bamboo Weekly #130: Jobs reporting
- Bamboo Weekly #127: European comparisons
- Bamboo Weekly #125: Shrinking dollars
- Bamboo Weekly #124: NATO Spending
- Bamboo Weekly #123: Missiles
- Bamboo Weekly #122: Economic growth
- Bamboo Weekly #120: Pennies
- Bamboo Weekly #117: Electricity
- Bamboo Weekly #116: Philadelphia Fed survey
- Bamboo Weekly #109: Cacao nibs
- Bamboo Weekly #107: Consumer confidence
- Bamboo Weekly #98: Retail sales
- Bamboo Weekly #96: Taylor Swift
- Bamboo Weekly #94: Strategic Wine Reserve
- Bamboo Weekly #92: Climate disaster costs
- Bamboo Weekly #90: Voter participation
- Bamboo Weekly #84: Central banks
- Bamboo Weekly #73: Avocado hand
- Bamboo Weekly #70: Moon missions
- Bamboo Weekly #68: Dangerously hot weather
- Bamboo Weekly #61: Solar eclipse
- Bamboo Weekly #58: NATO
- Bamboo Weekly #54: Household debt
- Bamboo Weekly #45: Netflix
- Bamboo Weekly #41: Wine production
- Bamboo Weekly #39: WeWork
- Bamboo Weekly #37: Consumer finances
- Bamboo Weekly #31: Poverty
- Bamboo Weekly #30: Uncertainty
- Bamboo Weekly #28: Pret a Manger
- Bamboo Weekly #24: Wildfire smoke
- Bamboo Weekly #13: Python developers
- Bamboo Weekly #11: Software jobs
Part of the Pandas Methods Index. See also practice by skill.