Skip to main content
MahiDev — Learn. Build. Become AI-ready.

Lesson 17

Iterating Rows and Columns

Walk a DataFrame row by row with iterrows and itertuples, loop over its columns, and know why whole-column work is usually the better answer.

Intermediate25 min
Iterating Rows and Columns hero Module 24 Lesson 17Iterating Rows and Columns hero Module 24 Lesson 17

You can loop over a DataFrame, and sometimes you should. But a loop in Python touches one value at a time while a column operation hands the whole column to compiled code, so reach for the loop only when there is no column form.

Five ways to work through a table, ordered from fastest to slowestFive ways to work through a table, ordered from fastest to slowest

Prefer whole columns

Anything arithmetic or comparative has a column form. It is shorter to write, easier to read, and far quicker on a real table.

python
import pandas as pd

learners = pd.DataFrame({
    "name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
    "track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
    "city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
    "hours": [42, 31, 55, 24, 47, 38],
    "score": [88, 74, 91, 65, 82, 79],
})
learners["per_hour"] = (learners["score"] / learners["hours"]).round(2)
print(learners[["name", "per_hour"]].head(3))

Output:

text
     name  per_hour
0   Avery      2.10
1  Jordan      2.39
2   Casey      1.65

iterrows gives a label and a Series

Each turn of the loop yields the row label and the row as a Series, so you reach the fields by name. It is the friendliest row loop and the slowest, because a fresh Series is built for every row.

python
import pandas as pd

learners = pd.DataFrame({
    "name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
    "track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
    "city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
    "hours": [42, 31, 55, 24, 47, 38],
    "score": [88, 74, 91, 65, 82, 79],
})
for label, row in learners.head(3).iterrows():
    print(label, row["name"], row["hours"])

Output:

text
0 Avery 42
1 Jordan 31
2 Casey 55

A row Series shares one dtype, so numbers in a mixed table arrive as object. Convert if you need to compare them.

itertuples is the one to use

It yields a light named tuple per row, keeps each column's own type, and runs several times faster than iterrows. Reach the fields with a dot, and pass index=False if you do not need the label.

python
import pandas as pd

learners = pd.DataFrame({
    "name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
    "track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
    "city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
    "hours": [42, 31, 55, 24, 47, 38],
    "score": [88, 74, 91, 65, 82, 79],
})
for row in learners.head(3).itertuples():
    print(row.Index, row.name, row.score + 1)

Output:

text
0 Avery 89
1 Jordan 75
2 Casey 92

Loop over the columns

items() yields each column name with the column as a Series. Looping over df.columns and selecting is the same thing spelled out, and both are useful when you want a report per column.

python
import pandas as pd

learners = pd.DataFrame({
    "name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
    "track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
    "city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
    "hours": [42, 31, 55, 24, 47, 38],
    "score": [88, 74, 91, 65, 82, 79],
})
for name, column in learners[["hours", "score"]].items():
    print(name, column.min(), column.max())

for name in learners.columns:
    print(name, learners[name].dtype)

Output:

text
hours 24 55
score 65 91
name object
track object
city object
hours int64
score int64

apply, once you need a function

apply on a Series runs your function per value. On a DataFrame with axis=1 it runs per row and hands you the row as a Series, which covers the cases a plain column expression cannot.

python
import pandas as pd

learners = pd.DataFrame({
    "name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
    "track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
    "city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
    "hours": [42, 31, 55, 24, 47, 38],
    "score": [88, 74, 91, 65, 82, 79],
})
def band(score):
    if score >= 85:
        return "high"
    if score >= 75:
        return "mid"
    return "low"

learners["band"] = learners["score"].apply(band)
learners["summary"] = learners.apply(
    lambda row: f"{row['name']} on {row['track']}", axis=1
)
print(learners[["name", "band", "summary"]].head(3))

Output:

text
     name  band          summary
0   Avery  high  Avery on Python
1  Jordan   low  Jordan on NumPy
2   Casey  high  Casey on Python

apply is still a Python loop underneath, so keep the column form when one exists.

Key takeaways

  • Whole-column work beats a loop for anything arithmetic

  • iterrows yields a label and a Series, and it is the slowest option

  • itertuples keeps the column types and runs much faster

  • items loops over the columns, and apply runs a function per value or per row

  • Next: Grouping with groupby

  • Previous: Working with Missing Data

Loading playground…

Check your understanding

Iterating Rows and Columns Quiz

What does iterrows yield each turn?

1 / 10