Selecting Columns and Rows
Overview
loc goes by label, iloc goes by position, and the difference matters.
What You Will Learn in This Lesson
By the end of this lesson, you will be able to:
- Take one column as a Series and several as a smaller table.
- Say what
locandiloceach read. - Explain why they stop agreeing after a filter or a sort.
- Find a row by what it contains rather than by where it sits.
Why This Matters
Almost every "why is this the wrong row" question comes from reaching for a position when you meant a label, or the reverse. The two look interchangeable on a freshly loaded table and stop being interchangeable the moment you sort or filter it — which is to say, immediately.
Step 1: Columns by name
One name gives a Series; a list of names gives a smaller DataFrame. The double brackets are not a typo — the inner pair is the list.
One column, or several
Single brackets with one name give the column itself. Wrap the name in a list and you get a table back, with one column in it.
import pandas as pd
df = pd.DataFrame({"name": ["Ada", "Grace"], "score": [92, 88]})
print(type(df["score"]).__name__)
print(type(df[["score"]]).__name__)
print(df[["name", "score"]])
Mini Practice #1: Series or table
Try It YourselfPrint the type of each of these three. Two of them are the same and one is not.
Step 2: Rows by label or by position
loc reads the index label; iloc reads the position. They agree on a default index and disagree the moment it changes.
The two accessors
Both take a row and optionally a column. loc names them; iloc counts them.
import pandas as pd
df = pd.DataFrame({"score": [92, 88, 79]}, index=["a", "b", "c"])
print(df.loc["b", "score"]) # by label
print(df.iloc[1]["score"]) # by position
Where they part company
Sort the table and the labels travel with their rows while the positions do not. After this sort, position 0 and label 0 are different rows.
import pandas as pd
df = pd.DataFrame({"score": [70, 99, 80]})
best = df.sort_values("score", ascending=False)
print(best)
print("iloc[0]:", best.iloc[0]["score"]) # the top score
print("loc[0]:", best.loc[0]["score"]) # still the original row 0
Neither is wrong. They are answering different questions, and a filtered or sorted table is where the difference becomes visible.
One more asymmetry
A loc slice includes its end label; an iloc slice excludes its end position, like every other slice in Python. This is worth knowing once rather than debugging twice.
import pandas as pd
df = pd.DataFrame({"score": [10, 20, 30, 40]})
print(len(df.loc[0:2])) # 3 -- inclusive
print(len(df.iloc[0:2])) # 2 -- exclusive
Mini Practice #2: After a sort they disagree
Try It YourselfSort this table by score descending, then print iloc[0] and loc[0]. Say which one is "the best score" before you run it.
Step 3: Finding a row by what is in it
Usually you do not know either the label or the position — you know a value. Mask first, then read off what survives.
Mask, then take
The mask keeps the rows you meant. .iloc[0] on the survivors takes the first of them, which is safe because you are indexing into the filtered result, not the original table.
import pandas as pd
df = pd.DataFrame({"name": ["Zoe", "Ada"], "score": [10, 92]})
match = df[df["name"] == "Ada"]
print(match)
print(int(match["score"].iloc[0]))
Step 4: When to Use It
loc for labels
Rows by index label and columns by name, with slices that include the end.
df.loc[df["score"] >= 60, ["name", "score"]]
iloc for positions
The first n rows, the last column — anything defined by position rather than name.
When Not to Use It
Do not chain selections and then assign, as in df[mask]["score"] = 0. It may change a temporary copy and leave df alone; do it in one .loc call.
End-of-Lesson Exercises
Exercise 1: The score belonging to a name
Write score_for(df, name) that returns the score belonging to name, where the table has a name column and a score column.
Mask on the name column, then read the score off what survives. Assuming a position is the bug the second hidden case is looking for.
Exercise 2: The top row, by position
Write best_name(df) that sorts the table by score descending and returns the name on the top row. Take the row by position on the sorted table, not by label.
sort_values("score", ascending=False) then .iloc[0]["name"]. Using .loc[0] would give you the original row 0, which the first hidden case is checking.