Two related ideas
- Reindexing — you explicitly ask pandas to rearrange an object onto a new index,
introducing
NaNfor any label that wasn't already there. - Alignment — pandas does this automatically whenever you do arithmetic between two Series or DataFrames with different indexes.
Both boil down to the same rule: match by label, not by position.
reindex: rearranging onto a new index
import pandas as pd
obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=["d", "b", "a", "c"])
print(obj.reindex(["a", "b", "c", "d", "e"])) # "e" wasn't there -> NaNFor ordered data, method="ffill" fills in the gaps using the last known value
instead of leaving NaN:
import numpy as np
obj3 = pd.Series(["blue", "purple", "yellow"], index=[0, 2, 4])
print(obj3.reindex(np.arange(6), method="ffill"))Reindexing a DataFrame — rows, columns, or both
frame = pd.DataFrame(
np.arange(9).reshape((3, 3)),
index=["a", "c", "d"],
columns=["Ohio", "Texas", "California"],
)
print(frame.reindex(index=["a", "b", "c", "d"])) # "b" is new -> NaN row
print(frame.reindex(columns=["Texas", "Utah", "California"])) # "Utah" is new, "Ohio" droppedAutomatic alignment in arithmetic
You never call reindex yourself before adding two Series — pandas does it for you,
using the union of both indexes:
s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=["a", "c", "d", "e"])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=["a", "c", "e", "f", "g"])
print(s1 + s2)Labels present in only one of the two Series become NaN in the result — "d" and
"f"/"g" never had a matching partner to add against.
Filling gaps during arithmetic: fill_value
If you'd rather treat a missing label as 0 (or some other value) instead of NaN,
use the method form of the operator with fill_value:
df1 = pd.DataFrame(np.arange(9.0).reshape(3, 3), columns=list("bcd"))
df2 = pd.DataFrame(np.arange(12.0).reshape(4, 3), columns=list("bde"))
print(df1.add(df2, fill_value=0))The same fill_value idea also works directly on reindex:
print(df1.reindex(columns=df2.columns, fill_value=0))Common pitfalls
df1 + df2(the operator) always producesNaNfor non-overlapping labels — use.add(),.sub(),.mul(),.div()withfill_valueif you want zeros instead.reindexcreates missing rows/columns; it never drops data implicitly unless a label from the original index is missing from the new one.method="ffill"onreindexrequires the index to already be sorted in the order you expect the fill to follow.
Visualize it
flowchart LR A["Series 1
index: a, c, d, e"] --> C["Union index
a, c, d, e, f, g"] B["Series 2
index: a, c, e, f, g"] --> C C --> D["s1 + s2
NaN where either side is missing"]
🧪 Try It Yourself
Exercise 1 – Reindex Onto a Larger Index
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – Automatic Alignment in Arithmetic
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Fill Gaps Instead of Producing NaN
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Check your understanding
What does obj.reindex(["a", "b", "c", "d", "e"]) give for the label e when obj has only a, b, c and d?
Reindex introduces NaN for any label that was not in the original index unless you ask for a fill.
Show answer
A — NaN — Reindex introduces NaN for any label that was not in the original index unless you ask for a fill.
For s1 with labels a, c, d, e and s2 with labels a, c, e, f, g, how many labels does s1 + s2 have and which are NaN?
The result uses the union of the indexes. d, f and g appear in only one Series so they are NaN.
Show answer
A — 6 labels, with d, f and g NaN — The result uses the union of the indexes. d, f and g appear in only one Series so they are NaN.
How do you add two DataFrames and treat a missing label as 0 instead of NaN?
The operator form always gives NaN for non-overlapping labels. The method form accepts fill_value.
Show answer
A — df1.add(df2, fill_value=0) — The operator form always gives NaN for non-overlapping labels. The method form accepts fill_value.
For obj3 with values blue, purple, yellow at index 0, 2, 4, what does obj3.reindex(np.arange(6), method="ffill") return at label 1?
ffill carries the last known value forward, so label 1 gets blue from label 0.
Show answer
A — blue — ffill carries the last known value forward, so label 1 gets blue from label 0.
Next
With your index aligned the way you want, you're ready to narrow rows down further — continue to Filtering with Conditions (and, or, isin, query).