Pandas 3.0 ile Veri Birleştirme Neden Bu Kadar Önemli?
Şöyle bir düşünün: gerçek dünya projelerinde veriler neredeyse hiçbir zaman tek bir tabloda durmuyor. Müşteri bilgileri bir yerde, sipariş kayıtları başka bir yerde, ürün detayları bambaşka bir kaynakta. Bu dağınık verileri anlamlı bir bütüne dönüştürmek, veri bilimcilerin günlük ekmeği gibi bir şey.
Pandas kütüphanesi, SQL benzeri birleştirme işlemlerini Python ortamına taşıyarak bu süreci inanılmaz kolaylaştırıyor. Ama işin güzel tarafı şu — Ocak 2026'da yayımlanan Pandas 3.0, oyunun kurallarını epey değiştirdi.
Copy-on-Write (CoW) artık varsayılan davranış, yeni str veri tipi geldi, pd.col() ifade sözdizimi eklendi. Bunlar birleştirme işlemlerinin hem performansını hem güvenilirliğini doğrudan etkiliyor. Bu rehberde merge(), join() ve concat() fonksiyonlarını Pandas 3.0 üzerinde, güncel örneklerle derinlemesine inceleyeceğiz.
Ortamın Hazırlanması
Önce Pandas 3.0 veya üzerinin kurulu olduğundan emin olalım. Pandas 3.0, Python 3.11+ gerektiriyor — bunu gözden kaçırmayın.
pip install pandas>=3.0.0 pyarrow
PyArrow kurulumu teknik olarak zorunlu değil ama açıkçası şiddetle öneriyorum. Pandas 3.0'ın yeni string veri tipinin performansını ciddi oranda artırıyor. Şimdi örnek veri setlerimizi oluşturalım:
import pandas as pd
import numpy as np
# Müşteri tablosu
musteriler = pd.DataFrame({
"musteri_id": [1, 2, 3, 4, 5],
"ad": ["Elif", "Burak", "Canan", "Deniz", "Emre"],
"sehir": ["İstanbul", "Ankara", "İzmir", "Bursa", "Antalya"]
})
# Sipariş tablosu
siparisler = pd.DataFrame({
"siparis_id": [101, 102, 103, 104, 105, 106],
"musteri_id": [1, 2, 2, 3, 5, 6],
"urun": ["Laptop", "Tablet", "Kulaklık", "Monitör", "Klavye", "Mouse"],
"tutar": [15000, 8000, 500, 4500, 350, 200]
})
# Ürün detayları tablosu
urunler = pd.DataFrame({
"urun": ["Laptop", "Tablet", "Kulaklık", "Monitör", "Klavye", "Mouse", "Kamera"],
"kategori": ["Bilgisayar", "Mobil", "Aksesuar", "Bilgisayar", "Aksesuar", "Aksesuar", "Elektronik"],
"stok": [25, 40, 150, 30, 200, 180, 15]
})
merge() ile SQL Tarzı Birleştirme
pd.merge(), Pandas'ın en esnek birleştirme fonksiyonu. SQL'deki JOIN işlemleriyle neredeyse birebir örtüşüyor ve sütun bazlı eşleşme yapıyor. Kişisel olarak en çok kullandığım yöntem de bu.
Inner Join — Yalnızca Ortak Kayıtlar
Varsayılan birleştirme türü inner. Yani her iki tabloda da eşleşen kayıtları döndürüyor:
# Inner join: Yalnızca her iki tabloda da bulunan müşteriler
sonuc = pd.merge(musteriler, siparisler, on="musteri_id")
print(sonuc)
Çıktıda yalnızca musteri_id değeri her iki tabloda da bulunan satırlar görünecek. Müşteri 4 (Deniz) hiç sipariş vermediği için sonuçta yok. Müşteri 6 da müşteri tablosunda bulunmadığından dahil edilmiyor.
Left Join — Sol Tablonun Tamamı
Sol tablodaki tüm kayıtları korur, eşleşme olmayan yerleri NaN ile doldurur:
# Left join: Tüm müşteriler, sipariş olsun ya da olmasın
sonuc_left = pd.merge(musteriler, siparisler, on="musteri_id", how="left")
print(sonuc_left)
Bu yöntem özellikle sipariş vermemiş müşterileri bulmak için harika. Hedefli pazarlama kampanyaları planlarken sürekli kullanıyorum.
Right Join — Sağ Tablonun Tamamı
# Right join: Tüm siparişler, müşteri bilgisi olsun ya da olmasın
sonuc_right = pd.merge(musteriler, siparisler, on="musteri_id", how="right")
print(sonuc_right)
Dürüst olmak gerekirse right join'i pratikte çok az kullanıyorum. Genelde tabloların sırasını değiştirip left join yapmak daha okunabilir oluyor.
Outer Join — Tüm Kayıtlar
Her iki tablodaki tüm kayıtları birleştirir, eşleşmeyen değerler NaN ile doldurulur:
# Outer join: Her iki tablodaki tüm kayıtlar
sonuc_outer = pd.merge(musteriler, siparisler, on="musteri_id", how="outer")
print(sonuc_outer)
Farklı Sütun İsimleriyle Birleştirme
İki tablodaki anahtar sütunlar farklı isimler taşıyorsa (ki bu gerçek projelerde çok sık karşılaşılan bir durum), left_on ve right_on parametrelerini kullanabilirsiniz:
# Farklı sütun isimleriyle birleştirme
satislar = pd.DataFrame({
"satis_musteri": [1, 3, 5],
"gelir": [25000, 4500, 350]
})
sonuc = pd.merge(
musteriler, satislar,
left_on="musteri_id",
right_on="satis_musteri"
)
print(sonuc)
Çoklu Anahtar ile Birleştirme
Birden fazla sütun üzerinden birleştirme yapmak istiyorsanız, on parametresine bir liste geçirmeniz yeterli:
# Çoklu anahtar birleştirme örneği
satis_q1 = pd.DataFrame({
"musteri_id": [1, 2, 2, 3],
"urun": ["Laptop", "Tablet", "Kulaklık", "Monitör"],
"ceyrek": ["Q1", "Q1", "Q1", "Q1"],
"adet": [1, 2, 5, 1]
})
satis_q2 = pd.DataFrame({
"musteri_id": [1, 2, 3, 4],
"urun": ["Laptop", "Tablet", "Klavye", "Monitör"],
"ceyrek": ["Q2", "Q2", "Q2", "Q2"],
"adet": [2, 1, 3, 1]
})
tum_satislar = pd.concat([satis_q1, satis_q2], ignore_index=True)
# Müşteri ve ürün bazlı birleştirme
detay = pd.merge(tum_satislar, urunler, on="urun")
print(detay)
suffixes Parametresi ile Çakışan Sütun İsimleri
İki tabloda aynı isimli ama anahtar olmayan sütunlar varsa, suffixes ile ayırt edebilirsiniz. Bence en çok göz ardı edilen parametrelerden biri:
df_2025 = pd.DataFrame({
"urun": ["Laptop", "Tablet"],
"fiyat": [14000, 7500]
})
df_2026 = pd.DataFrame({
"urun": ["Laptop", "Tablet"],
"fiyat": [15000, 8000]
})
karsilastirma = pd.merge(df_2025, df_2026, on="urun", suffixes=("_2025", "_2026"))
print(karsilastirma)
# Fiyat değişimini hesaplayın
karsilastirma["degisim_yuzde"] = (
(karsilastirma["fiyat_2026"] - karsilastirma["fiyat_2025"])
/ karsilastirma["fiyat_2025"] * 100
)
print(karsilastirma)
validate Parametresi ile Veri Bütünlüğü Kontrolü
Bu parametre birçok kişinin bilmediği ama gerçekten hayat kurtaran bir özellik. Beklenmedik çoka-çok birleştirmeleri önlemeye yarıyor:
# Bire-çok ilişki doğrulaması
sonuc = pd.merge(
musteriler, siparisler,
on="musteri_id",
validate="one_to_many" # "1:m", "m:1", "1:1", "m:m"
)
Eğer beklenen ilişki türüne uyulmazsa Pandas MergeError hatası fırlatır. Veri kalitesini erken aşamada kontrol etmek için biçilmiş kaftan.
indicator Parametresi ile Birleştirme Tanılama
Hangi satırın hangi tablodan geldiğini merak ediyorsanız, indicator=True tam aradığınız şey:
sonuc = pd.merge(
musteriler, siparisler,
on="musteri_id",
how="outer",
indicator=True
)
print(sonuc["_merge"].value_counts())
Çıktıda both, left_only ve right_only etiketleri göreceksiniz. Veri kalitesi kontrolünde inanılmaz faydalı bir özellik.
concat() ile DataFrame'leri Birleştirme
pd.concat(), DataFrame'leri satır veya sütun ekseninde üst üste yığmak için kullanılıyor. SQL'deki UNION işleminin Pandas karşılığı diyebiliriz.
Dikey Birleştirme (Satır Bazlı)
# İki dönemin verilerini alt alta birleştir
ocak = pd.DataFrame({
"tarih": ["2026-01-15", "2026-01-20"],
"urun": ["Laptop", "Tablet"],
"satis": [5, 10]
})
subat = pd.DataFrame({
"tarih": ["2026-02-10", "2026-02-18"],
"urun": ["Laptop", "Klavye"],
"satis": [8, 25]
})
yillik = pd.concat([ocak, subat], ignore_index=True)
print(yillik)
ignore_index=True kullanmak önemli — bu sayede birleştirme sonrası indeksler sıfırdan yeniden numaralanıyor. Yoksa kaynak DataFrame'lerin orijinal indeksleri korunur ve tekrar eden indekslerle uğraşmak zorunda kalırsınız.
keys ile Kaynak Takibi
Birleştirilen verilerin hangi kaynaktan geldiğini takip etmek istiyorsanız keys parametresi çok işe yarıyor:
yillik = pd.concat([ocak, subat], keys=["Ocak", "Şubat"])
print(yillik)
# Belirli bir ayın verisine erişim
print(yillik.loc["Ocak"])
Bu yöntem çok indeksli (MultiIndex) bir yapı oluşturuyor. Verinin kaynağına kolayca erişebilmeniz için oldukça pratik bir çözüm.
Yatay Birleştirme (Sütun Bazlı)
# Aynı indekse sahip iki DataFrame'i yan yana koy
ogrenci_bilgi = pd.DataFrame({
"ad": ["Ali", "Ayşe", "Mehmet"],
"yas": [22, 24, 23]
}, index=[0, 1, 2])
ogrenci_not = pd.DataFrame({
"matematik": [85, 92, 78],
"fizik": [90, 88, 82]
}, index=[0, 1, 2])
tablo = pd.concat([ogrenci_bilgi, ogrenci_not], axis=1)
print(tablo)
join Parametresi: inner ve outer
concat() fonksiyonunda join parametresi, diğer eksenin nasıl işleneceğini belirliyor:
df_a = pd.DataFrame({"x": [1, 2]}, index=[0, 1])
df_b = pd.DataFrame({"y": [3, 4]}, index=[1, 2])
# Outer (varsayılan): Tüm indeksler korunur
print(pd.concat([df_a, df_b], axis=1, join="outer"))
# Inner: Yalnızca ortak indeksler
print(pd.concat([df_a, df_b], axis=1, join="inner"))
Performans İpucu: Döngüde concat() Kullanmayın
Bu muhtemelen en sık gördüğüm hata. concat() her çağrıda verinin tamamını kopyalıyor. Bir döngü içinde tekrar tekrar çağırmak ciddi performans kaybına yol açıyor:
# YANLIŞ — Her iterasyonda tam kopya oluşur
sonuc = pd.DataFrame()
for dosya in dosya_listesi:
df = pd.read_csv(dosya)
sonuc = pd.concat([sonuc, df]) # Yavaş!
# DOĞRU — Liste oluşturup tek seferde birleştir
parcalar = [pd.read_csv(dosya) for dosya in dosya_listesi]
sonuc = pd.concat(parcalar, ignore_index=True) # Hızlı!
Ciddiye alın, büyük veri setlerinde fark on katı aşabiliyor.
join() ile İndeks Tabanlı Birleştirme
DataFrame.join(), indeks değerleri üzerinden birleştirme yapıyor. Aslında merge() etrafında pratik bir kısayol. Varsayılan birleştirme türü left.
# İndeks tabanlı birleştirme
gelirler = pd.DataFrame({
"gelir": [25000, 12000, 4500]
}, index=[1, 2, 3])
giderler = pd.DataFrame({
"gider": [8000, 3000, 1500, 500]
}, index=[1, 2, 3, 4])
# Left join (varsayılan)
kar_tablosu = gelirler.join(giderler)
print(kar_tablosu)
# Outer join
tam_tablo = gelirler.join(giderler, how="outer")
print(tam_tablo)
Sütun Üzerinden join() Kullanma
Sağ tablonun indeksini sol tablonun bir sütunuyla eşleştirmek isterseniz, on parametresini kullanabilirsiniz:
# Sütun-indeks eşleştirmesi
sehir_nufus = pd.DataFrame({
"nufus_milyon": [16.0, 5.7, 4.4, 3.2, 2.6]
}, index=["İstanbul", "Ankara", "İzmir", "Bursa", "Antalya"])
sonuc = musteriler.join(sehir_nufus, on="sehir")
print(sonuc)
Pandas 3.0 Copy-on-Write ve Birleştirme İşlemleri
Gelelim Pandas 3.0'ın en büyük değişikliğine. Copy-on-Write (CoW) mekanizması artık varsayılan ve birleştirme işlemlerini doğrudan etkiliyor. Her birleştirme sonucu, orijinal DataFrame'den tamamen bağımsız bir kopya gibi davranıyor.
CoW Öncesi Sorun
Eski davranışı hatırlayalım — gerçekten sinir bozucuydu:
# Pandas 2.x'de belirsiz davranış
sonuc = pd.merge(musteriler, siparisler, on="musteri_id")
sonuc["ad"] = sonuc["ad"].str.upper()
# musteriler DataFrame'i değişir mi? Belirsizdi!
CoW Sonrası Tutarlı Davranış
# Pandas 3.0'da tutarlı davranış
sonuc = pd.merge(musteriler, siparisler, on="musteri_id")
sonuc["ad"] = sonuc["ad"].str.upper()
# musteriler DataFrame'i KESİNLİKLE değişmez
# Orijinal veriyi değiştirmek istiyorsanız .loc kullanın
musteriler.loc[0, "ad"] = "ELİF"
Bu değişiklik sayesinde .copy() çağrısına artık gerek yok ve o meşhur SettingWithCopyWarning uyarısı tamamen kaldırıldı. Sonunda! Birleştirme sonrası elde ettiğiniz DataFrame üzerinde gönül rahatlığıyla işlem yapabilirsiniz.
PyArrow Destekli Performans İyileştirmesi
Pandas 3.0, merge() işlemlerinde Arrow destekli veri tipleri için optimize edilmiş bir yol kullanıyor. PyArrow kuruluysa, string sütunlar üzerindeki birleştirmeler belirgin şekilde hızlanıyor:
# PyArrow destekli string performansını test edin
import time
n = 500_000
df1 = pd.DataFrame({
"anahtar": [f"id_{i}" for i in range(n)],
"deger_1": np.random.randn(n)
})
df2 = pd.DataFrame({
"anahtar": [f"id_{i}" for i in range(n)],
"deger_2": np.random.randn(n)
})
baslangic = time.time()
sonuc = pd.merge(df1, df2, on="anahtar")
sure = time.time() - baslangic
print(f"Birleştirme süresi: {sure:.3f} saniye")
print(f"Anahtar sütun tipi: {df1['anahtar'].dtype}")
merge(), join() ve concat() Karşılaştırması
Tamam, üç fonksiyonu da detaylıca gördük. Şimdi hangisini ne zaman kullanacağız? Bence asıl mesele bu.
Karşılaştırma Tablosu
| Özellik | merge() | join() | concat() |
|---|---|---|---|
| Birleştirme Temeli | Sütun veya indeks | İndeks | İndeks veya eksen |
| Varsayılan Join | inner | left | outer |
| SQL Karşılığı | JOIN | JOIN (indeks bazlı) | UNION / UNION ALL |
| Çoklu DataFrame | Yalnızca 2 | Birden fazla | Birden fazla |
| Validate Desteği | Var | Var | Yok |
| Indicator Desteği | Var | Yok | Yok |
| En İyi Kullanım | Sütun bazlı eşleşme | İndeks bazlı hızlı birleştirme | Verileri üst üste yığma |
Karar Akışı
Kafanız karışmasın, şu basit kuralları izleyin:
- Farklı tablolardan sütunlar üzerinden eşleştirme mi yapıyorsunuz? →
merge()kullanın - İndeks değerleri üzerinden birleştirme mi? →
join()kullanın - Aynı yapıdaki verileri alt alta mı yığıyorsunuz? →
concat()kullanın - Yan yana (sütun bazlı) ekleme mi? →
concat(axis=1)veyajoin()kullanın
Gerçek Dünya Senaryosu: E-Ticaret Veri Analizi
Teorik kısımları geçtik, şimdi öğrendiklerimizi gerçek bir senaryoda birleştirelim. Bir e-ticaret platformunun farklı tablolarından müşteri segmentasyonu yapacağız. Bu tarz bir analiz, iş hayatında neredeyse her hafta karşınıza çıkar.
import pandas as pd
import numpy as np
# 1. Veri kaynaklarını oluştur
musteriler = pd.DataFrame({
"musteri_id": range(1, 8),
"ad": ["Elif", "Burak", "Canan", "Deniz", "Emre", "Fatma", "Gökhan"],
"kayit_tarihi": pd.to_datetime([
"2024-01-15", "2024-03-20", "2024-06-10",
"2025-01-05", "2025-04-18", "2025-08-22", "2026-01-03"
]),
"segment": ["Premium", "Standart", "Premium", "Standart",
"Premium", "Standart", "Yeni"]
})
siparisler = pd.DataFrame({
"siparis_id": range(1001, 1011),
"musteri_id": [1, 1, 2, 3, 3, 3, 4, 5, 5, 8],
"tarih": pd.to_datetime([
"2026-01-10", "2026-02-15", "2026-01-20",
"2026-01-05", "2026-02-01", "2026-03-01",
"2026-02-10", "2026-01-25", "2026-03-05", "2026-03-08"
]),
"tutar": [15000, 3500, 8200, 1200, 4500, 6700, 950, 22000, 1800, 500]
})
iade_kayitlari = pd.DataFrame({
"siparis_id": [1002, 1004, 1008],
"iade_tarihi": pd.to_datetime(["2026-02-20", "2026-01-12", "2026-02-01"]),
"iade_nedeni": ["Beden uyumsuzluğu", "Hasarlı ürün", "Fikir değişikliği"]
})
# 2. Siparişleri müşteri bilgileriyle birleştir (left join)
siparis_detay = pd.merge(
siparisler, musteriler,
on="musteri_id",
how="left",
indicator=True
)
# Eşleşmeyen kayıtları kontrol et
print("Birleştirme durumu:")
print(siparis_detay["_merge"].value_counts())
# 3. İade bilgilerini ekle (left join)
tam_veri = pd.merge(
siparis_detay.drop(columns="_merge"),
iade_kayitlari,
on="siparis_id",
how="left"
)
tam_veri["iade_var"] = tam_veri["iade_tarihi"].notna()
# 4. Müşteri bazlı özet hesapla
ozet = tam_veri.groupby("musteri_id").agg(
toplam_siparis=("siparis_id", "count"),
toplam_harcama=("tutar", "sum"),
iade_sayisi=("iade_var", "sum"),
ortalama_siparis=("tutar", "mean")
).reset_index()
# 5. Özeti müşteri bilgileriyle birleştir
musteri_rapor = pd.merge(musteriler, ozet, on="musteri_id", how="left")
musteri_rapor["toplam_harcama"] = musteri_rapor["toplam_harcama"].fillna(0)
musteri_rapor["toplam_siparis"] = musteri_rapor["toplam_siparis"].fillna(0)
print("\nMüşteri Raporu:")
print(musteri_rapor[["ad", "segment", "toplam_siparis", "toplam_harcama"]])
Sık Yapılan Hatalar ve Çözümleri
Yıllar içinde (ve birçok hata ayıklama seansından sonra) en sık karşılaşılan birleştirme hatalarını derledim.
1. Beklenmedik Satır Çoğalması
Bu klasik bir tuzak. Çoka-çok birleştirmeler Kartezyen çarpım oluşturabiliyor ve satır sayısı bir anda patlıyor:
# Problemi tespit edin
print(f"Sol tablo: {len(df_sol)} satır")
print(f"Sağ tablo: {len(df_sag)} satır")
sonuc = pd.merge(df_sol, df_sag, on="anahtar")
print(f"Sonuç: {len(sonuc)} satır") # Beklenenden fazla olabilir!
# Çözüm: validate ile kontrol edin
sonuc = pd.merge(df_sol, df_sag, on="anahtar", validate="one_to_many")
2. Sütun İsmi Çakışmaları
Birleştirme sırasında aynı isimdeki sütunlara otomatik olarak _x ve _y ekleri geliyor. Bu pek açıklayıcı değil — anlamlı son ekler kullanın:
# Anlamlı suffixes kullanın
sonuc = pd.merge(df1, df2, on="id", suffixes=("_siparis", "_iade"))
3. Veri Tipi Uyumsuzlukları
Bu hata sessizce oluştuğu için tehlikeli. Anahtar sütunların veri tipleri eşleşmezse birleştirme hatalı sonuçlar üretebilir:
# Tip kontrolü ve dönüşüm
print(df1["id"].dtype, df2["id"].dtype)
# Gerekirse tip dönüşümü yapın
df2["id"] = df2["id"].astype(df1["id"].dtype)
sonuc = pd.merge(df1, df2, on="id")
4. NaN Değerler Anahtar Sütunda
Pandas 3.0.1 ile PyArrow destekli string sütunlarında NaN eşleşme hatası düzeltildi. Yine de anahtar sütunlarda NaN olmamasına dikkat etmekte fayda var:
# NaN içeren anahtarları birleştirme öncesi temizleyin
df = df.dropna(subset=["anahtar_sutun"])
Sıkça Sorulan Sorular
merge() ve join() arasındaki fark nedir?
merge(), sütun veya indeks bazlı birleştirme yapabilen en esnek fonksiyon ve varsayılan olarak inner join uyguluyor. join() ise yalnızca indeks bazlı birleştirme yapıyor, varsayılanı left join. Pratikte join(), merge() üzerine inşa edilmiş bir kısayol. Sütun bazlı eşleşme lazımsa merge(), indeks bazlı hızlı birleştirme için join() tercih edin.
concat() ile merge() ne zaman kullanılır?
concat(), aynı yapıdaki DataFrame'leri üst üste yığmak için — aylık raporları birleştirmek gibi düşünün. merge() ise farklı tablolardan verileri ortak bir anahtar üzerinden eşleştirmek için kullanılıyor. Müşteri tablosuyla sipariş tablosunu birleştirmek buna güzel bir örnek.
Pandas 3.0'da Copy-on-Write birleştirmeyi nasıl etkiler?
Pandas 3.0'da Copy-on-Write varsayılan davranış oldu. Yani merge(), join() veya concat() sonucu elde edilen DataFrame, orijinal verileri asla değiştirmiyor. .copy() çağrısına gerek kalmadı ve SettingWithCopyWarning uyarısı tamamen kaldırıldı. Performans tarafında ise gereksiz kopyalar azaldığından birleştirme işlemleri daha hızlı çalışıyor.
Büyük veri setlerinde birleştirme performansını nasıl artırabilirim?
Birkaç strateji var: (1) PyArrow kurarak string sütunlarındaki birleştirmeleri hızlandırın, (2) döngüde concat() yerine liste birikimi yapıp tek seferde birleştirin, (3) gereksiz sütunları birleştirme öncesi düşürün, (4) anahtar sütunları kategorik tipe dönüştürün. Çok büyük veri setlerinde (milyonlarca satır) Polars kütüphanesine geçmeyi de düşünebilirsiniz.
Birden fazla DataFrame'i tek seferde nasıl birleştiririm?
concat() birden fazla DataFrame'i tek seferde birleştirebilir: pd.concat([df1, df2, df3, df4]). merge() ise yalnızca iki DataFrame arasında çalışır; birden fazla tablo için zincirleme kullanabilirsiniz: df1.merge(df2, on="id").merge(df3, on="id"). Ya da functools.reduce ile daha temiz bir yaklaşım tercih edebilirsiniz.