Mathew K Analytics

Lesson 26 · Mastering Pandas

How to Concatenate DataFrames Vertically and Horizontally Using Pandas in Python

This lesson will help you master combining DataFrames using pandas. You will learn by hands-on typing, merging data from realistic sources. Let us explore…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Concatenating DataFrames Vertically and Horizontally in Pandas#

This lesson will help you master combining DataFrames using pandas.

You will learn by hands-on typing, merging data from realistic sources.

Let us explore real-world use cases and unlock new possibilities with your data!

# Suppress pandas warnings for a cleaner notebook
import warnings
import numpy as np
np.random.seed(42)
warnings.filterwarnings("ignore")
# Data setup (Titanic Dataset)
import pandas as pd
url = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'
df = pd.read_csv(url)
print(df.shape)
print(df.head(3))
(891, 12)
   PassengerId  Survived  Pclass  \
0            1         0       3   
1            2         1       1   
2            3         1       3   

                                                Name     Sex   Age  SibSp  \
0                            Braund, Mr. Owen Harris    male  22.0      1   
1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1   
2                             Heikkinen, Miss. Laina  female  26.0      0   

   Parch            Ticket     Fare Cabin Embarked  
0      0         A/5 21171   7.2500   NaN        S  
1      0          PC 17599  71.2833   C85        C  
2      0  STON/O2. 3101282   7.9250   NaN        S  

Why Concatenation?#

Imagine you receive monthly Titanic passenger data as separate sheets. Or maybe you want to add more columns with new passenger info.

Concatenation helps you stack tables together or join side by side.

It is vital for data cleaning, tracking updates, and analysis.

# Split the Titanic data into two DataFrames (simulate monthly arrivals)
df_jan = df.iloc[:400].copy()
df_feb = df.iloc[400:].copy()
print('January shape:', df_jan.shape)
print('February shape:', df_feb.shape)
January shape: (400, 12)
February shape: (491, 12)
# Concatenate DataFrames vertically (one below the other)
df_vertical = pd.concat([df_jan, df_feb], axis=0)
print('Combined shape:', df_vertical.shape)
Combined shape: (891, 12)
# Reset index after concatenation for clean index numbers
df_vertical = df_vertical.reset_index(drop=True)
df_vertical.head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S

Vertical Concatenation Details#

Vertical (row-wise) concatenation stacks data tables on top of each other. All columns must match or missing columns will get NaN values.

This is helpful when combining multiple files or appending new records.

# Concatenate with some columns missing: add an 'AgeGroup' column to one DataFrame
df_jan['AgeGroup'] = pd.cut(df_jan['Age'], bins=[0, 12, 18, 50, 80], labels=['Child', 'Teen', 'Adult', 'Senior'])
df_mixed = pd.concat([df_jan, df_feb], axis=0)
print(df_mixed[['Name', 'Age', 'AgeGroup']].head(5))
print(df_mixed[['Name', 'Age', 'AgeGroup']].tail(5))
                                                Name   Age AgeGroup
0                            Braund, Mr. Owen Harris  22.0    Adult
1  Cumings, Mrs. John Bradley (Florence Briggs Th...  38.0    Adult
2                             Heikkinen, Miss. Laina  26.0    Adult
3       Futrelle, Mrs. Jacques Heath (Lily May Peel)  35.0    Adult
4                           Allen, Mr. William Henry  35.0    Adult
                                         Name   Age AgeGroup
886                     Montvila, Rev. Juozas  27.0      NaN
887              Graham, Miss. Margaret Edith  19.0      NaN
888  Johnston, Miss. Catherine Helen "Carrie"   NaN      NaN
889                     Behr, Mr. Karl Howell  26.0      NaN
890                       Dooley, Mr. Patrick  32.0      NaN

Horizontal Concatenation (Column-wise)#

Next, let us see how to add extra columns that belong together.

This is useful for 'joining' details from two sourceslike adding ticket or contact info for each passenger.

# Create a new DataFrame with alternative contact information
contact_info = pd.DataFrame({
    'PassengerId': df_jan['PassengerId'],
    'Contact': ['contact'+str(i)+'@mail.com' for i in df_jan['PassengerId']]
})
contact_info.head(3)
PassengerId Contact
0 1 contact1@mail.com
1 2 contact2@mail.com
2 3 contact3@mail.com
# Add contact information columns to the original January DataFrame (horizontal concat)
df_horiz = pd.concat([df_jan.reset_index(drop=True), contact_info.drop('PassengerId', axis=1).reset_index(drop=True)], axis=1)
df_horiz[['Name', 'Age', 'Contact']].head(3)
Name Age Contact
0 Braund, Mr. Owen Harris 22.0 contact1@mail.com
1 Cumings, Mrs. John Bradley (Florence Briggs Th... 38.0 contact2@mail.com
2 Heikkinen, Miss. Laina 26.0 contact3@mail.com
# Horizontal concat WITHOUT resetting index causes mismatches if DataFrames differ in rows
short_contacts = contact_info.iloc[:10]
horiz_bad = pd.concat([df_jan, short_contacts.drop('PassengerId',axis=1)], axis=1)
print(horiz_bad[['Name', 'Contact']].head(12))
                                                 Name             Contact
0                             Braund, Mr. Owen Harris   contact1@mail.com
1   Cumings, Mrs. John Bradley (Florence Briggs Th...   contact2@mail.com
2                              Heikkinen, Miss. Laina   contact3@mail.com
3        Futrelle, Mrs. Jacques Heath (Lily May Peel)   contact4@mail.com
4                            Allen, Mr. William Henry   contact5@mail.com
5                                    Moran, Mr. James   contact6@mail.com
6                             McCarthy, Mr. Timothy J   contact7@mail.com
7                      Palsson, Master. Gosta Leonard   contact8@mail.com
8   Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)   contact9@mail.com
9                 Nasser, Mrs. Nicholas (Adele Achem)  contact10@mail.com
10                    Sandstrom, Miss. Marguerite Rut                 NaN
11                           Bonnell, Miss. Elizabeth                 NaN

Common Parameters and Pitfalls#

  • axis=0: stack rows (default).
  • axis=1: stack columns.
  • ignore_index=True: reset row labels.
  • join='outer': union of columns (default for concat).
  • join='inner': only columns (or rows) shared by all.

Always check your index, shape, and column line-up after concatenation.

# Using ignore_index for fresh row numbers after vertical stacking
df_stack = pd.concat([df_jan, df_feb], axis=0, ignore_index=True)
print(df_stack.index[:8])
RangeIndex(start=0, stop=8, step=1)
# Inner join: keep ONLY columns present in both DataFrames
small_jan = df_jan[['PassengerId', 'Name', 'Age']]
small_feb = df_feb[['PassengerId', 'Name', 'Fare']]
joined = pd.concat([small_jan, small_feb], axis=0, join='inner', ignore_index=True)
print(joined.head(3))
print(joined.tail(3))
   PassengerId                                               Name
0            1                            Braund, Mr. Owen Harris
1            2  Cumings, Mrs. John Bradley (Florence Briggs Th...
2            3                             Heikkinen, Miss. Laina
     PassengerId                                      Name
888          889  Johnston, Miss. Catherine Helen "Carrie"
889          890                     Behr, Mr. Karl Howell
890          891                       Dooley, Mr. Patrick
# Mini-project: Merge two datasets on matching column (merge vs. concat demo)
df_ticket = df[['PassengerId', 'Ticket', 'Cabin']].sample(10, random_state=42).reset_index(drop=True)
df_sample = df[['PassengerId', 'Name', 'Age']].sample(10, random_state=24).reset_index(drop=True)
merged = pd.merge(df_sample, df_ticket, on='PassengerId', how='left')
print(merged)
   PassengerId                                               Name   Age  \
0          170                                      Ling, Mr. Lee  28.0   
1          557  Duff Gordon, Lady. (Lucille Christiana Sutherl...  48.0   
2          207                         Backstrom, Mr. Karl Alfred  32.0   
3           72                         Goodwin, Miss. Lillian Amy  16.0   
4          678                            Turja, Miss. Anna Sofia  18.0   
5          840                               Marechal, Mr. Pierre   NaN   
6          836                        Compton, Miss. Sara Rebecca  39.0   
7          262                  Asplund, Master. Edvin Rojj Felix   3.0   
8          180                                Leonard, Mr. Lionel  36.0   
9          283                          de Pelsmaeker, Mr. Alfons  16.0   

  Ticket Cabin  
0    NaN   NaN  
1    NaN   NaN  
2    NaN   NaN  
3    NaN   NaN  
4    NaN   NaN  
5    NaN   NaN  
6    NaN   NaN  
7    NaN   NaN  
8    NaN   NaN  
9    NaN   NaN  

Challenge: Practice Problem#

Suppose you have two DataFrames:

  • One holds the first 5 passengers' names and ages.
  • The other has their genders and fares.

Challenge yourself: Vertically stack both DataFrames, then horizontally join as well.

What differences do you notice?

# Try: Concatenate first 5 names/ages with first 5 sexes/fares vertically and horizontally
A = df[['Name', 'Age']].head(5)
B = df[['Sex', 'Fare']].head(5)
vert = pd.concat([A, B], axis=0, ignore_index=True)
horiz = pd.concat([A.reset_index(drop=True), B.reset_index(drop=True)], axis=1)
print('Vertical stack:\n', vert)
print('Horizontal stack:\n', horiz)
Vertical stack:
                                                 Name   Age     Sex     Fare
0                            Braund, Mr. Owen Harris  22.0     NaN      NaN
1  Cumings, Mrs. John Bradley (Florence Briggs Th...  38.0     NaN      NaN
2                             Heikkinen, Miss. Laina  26.0     NaN      NaN
3       Futrelle, Mrs. Jacques Heath (Lily May Peel)  35.0     NaN      NaN
4                           Allen, Mr. William Henry  35.0     NaN      NaN
5                                                NaN   NaN    male   7.2500
6                                                NaN   NaN  female  71.2833
7                                                NaN   NaN  female   7.9250
8                                                NaN   NaN  female  53.1000
9                                                NaN   NaN    male   8.0500
Horizontal stack:
                                                 Name   Age     Sex     Fare
0                            Braund, Mr. Owen Harris  22.0    male   7.2500
1  Cumings, Mrs. John Bradley (Florence Briggs Th...  38.0  female  71.2833
2                             Heikkinen, Miss. Laina  26.0  female   7.9250
3       Futrelle, Mrs. Jacques Heath (Lily May Peel)  35.0  female  53.1000
4                           Allen, Mr. William Henry  35.0    male   8.0500

Recap: Concatenation Basics#

In this lesson, you practiced combining DataFrames using vertical and horizontal concatenation.

You learned to split, append, and align tablesessential for real-world data work.

Keep experimenting and double-check your index, shape, and columns as you merge!

Next: Try using concat on your own datasets.

Thanks for Learning!#

If you found this helpful, subscribe to our channel for more pandas coding tutorials.

Type your favorite tip from this lesson in the comments below!

Happy coding and see you in the next Jupyter lesson!

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.