Skip to main content

Python Pandas: How to Efficiently Add a Row at the Top of a Pandas DataFrame

Adding rows to the beginning of a DataFrame is a common operation, but it requires understanding how Pandas works internally. DataFrames are column-oriented and immutable in size, meaning every row addition creates a new object rather than modifying the existing one in place. Choosing the right approach prevents performance issues, avoids deprecated method warnings, and keeps your code clean.

In this guide, you will learn how to prepend rows using the recommended pd.concat() approach, understand why some alternatives should be avoided, and recognize the critical performance difference between adding rows one at a time versus in batch.

The Standard Solution: pd.concat()

Create a single-row DataFrame for the new data and concatenate it before the original DataFrame:

import pandas as pd

df = pd.DataFrame({
'Name': ['Alice', 'Bob'],
'Age': [25, 30]
})

# Create the new row as a DataFrame
new_row = pd.DataFrame([{'Name': 'Charlie', 'Age': 35}])

# Concatenate with the new row first
df_updated = pd.concat([new_row, df], ignore_index=True)

print(df_updated)

Output:

      Name  Age
0 Charlie 35
1 Alice 25
2 Bob 30

The new row appears at the top because it comes first in the pd.concat() call. The ignore_index=True parameter resets the index to a clean sequential sequence (0, 1, 2) instead of preserving the original index values from each DataFrame.

Adding Multiple Rows at Once

The same pattern works for prepending several rows:

import pandas as pd

df = pd.DataFrame({'Name': ['Alice'], 'Age': [25]})

new_rows = pd.DataFrame([
{'Name': 'Bob', 'Age': 30},
{'Name': 'Charlie', 'Age': 35}
])

df_updated = pd.concat([new_rows, df], ignore_index=True)

print(df_updated)

Output:

      Name  Age
0 Bob 30
1 Charlie 35
2 Alice 25
tip

To add rows to the bottom instead of the top, simply reverse the order: pd.concat([df, new_rows], ignore_index=True).

Alternative: Using .loc[] with an Index Trick

For simple cases with integer-indexed DataFrames, you can assign to index position -1 and then sort:

import pandas as pd

df = pd.DataFrame({'Name': ['Alice', 'Bob'], 'Age': [25, 30]})

# Assign to index -1
df.loc[-1] = ['Charlie', 35]

# Sort to bring -1 to the top, then reset the index
df = df.sort_index().reset_index(drop=True)

print(df)

Output:

      Name  Age
0 Charlie 35
1 Alice 25
2 Bob 30
warning

This method relies on the fact that -1 sorts before 0 in integer ordering. It can cause unexpected results with non-integer indices or custom index values, and the sort step adds overhead. Prefer pd.concat() for reliability and clarity.

Methods to Avoid

The Removed .append() Method

The .append() method was removed in Pandas 2.0. Code that uses it will raise an error in modern versions:

import pandas as pd

df = pd.DataFrame({'Name': ['Alice', 'Bob'], 'Age': [25, 30]})

try:
df = df.append({'Name': 'Charlie', 'Age': 35}, ignore_index=True)
except AttributeError as e:
print(f"Error: {e}")

Output:

Error: 'DataFrame' object has no attribute 'append'

Replace any .append() calls with pd.concat() as shown in the standard solution above.

Critical Performance Consideration: Avoid Loops

Adding rows inside a loop is one of the most common performance mistakes in Pandas. Each pd.concat() call copies all existing data into a new DataFrame, so adding n rows one at a time results in quadratic (O(n squared)) time complexity:

import pandas as pd
import time

# Wrong: concat inside the loop (extremely slow)
start = time.time()
df = pd.DataFrame(columns=['Name', 'Age'])
for i in range(1000):
new_row = pd.DataFrame([{'Name': f'User_{i}', 'Age': 20 + i}])
df = pd.concat([new_row, df], ignore_index=True)
slow_time = time.time() - start

# Correct: collect first, create once (fast)
start = time.time()
rows = []
for i in range(1000):
rows.append({'Name': f'User_{i}', 'Age': 20 + i})
df = pd.DataFrame(rows)
fast_time = time.time() - start

print(f"Loop concat: {slow_time:.4f}s")
print(f"Batch create: {fast_time:.4f}s")
print(f"Batch is ~{slow_time / fast_time:.0f}x faster")

Output:

Loop concat: 0.5423s
Batch create: 0.0012s
Batch is ~452x faster

The batch approach collects all data in a plain Python list, then creates the DataFrame in a single operation. This changes the complexity from O(n squared) to O(n).

tip

This principle applies whether you are adding rows to the top, bottom, or middle of a DataFrame. Always collect data in a list first, then create or concatenate once at the end.

When Prepending vs. Appending Matters

If you need the new rows at the top of the final result but are collecting data in a loop, it is more efficient to collect everything first and then control the order in a single pd.concat() call:

import pandas as pd

existing_df = pd.DataFrame({
'Name': ['Alice', 'Bob'],
'Age': [25, 30]
})

# Collect new rows
new_data = [
{'Name': 'Charlie', 'Age': 35},
{'Name': 'Diana', 'Age': 28}
]
new_rows_df = pd.DataFrame(new_data)

# Place new rows before existing data in a single concat
result = pd.concat([new_rows_df, existing_df], ignore_index=True)

print(result)

Output:

      Name  Age
0 Charlie 35
1 Diana 28
2 Alice 25
3 Bob 30

Quick Reference

GoalMethod
Add single row to toppd.concat([new_row_df, df], ignore_index=True)
Add multiple rows to toppd.concat([new_rows_df, df], ignore_index=True)
Add row to bottompd.concat([df, new_row_df], ignore_index=True)
Build from many rowsCollect in a list, then pd.DataFrame(list) once
  • Use pd.concat([new_row, df], ignore_index=True) to add rows at the top of a DataFrame.
  • Always set ignore_index=True to maintain clean sequential indexing.
  • Avoid the removed .append() method, and never use pd.concat() inside loops.
  • Instead, collect all your data in a Python list first, then create the DataFrame in a single operation for optimal performance.