apply() on a Series
Run your own Python function on every value of a Series with Series.apply() — custom ratings, extra arguments, and functions that return several values.
Watch the lesson
1 video on CodeWithMunnaX
Pandas has ready-made methods for common jobs — sum(), round(), .str.upper(), and so on. But real analysis always needs some custom logic that pandas does not ship with: "give each batter a rating — Excellent for 100+, Good for 50+, otherwise Average". You could write a for loop, but pandas gives you a cleaner tool: apply(). You write the logic once, as a normal Python function, and pandas calls it for every value.
This is the topic of video "08b apply()" and notebook 12 of the course.
Definition
Series.apply(func)calls the functionfunconce for every element of the Series, passing the element as the first argument, and collects the return values into a new Series with the same index. The original Series is not changed.
- Once for every element — if the Series has 5 values, your function runs 5 times, each time receiving one plain value (an
int, astr, afloat…). - Same index — the result lines up with the original, so you can store it straight into a new column:
df["rating"] = df["runs"].apply(rating). - New Series —
applynever modifies the data in place; you must assign the result. - If your function returns a Series instead of a single value, pandas builds a DataFrame (one column per key) — see the third example.
Syntax
python
series.apply(func, args=(), *, by_row="compat", **kwargs)| Parameter | Meaning | Default |
|---|---|---|
func | the function to call — a def function, a lambda, a built-in like len, or a NumPy ufunc like np.sqrt | required |
args | a tuple of extra positional arguments passed after the value: func(value, *args) | () |
**kwargs | extra keyword arguments passed to func, e.g. cap=130 | — |
by_row | only matters when func is a list/dict of functions; leave it alone | "compat" |
Note (pandas 3): older tutorials show
Series.apply(func, convert_dtype=False). Theconvert_dtypeparameter was removed in pandas 3 — if you need a specific dtype, call.astype(...)on the result.
Example — a custom rating for every score
python
import pandas as pd
runs = pd.Series([120, 45, 75, 30, 95],
index=["Virat", "Rohit", "Dhoni", "Gill", "Rahul"], name="runs")
def rating(r):
if r >= 100:
return "Excellent"
elif r >= 50:
return "Good"
else:
return "Average"
print(runs.apply(rating))text
Virat Excellent
Rohit Average
Dhoni Good
Gill Average
Rahul Good
Name: runs, dtype: strHow it works: pandas called rating(120), rating(45), rating(75)… one by one and put the 5 answers into a new Series. The index (player names) and the name runs are kept. Because the function returned text, the result has the pandas 3 str dtype.
Note: pass the function name —
apply(rating)— notapply(rating()). With brackets you would call the function yourself (with no argument) and get an error.
Example — extra arguments with args and keywords
Sometimes the function needs more than the value. args (a tuple) and keyword arguments are forwarded to your function:
python
def add_bonus(r, bonus, cap=150):
return min(r + bonus, cap)
print(runs.apply(add_bonus, args=(40,)))
print(runs.apply(add_bonus, args=(40,), cap=130))text
Virat 150
Rohit 85
Dhoni 115
Gill 70
Rahul 135
Name: runs, dtype: int64
Virat 130
Rohit 85
Dhoni 115
Gill 70
Rahul 130
Name: runs, dtype: int64How it works: for Virat, pandas calls add_bonus(120, 40) → min(160, 150) = 150. In the second call it calls add_bonus(120, 40, cap=130) → 130. Note the comma in args=(40,) — without it, (40) is just the number 40, not a tuple.
Example — built-ins, ufuncs and returning several values
apply accepts any callable: built-in functions like len, string methods like str.upper, and NumPy functions:
python
import numpy as np
names = pd.Series(["virat kohli", "rohit sharma", "ms dhoni"])
print(names.apply(len))
print(names.apply(lambda s: s.split()[-1].title()))
print(runs.apply(np.sqrt).round(2))text
0 11
1 12
2 8
dtype: int64
0 Kohli
1 Sharma
2 Dhoni
dtype: str
Virat 10.95
Rohit 6.71
Dhoni 8.66
Gill 5.48
Rahul 9.75
Name: runs, dtype: float64If the function returns a Series, every key becomes a column and you get a DataFrame back:
python
print(runs.apply(lambda r: pd.Series({"tens": r // 10, "units": r % 10})))text
tens units
Virat 12 0
Rohit 4 5
Dhoni 7 5
Gill 3 0
Rahul 9 5Tip:
runs.apply(np.sqrt)works, butnp.sqrt(runs)gives the same answer much faster because it is vectorised (one C loop instead of one Python call per value). Useapplyfor logic that has no built-in vectorised version — like theif/elif/elserating above.
apply vs map vs vectorised code on a Series
| Tool | Accepts | Best for |
|---|---|---|
s.apply(func) | a function (+ args/kwargs) | custom element logic; functions that return a Series (→ DataFrame) |
s.map(arg) | a function, a dict or a Series | look-ups / replacing values from a dictionary (next topics) |
vectorised (s * 2, np.sqrt(s), s.str.upper()) | — | anything pandas/NumPy already knows how to do — always fastest |
Common mistakes
- ❌
df["runs"].apply(rating())✅df["runs"].apply(rating)— pass the function, don't call it. - ❌
args=(40)✅args=(40,)—argsmust be a tuple. - ❌ Expecting
runs.apply(...)to changeruns. ✅ Assign it:df["rating"] = df["runs"].apply(rating). - ❌ Using
applyfor simple maths (apply(lambda x: x * 2)). ✅ Writes * 2— it is clearer and many times faster.
Interview tip
Q: What is the difference between Series.apply and Series.map?
A: Both run element-wise. apply only takes a callable but can forward extra arguments (args, kwargs) and can expand a returned Series into a DataFrame. map also accepts a dict or Series for look-up style replacement, and has na_action="ignore" to skip missing values.
Key points to remember
- Series.apply(func) calls func once per element and returns a new Series with the same index.
- Pass the function itself (apply(rating)), never the result of calling it (apply(rating())).
- Extra positional arguments go in args as a tuple — args=(40,) — and keyword arguments are forwarded directly.
- apply accepts any callable: def functions, lambdas, built-ins like len, str methods and NumPy ufuncs.
- If func returns a Series, apply builds a DataFrame with one column per key.
- apply does not modify the original — assign the result to a column.
- For maths that pandas/NumPy can already do (s * 2, np.sqrt(s)), vectorised code is faster than apply.
Try it yourself
Rate every batter with apply()
Extra arguments and returning several values
Practice: surname and name length
Revision Flashcards
Tap a card to flip it and see the answer.
