Skip to content

Python Pandas: How to Combine Two Pandas Series into a DataFrame

Learn how to merge Pandas Series into a DataFrame while avoiding NaN values from index misalignment, with methods for positional alignment and handling mismatched indices.

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])

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.

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.

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.

Navigation