How do I line up two Series with different indexes in pandas?
Short answer
Use reindex to force one object onto another index, or align to bring both onto their union. Both make the missing entries explicit as NaN, rather than letting a silent mismatch produce wrong results.
The short version
Use reindex to force an object onto a specific index, and align to bring two objects onto a common index. Both surface the mismatches as NaN rather than letting them cause a quiet misalignment, which is why doing this explicitly is safer than relying on automatic alignment.
Reindexing onto a known index
import pandas as pd
s = pd.Series([1, 2], index=["a", "b"])
s.reindex(["a", "b", "c"]).tolist() # [1.0, 2.0, nan]
s.reindex(["a", "b", "c"], fill_value=0).tolist() # [1, 2, 0]
The label c did not exist, so it becomes NaN, or whatever fill_value you supply. This is how you guarantee a Series has exactly the labels you expect, in the order you expect.
Aligning two objects
x = pd.Series([1, 2], index=[0, 1])
y = pd.Series([3], index=[1])
ax, ay = x.align(y)
ax.tolist() # [1, 2]
ay.tolist() # [nan, 3.0]
Both are now on the union of the two indexes, so they can be compared or combined position by position with confidence. The missing entry in y is explicitly NaN rather than silently absent.
Why this is safer than implicit alignment
Pandas aligns automatically during arithmetic, which is usually helpful but hides the mismatch. Adding two Series with partly different indexes produces NaN wherever they do not overlap, and it is easy to miss that this happened.
Aligning explicitly makes the shape of the problem visible before you compute anything, so you can decide what to do about the gaps rather than discovering them afterwards as mysterious NaN values in your result.
Choosing how to align
join controls which labels survive.
x.align(y, join="inner") # only labels present in both
x.align(y, join="left") # x's labels, y reindexed onto them
x.align(y, join="outer") # the union (the default)
This mirrors the merge vocabulary, and the choice matters for the same reason: inner silently discards labels, and you should choose that deliberately rather than by accident.
Reindexing to fill a complete calendar
A very common use is ensuring every period appears, even those with no data.
full = pd.date_range("2021-01-01", "2021-01-31", freq="D")
daily = daily.reindex(full)
Now every day exists, and days with no observation are NaN rather than simply absent. That distinction is exactly the one that resample(...).sum() destroys by filling with zero, so reindexing is the honest way to build a complete time axis.
Filling after reindexing
Decide deliberately what a missing label means.
daily.reindex(full) # gaps stay NaN, honest
daily.reindex(full, fill_value=0) # gaps become zero, a claim
daily.reindex(full).ffill() # gaps repeat the last value, a different claim
Each of these says something different about the missing periods. NaN says "we do not know", zero says "nothing happened", and forward filling says "the previous state persisted". Only you know which is true, and the point of doing this explicitly is to make that choice visible.