Glue DataFrames together with pd.concat, top to bottom by default and side by side with axis=1.
pd.concat takes a list of DataFrames and returns one DataFrame. By default it stacks them vertically: the rows of the second frame go under the rows of the first.
In this lesson I stack two small frames, fix the index that comes out of it, stack two months of prices into one table, and then put two frames side by side with axis=1.
Step 1. Two frames, one on top of the other
Both frames have the same two columns, so the result has those two columns and all four rows.
import pandas as pda = pd.DataFrame({"Ticker": ["AAPL", "MSFT"], "Close": [186.66, 416.98]})b = pd.DataFrame({"Ticker": ["NVDA", "TSLA"], "Close": [125.18, 248.40]})both = pd.concat([a, b]) # a list of frames goes in, one frame comes outprint(both)# -> Ticker Close# -> 0 AAPL 186.66# -> 1 MSFT 416.98# -> 0 NVDA 125.18# -> 1 TSLA 248.40print(both.shape) # -> (4, 2)print(both.index.tolist()) # -> [0, 1, 0, 1]
concat puts the frames down in the order I listed them and does no sorting. To get one ticker’s whole history together and in date order, sort afterwards.
srt = stacked.sort_values(["Ticker", "Date"], ignore_index=True)print(srt.head(2))# -> Date Ticker Close Volume# -> 0 2026-01-02 AAPL 186.66 2183494# -> 1 2026-01-05 AAPL 186.38 8463199print(srt.tail(2))# -> Date Ticker Close Volume# -> 118 2026-02-25 NVDA 108.88 8875303# -> 119 2026-02-26 NVDA 107.57 7025675
Date Ticker Close Volume
0 2026-01-02 AAPL 186.66 2183494
1 2026-01-05 AAPL 186.38 8463199
Date Ticker Close Volume
118 2026-02-25 NVDA 108.88 8875303
119 2026-02-26 NVDA 107.57 7025675
Step 3. Side by side with axis=1
axis=1 glues frames left to right instead of top to bottom. The columns sit next to each other and the rows are matched on the index, not on position. Here AAPL has four dates and MSFT has three.
aapl = prices[prices["Ticker"] =="AAPL"].set_index("Date")[["Close"]].head(4)msft = prices[prices["Ticker"] =="MSFT"].set_index("Date")[["Close"]].head(3)aapl.columns = ["AAPL"] # rename so the two Close columns do not collidemsft.columns = ["MSFT"]side = pd.concat([aapl, msft], axis=1)print(side)# -> AAPL MSFT# -> Date# -> 2026-01-02 186.66 416.98# -> 2026-01-05 186.38 423.19# -> 2026-01-06 188.51 423.95# -> 2026-01-07 187.24 NaN
AAPL MSFT
Date
2026-01-02 186.66 416.98
2026-01-05 186.38 423.19
2026-01-06 188.51 423.95
2026-01-07 187.24 NaN
The result carries every date from either frame, and 2026-01-07 gets NaN for MSFT because that frame has no such row.
Your turn
Three frames hold one day of closes each, for the same two tickers. Stack them into one frame of six rows numbered 0 to 5.
Ticker Close Volume
0 AAPL 186.66 NaN
1 NVDA 125.18 7447373.0
Volume became a float, because NaN is a float.
NoteDataFrame.append is gone
Older code adds a row with df.append(other). That method was removed in pandas 2.0.
print(hasattr(pd.DataFrame, "append")) # -> False
False
pd.concat([df, other]) does the job. It builds a whole new frame every time, so calling it once per row inside a loop copies everything on each pass. Collect the frames in a list and pass the list to concat once.