Concatenating DataFrames

pandas · Practical · 10 min

Combining monthly files, batches or feature blocks into one table is routine data plumbing.

`pd.concat(list_of_frames)` glues DataFrames (or Series) together along an axis. By default it stacks rows (axis=0).

Columns are aligned by name, not position. If one frame has a column the other lacks, the result contains it with NaN for the rows that didn't have it. join="inner" keeps only columns common to all frames instead.

The original index labels are kept, so stacking two frames with default indexes gives duplicate labels 0, 1, 2, 0, 1, 2. Pass `ignore_index=True` to renumber, which is what you usually want for row-wise stacking. Alternatively keys=["jan", "feb"] adds an outer index level recording where each row came from, which can later become a column via reset_index().

With axis=1, frames are placed side by side and rows are aligned on the index: rows with labels missing in one frame get NaN, much like an outer join on the index. That's convenient for combining several Series that share an index, such as per-day metrics, into one table.

The idiomatic pattern for reading many files:

frames = [pd.read_csv(p) for p in paths]
df = pd.concat(frames, ignore_index=True)

Concatenate once at the end. Appending to a DataFrame inside a loop (the removed DataFrame.append, or pd.concat([df, row]) per iteration) copies all existing data each time, which is quadratic. Collect pieces in a list first.

concat is for stacking; when rows should be matched by a key column, use merge. Check dtypes afterwards too: concatenating an int column with a frame that lacks it produces float (because of the NaNs), and mismatched dtypes across frames may produce object.

Example

import pandas as pd

jan = pd.DataFrame({"sku": ["a", "b"], "units": [5, 3]})
feb = pd.DataFrame({"sku": ["a", "c"], "units": [7, 1], "promo": [True, False]})

both = pd.concat([jan, feb], ignore_index=True)
print(both)
#   sku  units  promo
# 0   a      5    NaN
# 1   b      3    NaN
# 2   a      7   True
# 3   c      1  False

tagged = pd.concat([jan, feb], keys=["jan", "feb"], join="inner")
print(tagged.reset_index(level=0).rename(columns={"level_0": "month"}).values.tolist())
# [['jan', 'a', 5], ['jan', 'b', 3], ['feb', 'a', 7], ['feb', 'c', 1]]

views = pd.Series([100, 120], index=["mon", "tue"], name="views")
clicks = pd.Series([4, 9, 2], index=["mon", "tue", "wed"], name="clicks")
print(pd.concat([views, clicks], axis=1))   # wed row: views NaN

parts = [pd.DataFrame({"i": [k]}) for k in range(3)]
print(pd.concat(parts, ignore_index=True)["i"].tolist())   # [0, 1, 2]

Remember

  • pd.concat stacks rows by default and aligns columns by name
  • Use ignore_index=True to renumber, or keys= to label sources
  • axis=1 places frames side by side, aligned on the index
  • Collect pieces in a list and concatenate once

Watch out for

  • Concatenating inside a loop, which re-copies all data on every iteration.

Check yourself

  1. Two frames with default 0..2 indexes are concatenated without options. What does `result.loc[0]` return?
  2. Frame A has columns [x, y]; frame B has [x, z]. What columns does `pd.concat([A, B])` have?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pandas.pydata.org/docs/reference/api/pandas.concat.html

Builds on

Unlocks

Nothing builds on this yet.