← Kursa Dön
📄 Text · 20 min

Veri İşleme: pandas ve NumPy Temelleri

Excel'de hiç büyük bir veri setiyle çalıştın mı? Binlerce satırı filtreleme, toplam alma, grafiğe dökme... Bir noktadan sonra Excel donuyor, formüller karmaşıklaşıyor ve "bunu programla yapsam keşke" diyorsun. İşte pandas tam olarak bu ihtiyacı karşılıyor — Excel'in yapabildiği her şeyi ve çok daha fazlasını, Python kodu ile yapmanı sağlıyor.

Bu derste önce pandas'ın temelini oluşturan NumPy'ı tanıyacak, ardından pandas ile veri okuma, filtreleme, gruplama, temizleme ve birleştirme işlemlerini öğreneceksin. Ders sonunda gerçek bir satış verisi analizi projesi yapacağız.


1. Neden pandas?

📊 Analoji: Excel'in Programlanabilir Hali

Excel'i düşün: satırlar, sütunlar, hücreler. Filtre koyarsın, TOPLA formülü yazarsın, pivot tablo oluşturursun. pandas tam olarak bu — ama Excel yerine Python kodu ile kontrol ediyorsun. Bir formül yerine bir satır kod, bir makro yerine bir fonksiyon.

Fark ne? Excel 1 milyon satırda tıkanır. pandas milyonlarca satırı saniyeler içinde işler. Excel'de tekrarlayan işler için makro yazmak zahmetlidir. pandas'ta bir script yazarsın, her seferinde aynı analizi otomatik çalıştırırsın.

pandas Python'un veri bilimi ekosisteminin merkezinde durur. Veri okuma, temizleme, dönüştürme, analiz ve görselleştirme — hepsinin başlangıç noktası pandas'tır.


2. NumPy: pandas'ın Temeli

pandas'ı anlamadan önce NumPy'ı bilmek gerekir. pandas, verileri arka planda NumPy dizileri (ndarray) olarak tutar. NumPy'ın hız avantajı pandas'a da yansır.

ndarray Nedir?

Python'un yerleşik list'i her türden veri tutar — string, int, float karışık olabilir. Bu esneklik bir bedel getirir: yavaşlık. NumPy'ın ndarray'i ise tek tip veri tutar (hepsi int, hepsi float). Bu sayede bellekte ardışık depolanır ve C seviyesinde hızlı işlem yapılır.

import numpy as np

# Python listesi
python_list = [1, 2, 3, 4, 5]

# NumPy dizisi
numpy_array = np.array([1, 2, 3, 4, 5])

print(type(numpy_array))  # <class 'numpy.ndarray'>
print(numpy_array.dtype)   # int64
print(numpy_array.shape)   # (5,)

Temel Operasyonlar

NumPy'ın en güçlü özelliği vektörel operasyonlar. Döngü yazmadan tüm elemanlara işlem uygularsın:

import numpy as np

fiyatlar = np.array([100, 200, 350, 450, 600])

# Tüm fiyatlara %18 KDV ekle — döngü yok!
kdv_dahil = fiyatlar * 1.18
print(kdv_dahil)  # [118. 236. 413. 531. 708.]

# Karşılaştırma — boolean dizi döner
pahalı = fiyatlar > 300
print(pahalı)  # [False False  True  True  True]

# Boolean indexing — filtreleme
print(fiyatlar[pahalı])  # [350 450 600]

# İstatistik
print(f"Ortalama: {fiyatlar.mean()}")  # 340.0
print(f"Toplam: {fiyatlar.sum()}")     # 1700
print(f"Max: {fiyatlar.max()}")        # 600

For döngüsüyle aynı işi yapmaya kıyasla bu yaklaşım hem daha okunabilir hem çok daha hızlı.

Broadcasting

Farklı boyuttaki diziler arasında işlem yapabilirsin. NumPy küçük diziyi otomatik "genişletir":

import numpy as np

# 3x3 matris
matris = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])

# Her sütuna farklı çarpan uygula
carpanlar = np.array([10, 100, 1000])

sonuc = matris * carpanlar
print(sonuc)
# [[  10  200  3000]
#  [  40  500  6000]
#  [  70  800  9000]]

carpanlar dizisi (3 eleman) her satıra otomatik olarak uygulandı. Elle döngü yazmaya gerek kalmadı.

Performans Farkı: list vs ndarray

NumPy'ın gerçek gücünü rakamlarla görelim:

import numpy as np
import time

boyut = 1_000_000

# Python listesi ile
python_list = list(range(boyut))
start = time.time()
result_list = [x * 2 for x in python_list]
list_sure = time.time() - start

# NumPy dizisi ile
numpy_array = np.arange(boyut)
start = time.time()
result_numpy = numpy_array * 2
numpy_sure = time.time() - start

print(f"List: {list_sure:.4f} saniye")    # ~0.08 saniye
print(f"NumPy: {numpy_sure:.4f} saniye")   # ~0.001 saniye
print(f"NumPy {list_sure / numpy_sure:.0f}x daha hızlı!")  # ~50-100x

Bu fark büyük veri setlerinde kritik hale gelir. Milyonlarca satırlık bir CSV dosyasını işlerken saniyelerle dakikalar arasında seçim yapmak gibi.

💡 İpucu: NumPy'ı "hızlı matematik motoru" olarak düşün. pandas ise bu motorun üzerine kurulmuş kullanıcı dostu bir arayüz. Doğrudan NumPy kullanman gereken durumlar (makine öğrenimi, bilimsel hesaplama) dışında pandas ile çalışmak çoğu zaman yeterli.


3. pandas Kurulum ve Temel Yapılar

Kurulum

pip install pandas numpy

pandas kurulunca NumPy da otomatik gelir (bağımlılık). Excel dosyaları okumak istersen ek olarak openpyxl paketini de kurabilirsin:

pip install openpyxl  # .xlsx dosyaları için

Series: Tek Boyutlu Veri

Series, etiketli bir NumPy dizisidir. Bir sütun gibi düşünebilirsin — hem veriler hem de her verinin bir etiketi (index) var:

import pandas as pd

# Listeden Series oluşturma
notlar = pd.Series([85, 92, 78, 95, 88], 
                    index=["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"])

print(notlar)
# Ali       85
# Ayşe      92
# Mehmet    78
# Zeynep    95
# Can       88
# dtype: int64

# İndeksle erişim
print(notlar["Ayşe"])    # 92
print(notlar[notlar > 90])  # Ayşe: 92, Zeynep: 95

# Temel istatistikler
print(f"Ortalama: {notlar.mean()}")  # 87.6
print(f"Medyan: {notlar.median()}")  # 88.0

DataFrame: İki Boyutlu Veri — Asıl Yıldız

DataFrame, pandas'ın kalbidir. Bir Excel tablosu gibi satırlar ve sütunlardan oluşur. Her sütun bir Series'tir:

import pandas as pd

# Dict'ten DataFrame oluşturma
veri = {
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"],
    "yas": [25, 30, 35, 28, 32],
    "departman": ["IT", "HR", "IT", "Finans", "HR"],
    "maas": [15000, 18000, 22000, 20000, 16000]
}

df = pd.DataFrame(veri)
print(df)
#      isim  yas departman   maas
# 0     Ali   25        IT  15000
# 1    Ayşe   30        HR  18000
# 2  Mehmet   35        IT  22000
# 3  Zeynep   28    Finans  20000
# 4     Can   32        HR  16000

# Temel bilgiler
print(f"Boyut: {df.shape}")     # (5, 4) — 5 satır, 4 sütun
print(f"Sütunlar: {list(df.columns)}")
print(f"Veri tipleri:\n{df.dtypes}")

DataFrame oluşturmanın birkaç farklı yolu vardır:

import pandas as pd

# Liste of dict'lerden
kayitlar = [
    {"isim": "Ali", "puan": 85},
    {"isim": "Ayşe", "puan": 92},
    {"isim": "Mehmet", "puan": 78}
]
df_from_dicts = pd.DataFrame(kayitlar)

# İç içe listelerden
satırlar = [["Ali", 85], ["Ayşe", 92], ["Mehmet", 78]]
df_from_lists = pd.DataFrame(satırlar, columns=["isim", "puan"])

print(df_from_dicts)
print(df_from_lists)
# İkisi de aynı sonucu verir

Hızlı Keşif Komutları

Bir DataFrame'le ilk tanışma anında kullanacağın komutlar:

import pandas as pd

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"],
    "yas": [25, 30, 35, 28, 32],
    "maas": [15000, 18000, 22000, 20000, 16000]
})

# İlk / son satırlar
print(df.head(3))  # İlk 3 satır
print(df.tail(2))  # Son 2 satır

# Genel bilgi
df.info()
# RangeIndex: 5 entries, 0 to 4
# Data columns (total 3 columns):
#  #   Column  Non-Null Count  Dtype
# ---  ------  --------------  -----
#  0   isim    5 non-null      object
#  1   yas     5 non-null      int64
#  2   maas    5 non-null      int64

# Sayısal sütunların istatistikleri
print(df.describe())
#             yas          maas
# count   5.000000      5.000000
# mean   30.000000  18200.000000
# std     3.807887   2863.564213
# min    25.000000  15000.000000
# ...

info() veri tiplerini ve eksik değerleri gösterir. describe() sayısal sütunların temel istatistiklerini verir. Yeni bir veri setiyle çalışırken ilk adımın her zaman bu iki komut olmalı.


4. Dosya Okuma ve Yazma

Gerçek dünyada veriler genellikle dosyalarda yaşar — CSV, JSON, Excel. pandas bunları okumak ve yazmak için güçlü fonksiyonlar sunar.

CSV Okuma ve Yazma

CSV (Comma-Separated Values) veri dünyasının evrensel dili. pandas'ın read_csv() fonksiyonu inanılmaz esnek:

import pandas as pd

# Basit CSV okuma
df = pd.read_csv("satislar.csv")

# Özelleştirilmiş okuma
df = pd.read_csv(
    "satislar.csv",
    sep=";",              # Ayırıcı karakter (Türk Excel'i ; kullanır)
    encoding="utf-8",     # Karakter kodlaması
    parse_dates=["tarih"],# Tarih sütununu datetime'a çevir
    index_col="id",       # id sütununu indeks yap
    usecols=["id", "urun", "fiyat", "tarih"],  # Sadece bu sütunları oku
    nrows=1000            # İlk 1000 satırı oku (büyük dosyalar için)
)

print(df.head())

CSV yazmak da bir o kadar kolay:

import pandas as pd

df = pd.DataFrame({
    "urun": ["Laptop", "Mouse", "Klavye"],
    "fiyat": [25000, 500, 1200],
    "stok": [10, 150, 75]
})

# CSV'ye yaz
df.to_csv("urunler.csv", index=False)  # index=False: satır numarası yazma

# Türkçe Excel uyumlu (noktalı virgül + UTF-8 BOM)
df.to_csv("urunler_excel.csv", sep=";", encoding="utf-8-sig", index=False)

⚠️ Dikkat: Türkçe karakterler içeren CSV dosyalarını Excel'de açarken utf-8-sig encoding kullan. Normal utf-8 ile Türkçe karakterler bozuk görünebilir. utf-8-sig başa BOM (Byte Order Mark) ekler ve Excel bunu doğru yorumlar.

JSON Okuma ve Yazma

API'lardan gelen veriler genellikle JSON formatındadır:

import pandas as pd

# JSON dosyasından okuma
df = pd.read_json("veriler.json")

# API'dan gelen JSON string'den
import json

json_str = '''
[
    {"isim": "Ali", "sehir": "İstanbul", "puan": 85},
    {"isim": "Ayşe", "sehir": "Ankara", "puan": 92},
    {"isim": "Mehmet", "sehir": "İzmir", "puan": 78}
]
'''
df = pd.read_json(json_str)
print(df)

# JSON'a yazma
df.to_json("cikti.json", orient="records", force_ascii=False, indent=2)

force_ascii=False parametresi Türkçe karakterlerin korunmasını sağlar. Aksi halde "İstanbul" gibi kelimeler \u0130stanbul şeklinde yazılır.

Excel Okuma ve Yazma

Excel dosyaları ile çalışmak da mümkün (openpyxl paketi gerekir):

import pandas as pd

# Excel'den okuma
df = pd.read_excel("rapor.xlsx", sheet_name="Satışlar")

# Birden fazla sayfa okuma
tum_sayfalar = pd.read_excel("rapor.xlsx", sheet_name=None)  # dict döner
print(tum_sayfalar.keys())  # dict_keys(['Satışlar', 'Müşteriler', ...])

# Excel'e yazma
df.to_excel("cikti.xlsx", sheet_name="Sonuçlar", index=False)

5. Filtreleme ve Seçim

Veriyi okudun, şimdi içinden istediğin parçaları seçme zamanı. pandas'ta filtreleme dört temel yolla yapılır.

Sütun Seçimi

import pandas as pd

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"],
    "yas": [25, 30, 35, 28, 32],
    "departman": ["IT", "HR", "IT", "Finans", "HR"],
    "maas": [15000, 18000, 22000, 20000, 16000]
})

# Tek sütun — Series döner
isimler = df["isim"]

# Birden fazla sütun — DataFrame döner
secim = df[["isim", "maas"]]
print(secim)
#      isim   maas
# 0     Ali  15000
# 1    Ayşe  18000
# ...

Boolean Indexing — En Sık Kullanılan Yöntem

Bir koşul yaz, koşulu sağlayan satırları al:

import pandas as pd

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"],
    "yas": [25, 30, 35, 28, 32],
    "departman": ["IT", "HR", "IT", "Finans", "HR"],
    "maas": [15000, 18000, 22000, 20000, 16000]
})

# Maaşı 17000'den yüksek olanlar
yuksek_maas = df[df["maas"] > 17000]
print(yuksek_maas)
#      isim  yas departman   maas
# 1    Ayşe   30        HR  18000
# 2  Mehmet   35        IT  22000
# 3  Zeynep   28    Finans  20000

# Birden fazla koşul: & (ve), | (veya)
it_ve_yuksek = df[(df["departman"] == "IT") & (df["maas"] > 16000)]
print(it_ve_yuksek)
#      isim  yas departman   maas
# 2  Mehmet   35        IT  22000

Birden fazla koşulu birleştirirken her koşulu parantez içine almayı unutma. df[df["departman"] == "IT" & df["maas"] > 16000] yazmak hata verir çünkü & operatörü == ve >'den önce değerlendirilir.

.loc[] ve .iloc[]

loc etiket tabanlı, iloc pozisyon tabanlı seçim yapar:

import pandas as pd

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"],
    "yas": [25, 30, 35, 28, 32],
    "maas": [15000, 18000, 22000, 20000, 16000]
})

# iloc — pozisyon ile (0-based)
print(df.iloc[0])        # İlk satır (Series)
print(df.iloc[1:3])      # 2. ve 3. satırlar
print(df.iloc[0, 2])     # 1. satır, 3. sütun → 15000
print(df.iloc[:, 0:2])   # Tüm satırlar, ilk 2 sütun

# loc — etiket ile
df.index = ["a", "b", "c", "d", "e"]  # Özel index
print(df.loc["b"])              # "b" etiketli satır
print(df.loc["a":"c"])          # "a"dan "c"ye (c dahil!)
print(df.loc["b", "maas"])      # 18000

iloc'ta 1:3 → indeks 1 ve 2 (3 hariç, Python geleneği). loc'ta "a":"c" → a, b ve c (c dahil!). Bu farka dikkat et.

.query() — SQL Benzeri Filtreleme

Karmaşık filtrelerde daha okunabilir bir alternatif:

import pandas as pd

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can"],
    "yas": [25, 30, 35, 28, 32],
    "departman": ["IT", "HR", "IT", "Finans", "HR"],
    "maas": [15000, 18000, 22000, 20000, 16000]
})

# Boolean indexing ile
sonuc1 = df[(df["departman"] == "IT") & (df["yas"] > 25)]

# query() ile — aynı sonuç, daha okunabilir
sonuc2 = df.query("departman == 'IT' and yas > 25")

print(sonuc2)
#      isim  yas departman   maas
# 2  Mehmet   35        IT  22000

# Değişken kullanımı: @ ile
min_maas = 17000
sonuc3 = df.query("maas > @min_maas")
print(sonuc3)

query() özellikle birden fazla koşul kombinasyonu olduğunda kodu çok daha temiz yapar.


6. Gruplama ve Aggregation

SQL'deki GROUP BY ne ise pandas'ta groupby() odur. Veriyi bir sütuna göre grupla, her gruba bir işlem uygula.

groupby() Temelleri

import pandas as pd

df = pd.DataFrame({
    "departman": ["IT", "HR", "IT", "Finans", "HR", "IT"],
    "calisan": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can", "Deniz"],
    "maas": [15000, 18000, 22000, 20000, 16000, 19000]
})

# Departmana göre ortalama maaş
ort_maas = df.groupby("departman")["maas"].mean()
print(ort_maas)
# departman
# Finans    20000.000000
# HR        17000.000000
# IT        18666.666667
# Name: maas, dtype: float64

# Birden fazla aggregation
ozet = df.groupby("departman")["maas"].agg(["mean", "min", "max", "count"])
print(ozet)
#               mean    min    max  count
# departman
# Finans     20000.0  20000  20000      1
# HR         17000.0  16000  18000      2
# IT         18666.7  15000  22000      3

agg() ile Özelleştirilmiş Aggregation

Farklı sütunlara farklı fonksiyonlar uygulamak istediğinde agg() kullanırsın:

import pandas as pd

df = pd.DataFrame({
    "departman": ["IT", "HR", "IT", "Finans", "HR", "IT"],
    "calisan": ["Ali", "Ayşe", "Mehmet", "Zeynep", "Can", "Deniz"],
    "maas": [15000, 18000, 22000, 20000, 16000, 19000],
    "deneyim_yil": [2, 5, 8, 6, 3, 4]
})

sonuc = df.groupby("departman").agg(
    ortalama_maas=("maas", "mean"),
    max_maas=("maas", "max"),
    calisan_sayisi=("calisan", "count"),
    toplam_deneyim=("deneyim_yil", "sum")
)

print(sonuc)
#            ortalama_maas  max_maas  calisan_sayisi  toplam_deneyim
# departman
# Finans          20000.0     20000               1               6
# HR              17000.0     18000               2               8
# IT              18666.7     22000               3              14

pivot_table() — Excel Pivot Tablosu

Excel'deki pivot tablosunun birebir karşılığı:

import pandas as pd

df = pd.DataFrame({
    "ay": ["Ocak", "Ocak", "Şubat", "Şubat", "Ocak", "Şubat"],
    "departman": ["IT", "HR", "IT", "HR", "IT", "HR"],
    "satis": [50000, 30000, 55000, 35000, 48000, 32000]
})

pivot = pd.pivot_table(
    df,
    values="satis",
    index="departman",
    columns="ay",
    aggfunc="sum"
)

print(pivot)
# ay          Ocak   Şubat
# departman
# HR         30000   67000
# IT         98000   55000

pivot_table() satır-sütun çaprazında özetleme yapar. Raporlama ve veri analizi için vazgeçilmez bir araç.


7. Eksik Veri Yönetimi

Gerçek dünyada veri asla temiz gelmez. Eksik değerler (NaN — Not a Number) her veri setinde karşına çıkacak. pandas eksik veriyle başa çıkmak için güçlü araçlar sunar.

Eksik Veriyi Tespit Etme

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep", None],
    "yas": [25, np.nan, 35, 28, 32],
    "maas": [15000, 18000, np.nan, np.nan, 16000]
})

print(df)
#      isim   yas      maas
# 0     Ali  25.0   15000.0
# 1    Ayşe   NaN   18000.0
# 2  Mehmet  35.0       NaN
# 3  Zeynep  28.0       NaN
# 4    None  32.0   16000.0

# Eksik mi? Boolean matrisi
print(df.isna())
#     isim    yas   maas
# 0  False  False  False
# 1  False   True  False
# 2  False  False   True
# 3  False  False   True
# 4   True  False  False

# Sütun başına eksik sayısı
print(df.isna().sum())
# isim    1
# yas     1
# maas    2
# dtype: int64

# Toplam eksik oranı
toplam_hucre = df.shape[0] * df.shape[1]
eksik_oran = df.isna().sum().sum() / toplam_hucre * 100
print(f"Eksik veri oranı: %{eksik_oran:.1f}")  # %26.7

Eksik Veriyi Silme: dropna()

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep"],
    "yas": [25, np.nan, 35, 28],
    "maas": [15000, 18000, np.nan, 20000]
})

# Herhangi bir NaN olan satırı sil
temiz = df.dropna()
print(temiz)
#     isim   yas      maas
# 0    Ali  25.0   15000.0
# 3  Zeynep 28.0   20000.0

# Sadece belirli sütunlardaki NaN'lara bak
temiz2 = df.dropna(subset=["maas"])
print(temiz2)
#      isim   yas      maas
# 0     Ali  25.0   15000.0
# 1    Ayşe   NaN   18000.0
# 3  Zeynep  28.0   20000.0

# Tüm değerleri NaN olan satırları sil (how="all")
df2 = pd.DataFrame({
    "a": [1, np.nan, 3],
    "b": [np.nan, np.nan, 6]
})
print(df2.dropna(how="all"))  # Sadece tamamı NaN olan satır silinir

Eksik Veriyi Doldurma: fillna()

Silmek yerine doldurmak genellikle daha iyi bir strateji:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep"],
    "yas": [25, np.nan, 35, 28],
    "maas": [15000, np.nan, np.nan, 20000]
})

# Sabit değerle doldur
df_sabit = df.fillna({"yas": 0, "maas": df["maas"].mean()})
print(df_sabit)
#      isim   yas      maas
# 0     Ali  25.0   15000.0
# 1    Ayşe   0.0   17500.0
# 2  Mehmet  35.0   17500.0
# 3  Zeynep  28.0   20000.0

# Önceki değerle doldur (forward fill)
df_ffill = df["maas"].ffill()
print(df_ffill)
# 0    15000.0
# 1    15000.0  ← Önceki değeri aldı
# 2    15000.0  ← Önceki değeri aldı
# 3    20000.0

# Sonraki değerle doldur (backward fill)
df_bfill = df["maas"].bfill()
print(df_bfill)
# 0    15000.0
# 1    20000.0  ← Sonraki değeri aldı
# 2    20000.0  ← Sonraki değeri aldı
# 3    20000.0

Hangi stratejiyi seçeceğin verinin doğasına bağlı. Yaş için ortalama ile doldurmak mantıklı olabilir. İsim için ise "Bilinmiyor" string'i daha uygun. Finansal veriler için forward fill (son bilinen değeri taşıma) yaygın bir tercihtir.


8. DataFrame Birleştirme

Gerçek projelerde veri genellikle tek bir tabloda olmaz. Müşteriler bir tabloda, siparişler başka tabloda, ürünler başka tabloda. Bunları birleştirmen gerekir.

merge() — SQL JOIN Karşılığı

import pandas as pd

musteriler = pd.DataFrame({
    "musteri_id": [1, 2, 3, 4],
    "isim": ["Ali", "Ayşe", "Mehmet", "Zeynep"]
})

siparisler = pd.DataFrame({
    "siparis_id": [101, 102, 103, 104, 105],
    "musteri_id": [1, 2, 1, 3, 5],  # 5 numaralı müşteri yok!
    "tutar": [500, 1200, 300, 800, 950]
})

# INNER JOIN — sadece eşleşenler
inner = pd.merge(musteriler, siparisler, on="musteri_id", how="inner")
print(inner)
#    musteri_id    isim  siparis_id  tutar
# 0           1     Ali         101    500
# 1           1     Ali         103    300
# 2           2    Ayşe         102   1200
# 3           3  Mehmet         104    800
# musteri_id=4 (Zeynep) ve musteri_id=5 eşleşmediği için yok

# LEFT JOIN — sol tablodaki herkes
left = pd.merge(musteriler, siparisler, on="musteri_id", how="left")
print(left)
#    musteri_id    isim  siparis_id   tutar
# 0           1     Ali       101.0   500.0
# 1           1     Ali       103.0   300.0
# 2           2    Ayşe       102.0  1200.0
# 3           3  Mehmet       104.0   800.0
# 4           4  Zeynep         NaN     NaN  ← Siparişi yok, NaN

# Sütun adları farklıysa
df_a = pd.DataFrame({"id_col": [1, 2], "deger": ["x", "y"]})
df_b = pd.DataFrame({"ref_col": [1, 2], "bilgi": ["a", "b"]})
birlesik = pd.merge(df_a, df_b, left_on="id_col", right_on="ref_col")
print(birlesik)

concat() — Alt Alta veya Yan Yana Birleştirme

import pandas as pd

# Alt alta birleştirme (satır ekleme)
ocak = pd.DataFrame({
    "urun": ["Laptop", "Mouse"],
    "satis": [10, 50]
})

subat = pd.DataFrame({
    "urun": ["Laptop", "Klavye"],
    "satis": [12, 30]
})

birlesik = pd.concat([ocak, subat], ignore_index=True)
print(birlesik)
#      urun  satis
# 0  Laptop     10
# 1   Mouse     50
# 2  Laptop     12
# 3  Klavye     30

# Yan yana birleştirme (sütun ekleme)
isimler = pd.DataFrame({"isim": ["Ali", "Ayşe"]})
puanlar = pd.DataFrame({"puan": [85, 92]})

yan_yana = pd.concat([isimler, puanlar], axis=1)
print(yan_yana)
#    isim  puan
# 0   Ali    85
# 1  Ayşe    92

ignore_index=True parametresi yeni bir 0'dan başlayan index oluşturur. Yoksa orijinal index'ler korunur ve tekrar edebilir (0, 1, 0, 1 gibi).

💡 İpucu: merge() SQL benzeri birleştirme (anahtar sütunla), concat() ise fiziksel yapıştırma (alt alta veya yan yana). İkisi farklı işler yapar — doğru olanı seç.


9. Gerçek Dünya Projesi: Satış Verisi Analizi

Şimdi öğrendiğimiz her şeyi birleştiren bir proje yapalım. Bir mağaza zincirine ait satış verilerini okuyacak, temizleyecek, analiz edecek ve sonuçları dosyaya yazacağız.

Adım 1: Veriyi Oluştur ve Oku

Gerçek bir senaryoda CSV dosyasından okursun. Biz örnek veriyi kod ile oluşturuyoruz:

import pandas as pd
import numpy as np

# Örnek satış verisi oluştur
np.random.seed(42)  # Tekrarlanabilirlik için

n = 200
veri = {
    "tarih": pd.date_range("2024-01-01", periods=n, freq="D"),
    "magaza": np.random.choice(["İstanbul", "Ankara", "İzmir"], n),
    "kategori": np.random.choice(["Elektronik", "Giyim", "Gıda", "Kozmetik"], n),
    "urun_sayisi": np.random.randint(1, 20, n),
    "birim_fiyat": np.random.uniform(50, 500, n).round(2),
}

df = pd.DataFrame(veri)
df["toplam_tutar"] = df["urun_sayisi"] * df["birim_fiyat"]

# Gerçekçi olması için bazı değerleri NaN yap
mask = np.random.random(n) < 0.05  # %5 eksik veri
df.loc[mask, "birim_fiyat"] = np.nan
df.loc[mask, "toplam_tutar"] = np.nan

print("=== Veri Seti Özeti ===")
print(f"Boyut: {df.shape}")
df.info()
print(f"\nİlk 5 satır:\n{df.head()}")

Adım 2: Veriyi Temizle

# Eksik veri analizi
print("\n=== Eksik Veri ===")
print(df.isna().sum())

# Eksik fiyatları medyan ile doldur (ortalama aykırı değerlere hassas)
medyan_fiyat = df["birim_fiyat"].median()
df["birim_fiyat"] = df["birim_fiyat"].fillna(medyan_fiyat)

# Toplam tutarı yeniden hesapla
df["toplam_tutar"] = df["urun_sayisi"] * df["birim_fiyat"]

# Kontrol
print(f"\nTemizleme sonrası eksik: {df.isna().sum().sum()}")

Adım 3: Analiz

# 1. Mağaza bazında toplam satış
print("\n=== Mağaza Bazında Satış ===")
magaza_satis = df.groupby("magaza").agg(
    toplam_satis=("toplam_tutar", "sum"),
    ortalama_satis=("toplam_tutar", "mean"),
    siparis_sayisi=("toplam_tutar", "count")
).round(2)
print(magaza_satis)

# 2. Kategori bazında analiz
print("\n=== Kategori Bazında Satış ===")
kategori_satis = df.groupby("kategori").agg(
    toplam_satis=("toplam_tutar", "sum"),
    ortalama_birim_fiyat=("birim_fiyat", "mean"),
    toplam_urun=("urun_sayisi", "sum")
).round(2)
print(kategori_satis.sort_values("toplam_satis", ascending=False))

# 3. Aylık trend
df["ay"] = df["tarih"].dt.to_period("M")
aylik_satis = df.groupby("ay")["toplam_tutar"].sum()
print(f"\n=== Aylık Satış Trendi ===")
print(aylik_satis)

# 4. Pivot tablo: Mağaza x Kategori
print("\n=== Mağaza-Kategori Pivot ===")
pivot = pd.pivot_table(
    df,
    values="toplam_tutar",
    index="magaza",
    columns="kategori",
    aggfunc="sum"
).round(2)
print(pivot)

# 5. En yüksek 10 satış
print("\n=== En Yüksek 10 Satış ===")
top10 = df.nlargest(10, "toplam_tutar")[["tarih", "magaza", "kategori", "toplam_tutar"]]
print(top10.to_string(index=False))

Adım 4: Sonuçları Kaydet

# Ana veriyi CSV'ye yaz
df.to_csv("satis_temiz.csv", index=False, encoding="utf-8-sig")

# Özet raporu Excel'e yaz (birden fazla sayfa)
with pd.ExcelWriter("satis_raporu.xlsx", engine="openpyxl") as writer:
    magaza_satis.to_excel(writer, sheet_name="Mağaza Bazında")
    kategori_satis.to_excel(writer, sheet_name="Kategori Bazında")
    pivot.to_excel(writer, sheet_name="Pivot Tablo")

print("\n✅ Rapor dosyaları oluşturuldu!")
print("  - satis_temiz.csv")
print("  - satis_raporu.xlsx")

Bu proje gerçek dünyada yapacağın veri analizi iş akışını temsil ediyor: oku → keşfet → temizle → analiz et → raporla. pandas bu zincirin her adımında yanında.


10. Yaygın Hatalar ve İpuçları

Hata 1: SettingWithCopyWarning

pandas'ın en kafa karıştırıcı uyarısı. Bir filtrelenmiş DataFrame'e atama yapmaya çalışınca ortaya çıkar:

import pandas as pd

df = pd.DataFrame({
    "isim": ["Ali", "Ayşe", "Mehmet"],
    "maas": [15000, 18000, 22000]
})

# ❌ Uyarı verebilir
filtreli = df[df["maas"] > 16000]
filtreli["maas"] = filtreli["maas"] * 1.1  # SettingWithCopyWarning!

# ✅ .copy() ile güvenli
filtreli = df[df["maas"] > 16000].copy()
filtreli["maas"] = filtreli["maas"] * 1.1  # Sorunsuz

# ✅ Veya .loc ile doğrudan
df.loc[df["maas"] > 16000, "maas"] *= 1.1  # Orijinal df'i günceller

Hata 2: Dtype Karışıklığı

CSV'den okunan sayılar bazen string olarak gelir:

import pandas as pd

# Sorun: "fiyat" sütunu string olarak okunmuş
df = pd.DataFrame({"fiyat": ["100", "200", "300"]})
print(df["fiyat"].dtype)  # object (string)

# Çözüm: dönüştür
df["fiyat"] = pd.to_numeric(df["fiyat"], errors="coerce")  # Hatalılar NaN olur
print(df["fiyat"].dtype)  # float64 veya int64

Hata 3: Index Kargaşası

Filtreleme sonrası index'ler atlanır, bu da karışıklığa yol açar:

import pandas as pd

df = pd.DataFrame({"x": [10, 20, 30, 40, 50]})
filtreli = df[df["x"] > 20]
print(filtreli.index.tolist())  # [2, 3, 4] — 0'dan başlamıyor!

# Çözüm: reset_index
filtreli = filtreli.reset_index(drop=True)
print(filtreli.index.tolist())  # [0, 1, 2]

Özet

  • NumPy, Python listelerinden 50-100 kat daha hızlı sayısal hesaplama yapar; pandas'ın altyapısıdır.

  • pandas Series tek sütun, DataFrame ise çok sütunlu tablo yapısıdır — veri analizinin temel birimleri.

  • read_csv(), read_json(), read_excel() ile her formattan veri okursun; to_csv(), to_excel() ile yazarsın.

  • Boolean indexing, .loc[], .iloc[] ve .query() ile veriyi dilediğin gibi filtrelersin.

  • groupby() + agg() ve pivot_table() ile verileri gruplayıp özetlersin — SQL'deki GROUP BY'ın karşılığı.

  • Eksik verilerle isna(), fillna(), dropna() ile başa çıkarsın; merge() ve concat() ile tabloları birleştirirsin.