Vectorized Operations & Alignment
Do maths on whole Series at once, and understand index alignment — pandas matches values by label, producing NaN where labels don't match.
In NumPy you learned vectorisation: array * 2 multiplies every element without a loop. A Series supports all of that — and adds one powerful idea NumPy does not have: automatic alignment by index label. This is one of the most important (and most surprising) ideas in all of pandas.
Definition
A vectorized operation applies an operator or function to every element of a Series at once (in fast compiled code) and returns a new Series. When two Series are combined, pandas performs index alignment: values are paired by matching labels, not by position, and the result's index is the union of both indexes — any label present in only one Series gets
NaN.
- Every element at once —
runs + 10,runs * 2,runs > 50,np.sqrt(runs)— noforloop. - Paired by matching labels —
a + badds Virat's value inato Virat's value inb, wherever Virat sits in each Series. - Union of both indexes — every label from either side appears in the result (sorted, when the indexes differ).
NaNfor unmatched labels — if a label has no partner, pandas cannot compute a result, so it marks it missing.
Syntax
python
s + 10 s - 5 s * 2 s / 4 s // 3 s % 3 s ** 2 # scalar
s1 + s2 s1 / s2 # aligned by label
s > 50 s == "MI" s != 0 # comparisons -> bool Series
np.sqrt(s) np.log(s) np.round(s, 1) # NumPy ufuncs keep the index
s1.add(s2, fill_value=0) # + with a fill value for unmatched labels
s1.sub(s2, fill_value=0) # -
s1.mul(s2, fill_value=1) # *
s1.div(s2, fill_value=1) # /| Method | Operator | Parameter | Meaning | Default |
|---|---|---|---|---|
add | + | other | Series or scalar to combine with | required |
sub | - | fill_value | value used for a label missing on one side before computing | None (→ NaN) |
mul | * | |||
div / truediv | / |
Example — scalar and element-wise maths
python
import pandas as pd
import numpy as np
runs = pd.Series([45, 78, 12, 101], index=["Rohit", "Virat", "Gill", "Pant"])
balls = pd.Series([30, 48, 15, 60], index=["Rohit", "Virat", "Gill", "Pant"])
print(runs + 10)
print((runs / balls * 100).round(2))
print(runs >= 50)text
Rohit 55
Virat 88
Gill 22
Pant 111
dtype: int64
Rohit 150.00
Virat 162.50
Gill 80.00
Pant 168.33
dtype: float64
Rohit False
Virat True
Gill False
Pant True
dtype: boolHow it works: runs + 10 broadcasts the scalar to every element. runs / balls * 100 computes the strike rate for every batter in one line. A comparison returns a boolean Series — perfect for filtering (runs[runs >= 50]). NumPy ufuncs work too and keep the labels: np.sqrt(runs) returns a Series indexed by player.
Example — alignment: order does not matter
python
balls2 = pd.Series([60, 15, 48, 30], index=["Pant", "Gill", "Virat", "Rohit"])
print((runs / balls2 * 100).round(2))text
Gill 80.00
Pant 168.33
Rohit 150.00
Virat 162.50
dtype: float64How it works: balls2 lists the same players in a different order. NumPy would divide position by position and give wrong answers. pandas matches Pant with Pant, Gill with Gill…, so every strike rate is still correct. Because the two indexes were not identical in order, the result index is sorted.
Example — alignment with unmatched labels → NaN
python
match1 = pd.Series([45, 78, 12], index=["Rohit", "Virat", "Gill"])
match2 = pd.Series([20, 64, 55], index=["Virat", "Gill", "Pant"])
print(match1 + match2)text
Gill 76.0
Pant NaN
Rohit NaN
Virat 98.0
dtype: float64text
match1 match2 match1 + match2
Rohit 45 (no Rohit) -> Rohit 45 + ? = NaN
Virat 78 Virat 20 -> Virat 78 + 20 = 98
Gill 12 Gill 64 -> Gill 12 + 64 = 76
(no Pant) Pant 55 -> Pant ? + 55 = NaNHow it works: Rohit batted only in match 1 and Pant only in match 2 — pandas has nothing to add them to, so the result is NaN, and the dtype becomes float64 because of it. This is a feature: pandas refuses to silently pair the wrong players.
Example — fill_value: treat missing as 0
python
print(match1.add(match2, fill_value=0))text
Gill 76.0
Pant 55.0
Rohit 45.0
Virat 98.0
dtype: float64With fill_value=0, a label missing on one side is treated as 0 before adding, so totals across matches come out right. (If both values for a label are NaN, the result is still NaN.)
Aggregations — one number from a Series
python
print(runs.sum(), runs.mean(), runs.median(), runs.min(), runs.max(), runs.std().round(2), runs.count())
print(runs.agg(["min", "max", "mean", "sum"]))text
236 59.0 61.5 12 101 38.86 4
min 12.0
max 101.0
mean 59.0
sum 236.0
dtype: float64Aggregations skip NaN by default (skipna=True): pd.Series([45, None, 12]).mean() is 28.5 (average of 45 and 12), whereas NumPy's np.array([45, np.nan, 12]).mean() gives nan. Pass skipna=False to make the missing value count.
Comparison — NumPy array vs Series arithmetic
NumPy a + b | pandas s1 + s2 | |
|---|---|---|
| Pairs elements by | position | index label |
| Different order | wrong pairs, silently | correct pairs |
| Different lengths | ValueError (unless broadcastable) | union of labels, NaN where unmatched |
| Missing values in stats | nan poisons the result | skipped by default |
Python lists [1, 2] + [3, 4] | — | (lists concatenate: [1, 2, 3, 4]) |
Common mistakes
- ❌ Adding Series with different labels and being surprised by
NaN. ✅ That is alignment — use.add(other, fill_value=0)or make the indexes match. - ❌ Expecting position-by-position maths between two Series. ✅ If you truly want positions, use
s1.to_numpy() + s2.to_numpy()(same length required). - ❌
if runs > 50:→ValueError: The truth value of a Series is ambiguous. ✅ Use the boolean Series to filter, or(runs > 50).any()/.all(). - ❌ Using
and/orbetween conditions. ✅ Use&/|with parentheses.
Interview tip
Q: You add two Series of equal length and get NaN values. Why?
A: pandas aligns on the index, not on position. If the two Series have different labels (e.g. one has a RangeIndex 0–4 and the other was filtered and has 2, 5, 7…), unmatched labels produce NaN. Fix it by aligning the indexes (reset_index(drop=True)), using fill_value, or working with .to_numpy().
Key points to remember
- Vectorized operations (s + 10, s * 2, s > 50, np.sqrt(s)) apply to every element without a loop.
- Binary operations between two Series align by index label, not position.
- The result index is the union of both indexes; labels present on only one side become NaN.
- Order does not matter — pandas still pairs the right labels.
- Methods .add/.sub/.mul/.div accept fill_value to treat a missing side as a value (e.g. 0).
- Aggregations like sum and mean skip NaN by default (skipna=True).
- Use .to_numpy() if you deliberately want position-by-position maths.
Try it yourself
Vectorized maths: strike rates in one line
Index alignment and NaN
Aggregations skip NaN
Practice: total runs over two matches
Revision Flashcards
Tap a card to flip it and see the answer.
