Unit 4 • Lesson 3

Selecting Columns and Rows

Overview

loc goes by label, iloc goes by position, and the difference matters.

Intermediate 20–25 min

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 loc and iloc each 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.

1

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 Yourself

Print the type of each of these three. Two of them are the same and one is not.

Press Run to see output

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.

1

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
2

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.

3

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 Yourself

Sort this table by score descending, then print iloc[0] and loc[0]. Say which one is "the best score" before you run it.

Press Run to see output

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.

1

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.

Press Run to try it, then Check my work to mark it.

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.

Press Run to try it, then Check my work to mark it.