Data usually lives in several tables. merge joins two of them on a column they share, called the key:
df.merge(customers, on="customer_id", how="left")
how decides what happens to rows without a match:
"inner" (the default) keeps only rows with a match in both tables.
"left" keeps every row of the left table and fills the right table's columns with NaN where there's no match.
"right" and "outer" keep all rows of the right table, or of both.
Check the row count before and after. If an inner join returns fewer rows than you started with, some keys had no match. If a join returns more, a key appears more than once in the other table.
To find rows with no match, use isin with ~: df[~df["customer_id"].isin(customers["customer_id"])]. Or merge with indicator=True and look at the _merge column.
When both tables have a column with the same name that isn't the key, pandas adds _x and _y to tell them apart.