Pandas Merge, Join i Concat: Vodič za spajanje DataFrame-ova u Pythonu

Naučite kako spajati DataFrame-ove u pandasu koristeći merge(), join(), concat() i merge_asof(). Vodič pokriva sve tipove JOIN-ova, novi anti join u pandas 3.0, česte greške i praktične savjete za optimizaciju.

Ako ste ikada radili s podacima iz stvarnog svijeta, znate da se rijetko kada svi potrebni podaci nalaze u jednoj tablici. Podaci o zaposlenicima su u jednoj bazi, odjeli negdje drugdje, a plaće — naravno — u trećoj. I tu kreće zabava. Sposobnost spajanja i kombiniranja DataFrame-ova jedna je od najvažnijih vještina svakog tko radi s podacima, bilo da ste analitičar, data scientist ili tek učite pandas.

Ovaj članak je nastavak naše serije o pandas biblioteci, u kojoj smo već obradili čišćenje podataka i GroupBy operacije za grupiranje i agregaciju. Ovaj put fokusiramo se na spajanje DataFrame-ova — od klasičnog merge() koji radi poput SQL JOIN-a, preko join() metode za spajanje po indeksu, do concat() za slaganje podataka jedan na drugi. Posebnu pažnju posvetit ćemo i novostima u pandas 3.0, uključujući dugo očekivani anti join.

Hajde, krenimo.

Postavljanje radnog okruženja

Prije svega, provjerite da imate instaliranu najnoviju verziju pandasa. Za sve značajke koje ćemo demonstrirati, trebat će vam pandas 3.0 ili noviji:

pip install pandas>=3.0.0

Sada uvezimo potrebne biblioteke i napravimo primjerne DataFrame-ove koje ćemo koristiti kroz cijeli članak. Simulirat ćemo jednostavan sustav s podacima o zaposlenicima, odjelima i plaćama — ništa komplicirano, ali dovoljno za demonstraciju svih tipova spajanja:

import pandas as pd
import numpy as np

# DataFrame sa zaposlenicima
zaposlenici = pd.DataFrame({
    'zaposlenik_id': [1, 2, 3, 4, 5, 6],
    'ime': ['Ana', 'Marko', 'Ivana', 'Petar', 'Maja', 'Luka'],
    'odjel_id': [101, 102, 101, 103, 102, 104],
    'grad': ['Zagreb', 'Split', 'Zagreb', 'Rijeka', 'Split', 'Osijek']
})

# DataFrame s odjelima
odjeli = pd.DataFrame({
    'odjel_id': [101, 102, 103, 105],
    'naziv_odjela': ['Razvoj', 'Marketing', 'Financije', 'HR'],
    'voditelj': ['Tomislav', 'Jelena', 'Boris', 'Katarina']
})

# DataFrame s plaćama
place = pd.DataFrame({
    'id_zaposlenika': [1, 2, 3, 4, 5, 6],
    'placa': [8500, 7200, 9100, 6800, 7500, 8000],
    'bonus': [1000, 500, 1200, 300, 600, 800]
})

print("Zaposlenici:")
print(zaposlenici)
print("\nOdjeli:")
print(odjeli)

Primijetite da su podaci namjerno nepotpuni — zaposlenik Luka pripada odjelu 104 koji ne postoji u tablici odjela, a odjel HR (105) nema nijednog zaposlenika. Upravo takve situacije su česte u praksi i savršene su za pokazivanje razlika između tipova spajanja.

pd.merge() — SQL stil spajanja podataka

Funkcija pd.merge() je, bez pretjerivanja, najmoćniji alat za spajanje DataFrame-ova u pandasu. Ako poznajete SQL, odmah ćete se snalaziti — merge radi upravo kao SQL JOIN.

Pogledajmo sve dostupne tipove spajanja.

Inner join (unutarnje spajanje)

Inner join vraća samo retke koji imaju podudaranje u oba DataFrame-a. To je zadano ponašanje merge() funkcije, pa ga zapravo ne morate ni navoditi eksplicitno:

# Inner join — samo zaposlenici koji imaju odgovarajući odjel
rezultat_inner = pd.merge(zaposlenici, odjeli, on='odjel_id', how='inner')
print(rezultat_inner)

U rezultatu nećete vidjeti Luku (odjel 104 ne postoji u tablici odjela) ni odjel HR (nema zaposlenika s odjel_id 105). Inner join zadržava samo retke s podudaranjem u obje tablice — ostalo otpada.

Left join (lijevo spajanje)

Left join zadržava sve retke iz lijevog DataFrame-a, čak i ako nemaju par u desnom. Ovo je vjerojatno najčešće korišteni tip spajanja u praksi:

# Left join — svi zaposlenici, čak i bez odjela
rezultat_left = pd.merge(zaposlenici, odjeli, on='odjel_id', how='left')
print(rezultat_left)

Sada vidimo sve zaposlenike, uključujući Luku. No za njega su stupci naziv_odjela i voditelj popunjeni s NaN jer odjel 104 jednostavno ne postoji u drugoj tablici.

Right join (desno spajanje)

Right join je zrcalna slika left joina — zadržava sve retke iz desnog DataFrame-a:

# Right join — svi odjeli, čak i bez zaposlenika
rezultat_right = pd.merge(zaposlenici, odjeli, on='odjel_id', how='right')
print(rezultat_right)

Ovdje ćemo vidjeti odjel HR unatoč tome što nema ni jednog zaposlenika. Stupci iz tablice zaposlenika bit će NaN za taj redak. Iskreno, right join se u praksi rjeđe koristi jer uvijek možete postići isti rezultat zamjenom redoslijeda DataFrame-ova i korištenjem left joina.

Outer join (potpuno vanjsko spajanje)

Outer join kombinira left i right join — zadržava apsolutno sve retke iz oba DataFrame-a:

# Outer join — svi zaposlenici I svi odjeli
rezultat_outer = pd.merge(zaposlenici, odjeli, on='odjel_id', how='outer')
print(rezultat_outer)

Rezultat sadrži i Luku (bez podataka o odjelu) i HR odjel (bez zaposlenika). Koristite outer join kada želite potpunu sliku — svaki podatak iz obje tablice, bez obzira na to postoji li podudaranje ili ne.

Cross join (unakrsno spajanje)

Cross join stvara kartezijski produkt — svaki redak iz prvog DataFrame-a kombinira se sa svakim retkom iz drugog. Zvuči egzotično, ali ima svojih primjena:

# Cross join — svaki zaposlenik sa svakim odjelom
rezultat_cross = pd.merge(zaposlenici, odjeli, how='cross')
print(f"Broj redaka: {len(rezultat_cross)}")
print(rezultat_cross.head(8))

Cross join stvara m × n redaka (u našem slučaju 6 × 4 = 24). Budite oprezni s ovim — na velikim tablicama može generirati ogromne rezultate. Ali za stvari poput generiranja svih mogućih kombinacija proizvoda i trgovina, savršeno funkcionira.

Novo u pandas 3.0: Anti join (left_anti i right_anti)

Ovo je, po mom mišljenju, jedna od najboljih novosti u pandas 3.0. Anti join vraća retke iz jednog DataFrame-a koji nemaju podudaranje u drugom — dakle, upravo suprotno od inner joina.

Da vidite zašto je ovo toliko dobrodošlo, pogledajmo prvo kako smo to morali raditi u starijim verzijama:

# STARI NAČIN (pandas < 3.0) — korištenje indicator parametra
stari_nacin = pd.merge(zaposlenici, odjeli, on='odjel_id', how='left', indicator=True)
# Filtriramo samo retke bez podudaranja
samo_lijevi = stari_nacin[stari_nacin['_merge'] == 'left_only'].drop(columns='_merge')
print("Zaposlenici bez odjela (stari način):")
print(samo_lijevi)

Funkcioniralo je, ali bilo je neintuitivno i zahtijevalo dodatne korake. U pandas 3.0, ovo je postalo elegantno jednostavno:

# NOVI NAČIN (pandas 3.0+) — left_anti join
bez_odjela = pd.merge(zaposlenici, odjeli, on='odjel_id', how='left_anti')
print("Zaposlenici bez odjela (pandas 3.0):")
print(bez_odjela)

Rezultat sadrži samo Luku — jedinog zaposlenika čiji odjel (104) ne postoji u tablici odjela. I primijetite da rezultat sadrži samo stupce iz lijevog DataFrame-a, što ima smisla jer podudaranja u desnom nema.

Naravno, postoji i right_anti za obrnuti smjer:

# Right anti join — odjeli bez zaposlenika
odjeli_bez_zaposlenika = pd.merge(zaposlenici, odjeli, on='odjel_id', how='right_anti')
print("Odjeli bez zaposlenika:")
print(odjeli_bez_zaposlenika)

Rezultat pokazuje samo HR odjel (105). Anti join je fantastičan za pronalaženje nedostajućih veza, validaciju referencijalnog integriteta i čišćenje podataka — teme koje smo detaljno obradili u našem članku o čišćenju podataka.

Parametri merge() funkcije koje morate poznavati

Funkcija merge() ima nekoliko parametara koji vam daju preciznu kontrolu nad spajanjem. Evo onih najvažnijih.

on, left_on i right_on

Kada stupci za spajanje imaju isti naziv, koristite on. Ali u praksi? Stupci često imaju različite nazive u različitim tablicama (jer ih je netko drugi radio, naravno):

# Stupci imaju različite nazive: 'zaposlenik_id' vs 'id_zaposlenika'
rezultat = pd.merge(
    zaposlenici, place,
    left_on='zaposlenik_id',
    right_on='id_zaposlenika',
    how='inner'
)
print(rezultat)

Možete spajati i po više stupaca istovremeno — jednostavno proslijedite listu naziva stupaca.

suffixes

Kada oba DataFrame-a imaju stupce s istim nazivom (koji nisu ključ za spajanje), pandas im automatski dodaje sufikse. Zadano je ('_x', '_y'), ali to često nije previše opisno, pa to možete prilagoditi:

# Prilagođeni sufiksi za preglednost
df1 = pd.DataFrame({'kljuc': [1, 2], 'vrijednost': [100, 200]})
df2 = pd.DataFrame({'kljuc': [1, 2], 'vrijednost': [300, 400]})

rezultat = pd.merge(df1, df2, on='kljuc', suffixes=('_staro', '_novo'))
print(rezultat)
# Stupci: kljuc, vrijednost_staro, vrijednost_novo

validate

Parametar validate je nešto što bih svima preporučio koristiti češće. On provjerava kardinalnost spajanja i baca grešku ako podaci ne odgovaraju očekivanom odnosu:

# Osiguravamo da je spajanje one-to-many (jedan odjel, mnogo zaposlenika)
rezultat = pd.merge(
    zaposlenici, odjeli,
    on='odjel_id',
    how='left',
    validate='many_to_one'  # Mnogo zaposlenika na jedan odjel
)

# Opcije: 'one_to_one', 'one_to_many', 'many_to_one', 'many_to_many'
# Ako podaci ne zadovoljavaju uvjet, pandas baca MergeError

Ovaj parametar može vam uštedjeti sate debuggiranja. Koliko puta sam se borio s neočekivanim duplikatima u rezultatima, da bi se na kraju pokazalo kako ključ nije bio jedinstven — to ne želite prolaziti.

indicator

Parametar indicator=True dodaje stupac _merge koji pokazuje odakle dolazi svaki redak:

# Dodajemo indikator izvora retka
rezultat = pd.merge(zaposlenici, odjeli, on='odjel_id', how='outer', indicator=True)
print(rezultat[['ime', 'naziv_odjela', '_merge']])
# Vrijednosti: 'both', 'left_only', 'right_only'

Iako anti join u pandas 3.0 smanjuje potrebu za ovim pristupom, indicator je i dalje koristan za dijagnostiku i razumijevanje rezultata spajanja.

DataFrame.join() — Spajanje po indeksu

Metoda .join() je praktičan prečac za spajanje DataFrame-ova po indeksu. Glavna razlika u odnosu na merge()? Metoda join() zadano koristi indeks desnog DataFrame-a za spajanje:

# Postavimo odjel_id kao indeks tablice odjela
odjeli_indeksirani = odjeli.set_index('odjel_id')

# Spajamo po indeksu desnog DataFrame-a i stupcu lijevog
rezultat_join = zaposlenici.join(
    odjeli_indeksirani,
    on='odjel_id',  # stupac iz lijevog DataFrame-a
    how='left'
)
print(rezultat_join)

Metoda join() je posebno korisna kada su podaci već indeksirani, recimo vremenski nizovi s DatetimeIndex-om. Spajanje po indeksu je brže od spajanja po stupcu jer pandas koristi optimizirane strukture podataka za pristup indeksu.

Bonus: možete spojiti više DataFrame-ova odjednom prosljeđivanjem liste:

# Spajanje više DataFrame-ova po zajedničkom indeksu
df_a = pd.DataFrame({'a': [1, 2, 3]}, index=['x', 'y', 'z'])
df_b = pd.DataFrame({'b': [4, 5, 6]}, index=['x', 'y', 'z'])
df_c = pd.DataFrame({'c': [7, 8, 9]}, index=['x', 'y', 'z'])

rezultat = df_a.join([df_b, df_c])
print(rezultat)

pd.concat() — Slaganje DataFrame-ova

Dok merge() spaja DataFrame-ove horizontalno (dodaje stupce), concat() ih najčešće slaže vertikalno (dodaje retke). Zamislite ga kao lijepljenje tablica jedne ispod druge — ili, kad treba, jedne pored druge.

Vertikalno slaganje (axis=0)

# Podaci o zaposlenicima iz dva ureda
ured_zagreb = pd.DataFrame({
    'ime': ['Ana', 'Ivana', 'Tomislav'],
    'pozicija': ['Developer', 'Analitičar', 'Dizajner'],
    'placa': [8500, 9100, 7800]
})

ured_split = pd.DataFrame({
    'ime': ['Marko', 'Maja'],
    'pozicija': ['Developer', 'PM'],
    'placa': [7200, 7500]
})

# Vertikalno spajanje — slaganje jednog ispod drugog
svi_zaposlenici = pd.concat([ured_zagreb, ured_split], ignore_index=True)
print(svi_zaposlenici)

Parametar ignore_index=True resetira indeks da dobijete kontinuirani niz brojeva umjesto dupliciranih indeksa iz izvornih DataFrame-ova. Skoro uvijek ćete ga htjeti koristiti.

Horizontalno slaganje (axis=1)

# Horizontalno spajanje — dodavanje stupaca
osobni_podaci = pd.DataFrame({
    'ime': ['Ana', 'Marko', 'Ivana'],
    'dob': [28, 35, 31]
})

radni_podaci = pd.DataFrame({
    'pozicija': ['Developer', 'Marketing', 'Analitičar'],
    'staz_godina': [3, 7, 5]
})

kombinirano = pd.concat([osobni_podaci, radni_podaci], axis=1)
print(kombinirano)

Parametar keys za hijerarhijski indeks

Parametar keys stvara MultiIndex koji označava izvor svakog retka — korisno za praćenje odakle podaci dolaze:

# Označavanje izvora podataka pomoću keys
svi = pd.concat(
    [ured_zagreb, ured_split],
    keys=['Zagreb', 'Split'],
    names=['Ured', 'Redak']
)
print(svi)

# Pristup podacima iz pojedinog ureda
print(svi.loc['Zagreb'])

Ovo je posebno zgodno pri spajanju podataka iz više CSV datoteka ili vremenskih razdoblja kada želite znati koji redak je došao otkud.

pd.merge_asof() — Spajanje po najbližem ključu

Funkcija merge_asof() je specijalizirana za vremenski temeljeno spajanje i, moram priznati, bila mi je game changer kad sam je otkrio. Umjesto traženja točnog podudaranja, ona pronalazi najbliži ključ koji je manji ili jednak traženom.

# Podaci o trgovanju dionicama
trgovanja = pd.DataFrame({
    'vrijeme': pd.to_datetime([
        '2026-01-15 09:30:00',
        '2026-01-15 09:31:15',
        '2026-01-15 09:33:42',
        '2026-01-15 09:36:05'
    ]),
    'dionica': ['AAPL', 'AAPL', 'AAPL', 'AAPL'],
    'cijena_trgovanja': [185.50, 185.75, 186.00, 185.90]
})

# Kotacije (bid/ask) — dolaze u različitim vremenima
kotacije = pd.DataFrame({
    'vrijeme': pd.to_datetime([
        '2026-01-15 09:30:00',
        '2026-01-15 09:30:45',
        '2026-01-15 09:32:00',
        '2026-01-15 09:35:30'
    ]),
    'dionica': ['AAPL', 'AAPL', 'AAPL', 'AAPL'],
    'bid': [185.40, 185.60, 185.85, 185.80],
    'ask': [185.55, 185.80, 186.05, 186.00]
})

# Spajanje po najbližem vremenu
rezultat = pd.merge_asof(
    trgovanja, kotacije,
    on='vrijeme',
    by='dionica',          # grupiraj po dionici
    direction='backward'   # uzmi zadnju kotaciju prije trgovanja
)
print(rezultat)

Svako trgovanje sada ima pridruženu najbližu kotaciju koja je bila aktivna u tom trenutku. Parametar direction može biti 'backward' (zadnja prije), 'forward' (sljedeća nakon) ili 'nearest' (najbliža u oba smjera). Tu je i parametar tolerance za ograničavanje maksimalne udaljenosti spajanja, što je korisno da ne biste slučajno spojili podatke koji su previše udaljeni vremenski.

Kada koristiti merge(), join(), concat() ili merge_asof()?

S četiri različite metode, logično je zapitati se — koju kada koristiti? Evo pregledne tablice koja to lijepo sažima:

Metoda Primarna upotreba Tip spajanja Primjer situacije
pd.merge() SQL-stil JOIN po stupcima Horizontalno (dodaje stupce) Spajanje zaposlenika s odjelima po odjel_id
df.join() Spajanje po indeksu Horizontalno (dodaje stupce) Spajanje indeksiranih vremenskih nizova
pd.concat() Slaganje tablica vertikalno ili horizontalno Vertikalno (dodaje retke) ili horizontalno Spajanje mjesečnih izvještaja u godišnji
pd.merge_asof() Spajanje po najbližem ključu Horizontalno (približno podudaranje) Pridruživanje kotacija trgovanjima po vremenu

Jednostavno pravilo: Trebate SQL JOIN? Koristite merge(). Lijepite tablice jednu ispod druge? To je concat(). Podaci su već indeksirani? Razmotrite join(). Spajate vremenske nizove po približnom ključu? Definitivno merge_asof().

Najčešće greške i kako ih riješiti

Spajanje DataFrame-ova može producirati neočekivane rezultate ako niste oprezni. Prošao sam kroz sve ove greške osobno (neke i više puta), pa evo najčešćih zamki i kako ih izbjeći.

Nepodudaranje tipova podataka

Ovo je, po mom iskustvu, najčešća i najfrustrirajućija greška. Nastaje kada su ključevi za spajanje različitih tipova u dva DataFrame-a:

# Problem: odjel_id je int u jednom, a string u drugom DataFrame-u
df1 = pd.DataFrame({'odjel_id': [1, 2, 3], 'ime': ['Ana', 'Marko', 'Ivana']})
df2 = pd.DataFrame({'odjel_id': ['1', '2', '3'], 'naziv': ['Razvoj', 'Marketing', 'HR']})

# Ovo neće pronaći podudaranja!
los_rezultat = pd.merge(df1, df2, on='odjel_id', how='inner')
print(f"Broj redaka: {len(los_rezultat)}")  # 0 redaka!

# Rješenje: uskladite tipove prije spajanja
df2['odjel_id'] = df2['odjel_id'].astype(int)
dobar_rezultat = pd.merge(df1, df2, on='odjel_id', how='inner')
print(f"Broj redaka: {len(dobar_rezultat)}")  # 3 retka

Pro tip: uvijek provjerite tipove s df.dtypes prije spajanja. Uštedjet će vam živce.

Nema zajedničkih stupaca

# Problem: pandas ne zna po čemu spajati
df_a = pd.DataFrame({'id_zaposlenika': [1, 2], 'ime': ['Ana', 'Marko']})
df_b = pd.DataFrame({'sifra_odjela': [101, 102], 'naziv': ['Razvoj', 'Marketing']})

# Ovo baca MergeError jer nema zajedničkih stupaca
# pd.merge(df_a, df_b)  # Greška!

# Rješenje: eksplicitno navedite stupce
rezultat = pd.merge(df_a, df_b, left_on='id_zaposlenika', right_on='sifra_odjela')

Neočekivani kartezijski produkt (many-to-many)

Ovo je ona greška koja vas može uhvatiti nespremne. Ako obje tablice imaju duplicirane vrijednosti ključa, merge stvara kartezijski produkt tih redaka:

# Problem: duplikati na obje strane
df1 = pd.DataFrame({'kljuc': ['A', 'A', 'B'], 'val1': [1, 2, 3]})
df2 = pd.DataFrame({'kljuc': ['A', 'A', 'B'], 'val2': [10, 20, 30]})

rezultat = pd.merge(df1, df2, on='kljuc')
print(f"Očekivali ste 3 retka? Dobili ste {len(rezultat)}!")
print(rezultat)
# 'A' × 'A' daje 2×2 = 4 kombinacije + 1 za 'B' = 5 redaka

# Rješenje: koristite validate za rano otkrivanje
# pd.merge(df1, df2, on='kljuc', validate='one_to_one')  # Baca grešku!

NaN vrijednosti u ključevima za spajanje

# Problem: NaN se ne podudara ni sa čim (čak ni s drugim NaN-om)
df1 = pd.DataFrame({'kljuc': [1, 2, np.nan], 'val': ['a', 'b', 'c']})
df2 = pd.DataFrame({'kljuc': [1, np.nan, 3], 'val2': ['x', 'y', 'z']})

rezultat = pd.merge(df1, df2, on='kljuc', how='inner')
print(rezultat)  # Samo 1 redak (kljuc=1), NaN se ne podudara

# Rješenje: popunite NaN prije spajanja ili koristite outer join
df1['kljuc'] = df1['kljuc'].fillna(-1)
df2['kljuc'] = df2['kljuc'].fillna(-1)
rezultat = pd.merge(df1, df2, on='kljuc', how='inner')
print(rezultat)  # Sada se NaN retci (zamijenjeni s -1) podudaraju

Savjeti za optimizaciju performansi

Kada radite s velikim DataFrame-ovima (govorimo o milijunima redaka), performanse postaju ozbiljna tema. Evo par savjeta iz prakse koji stvarno čine razliku.

  • Sortirajte po ključu prije spajanja — pandas može koristiti učinkovitije algoritme za sortirane podatke. Kod merge_asof() sortiranje je čak obavezno.
  • Koristite kategorijski tip — za stupce s malim brojem jedinstvenih vrijednosti (poput odjel_id ili grad), konverzija u category dtype smanjuje memoriju i ubrzava spajanje.
  • Izbacite nepotrebne stupce prije spajanja — svaki stupac viška povećava memorijsku potrošnju rezultata. Uzmite samo ono što vam treba.
  • Koristite manje tipove podatakaint32 umjesto int64 ako raspon vrijednosti to dopušta. Tim jednim potezom prepolovite potrošnju memorije za taj stupac.
# Primjer optimizacije za velike DataFrame-ove
veliki_df = zaposlenici.copy()

# Konverzija u kategorijski tip za stupce s malo jedinstvenih vrijednosti
veliki_df['grad'] = veliki_df['grad'].astype('category')
veliki_df['odjel_id'] = veliki_df['odjel_id'].astype('int32')

# Selektiramo samo potrebne stupce prije spajanja
potrebni_stupci = ['zaposlenik_id', 'ime', 'odjel_id']
rezultat = pd.merge(
    veliki_df[potrebni_stupci],
    odjeli[['odjel_id', 'naziv_odjela']],
    on='odjel_id',
    how='left'
)

# Za merge_asof — podaci MORAJU biti sortirani
trgovanja_sortirano = trgovanja.sort_values('vrijeme')
kotacije_sortirano = kotacije.sort_values('vrijeme')

Još jedan savjet koji se lako zaboravi: ako obavljate više spajanja u nizu, razmislite o redoslijedu. Počnite s joinovima koji najviše smanjuju broj redaka (inner join s manjom tablicom) da biste minimizirali veličinu međurezultata.

Često postavljana pitanja (FAQ)

Koja je razlika između merge() i join() u pandasu?

Osnovna razlika je u zadanom ponašanju. Funkcija pd.merge() spaja po zajedničkim stupcima i zadano radi inner join. Metoda df.join() spaja po indeksu desnog DataFrame-a i zadano radi left join. Pod haubom, join() zapravo poziva merge(), samo s drugačijim zadanim postavkama. U praksi, koristite merge() za većinu situacija, a join() kada su podaci već indeksirani po ključu za spajanje.

Kako spojiti DataFrame-ove s različitim nazivima stupaca?

Koristite parametre left_on i right_on umjesto parametra on:

# Stupci se zovu drugačije u različitim tablicama
rezultat = pd.merge(
    zaposlenici, place,
    left_on='zaposlenik_id',
    right_on='id_zaposlenika',
    how='inner'
)
# Nakon spajanja možete obrisati duplicirani stupac
rezultat = rezultat.drop(columns='id_zaposlenika')

Što je anti join i kako ga koristiti u pandas 3.0?

Anti join vraća retke iz jednog DataFrame-a koji nemaju podudaranje u drugom. U pandas 3.0 koristite how='left_anti' za retke iz lijevog DataFrame-a bez podudaranja u desnom, ili how='right_anti' za obrnuti smjer. Prije pandas 3.0, morali ste koristiti outer join s indicator=True i zatim ručno filtrirati rezultat — znatno složeniji i manje elegantan postupak.

Zašto merge() stvara duplikate redova?

Duplikati nastaju kada ključ za spajanje ima duplicirane vrijednosti u jednom ili oba DataFrame-a. Ako dva zaposlenika imaju isti odjel_id, a u tablici odjela postoji jedan redak za taj odjel, dobit ćete dva retka (one-to-many) — i to je očekivano ponašanje. Problem nastaje kad oba DataFrame-a imaju duplicirane ključeve jer tada dobivate kartezijski produkt koji može drastično povećati broj redaka. Koristite validate parametar za rano otkrivanje:

# Provjerite kardinalnost spajanja
pd.merge(df1, df2, on='kljuc', validate='many_to_one')

Kako spojiti više od dva DataFrame-a odjednom?

Za vertikalno slaganje, pd.concat() prihvaća listu DataFrame-ova bilo koje duljine — tu nema ograničenja. Za horizontalno spajanje putem merge-a, koristite reduce() iz modula functools:

from functools import reduce

lista_df = [df1, df2, df3, df4]

# Vertikalno slaganje
sve_tablice = pd.concat(lista_df, ignore_index=True)

# Sekvencijalno spajanje (merge)
spojeno = reduce(
    lambda lijevi, desni: pd.merge(lijevi, desni, on='zajednicki_kljuc', how='left'),
    lista_df
)

Ako su DataFrame-ovi indeksirani, df.join() može primiti listu DataFrame-ova i spojiti ih u jednom koraku, što je još elegantnije.

Spajanje DataFrame-ova temeljna je vještina za svakoga tko radi s podacima u Pythonu. S poznavanjem merge(), join(), concat() i merge_asof(), opremljeni ste za gotovo svaku situaciju u kojoj trebate kombinirati podatke iz različitih izvora. Ako još niste, svakako isprobajte anti join u pandas 3.0 — riješio je problem koji je godinama bio neugodnost u pandas ekosustavu. A u kombinaciji s tehnikama čišćenja podataka i GroupBy agregacijama koje smo ranije obradili, imate kompletan set alata za profesionalnu analizu podataka.

O Autoru Editorial Team

Our team of expert writers and editors.