Python Pandas: How to Combine Two Pandas Series into a DataFrame
Converting individual Series into a structured DataFrame is a common step when preparing data for analysis. You might have separate Series for names, scores, and dates that need to be brought together into a single table. While the operation itself is straightforward, understanding how Pandas handles index alignment during this process is critical to avoiding unexpected NaN values and mismatched rows.
In this guide, you will learn the different ways to combine Series into a DataFrame, understand the index alignment behavior that catches many users off guard, and know when to use each approach.
Using a Dictionary Constructor (Recommended)
The cleanest and most readable approach is to pass the Series as values in a dictionary, where the keys become column names:
import pandas as pd
names = pd.Series(['Alice', 'Bob', 'Charlie'])
scores = pd.Series([85, 92, 78])
df = pd.DataFrame({
'Student': names,
'Score': scores
})
print(df)
Output:
Student Score
0 Alice 85
1 Bob 92
2 Charlie 78
This method is explicit about column naming and produces clean, readable code. It works perfectly when both Series share the same index.
Using pd.concat() for Multiple Series
When combining three or more Series at once, pd.concat() with axis=1 is a convenient alternative:
import pandas as pd
names = pd.Series(['Alice', 'Bob', 'Charlie'])
scores = pd.Series([85, 92, 78])
ages = pd.Series([20, 22, 21])
# Combine horizontally
df = pd.concat([names, scores, ages], axis=1)
df.columns = ['Student', 'Score', 'Age']
print(df)
Output:
Student Score Age
0 Alice 85 20
1 Bob 92 22
2 Charlie 78 21
The axis=1 parameter tells pd.concat() to place the Series side by side as columns rather than stacking them vertically. You need to assign column names separately since pd.concat() uses the Series' own name attributes (which default to None).
The Index Alignment Trap
This is where most unexpected behavior occurs. When combining Series, Pandas aligns rows by index values, not by position. If the indices do not match, the result will contain NaN values where one Series has an index that the other does not:
import pandas as pd
s1 = pd.Series(['A', 'B', 'C'], index=[0, 1, 2])
s2 = pd.Series([10, 20, 30], index=[1, 2, 3])
df = pd.DataFrame({'Letters': s1, 'Numbers': s2})
print(df)
Output:
Letters Numbers
0 A NaN
1 B 10.0
2 C 20.0
3 NaN 30.0
Index 0 exists only in s1, so Numbers gets NaN at that position. Index 3 exists only in s2, so Letters gets NaN. This behavior is by design, but it can produce confusing results when you expect the values to line up positionally.
Solution 1: Use .values for Positional Alignment
If you want to combine Series purely by position, ignoring their indices entirely, extract the underlying NumPy arrays with .values:
import pandas as pd
s1 = pd.Series(['A', 'B', 'C'], index=[0, 1, 2])
s2 = pd.Series([10, 20, 30], index=[1, 2, 3])
df = pd.DataFrame({
'Letters': s1.values,
'Numbers': s2.values
})
print(df)
Output:
Letters Numbers
0 A 10
1 B 20
2 C 30
The indices are discarded, and the first value of each Series is paired with the first value of the other, regardless of what the original indices were.
Solution 2: Reset Indices First
Alternatively, reset both Series to default integer indices before combining:
import pandas as pd
s1 = pd.Series(['A', 'B', 'C'], index=[10, 20, 30])
s2 = pd.Series([1, 2, 3], index=[100, 200, 300])
df = pd.DataFrame({
'Letters': s1.reset_index(drop=True),
'Numbers': s2.reset_index(drop=True)
})
print(df)
Output:
Letters Numbers
0 A 1
1 B 2
2 C 3
The drop=True parameter prevents the old index from being added as a new column.
Choose .values when you are certain the Series represent the same data in the same order and the indices are irrelevant. Choose .reset_index(drop=True) when you want to keep working with Pandas Series objects throughout the process.
Handling Series with Different Lengths
When Series have different lengths, the dictionary constructor fills missing positions with NaN:
import pandas as pd
s1 = pd.Series(['A', 'B', 'C'])
s2 = pd.Series([1, 2]) # Shorter
df = pd.DataFrame({'Letters': s1, 'Numbers': s2})
print(df)
Output:
Letters Numbers
0 A 1.0
1 B 2.0
2 C NaN
The shorter Series is padded to match the longer one.
Using .values with different-length Series will raise a ValueError because NumPy arrays must have compatible shapes. If your Series have different lengths, use the dictionary constructor and handle the resulting NaN values with .fillna() or .dropna() as appropriate.
Adding a Series to an Existing DataFrame
If you already have a DataFrame and want to add a Series as a new column, assign it directly:
import pandas as pd
df = pd.DataFrame({'Name': ['Alice', 'Bob', 'Charlie']})
scores = pd.Series([85, 92, 78])
df['Score'] = scores
print(df)
Output:
Name Score
0 Alice 85
1 Bob 92
2 Charlie 78
The same index alignment rules apply here. If the Series has a different index than the DataFrame, values will be aligned by index and NaN will appear where there is no match.
Quick Reference
| Method | Best For | Index Behavior |
|---|---|---|
pd.DataFrame({'A': s1, 'B': s2}) | Clean, readable creation | Aligns by index |
pd.concat([s1, s2], axis=1) | Combining many Series | Aligns by index |
pd.DataFrame({'A': s1.values, 'B': s2.values}) | Positional pairing | Ignores index |
pd.DataFrame({'A': s1.reset_index(drop=True), ...}) | Standardizing indices | Resets to 0, 1, 2... |
Use the dictionary constructor for clean, readable DataFrame creation from two or more Series.
Always be aware that Pandas aligns by index rather than by position.
If your Series have mismatched indices and you want positional alignment, use .values to extract the raw arrays or .reset_index(drop=True) to standardize the indices before combining.