Select Data with loc and iloc
Learn to select columns and rows with loc and iloc in pandas. Master label-based and position-based indexing with hands-on examples, troubleshooting tips, and next steps in the Data Analysis with Python track.
Focus: select columns and rows with loc and iloc
You've loaded a DataFrame, glimpse at your data, and now you're staring at hundreds of columns and thousands of rows. Every time you need a specific slice — say, the price column for the last 50 customers — you find yourself writing clunky chain after chain of code, or worse, accidentally grabbing the wrong data because you confused a label with a position. That inefficiency is costing you time and risking silent errors. Selecting columns and rows with loc and iloc is the precise, fast, and readable way to extract exactly the data you need — and in this lesson, you'll master both so selection becomes second nature.
The problem this lesson solves
Without deliberate, consistent selection methods, data analysis code becomes a mess of guesswork. You might use df['column'] to get a column, but what about multiple columns? What about rows that match a condition? What about rows five through ten? Beginners often mix up label and position, leading to off-by-one errors or KeyErrors that are frustrating to debug.
These errors aren't just annoying — they undermine trust in your analysis. A single wrong row can skew an average, corrupt a merge, or mislead a business decision. The solution is to stop improvising and start using the two purpose-built pandas indexers: loc and iloc. Once you internalize the difference, you'll write selection code that is explicit, robust, and self-documenting.
By the end of this lesson, you'll not only be able to explain the core idea, but also apply it in a hands-on exercise — and you'll be ready to move on to the next step in the Data Analysis with Python track.
Core concept / mental model
Think of your DataFrame as a spreadsheet with labeled rows (the index) and labeled columns. There are two natural ways to refer to a cell or a block: by name or by number.
loc= location by label. You ask pandas: 'Give me the row whose index is'A', and the column named'price'.' The index label and column name can be any hashable object (string, integer, date, etc.). Crucially, if your index contains integers 0–9,loc[0]means the row labeled0, not necessarily the first row.iloc= integer location. You ask pandas: 'Give me the 0th row and the 2nd column.' Here,0means first,1means second, and so on — identical to Python list indexing, including negative values to count from the end.
A helpful analogy: loc is like using the postal address of a house, iloc is like using its coordinates on a map. Both can get you to the same place if the address and coordinates happen to align, but they are distinct systems.
The loc and iloc indexers return a new object (a view or copy depending on context), and they accept a wide range of inputs: single labels, lists of labels, slices (inclusive for loc, exclusive for iloc), boolean masks, and callables.
How it works step by step
The best way to master loc and iloc is to understand their grammar. Both follow the same syntax:
# General form
selection = df.loc[row_selector, column_selector]
selection = df.iloc[row_selector, column_selector]
Both selectors can be any of the following:
- A single label (or integer for
iloc): returns a scalar (if both are single) or a Series. - A list of labels (or integers): returns a DataFrame.
- A slice: for
loc, it's inclusive on both ends; foriloc, it's exclusive of the stop, just like Python lists. - A boolean Series or array (e.g., from a condition): returns rows where
True. - A callable that takes the DataFrame and returns one of the above.
Let's unpack the steps with a concrete sequence:
- Identify your rows. Ask: do I know the row names? If yes, use
loc. If I only know positions (e.g., the first 100 rows), useiloc. - Identify your columns. Same logic: column names? Use
loc. Column positions? Useiloc. - Combine the two with a comma inside the square brackets. If you omit the column selector (e.g.,
df.loc[['a','b']]), pandas returns all columns for those rows.
You can also mix row selection with column selection in a single call, which is far more readable than chaining df[df['x']>0]['y'] (and also avoids potential SettingWithCopyWarning later).
Hands-on walkthrough
Let's build a small DataFrame and try every core selection pattern. This is the same kind of data you'll encounter in the next lessons in the track.
import pandas as pd
df = pd.DataFrame(
{
"product": ["Laptop", "Mouse", "Monitor", "Keyboard", "SSD"],
"price": [1200, 25, 300, 80, 150],
"stock": [10, 500, 200, 300, 150],
},
index=["A", "B", "C", "D", "E"], # custom labels, just for demo
)
print(df)
Output:
product price stock
A Laptop 1200 10
B Mouse 25 500
C Monitor 300 200
D Keyboard 80 300
E SSD 150 150
Selecting columns with loc and iloc
# Single column by name (returns a Series)
print(df.loc[:, "price"])
# Multiple columns by name (returns a DataFrame)
print(df.loc[:, ["product", "price"]])
# Single column by position (the 2nd column, index 1)
print(df.iloc[:, 1])
# Multiple columns by position
print(df.iloc[:, [0, 2]])
Note that the row selector : (a slice from start to end) means 'all rows.' For iloc, you could also write df.iloc[range(len(df)), 1], but : is more idiomatic.
Selecting rows with loc and iloc
# Row by label (returns a Series)
print(df.loc["C"])
# Rows by list of labels (returns a DataFrame)
print(df.loc[["B", "D"]])
# Row by position (the 3rd row)
print(df.iloc[2])
# Rows by slice: label slice is INCLUSIVE -> B, C, D
print(df.loc["B":"D"])
# Position slice is EXCLUSIVE -> rows 1 and 2 (B and C)
print(df.iloc[1:3])
Output for the last two:
product price stock
B Mouse 25 500
C Monitor 300 200
D Keyboard 80 300
product price stock
B Mouse 25 500
C Monitor 300 200
Combine rows and columns
This is where loc/iloc shine — one call, no chaining:
# Label-based: rows B and D, columns 'product' and 'price'
print(df.loc[["B", "D"], ["product", "price"]])
# Position-based: rows 1 and 3, columns 0 and 2
print(df.iloc[[1, 3], [0, 2]])
# Label slice with column list
print(df.loc["B":"C", "price"])
Use conditions with loc
Boolean masks are the bread and butter of real analyses:
# All rows where price > 100, select 'product' and 'price'
print(df.loc[df["price"] > 100, ["product", "price"]])
Expected output:
product price
A Laptop 1200
C Monitor 300
E SSD 150
Pro tip:
df[df['price'] > 100]['product']works, butdf.loc[df['price'] > 100, 'product']is more explicit and keeps the selection in one place — and it avoids theSettingWithCopyWarningtrap when you later assign values.
Compare options / when to choose what
Not every selection needs loc or iloc. Here's a quick comparison of your common tools.
| Method | Syntax | When to use | Inclusive slice? | Fast? |
|---|---|---|---|---|
df['col'] |
Square bracket with column name | Get a single column (Series) | N/A | Yes |
df[['col1','col2']] |
List of column names | Get a DataFrame of multiple columns | N/A | Yes |
df[df['col'] > 0] |
Boolean mask (rows only) | Filter rows when you don't need column selection | N/A | Yes |
df.loc[rows, cols] |
Label-based selection | You know row/column names; inclusive ranges; boolean masks | Yes (for slices) | Slightly slower than iloc on large data |
df.iloc[rows, cols] |
Position-based selection | You know row/column positions; you want Python-style slicing | No (exclusive) | Fastest |
When to choose loc:
- Your index has meaningful labels (dates, user IDs, categories).
- You need inclusive slices (e.g.,
'2024-01-01':'2024-12-31'). - You're applying a boolean mask and also want specific columns in one go.
When to choose iloc:
- You want the first N rows (common with
df.head(), butdf.iloc[:N]is more flexible). - You're working with a positional loop or a calculated index.
- You need the fastest possible selection for a pure positional slice.
Avoid mixing them. If you have labels, use loc; if you have positions, use iloc. Mixing the two in one call will throw an error (e.g., df.loc[0] when index is ['A','B'] raises a KeyError).
Troubleshooting & edge cases
KeyError in loc
If you use loc with a label that doesn't exist in the index, pandas raises KeyError: 'X'. This is a common beginner stumble, especially when the index is a range of integers: df.loc[1] means the row labeled 1, not the second row. Always inspect df.index first.
Off-by-one in iloc slices
iloc uses exclusive stop, just like Python's range. So df.iloc[1:3] returns rows 1 and 2 — not row 3. If you need the first 5 rows, write df.iloc[:5], not df.iloc[1:5].
loc slice inclusivity surprises you
df.loc['A':'C'] includes both 'A' and 'C'. This is intentional, but if you're used to iloc or Python slicing, it catches you off guard. If you want to exclude 'C', you'll have to drop it explicitly (e.g., use a list ['A','B']).
Boolean mask length mismatch
If your condition returns a Series with a different index than your DataFrame, pandas will raise an error. For example, after sorting, filters may not align. Use .reindex() or align the filters to the DataFrame's index:
mask = some_series > 5
mask = mask.reindex(df.index, fill_value=False)
df = df.loc[mask]
SettingWithCopyWarning when using chained indexing
Avoid patterns like df[df['a']>0]['b'] = 5. This may silently fail or trigger warnings. Use loc for assignments:
df.loc[df["price"] > 500, "stock"] = 0 # sets stock to 0 for expensive items
This is the safe and explicit way, and it's a key reason you'll want loc in your arsenal.
What you learned & what's next
You now understand the core difference between loc (label-based) and iloc (position-based), and you can apply both to select columns and rows in a single, readable line. You've learned how to use slices, lists, and boolean masks, and you can choose the right tool for the job. You also know how to troubleshoot the most common errors — key misspellings, slice off-by-one, and mask alignment. Critically, you've seen how loc enables safe assignments without copy warnings.
This directly builds on the previous lesson (loading and inspecting DataFrames) and sets you up for the next one: filtering and sorting data in pandas. You'll combine these selection skills with logic to answer more complex questions — like 'What were the top 5 products by revenue last quarter?' — with elegance and speed.
Keep the analogy in mind: loc is the address, iloc is the map coordinate. Use both consciously, and your data analysis code will be a model of clarity.
Practice recap
Now reinforce these concepts: create your own DataFrame with custom string index labels, then practice selecting rows and columns using both loc and iloc. Try a boolean mask filtering rows where a numeric column exceeds a threshold, and update a value using loc with a condition. Confirm your slices behave as expected — note the inclusive vs exclusive difference — and then move on to the next lesson on filtering and sorting.
Common mistakes
- Using
locwith position integers when the index labels are also integers but not in the same order —df.loc[0]gives the row labeled0, not necessarily the first row. - Forgetting that
ilocslices are exclusive of the stop (like Python lists), sodf.iloc[1:3]gives rows 1 and 2, not rows 1, 2, and 3. - Assuming
locslices are exclusive — they are inclusive of both endpoints, which catches many beginners off guard when they expect a range like['A':'C']to exclude 'C'. - Chaining selections like
df[df['price']>100]['product']instead of using oneloccall — this risksSettingWithCopyWarningand makes the code harder to read and modify.
Variations
- You can use
df.query('column > 100')for label-based, condition-driven row filtering that is more readable for complex logical expressions. - NumPy-style boolean indexing with
df[df['col'] > 0]is a shortcut for row filtering, but it only selects rows — not specific columns — in a separate step. - For high-performance selection on large DataFrames, you can use the pandas
.atand.iataccessors to get or set a single scalar value by label or position respectively.
Real-world use cases
- Extracting customer records from the last year for a cohort analysis using
df.loc[df['date'] >= '2024-01-01', ['customer_id', 'revenue']]. - Pulling the first 1,000 rows of a sensor log for a quick sanity check before cleaning, via
df.iloc[:1000]. - Updating stock levels to zero for discontinued items identified by a condition, using
df.loc[df['status'] == 'discontinued', 'stock'] = 0.
Key takeaways
locselects by row/column label and uses inclusive slices;ilocselects by integer position and uses exclusive slices.- Both indexers accept single labels, lists, slices, boolean masks, and callables, and can select rows and columns in one call.
locis ideal for meaningful index labels, date ranges, and conditional row filtering combined with column selection.ilocis the fastest choice for positional slicing (e.g., first N rows) and when you know exact column positions.- Avoid chained indexing for assignments; use
locwith a boolean mask to preventSettingWithCopyWarningand ensure updates work as expected.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.