Ana içeriğe geç
Kesit Python
Orta · Veri analizine giriş

pandas giriş: Series ve DataFrame

Series/DataFrame yapısı, read_csv ile veri yükleme, head/info/describe ile tanıma, loc/iloc ve boolean seçim, yeni sütun ve sıralama.

Uygulamada aç, 29 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Tablo düşünmek

Anadolu Market'in sipariş verisi bir tablodur: her satır bir sipariş, her sütun bir alan (müşteri, şehir, tutar, tarih). Excel'de yaptığın filtreleme, sıralama, yeni sütun ekleme işlerini kodla ve tekrarlanabilir biçimde yapmanın aracı pandas'tır. İki temel yapı var: Series (tek sütun, etiketli dizi) ve DataFrame (sütunlardan oluşan tablo). DataFrame'in her sütunu bir Series'tir.

Series: etiketli tek boyut

import pandas as pd

stok = pd.Series({"kahve": 120, "çay": 340, "şeker": 15, "un": 0})
print(stok)
print(stok["çay"])              # etiketle erişim
print(stok[stok < 50])          # boolean maske
print(stok.sum(), stok.idxmax())
print((stok * 2).head(2))       # vektörleştirilmiş, numpy gibi

Series'in bir index'i (etiketler) ve values'u (numpy dizisi) vardır. Etiket vermezsen 0, 1, 2... kullanılır.

DataFrame oluşturma ve CSV yükleme

Gerçek işte veri genelde CSV, Excel ya da veritabanından gelir. pd.read_csv bir dosya yolu alır; burada dosya yerine io.StringIO ile bellekteki metni dosya gibi veriyoruz. Aynı fonksiyon, aynı parametreler.

import pandas as pd, io

csv_metni = """siparis_id,musteri,sehir,kategori,adet,fiyat,tarih
1001,Ayşe,İstanbul,Gıda,3,45.5,2024-03-01
1002,Mehmet,Ankara,Elektronik,1,899.0,2024-03-01
1003,Ayşe,İstanbul,Gıda,10,12.75,2024-03-02
1004,Zeynep,İzmir,Kozmetik,2,129.9,2024-03-02
1005,Ali,Ankara,Gıda,6,45.5,2024-03-03
1006,Zeynep,İzmir,Elektronik,1,1250.0,2024-03-03
"""
df = pd.read_csv(io.StringIO(csv_metni), parse_dates=["tarih"])
print(df.head(3))
print(df.shape, list(df.columns))
print(df.dtypes)
parse_datesTarih sütununu baştan datetime yapmak sonradan to_datetime çağırmaktan kolaydır. Biçim özelse date_format="%d.%m.%Y" ver.

Veriyi tanımak: head, info, describe

Yeni bir veri setine ilk bakışta üç komut: head() ilk satırlar, info() sütun türleri ve eksik sayısı, describe() sayısal özet (ortalama, çeyrekler, min/max). Ek olarak value_counts() kategorik sütunun dağılımını verir.

import pandas as pd, io

csv_metni = """siparis_id,musteri,sehir,kategori,adet,fiyat
1001,Ayşe,İstanbul,Gıda,3,45.5
1002,Mehmet,Ankara,Elektronik,1,899.0
1003,Ayşe,İstanbul,Gıda,10,12.75
1004,Zeynep,İzmir,Kozmetik,2,129.9
1005,Ali,Ankara,Gıda,6,45.5
"""
df = pd.read_csv(io.StringIO(csv_metni))
df.info()
print(df.describe().round(1))
print(df["sehir"].value_counts())
print(df["kategori"].unique(), df["kategori"].nunique())

Seçme: sütun, loc, iloc, maske

Dört yol, dört farklı soru:

YazımNe seçerÖrnek
df["tutar"]Tek sütun (Series)df["sehir"]
df[["a", "b"]]Birden çok sütun (DataFrame)df[["musteri", "sehir"]]
df.loc[etiket, sütun]Etiketle satır/sütundf.loc[2, "sehir"], df.loc[df.adet > 2, ["musteri"]]
df.iloc[konum, konum]Sıra numarasıyladf.iloc[0], df.iloc[:3, 1:3]
df[maske]Koşula uyan satırlardf[df.sehir == "Ankara"]
import pandas as pd, io

csv_metni = """siparis_id,musteri,sehir,kategori,adet,fiyat
1001,Ayşe,İstanbul,Gıda,3,45.5
1002,Mehmet,Ankara,Elektronik,1,899.0
1003,Ayşe,İstanbul,Gıda,10,12.75
1004,Zeynep,İzmir,Kozmetik,2,129.9
1005,Ali,Ankara,Gıda,6,45.5
"""
df = pd.read_csv(io.StringIO(csv_metni))
print(df.loc[1, "musteri"])                  # etiket 1 (varsayılan index)
print(df.iloc[-1])                           # son satır
print(df.loc[df["sehir"] == "Ankara", ["musteri", "fiyat"]])
print(df[(df["kategori"] == "Gıda") & (df["adet"] >= 5)])
print(df[df["sehir"].isin(["İzmir", "Ankara"])]["siparis_id"].tolist())
loc etiket, iloc konumVarsayılan index 0,1,2 olduğunda ikisi aynı sonucu verir ve fark gözükmez. Filtreledikten sonra index 1, 4 gibi seyrek kalır: loc[1] etiketi 1 olan satırı, iloc[1] ikinci satırı verir. Dilimde de fark var: loc[0:2] 2 dahil, iloc[0:2] hariç.

Yeni sütun ve sıralama

Sütunlar arası aritmetik numpy gibi vektörleştirilmiştir. Yeni sütun atamak için df["tutar"] = ... ya da zincirleme kullanımda df.assign(tutar=...). Sıralama sort_values, en büyük n için nlargest.

import pandas as pd, io

csv_metni = """siparis_id,musteri,sehir,adet,fiyat
1001,Ayşe,İstanbul,3,45.5
1002,Mehmet,Ankara,1,899.0
1003,Ayşe,İstanbul,10,12.75
1004,Zeynep,İzmir,2,129.9
1005,Ali,Ankara,6,45.5
"""
df = pd.read_csv(io.StringIO(csv_metni))
df["tutar"] = df["adet"] * df["fiyat"]
df["buyuk"] = df["tutar"] > 200
print(df.sort_values("tutar", ascending=False).head(3))
print(df.sort_values(["sehir", "tutar"], ascending=[True, False])[["sehir", "tutar"]])
print(df.nlargest(2, "tutar")[["musteri", "tutar"]])
print(df.assign(kdv=lambda d: d["tutar"] * 0.2)[["tutar", "kdv"]].head(2))
Kopya mı, görünüm mü?pandas 2.x ve 3.x, Copy-on-Write kuralına geçiyor: filtrelenmiş bir DataFrame'e df[maske]["tutar"] = 0 şeklinde zincirle atama yapma; asıl tabloyu değiştirmez. Her zaman df.loc[maske, "tutar"] = 0 yaz.
Derinleş

Derinleş: kurulum, sürümler ve veriyi doğru okumak

Gerçek işte pandas'la geçen zamanın büyük kısmı hesap değil, veriyi doğru okumaktır. Türkiye'de Excel'den alınmış bir CSV'de ayraç noktalı virgüldür, ondalık virgüldür, binlik nokta; posta kodunun baştaki sıfırı kaybolur, tarih gün.ay.yıl yazılır, Türkçe karakterler bozulur. Bu bölümde kurulumdan başlayıp read_csv'nin bu sorunları çözen parametrelerini, ardından Excel, JSON ve SQL kaynaklarını görüyoruz.

Kurulum ve sürüm

# Sanal ortam etkinken (Windows, macOS, Linux aynı)
python -m pip install pandas            # numpy da birlikte gelir
python -m pip install "pandas[excel]"   # read_excel/to_excel için openpyxl vb.
python -m pip install pyarrow           # hızlı okuma, parquet, string türü
python -c "import pandas as pd; print(pd.__version__)"

# requirements.txt ile sabitleme
pandas>=2.2,<4

Tarayıcıdaki motorda pandas 2.2, güncel bilgisayarlarda çoğunlukla 3.x bulunur. Bu dersteki kodlar iki sürümde de aynı sonucu verir. Hangi sürümle çalıştığını ve hangi yardımcı paketlerin kurulu olduğunu kodla da görebilirsin:

import pandas as pd

print(pd.__version__)
ana, kucuk = (int(x) for x in pd.__version__.split(".")[:2])
print("3.x mi?", ana >= 3)
# pd.show_versions()  # tüm bağımlılıkların raporu (hata bildirirken eklenir)
Konupandas 2.2pandas 3.xİki sürümde güvenli yazım
KopyalamaCopy-on-Write isteğe bağlı, zincirleme atama bazen çalışırCopy-on-Write her zaman açık; zincirleme atama asıl tabloyu değiştirmezdf.loc[maske, "s"] = x
Metin sütunu türüobjectvarsayılan str (string türü)pd.api.types.is_string_dtype(s) ile kontrol et, == object yazma
Aylık sıklık"M" uyarı verir"M" kaldırıldı"ME" (ay sonu), "MS" (ay başı)
JSON metni okumaHam metin uyarı verirHam metin hata verirpd.read_json(io.StringIO(metin))

read_csv: Türkiye'deki CSV'yi okumak

Excel Türkçe bölge ayarıyla CSV kaydettiğinde ayraç ; olur, çünkü virgül ondalık işaretidir. Aşağıdaki parametreler en sık ihtiyaç duyacağın setir:

ParametreNe yaparÖrnek
sepSütun ayracısep=";", sekme için sep="\t"
decimalOndalık işaretidecimal="," → "12,5" = 12.5
thousandsBinlik ayırıcıthousands="." → "1.250" = 1250
encodingKarakter kodlaması"utf-8", "utf-8-sig", "cp1254"
parse_dates + date_formatTarih sütunlarını datetime yaparparse_dates=["tarih"], date_format="%d.%m.%Y"
dtypeSütun türünü zorlardtype={"posta_kodu": str} baştaki sıfırı korur
usecolsYalnız gereken sütunlarusecols=["musteri", "tutar"]
na_valuesEksik sayılacak ek değerlerna_values=["-", "yok"]
nrows, skiprowsİlk n satırı oku / baştaki satırları atlaskiprows=2 (rapor başlığı varsa)
index_colBir sütunu index yaparindex_col="siparis_id"
chunksizeParça parça okur (büyük dosya)for parca in pd.read_csv(f, chunksize=100_000)
import io
import pandas as pd

excel_csv = """siparis_id;musteri_no;sehir;tutar;tarih
1001;00412;İstanbul;1.250,50;03.04.2024
1002;07730;Ankara;89,90;05.04.2024
1003;00412;İzmir;-;12.04.2024
"""
df = pd.read_csv(
    io.StringIO(excel_csv),
    sep=";", decimal=",", thousands=".",
    dtype={"musteri_no": str},          # 00412 sıfırıyla kalsın
    na_values=["-"],                    # "-" eksik değer
    parse_dates=["tarih"], date_format="%d.%m.%Y",
)
print(df)
print(df["tutar"].sum())                # 1340.4
print(df["tarih"].dt.month.tolist())    # [4, 4, 4]
print(df["musteri_no"].tolist())
Gün mü ay mı?"03.04.2024" biçim verilmezse ay.gün sırasıyla okunup 3 Nisan yerine 4 Mart olabilir ve hiçbir hata almazsın. Türkiye tarihlerinde her zaman date_format="%d.%m.%Y" (ya da pd.to_datetime(..., format="%d.%m.%Y")) ver.

Kirli başlık ve bozuk satırlar

ERP raporları çoğu zaman birkaç satırlık başlıkla ("Anadolu Market - Mart Raporu") başlar, sonunda "Toplam" satırı olur, arada da fazla ayraç içeren bozuk satırlar bulunur. skiprows baştaki satırları, skipfooter (yalnızca engine="python" ile) sondakileri atlar; on_bad_lines="skip" sütun sayısı uymayan satırları atlar, "warn" atlarken uyarı da yazar. Atlanan satırları sessizce kaybetmek yerine sayısını raporlamak iyi bir alışkanlıktır.

import io
import pandas as pd

rapor = """Anadolu Market - Mart Raporu
Oluşturma: 01.04.2024
id,sehir,tutar
1,Bursa,450
2,Ankara,120,FAZLA
3,İzmir,300
Toplam,,870
"""
df = pd.read_csv(io.StringIO(rapor), skiprows=2, skipfooter=1,
                 engine="python", on_bad_lines="skip", index_col="id")
print(df)
print(len(df), "satır okundu")

Okuduktan sonra mutlaka şu üç kontrolü yap: df.shape (satır sayısı beklenenle aynı mı?), df.dtypes (tutar sayı, tarih datetime mı?) ve df.isna().sum() (beklenmedik eksik var mı?). Büyük dosyalarda engine="pyarrow" (pyarrow kuruluysa) okumayı belirgin hızlandırır ama skipfooter gibi bazı parametreleri desteklemez.

Kodlama (encoding) sorunları

Eski sistemlerden ve Türkçe Windows'tan gelen dosyalar çoğunlukla cp1254 (Windows-1254) kodlamasındadır; UTF-8 sanılarak okunursa UnicodeDecodeError alırsın ya da "ÅŸ" gibi bozuk harfler görürsün. Excel'in Türkçe karakterleri doğru göstermesi için yazarken utf-8-sig (başında BOM işareti olan UTF-8) kullanılır.

import pandas as pd

with open("eski_sistem.csv", "w", encoding="cp1254") as f:
    f.write("sehir;ciro\nŞanlıurfa;1200\nÇorum;800\n")

try:
    pd.read_csv("eski_sistem.csv", sep=";")           # utf-8 varsayılır
except UnicodeDecodeError as e:
    print("Hata:", type(e).__name__)

df = pd.read_csv("eski_sistem.csv", sep=";", encoding="cp1254")
print(df["sehir"].tolist())

# Excel'de Türkçe açılsın: ; ayraç, virgül ondalık, BOM'lu UTF-8
df["ciro"] = df["ciro"] / 3
df.to_csv("rapor.csv", sep=";", decimal=",", encoding="utf-8-sig", index=False, float_format="%.2f")
print(open("rapor.csv", encoding="utf-8-sig").read())

Excel, JSON ve SQL kaynakları

read_excel için openpyxl paketi gerekir. Bu sayfada ilk kullanımda kendiliğinden iner (yaklaşık 270 KB); bilgisayarında şöyle kurarsın:

# python -m pip install openpyxl
import pandas as pd

df = pd.read_excel("satislar.xlsx", sheet_name="Mart", skiprows=2,
                   usecols="A:E", dtype={"musteri_no": str})
tum = pd.read_excel("satislar.xlsx", sheet_name=None)   # {sayfa_adı: DataFrame}
df.to_excel("ozet.xlsx", index=False, sheet_name="Özet")

API'lerden gelen JSON çoğunlukla kayıt listesidir; iç içe alanlar için pd.json_normalize sütunları musteri.sehir gibi düzleştirir. Veritabanı için pd.read_sql(sorgu, baglanti, params=...) sorgu sonucunu doğrudan DataFrame yapar; kullanıcıdan gelen değeri sorguya f-string ile değil params ile ver (SQL enjeksiyonu).

import io, json, sqlite3
import pandas as pd

api = '[{"id": 1, "tutar": 450.0, "musteri": {"ad": "Ayşe", "sehir": "Bursa"}},' \
      ' {"id": 2, "tutar": 120.5, "musteri": {"ad": "Ali", "sehir": "Ankara"}}]'
print(pd.read_json(io.StringIO(api)).columns.tolist())
duz = pd.json_normalize(json.loads(api))
print(duz.columns.tolist())

con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE siparis (id INTEGER, sehir TEXT, tutar REAL)")
con.executemany("INSERT INTO siparis VALUES (?, ?, ?)",
                [(1, "Bursa", 450.0), (2, "Ankara", 120.5), (3, "Bursa", 80.0)])
q = "SELECT sehir, SUM(tutar) AS ciro FROM siparis WHERE tutar >= ? GROUP BY sehir"
print(pd.read_sql(q, con, params=(100,)))
con.close()
Okuma fonksiyonu yazAynı kaynaktan her ay veri alıyorsan tüm parametreleri tek bir oku_siparis(yol) fonksiyonunda topla ve sonunda assert ile beklenen sütunları, türleri kontrol et. Kaynağın biçimi değiştiğinde hata raporu üretmeden önce yakalanır.
Gerçek işteMuhasebe, ERP ve banka ekstrelerinden alınan CSV dosyalarında en sık üç hata çıkar: tutarların metin kalması (decimal/thousands verilmemiş), müşteri ya da vergi numarasının baştaki sıfırı kaybetmesi (dtype=str verilmemiş) ve tarihlerde gün-ay karışması. Bir dosyayı okuduktan hemen sonra df.info() çıktısında tutarın float, tarihin datetime olduğunu görmeden analize geçme.
Derinleş

Derinleş: ders veri setleri (veri modülü)

Şimdiye kadar tabloları elle, küçük sözlüklerden kurduk. Gerçek işte ise veri hazır gelir: binlerce satırlık sipariş dökümü, on binlerce banka hareketi. Kesit Python bunun için tarayıcıda hazır bekleyen ortak veri setleri sunar. Banka, İK, teslimat, havayolu ve bisiklet setleri Kesit SQL ve Kesit Power BI ile satır satır aynıdır. Anadolu Market'te aynı şirket ve siparişler kullanılır, Power BI modeli zenginleştirilmiştir: anadolu_market SQL hâlidir, SQL ile yazdığın sorguyu burada pandas ile tekrar kurabilir, sonuçları karşılaştırabilirsin. Tüm veriler kurgusaldır; gerçek kişi ya da kuruma ait değildir.

Hangi setler var?

SetKonuÖne çıkan tablolar
anadolu_marketE-ticaretcustomers (150), orders (910), order_items (1.620), products, ab_test, raw_signups
ilgaz_bankBankacılıkbank_customers (500), bank_accounts (849), bank_txns (~42 bin)
pusula_ikİnsan kaynaklarıçalışanlar, maaşlar, izinler, performans
lezzet_kuryeYemek teslimatlk_orders, lk_couriers, lk_ratings
kanat_havayollariHavayoluhv_flights, hv_delays, hv_bookings
pedal_izmirBisiklet paylaşımıbk_trips (~117 bin), bk_stations, bk_weather
kesit_biPower BI yıldız şemasıSatislar, Urunler, Musteriler, Tarih, RFMSegmentleri
kirli_veriTemizlik pratiğibayi, hedef, urun, musteri (dağınık Excel çıktıları)

Listeyi ve bir setin tablolarını kodla görmek için:

import veri

veri.liste()                        # tüm setler, tablo ve satır sayıları
veri.tablolar("anadolu_market")     # tablolar ve sütunları
print(veri.sutunlar("anadolu_market", "orders"))
Set adı kodda geçmeliTarayıcı yalnız kodunda adı geçen veri setini indirir. Set adını bir değişkende birleştirerek (ör. "anadolu_" + "market") üretirsen dosya yüklenmez; adı her zaman açıkça yaz.

Okumanın dört yolu

FonksiyonNe döndürürNe zaman
veri.oku(set, tablo, **kw)pandas DataFrameAnalizin çoğu. Ek argümanlar pd.read_csv'ye geçer: parse_dates, usecols, dtype.
veri.kayitlar(set, tablo)list[dict]; sayılar int/float, boşlar Nonepandas gerekmeyen saf Python görevleri
veri.baglan(set, tablolar=[...])Bellek içi sqlite3 bağlantısıSQL yazmak, Kesit SQL sorgularını Python'dan çalıştırmak
veri.yol(set, tablo)CSV dosyasının yolupd.read_csv, csv modülü, polars ya da DuckDB ile kendin okumak
import veri
import pandas as pd

siparis = veri.oku("anadolu_market", "orders", parse_dates=["order_date"])
print(siparis.shape)                          # (910, 5)
print(siparis["order_date"].min().date(), siparis["order_date"].max().date())
print(siparis["channel"].value_counts().to_dict())

CSV dosyalarında tarih metin olarak saklanır. parse_dates vermezsen order_date sütunu metin kalır ve .dt erişimcisi hata verir. Bu, gerçek veride en sık karşılaşacağın ilk hatadır.

import veri

hesaplar = veri.kayitlar("ilgaz_bank", "bank_accounts")
kapali = [h for h in hesaplar if h["closed_date"] is not None]
print(len(hesaplar), "hesap,", len(kapali), "kapalı")
print(hesaplar[0]["account_type"], hesaplar[0]["credit_limit"])   # vadesiz None

SQL ile pandas yan yana

veri.baglan seti bellekteki bir SQLite veritabanına yükler. Kesit SQL'de yazdığın sorgu aynen çalışır; sonucu pd.read_sql_query ile DataFrame'e alıp pandas'la devam edebilirsin. Büyük setlerde (pedal_izmir, ilgaz_bank) tablolar=[...] ile yalnız gereken tabloları yükle; hem bellek hem süre kazanırsın.

import veri
import pandas as pd

con = veri.baglan("anadolu_market", tablolar=["orders", "customers"])
sql = """
SELECT c.segment, COUNT(*) AS siparis
FROM orders o JOIN customers c ON c.customer_id = o.customer_id
WHERE o.status = 'delivered'
GROUP BY c.segment ORDER BY siparis DESC
"""
print(pd.read_sql_query(sql, con))
con.close()
İki yoldan aynı sayıya ulaşBir rakamı raporlamadan önce onu iki farklı yoldan hesapla: bir kez SQL ile, bir kez pandas ile. İkisi tutuyorsa birleştirme (join) ya da filtre hatası yapmamışsındır. Analistlerin "mutabakat" (reconciliation) dediği alışkanlık budur.
Gerçek işteŞirketlerde veri ya bir veri ambarından SQL ile gelir ya da paylaşılan klasördeki CSV/Excel dosyalarından. veri modülü bu iki yolu taklit eder: baglan ambarı, oku ve yol dosyayı. Kendi bilgisayarında aynı kodu pd.read_csv("orders.csv") ya da sqlalchemy.create_engine(...) ile çalıştırırsın; analiz kısmı değişmez.

Sonraki ileri veri derslerinde (temizlik, groupby, zaman serisi, görselleştirme, istatistik, kümeleme, NLP) bu setlerden gerçek tablolarla alıştırmalar bulacaksın.

Uzmanlaş

Index kavramı

Index yalnızca satır numarası değildir; müşteri numarası ya da tarih olabilir (df.set_index("siparis_id")). O zaman loc gerçek iş anahtarıyla çalışır ve birleştirme/hizalama (alignment) otomatik olur: iki Series toplandığında aynı etiketler eşleşir, olmayanlar NaN olur. reset_index() geri döner.

Büyük veride

Milyonlarca satırda read_csv'ye usecols (gerekli sütunlar), dtype (küçük türler) ve chunksize (parça parça okuma) ver. df.memory_usage(deep=True) ile sütun başına bellek ölçülür; tekrar eden metinler için category türü çoğu zaman belleği onda birine indirir.

İş hayatında

Analist olarak ilk saatin hep aynıdır: yükle, info, describe, value_counts, birkaç maske. Bu "keşif" aşamasını küçük fonksiyonlara ayırırsan (yükle, filtrele, türet) hem test yazabilir hem de aynı raporu her ay tek komutla üretirsin.

Alıştırmalara geç (29)