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.
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.
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.
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:
name per_hour
0 Avery 2.10
1 Jordan 2.39
2 Casey 1.65iterrows 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.
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:
0 Avery 42
1 Jordan 31
2 Casey 55A 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.
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:
0 Avery 89
1 Jordan 75
2 Casey 92Loop 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.
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:
hours 24 55
score 65 91
name object
track object
city object
hours int64
score int64apply, 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.
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:
name band summary
0 Avery high Avery on Python
1 Jordan low Jordan on NumPy
2 Casey high Casey on Pythonapply 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