Skip to main content

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.

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.

tip

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.

warning

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

MethodBest ForIndex Behavior
pd.DataFrame({'A': s1, 'B': s2})Clean, readable creationAligns by index
pd.concat([s1, s2], axis=1)Combining many SeriesAligns by index
pd.DataFrame({'A': s1.values, 'B': s2.values})Positional pairingIgnores index
pd.DataFrame({'A': s1.reset_index(drop=True), ...})Standardizing indicesResets 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.