← pandas·Lesson 17·10 min·0/3 exercises

Stacking tables with concat

No keys, no matching — and two traps that come free with alignment.

pd.concatignore_indexaxis=1join=keys=index.is_unique

Watch it happen

Play it through, or step back and forth yourself.

a
city
cups
0
Delhi
120
1
Mumbai
80
b
city
cups
0
Pune
150
1
Delhi
95
→
pd.concat([a, b])
city
cups
0
Delhi
120
1
Mumbai
80
0
Pune
150
1
Delhi
95

pd.concat([a, b]) puts one table under another. No keys, no matching — this is for tables that are already the same shape: two months of the same export, a list of files read in a loop.

The idea

merge matches rows by a key. concat doesn't match anything — it stacks tables that are already the same shape. Two months of the same export, a folder of files read in a loop, results from several runs.

pd.concat([march, april])              # one under the other
pd.concat([march, april], ignore_index=True)

Trap one: the labels come along

Both inputs bring their own index, so a plain concat gives you repeated labels — 0, 1, 2, 0, 1, 2. Nothing warns you, and then:

combined.loc[0]            # returns TWO rows
combined.index.is_unique   # False

ignore_index=True renumbers from zero. Use it whenever the original labels were just row numbers, which is nearly always. If they meant something — dates, ids — keep them and check uniqueness deliberately.

Hand-drawn notes showing concat carrying both tables original row labels into the result, producing repeated index values unless ignore_index is passed.

Trap two: columns align too

Concat two frames with different columns and you get the union, with NaN wherever a frame didn't have that column. No error. This is index alignment from lesson 2, applied to the column axis.

It's how a typo in one file's header quietly doubles your column count — revenue and Revenue become two columns, each half full.

pd.concat([a, b])                  # union of columns, gaps become NaN
pd.concat([a, b], join="inner")    # only columns present in ALL of them

Compare the column lists before you concat, and the row count after. Both are one line and both catch this.

Hand-drawn notes showing concat matching columns by name rather than position, so a column present in only one table is filled with NaN for the other rows.

Sideways

pd.concat([a, b], axis=1)

This puts frames side by side, matching rows on the index. Useful when the index means something — two metrics for the same dates, say. Dangerous when it doesn't: with plain row numbers you're pairing unrelated rows by position, and you'll get a table that looks fine and says nothing true.

If you want to match on a column, that's merge, not concat.

Labelling the sources

pd.concat([march, april], keys=["march", "april"])

keys= adds an outer index level recording which frame each row came from — so you can still tell them apart afterwards, and .loc["march"] gets one back. That creates the MultiIndex you'll meet in lesson 20.

Building a table from many files

frames = [pd.read_csv(f) for f in files]
combined = pd.concat(frames, ignore_index=True)

Collect into a list and concat once. Concatenating inside the loop — combined = pd.concat([combined, new]) — copies the whole accumulated frame every iteration, which turns a linear job into a quadratic one.

Practice

Write it yourself. The answer is there when you want it.

Putting the kettle on…

Starting up…

Write it yourself

not graded

Stack march on april and print the index labels you get, then ask the index whether it is unique — concat keeps the old labels, collisions and all. Do it again with ignore_index=True and print those labels. Finish by concatenating two frames whose columns do not match, and look at what fills the holes.

Write something and press Run — the output appears here.

Your turn

3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Stack march and april into one frame with a clean 0..n index.

your answer

Is the index of pd.concat([march, april]) unique? Return the boolean.

your answer

Concat march[["city", "cups"]] with april[["city", "rating"]], keeping only the columns present in both.

your answer