---
title: 'Python ile Veri Analizi'
source: 'https://academia.sh/tr/kurslar/python-ile-analiz'
language: tr
updated: '2026-08-17T04:40:04+00:00'
license: 'CC BY-SA 4.0'
lessons: 22
outcomes:
    - 'Dizi ve veri çerçevesi soyutlamalarını kullanır ve her çağrının varsayılanını yazar'
    - 'Bellek ve ölçek kısıtlarını sayılan bir birimle yönetir ve parçalama kararını gerekçelendirir'
    - 'Analizini yeniden üretilebilir bir ortamda yürütür ve ortam seçimini ölçütlere bağlar'
---

# Python ile Veri Analizi

Dizi ve veri çerçevesi soyutlamalarının aynı işi ne kadar kısalttığının ve hangi kararı görünmez kıldığının birlikte ölçülmesi: yalıtılmış ortamın yeniden üretilebilirlik koşulu, listeyle dizi arayüzünün satır, geçiş ve bayt farkı, vektörleştirmenin gizlediği tip yükseltme, yayılımın sessizce ürettiği sonuç, etiketin üstlendiği hizalama, tip çıkarımının yanlış okuduğu sütun, eksik değerin süzgeçte ve grup anahtarında davranışı, birleştirme türlerinin ürettiği satır sayısı, yeniden örneklemede boş dönemin sayılması, tip seçiminin ayırdığı bayt, parça boyutunun bellek tepesiyle ilişkisi, sütunlu okumanın okuduğu değer sayısı, dağıtık geçişin bedeli, süzgeci veriye götürmenin taşıdığı satır ve ortam seçiminin ölçüt başına karşılaştırılması.

## Sayısal Hesaplama

Dizi soyutlamasının kazandırdığı ile gizlediğinin sayıyla ayrılması: yalıtılmış ortamda aynı defterin başka bir makinede aynı sayıyı vermesi için sabitlenmesi gereken kalem, aynı toplama işinin listeyle ve dizi arayüzüyle tuttuğu satır, geçiş ve bayt, vektörleştirmenin gizlediği tip yükseltme ve taşma, uyumsuz biçimlerde yayılımın hata yerine beklenmedik bir dizi vermesi, matris çarpımı ve çözümde elle döngü ile soyutlamanın geçiş sayısı farkı.

### Çalışma Ortamı

Source: https://academia.sh/tr/kurslar/python-ile-analiz/calisma-ortami

Bir defterin başka bir makinede aynı sayıyı vermesi için sabitlenmesi gereken kalemler sayılır: dokuz ortam kalemi ve M26/K03'ün on yedi adımlık hazırlama defteri karşılıklı yazıldığında hiçbir şey sabitlenmezken 17 adımın 2'si, bağımlılık listesiyle 5'i, kilit dosyasıyla 11'i yeniden üretilir; kalan 6 adım kilit dosyasının hiç yazmadığı beş kaleme bağlıdır. Aynı defter iki makinede koşturulduğunda yerel ayar 1.260 sayaç endeksinin tamamını değiştirir ve toplamı 1.236.664,22'den 41.433.984,33'e taşır, parça sayısı ortalamayı 0,006053 m³ oynatır (eşiğin 12 katı), defter hücre sırası 40 m³ üstü hücre sayısını 1.116'dan 1.264'e çıkarır.

Veri Toplama ve Hazırlama kursu on yedi adımlık bir hazırlama defteriyle kapandı. O defterin ölçüsü
**geri okunabilirlikti**: her adımın kararı ve satır hesabı çıktıya yazıldığında 39 kararın 39'u
koşumdan sonra okunabilir oldu. Geri okunabilirlik bir defterin **okunmasıyla** ilgilidir ve
kursun sorusunu tam olarak kapatmaz. Bu ders bir sonraki soruyu sorar: aynı defter başka bir
makinede koşturulduğunda aynı sayıyı verir mi.

Soru araç seçmeden önce gelir. Bir dizi ya da veri çerçevesi arayüzü kullanılmaya başlandığı anda
hesabın bir bölümü artık yazılan kodda değil, kurulan bileşenlerin içindedir; o bileşenlerin hangi
sürümde olduğu, makinenin sayıyı hangi sözleşmeyle okuduğu ve hesabın kaça bölündüğü sonucu
oynatır. Bu yüzden ortam burada bir kurulum komutu listesi olarak değil, **sabitlenmesi gereken
kalemlerin listesi** olarak ele alınır. Ölçü de komut sayısı değil, kaç adımın iki makinede aynı
sayıyı verdiğidir.

- **SH1.** Bir defter, ürettiği sayıyı belirleyen bütün kalemler yazılıysa başka bir makinede aynı
  sayıyı verir. Kilit dosyası bu kalemlerin yalnız bir bölümünü yazar; kalanı defterin kendisi
  bildirmezse hiçbir yerde durmaz.
- **SH2.** Ortamdan gelen bir kalem sayıyı iki biçimde oynatır: büyüklük düzeyinde, yani gözle
  görülür bir hatayla; ya da eşik civarında, yani yalnız karşılaştırıldığında görünen bir farkla.
  İkincisi satır sayısına bakan bir denetimden kaçar.

#### Ortamı Veri Olarak Yazmak

```python
# ortam.py — MODELDIR: bir kosum ortami ve onu sabitleyen belgeler veri olarak yaziliyor.
# "duzey" kalemi hangi belgenin sabitledigidir. "adim" M26/K03'un on yedi adimli
# hazirlama defterinden okundu; yaninda o adimin sonucunu belirleyen kalemler var.
DUZEY = ["kurulu", "liste", "kilit", "bildirim"]
KALEM = [
    ("bagimlilik adi",             "liste"),
    ("dogrudan bagimlilik surumu", "kilit"),
    ("dolayli bagimlilik surumu",  "kilit"),
    ("yorumlayici surumu",         "kilit"),
    ("yerel ayar",                 "bildirim"),
    ("parca sayisi",               "bildirim"),
    ("rastgelelik tohumu",         "bildirim"),
    ("defter hucre sirasi",        "bildirim"),
    ("veri dosyasi ozeti",         "bildirim"),
]
ADIM = [
    ("01 dosya bicimleri",        ["bagimlilik adi", "yerel ayar"]),
    ("02 veritabanindan cekme",   ["bagimlilik adi", "veri dosyasi ozeti"]),
    ("03 arayuzlerden toplama",   ["bagimlilik adi"]),
    ("04 yapisal ayristirma",     ["bagimlilik adi", "dolayli bagimlilik surumu"]),
    ("05 ornekleme",              ["dogrudan bagimlilik surumu", "rastgelelik tohumu"]),
    ("06 eksik degerler",         ["bagimlilik adi"]),
    ("07 yinelenen kayitlar",     ["dogrudan bagimlilik surumu"]),
    ("08 tip donusumleri",        ["yorumlayici surumu", "yerel ayar"]),
    ("09 aykiri degerler",        ["dogrudan bagimlilik surumu"]),
    ("10 kategorik duzeltme",     ["dolayli bagimlilik surumu"]),
    ("11 metin normallestirme",   ["yorumlayici surumu"]),
    ("12 birlestirme",            ["dogrudan bagimlilik surumu", "defter hucre sirasi"]),
    ("13 yeniden sekillendirme",  ["bagimlilik adi"]),
    ("14 gruplama",               ["dogrudan bagimlilik surumu", "parca sayisi"]),
    ("15 turetilmis degiskenler", ["yorumlayici surumu"]),
    ("16 kategorik kodlama",      []),
    ("17 hazirlama defteri",      []),
]
SABIT = dict(KALEM)
sabitlenen = lambda d: {a for a, k in KALEM if DUZEY.index(k) <= DUZEY.index(d)}
ureten = lambda d: [ad for ad, ba in ADIM if set(ba) <= sabitlenen(d)]

gen = lambda w: lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                                  for j, v in enumerate(h))
y = gen([12, 16, 22, 16])
print(y(["duzey", "sabit kalem", "yeniden uretilen adim", "oynayan adim"]))
for d in DUZEY:
    u = ureten(d)
    print(y([d, f"{len(sabitlenen(d))}/{len(KALEM)}", f"{len(u)}/{len(ADIM)}",
             len(ADIM) - len(u)]))

kilit = set(ureten("kilit"))
print("\nkilit dosyasindan sonra oynayan adimlar ve nedeni:")
for ad, ba in ADIM:
    if ad not in kilit:
        print("  " + ad + " <- " + ", ".join(b for b in ba if SABIT[b] == "bildirim"))
```

```
duzey            sabit kalem yeniden uretilen adim    oynayan adim
kurulu                   0/9                  2/17              15
liste                    1/9                  5/17              12
kilit                    4/9                 11/17               6
bildirim                 9/9                 17/17               0

kilit dosyasindan sonra oynayan adimlar ve nedeni:
  01 dosya bicimleri <- yerel ayar
  02 veritabanindan cekme <- veri dosyasi ozeti
  05 ornekleme <- rastgelelik tohumu
  08 tip donusumleri <- yerel ayar
  12 birlestirme <- defter hucre sirasi
  14 gruplama <- parca sayisi
```

Dört düzeyin her biri farklı bir belgeye karşılık gelir. **Kurulu** düzeyinde hiçbir belge yoktur;
bileşenler makineye sistem geneli kurulmuştur ve hangi sürümün orada olduğunu ancak makineye
bakarak öğrenirsin. On yedi adımın yalnız 2'si iki makinede aynı sonucu verir — bunlar dış hiçbir
kaleme dokunmayan adımlardır.

**Yalıtılmış ortam** bu düzeyin üstüne gelir ve tek bir şey yapar: defterin kullandığı bileşenleri
makinenin geri kalanından ayırır. Yalıtım kendi başına bir sayı sabitlemez, sabitlemeyi
**mümkün** kılar; ayrı bir kümede olmayan bir bileşenin sürümünü yazmanın anlamı yoktur, çünkü
başka bir iş onu değiştirebilir.

**Bağımlılık listesi** defterin doğrudan kullandığı bileşenlerin adını kaydeder ve genellikle bir
aralık verir. Bu, sabitlenen kalem sayısını 1'e çıkarır ve yeniden üretilen adımı 5'e taşır: yalnız
davranışı sürümden sürüme değişmeyen adımlar kazanılır. Liste **hangi bileşenin** kullanıldığını
söyler, **hangi sürümünün** kullanıldığını söylemez.

**Kilit dosyası** aradaki farkı kapatır. Doğrudan bileşenlerin tam sürümünü, onların çektiği
dolaylı bileşenleri ve yorumlayıcının sürümünü tek bir çözümde yazar; ikinci makine aynı çözümü
kurar. Sabitlenen kalem 4'e, yeniden üretilen adım **11'e** çıkar. Kazanç burada bitiyor ve
gizlenen karar tam bu noktada duruyor: kilit dosyası tam sürüm çözümü yazdığı için ortamın
tamamının sabitlendiği sanılır. Sabitlenmeyen beş kalem **6 adımı** oynatmaya devam eder ve bu altı
adımın hiçbiri sürümle ilgili değildir.

Son düzey bir dosya değil, defterin kendi başında duran bir **ortam bildirimidir**: hangi yerel
ayarla okunduğu, hesabın kaça bölündüğü, hangi tohumun kullanıldığı, hücrelerin hangi sırayla
koştuğu ve girdi dosyasının özeti. Bu beş kalem yazıldığında oran 17'de 17 olur.

Dört belgenin işlevi birbirinin yerine geçmez ve bu ayrım kurulum komutlarından okunamaz. Yalıtılmış
ortam bir **sınır** çizer, bağımlılık listesi bir **niyet** yazar, kilit dosyası o niyetin tek bir
**çözümünü** kaydeder, ortam bildirimi ise çözümün dışında kalan koşum koşullarını yazar. Aradaki
en sık karıştırılan ikili liste ile kilittir: liste insanın yazdığı ve elle güncellediği belgedir,
kilit ise listeden **türetilen** ve elle yazılmayan belgedir. Listeyi daraltmak kilidin yerini
tutmaz, çünkü dolaylı bileşenler listede hiç görünmez ve tablodaki 04 ile 10 numaralı adımlar tam
olarak onlara bağlıdır.

#### Aynı Defter, İki Makine

```python
# kosum.py — MODELDIR: ayni defter iki makinede kosturuluyor. Kurgu okuma kumesi
# K03'un hazirladigi kumedir: 1.260 abone, 3 donem, 3.780 hucrenin 3.199'u dolu.
def uretec(tohum):
    s = tohum & 0xFFFFFFFF
    def sonraki():
        nonlocal s
        s = (1664525 * s + 1013904223) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki

ABONE, DONEM = 1260, 3
r = uretec(20260218)
HAM = [round(4 + r() * 56, 2) for _ in range(ABONE * DONEM)]
r2 = uretec(9110723)
p = [r2() for _ in range(ABONE * DONEM)]
BOS = set(sorted(range(len(p)), key=lambda j: p[j])[:581])
OKUMA = [None if j in BOS else HAM[j] for j in range(ABONE * DONEM)]
ortalama = lambda v: sum(v) / len(v)

# Kalem 5 — yerel ayar. Sayac endeksi metin olarak geliyor; ondalik ve binlik
# ayracinin hangisi oldugu makinenin yerel ayarindan okunuyor.
ENDEKS = [round(200 + (i * 7919) % 1400
                + sum(x for x in OKUMA[i*DONEM:(i+1)*DONEM] if x is not None), 2)
          for i in range(ABONE)]
def yaz(v):
    tam, _, kes = f"{v:.2f}".partition(".")
    g = ""
    while len(tam) > 3:
        tam, g = tam[:-3], "." + tam[-3:] + g
    return tam + g + "," + kes
oku = lambda m, binlik, ondalik: float(m.replace(binlik, "").replace(ondalik, "."))
METIN = [yaz(v) for v in ENDEKS]
A = [oku(m, ".", ",") for m in METIN]   # binlik nokta, ondalik virgul
B = [oku(m, ",", ".") for m in METIN]   # binlik virgul, ondalik nokta

# Kalem 6 — parca sayisi. Toplulastirma parcalara bolununce parca ortalamalarinin
# ortalamasi aliniyor; parcalar esit degil, cunku eksik hucre dagilimi esit degil.
def parcali_ortalama(k):
    sinir = [i * ABONE // k for i in range(k + 1)]
    parca = [[x for j in range(sinir[i], sinir[i+1]) for x in OKUMA[j*DONEM:(j+1)*DONEM]
              if x is not None] for i in range(k)]
    return ortalama([ortalama(x) for x in parca])
ESIK = 0.0005  # m3; okuma cozunurlugu 0,01 m3'un yirmide biri, altinda kalan fark okunamaz

# Kalem 8 — defter hucre sirasi. Iki hucre var: bos hucreyi abonenin kendi
# ortalamasiyla doldurmak ve ust tarife basamagini (40 m3) asan hucreyi saymak.
def doldur(v):
    o = []
    for i in range(ABONE):
        h = [v[i*DONEM+d] for d in range(DONEM) if v[i*DONEM+d] is not None]
        o.append(ortalama(h) if h else 0.0)
    return [o[j // DONEM] if x is None else x for j, x in enumerate(v)]
say = lambda v: sum(1 for x in v if x is not None and x > 40.0)

z = gen([22, 20, 20, 14])
print(z(["ortam kalemi", "makine A", "makine B", "ayni mi"]))
print(z(["yerel ayar (toplam)", round(sum(A), 2), round(sum(B), 2), "hayir"]))
print(z(["parca sayisi (ort.)", round(parcali_ortalama(1), 6), round(parcali_ortalama(8), 6),
         "evet" if abs(parcali_ortalama(1) - parcali_ortalama(8)) < ESIK else "hayir"]))
print(z(["hucre sirasi (40 ust)", say(OKUMA), say(doldur(OKUMA)), "hayir"]))

kucul = sum(1 for a, b in zip(A, B) if b < a)
fark = abs(parcali_ortalama(1) - parcali_ortalama(8))
print(f"\nyerel ayar: {len(METIN)} endeksin {len(METIN)}'i degisti; {kucul} tanesi binlik "
      f"ayraci ondalik sayildigi icin kuculdu, {len(METIN)-kucul} tanesi buyudu")
print(f"parca sayisi: fark {fark:.6f} m3, esik {ESIK} m3'un {fark/ESIK:.0f} kati")
print(f"hucre sirasi: ayni iki hucre, ters sirada {say(doldur(OKUMA))-say(OKUMA)} hucre fazla "
      f"sayildi (yuzde {100*(say(doldur(OKUMA))-say(OKUMA))/say(OKUMA):.0f})")
```

```
ortam kalemi                      makine A            makine B       ayni mi
yerel ayar (toplam)             1236664.22         41433984.33         hayir
parca sayisi (ort.)              31.895661           31.889608         hayir
hucre sirasi (40 ust)                 1116                1264         hayir

yerel ayar: 1260 endeksin 1260'i degisti; 613 tanesi binlik ayraci ondalik sayildigi icin kuculdu, 647 tanesi buyudu
parca sayisi: fark 0.006053 m3, esik 0.0005 m3'un 12 kati
hucre sirasi: ayni iki hucre, ters sirada 148 hucre fazla sayildi (yuzde 13)
```

Üç satırın üçü de aynı kodu, aynı veriyi ve aynı kilit dosyasını varsayar. Farkı üreten şey yalnız
ortam kalemidir.

Birinci satır **büyüklük düzeyinde** bir farktır. Sayaç endeksi metin olarak geliyor ve iki
sözleşme arasında seçim yapılıyor: ayracın hangisi ondalık, hangisi binlik. 1.260 endeksin tamamı
değişiyor ve fark tek yönlü de değil — binlik ayracı taşıyan 613 endeks bin kat küçülüyor, ayraç
taşımayan 647 endeks yüz kat büyüyor. Toplam 1.236.664,22 m³ yerine 41.433.984,33 m³ çıkıyor. Bu
tür bir hata gözle görülür, ama görülmesi için birinin **iki makinede birden** bakması gerekir.

İkinci satır tam tersidir. Toplulaştırma sekiz parçaya bölündüğünde parça ortalamalarının
ortalaması alınıyor; parçalar eşit büyüklükte değil, çünkü eksik hücreler abonelere eşit
dağılmamış. Sonuç 31,895661 yerine 31,889608 m³. Kayan noktalı iki sayı `==` ile karşılaştırılmaz;
bunun yerine eşik yazılır. Buradaki eşik **0,0005 m³**, yani sayaç çözünürlüğü olan 0,01 m³'ün
yirmide biri: bu değerin altındaki bir fark ölçüm aygıtından zaten okunamaz, dolayısıyla aynı
sayılır. Fark 0,006053 m³ ile eşiğin 12 katı ve eşiğin üstünde kaldığı için iki koşum aynı sayıyı
vermiyor. Satır sayısı ise iki koşumda da 3.199; satır sayısına bakan bir denetim bu farkı görmez.

Üçüncü satır defterin **biçimiyle** ilgilidir. İki hücre var: boş hücreyi abonenin kendi
ortalamasıyla doldurmak ve üst tarife basamağını aşan hücreleri saymak. Doldurma önce koşarsa
581 hücre bir değer kazanır ve bunların bir bölümü 40 m³ eşiğini geçer; sayım 1.116 yerine 1.264
çıkar, 148 hücre fazla, yüzde 13. Hiçbir hücre yanlış yazılmamıştır; yalnız sıraları farklıdır.

#### Defterin Kaydettiği ve Kaydetmediği

Bu son satır defter biçiminin neden bir ortam kalemi olduğunu gösteriyor. Bir defter dosyası
hücreleri **yazıldıkları sırayla** saklar, koştukları sırayla değil; her hücrenin yanında yalnız
bir koşum sayacı durur. Sayaç 3, 1, 2 ise hücreler yukarıdan aşağıya okunduğunda doğru görünen bir
defter, gerçekte başka bir sırayla koşmuş demektir. Bu yüzden bir defterin yeniden üretilebilir
sayılması için tek koşullu bir kural yazılır: **defter baştan sona, tek seferde, temiz bir durumda
koşturulduğunda aynı çıktıyı vermelidir.** Kaydedilmiş çıktı bu kuralın kanıtı değildir, çünkü
kaydedilmiş çıktı hangi sırayla üretildiğini söylemez.

Ölçünün bu kursta neden ilk derste kurulduğu da buradan görünüyor. Bundan sonraki derslerde hesabın
gövdesi elle yazılan döngülerden çıkıp bir soyutlamanın içine girecek; o soyutlamanın öğe tipi,
biçimi ve toplama sırası hakkında verdiği kararlar tam olarak yukarıdaki tabloya yeni satırlar
eklemektir. Sekiz parçalı toplulaştırmanın ortalamayı 0,006053 m³ oynatması bir hata değil, bir
**varsayılandır**; kim kaça böleceğine karar verdiyse o sayıyı da o vermiştir. Ortam bildirimi bu
yüzden sürüm listesinin uzantısı değil, hesabın bir parçasıdır.

Aynı gerekçe kaydedilen çıktıya da uygulanır. Defter hem kodu hem çıktıyı tek dosyada tuttuğu için
okuması kolaydır, ama iki koşum arasındaki farkın **nerede** başladığını göstermez; bunu gösteren
şey her adımın kendi kararlarını ve satır hesabını yazdığı koşum defteridir. Kilit dosyası ortamı,
koşum defteri kararı, defterin baştan koşturulması da sırayı sabitler; üçü birden yazılmadan
"aynı sayı" bir iddia olarak kalır.

#### Özet

- Bir çalışma ortamı komut listesi değil, sabitlenmesi gereken dokuz kalemin listesidir; her kalemi
  farklı bir belge sabitler.
- Hiçbir belge yokken 17 adımın 2'si, bağımlılık listesiyle 5'i, kilit dosyasıyla 11'i iki makinede
  aynı sonucu verir.
- Kilit dosyasından sonra oynayan 6 adımın hiçbiri sürümle ilgili değildir; yerel ayar, parça
  sayısı, tohum, hücre sırası ve veri özeti kilit dosyasında yazılı değildir.
- Yerel ayar farkı 1.260 endeksin tamamını değiştirir ve toplamı 1.236.664,22'den 41.433.984,33'e
  taşır; 613 endeks küçülür, 647 endeks büyür.
- Parça sayısı ortalamayı 0,006053 m³ oynatır — 0,0005 m³ eşiğinin 12 katı — ama satır sayısını hiç
  değiştirmez; hücre sırası 40 m³ üstü hücre sayısını 1.116'dan 1.264'e çıkarır.

#### Sonraki Adım

Ortam sabitlendiğinde defter iki makinede aynı sayıyı verir, ama hesabın kendisi hâlâ önceki
kursta olduğu gibi elle yazılmış döngülerle yapılıyor. Bu bölümün kod bloklarında 3.780 hücre tek
tek gezildi, her hücre ayrı bir nesne olarak tutuldu ve her toplama işi için veri baştan sona
yeniden okundu. Sonraki ders aynı işi bir **dizi arayüzüyle** yapar ve kursun ölçü birimini kurar:
aynı toplama işi kaç satır kod, kaç geçiş ve kaç bayt tutuyor.

### Diziler

Source: https://academia.sh/tr/kurslar/python-ile-analiz/diziler

Kursun ölçü birimi kuruluyor: aynı iş (abone başına toplam, genel ortalama, 40 m³ üstü hücre sayısı) iç içe listelerle ve bir dizi arayüzüyle yapılır ve satır sayısı 22'den 2'ye, ayrılan bayt 188.864'ten 30.320'ye (öğe başına 49,96'dan 8,02 bayta) düşerken geçiş sayısı 3'ten 3'e, yani hiç düşmez. Gizlenen karar dizinin boş değeri olmamasıdır: 581 boş hücre sıfırla doldurulduğunda ortalama 26,993180, nöbetçi değerle 26,839476, yalnız dolu hücrelerle 31,895661 m³ çıkar; 40 m³ üstü sayım üç kararda da 1.116'dır. Öğe tipi seçimi 3.780 öğeyi 7.560 ile 30.240 bayt arasında tutar.

Önceki derste ortam sabitlendi ve defter iki makinede aynı sayıyı verir hale geldi. Hesabın
kendisi ise değişmedi: 3.780 hücre iç içe döngülerle tek tek gezildi, her hücre kendi başına bir
nesne olarak tutuldu ve her soru için veri baştan sona yeniden okundu. Bu ders aynı işi bir **dizi
arayüzüyle** yapar.

Ders yalnız bir kolaylık göstermez, kursun **ölçü birimini** kurar. Bundan sonraki her derste iki
sayı yan yana duracak: soyutlamanın kazandırdığı ve soyutlamanın gizlediği. Kazanç burada üç
birimle ölçülür — kaç satır kod, veri üzerinden kaç tam geçiş, kaç bayt. Gizlenen karar ise dizinin
sana sormadan verdiği karardır. Kütüphane çağrılmaz; dizi davranışı standart kitaplıkla
modellenir, çünkü amaç bir aracı taklit etmek değil davranışını ölçülebilir kılmaktır.

- **SH3.** Bir dizi arayüzü üç şeyi birlikte sabitler: **bitişik bellek**, **tek öğe tipi** ve
  **biçim**. Kazanç bu üçlünün sonucudur ve baytta ile çağrı yerindeki satır sayısında görünür;
  veri üzerindeki geçiş sayısında görünmez.
- **SH4.** Dizinin boş değeri yoktur. Eksik bir hücre bir sayıya dönüşmek zorundadır ve hangi
  sayıya dönüştüğü bir karardır. Bu karar bazı özetleri oynatır, bazılarını hiç oynatmaz.

#### Aynı İş, İki Yapı

```python
# diziler.py — MODELDIR: dizi arayuzunun uc ozelligi standart kitaplikla yaziliyor:
# bitisik bellek, tek oge tipi, bicim. Kutuphane cagrilmiyor; amac araci taklit
# etmek degil, davranisini olculebilir kilmak. Kume K03'un hazirladigi kurgu kumedir.
import inspect
import sys
from array import array

def uretec(tohum):
    s = tohum & 0xFFFFFFFF
    def sonraki():
        nonlocal s
        s = (1664525 * s + 1013904223) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki

ABONE, DONEM = 1260, 3
r = uretec(20260218)
HAM = [round(4 + r() * 56, 2) for _ in range(ABONE * DONEM)]
r2 = uretec(9110723)
p = [r2() for _ in range(ABONE * DONEM)]
BOS = set(sorted(range(len(p)), key=lambda j: p[j])[:581])
OKUMA = [None if j in BOS else HAM[j] for j in range(ABONE * DONEM)]
TABLO = [OKUMA[i*DONEM:(i+1)*DONEM] for i in range(ABONE)]

GECIS = 0  # veri uzerinden bastan sona bir tam tarama

def elle(t):
    global GECIS
    abone_toplami = []
    for s in t:
        a = 0.0
        for x in s:
            if x is not None:
                a += x
        abone_toplami.append(a)
    genel, n = 0.0, 0
    for s in t:
        for x in s:
            if x is not None:
                genel += x
                n += 1
    ust = 0
    for s in t:
        for x in s:
            if x is not None and x > 40.0:
                ust += 1
    GECIS += 3
    return abone_toplami, genel / n, ust

class Dizi:
    def __init__(self, veri, bicim, tip="d"):
        self.d, self.bicim, self.tip = array(tip, veri), bicim, tip
    @property
    def oge(self):
        return self.bicim[0] * self.bicim[1]
    def eksen_toplami(self, eksen):
        global GECIS
        GECIS += 1
        a, b = self.bicim
        if eksen == 1:
            return [sum(self.d[i*b:(i+1)*b]) for i in range(a)]
        return [sum(self.d[i*b+j] for i in range(a)) for j in range(b)]
    def toplam(self):
        global GECIS
        GECIS += 1
        return sum(self.d)
    def ustunde(self, esik):
        global GECIS
        GECIS += 1
        return sum(1 for x in self.d if x > esik)

def dizi_ile(a):
    return a.eksen_toplami(1), a.toplam() / a.oge, a.ustunde(40.0)

def liste_bayt(t):
    b = sys.getsizeof(t)
    for s in t:
        b += sys.getsizeof(s) + sum(sys.getsizeof(x) for x in s if x is not None)
    return b

DUZ = Dizi([0.0 if x is None else x for x in OKUMA], (ABONE, DONEM))
GECIS = 0
E = elle(TABLO)
ge = GECIS
GECIS = 0
D = dizi_ile(DUZ)
gd = GECIS
satir = lambda f: len(inspect.getsource(f).strip().splitlines())
bl, bd = liste_bayt(TABLO), sys.getsizeof(DUZ.d)

gen = lambda w: lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                                  for j, v in enumerate(h))
y = gen([20, 20, 16, 10])
print(f"bicim {DUZ.bicim}, oge sayisi {DUZ.oge}, oge tipi 64 bit kesirli, "
      f"bos hucre {len(BOS)}")
print("is: abone basina toplam, genel ortalama, 40 m3 ustu hucre sayisi\n")
print(y(["olcu", "elle yazilan dongu", "dizi arayuzu", "oran"]))
print(y(["satir sayisi", satir(elle), satir(dizi_ile), f"{satir(elle)/satir(dizi_ile):.1f}x"]))
print(y(["gecis sayisi", ge, gd, f"{ge/gd:.1f}x"]))
print(y(["ayrilan bayt", bl, bd, f"{bl/bd:.1f}x"]))
print(y(["oge basina bayt", f"{bl/DUZ.oge:.2f}", f"{bd/DUZ.oge:.2f}", f"{bl/bd:.1f}x"]))
print(f"\nayni is, iki sonuc: ortalama elle {E[1]:.6f} m3, dizi arayuzuyle {D[1]:.6f} m3; "
      f"40 m3 ustu hucre iki yolda da {E[2]}")
```

```
bicim (1260, 3), oge sayisi 3780, oge tipi 64 bit kesirli, bos hucre 581
is: abone basina toplam, genel ortalama, 40 m3 ustu hucre sayisi

olcu                  elle yazilan dongu    dizi arayuzu      oran
satir sayisi                          22               2     11.0x
gecis sayisi                           3               3      1.0x
ayrilan bayt                      188864           30320      6.2x
oge basina bayt                    49.96            8.02      6.2x

ayni is, iki sonuc: ortalama elle 31.895661 m3, dizi arayuzuyle 26.993180 m3; 40 m3 ustu hucre iki yolda da 1116
```

Üç ölçünün tanımı kurs boyunca sabit kalır. **Satır sayısı** çağrı yerinde yazılan kod satırıdır
ve koddan okunur, elle sayılmaz. **Geçiş sayısı** veri üzerinden baştan sona yapılan tam tarama
sayısıdır; modelde her tarama bir sayacı bir artırır, dolayısıyla iddia edilen değil ölçülen bir
sayıdır. **Ayrılan bayt** veriyi tutan yapının kendisinin ve içindeki nesnelerin kapladığı yerdir.
Üçü de koşumdan koşuma değişmez; süre değişir, bu yüzden süre ölçülmez. Bir soyutlamanın kazancı
bu üç sayıdan hangilerini düşürdüğüyle, bedeli ise hangilerini düşürmediğiyle tanımlanır.

Tablonun ilk satırı beklenen kazançtır. Aynı üç soru elle yazıldığında 22 satır tutuyor, dizi
arayüzünde 2 satır. Fark bir yazım kısalığı değil: elle yazılan sürümde eksik hücre denetimi üç
kez tekrarlanıyor, döngü sınırları üç kez yazılıyor ve her tekrarda bunlardan birini atlama
ihtimali var. Soyutlama bu üç tekrarı tek bir yere taşıyor.

Üçüncü satır ikinci kazançtır ve nedeni **biçim** ile **öğe tipi** kararının bir kez verilmiş
olmasıdır. İç içe listelerde 3.780 sayının her biri kendi başına bir nesnedir; nesnenin kendisi
yaklaşık 24 bayt, ona işaret eden gösterici 8 bayt, satırı tutan 1.260 küçük listenin her biri
ayrıca yer kaplar. Toplam 188.864 bayt, öğe başına 49,96. Dizide 3.780 sayı bitişik bir bellek
bloğunda, öğe başına tam 8 bayt duruyor ve biçim yalnız iki tamsayıdan ibaret: 30.320 bayt,
öğe başına 8,02. Oran 6,2 kat.

İkinci satır ise kazanç değil. Geçiş sayısı iki yolda da **3**, çünkü sorulan soru üç tane ve her
soru veriyi baştan sona bir kez okuyor. Dizi arayüzü döngüyü çağrı yerinden kaldırır, veriden
kaldırmaz. Bu kursun en sık yanlış okunan sayısıdır: iki satır kod yazıldığı için tek geçiş
yapıldığı sanılır. Zincirlenen her çağrı kendi geçişini ekler ve bir sonraki dersin ölçtüğü şey
tam olarak budur. Elle yazılan sürümde bu üç geçişi tek geçişe indirmek üç döngüyü tek döngüde
birleştirmek demektir ve bunun yazılması iki satır tutar; dizi arayüzünde aynı birleştirmeyi
yapmak için çağrı zincirinden çıkıp döngüye geri dönmek gerekir. Kısalık ile geçiş sayısı burada
birbirinin karşısındadır.

Son satır ise gizlenen kararı işaret ediyor. Aynı iş, aynı veri, iki farklı ortalama: 31,895661
ile 26,993180 m³. Aradaki 4,90 m³ bir hesap hatası değil, dizinin yapısından doğan bir karardır.

#### Biçim ve Eksen

Modeldeki `Dizi` sınıfı iki alan taşıyor ve ikisinin ayrı durması dizi arayüzünün en belirleyici
özelliğidir. `d` alanı 3.780 sayıyı tek bir düz blokta tutar; `bicim` alanı yalnız iki tamsayıdan
ibarettir ve o düz bloğun nasıl okunacağını söyler. Veri iki boyutlu değildir — **okunuşu** iki
boyutludur.

Bu ayrımın doğrudan sonucu, biçim değiştirmenin veriyi hiç kopyalamamasıdır. (1260, 3) yerine
(3, 1260) yazmak 30.240 baytın hiçbirini kımıldatmaz, yalnız `eksen_toplami` işlevindeki dilim
sınırlarını değiştirir. Bu ucuzluk soyutlamanın en kullanışlı yanıdır ve aynı zamanda en sessiz
yanıdır: yanlış bir biçim bir hata üretmez, yalnız başka bir sayı üretir.

**Eksen** o biçim içindeki bir yöndür ve modeldeki numaralandırma soldan sağadır: eksen 0 abone
yönü, eksen 1 dönem yönüdür. `eksen_toplami(1)` her abonenin üç dönemini toplar ve 1.260 değer
verir; `eksen_toplami(0)` her dönemin bütün abonelerini toplar ve 3 değer verir. İkisi de "toplam"
adını taşır, ikisi de aynı 102.034,22 m³'ü paylaştırır, ama biri abone davranışını, öteki dönem
davranışını anlatır. Elle yazılan döngüde hangi yönde toplandığı döngü satırlarından okunur; dizi
arayüzünde tek bir tamsayıdan okunur ve o tamsayı çoğu zaman varsayılan olarak bırakılır. Bu ders
kapsamında eksen açıkça yazılıyor; bir sonraki derse kadar tutulacak kural şudur: **eksen
yazılmayan bir toplulaştırma, yönü seçilmemiş bir toplulaştırmadır.**

#### Dizinin Boş Değeri Yoktur

```python
# bos.py — MODELDIR: dizinin "bos" degeri yok. 581 bos hucre bir sayiya donusmek
# zorunda ve hangi sayiya donustugu bir karar. Uc karar, uc ortalama.
KARAR = [
    ("sifirla doldur", lambda v: [0.0 if x is None else x for x in v]),
    ("nobetci -1", lambda v: [-1.0 if x is None else x for x in v]),
    ("yalniz dolu hucre", lambda v: [x for x in v if x is not None]),
]
z = gen([24, 12, 16, 12])
print(z(["581 bos hucrenin yeri", "oge sayisi", "ortalama (m3)", "40 m3 ustu"]))
for ad, f in KARAR:
    a = Dizi(f(OKUMA), (len(f(OKUMA)), 1))
    print(z([ad, a.oge, f"{a.toplam()/a.oge:.6f}", a.ustunde(40.0)]))

TIP = [("16 bit tamsayi", "h"), ("32 bit tamsayi", "i"),
       ("32 bit kesirli", "f"), ("64 bit kesirli", "d")]
LITRE = [int(round((0.0 if x is None else x) * 1000)) for x in OKUMA]
z2 = gen([18, 10, 10, 30])
print("\n" + z2(["oge tipi", "bayt/oge", "3780 oge", "temsil araligi"]))
for ad, t in TIP:
    b = array(t).itemsize
    aralik = (f"-{2**(8*b-1)} .. {2**(8*b-1)-1}" if t in ("h", "i")
              else ("7 anlamli basamak" if t == "f" else "15 anlamli basamak"))
    print(z2([ad, b, b * DUZ.oge, aralik]))

TOPLAM = DUZ.toplam()
kucuk = array("f", [TOPLAM])[0]
print(f"\nen buyuk okuma {max(LITRE)} litre; 16 bit tamsayi bu degeri tasimaz")
print(f"kumenin toplami {TOPLAM:.2f} m3; 32 bit kesirli tipte {kucuk} olarak tutulur, "
      f"fark {abs(kucuk-TOPLAM):.5f} m3 ve 01. dersin 0,0005 m3 esiginin ustunde")
```

```
581 bos hucrenin yeri     oge sayisi   ortalama (m3)  40 m3 ustu
sifirla doldur                  3780       26.993180        1116
nobetci -1                      3780       26.839476        1116
yalniz dolu hucre               3199       31.895661        1116

oge tipi            bayt/oge  3780 oge                temsil araligi
16 bit tamsayi             2      7560               -32768 .. 32767
32 bit tamsayi             4     15120     -2147483648 .. 2147483647
32 bit kesirli             4     15120             7 anlamli basamak
64 bit kesirli             8     30240            15 anlamli basamak

en buyuk okuma 60000 litre; 16 bit tamsayi bu degeri tasimaz
kumenin toplami 102034.22 m3; 32 bit kesirli tipte 102034.21875 olarak tutulur, fark 0.00125 m3 ve 01. dersin 0,0005 m3 esiginin ustunde
```

Üç satır üç savunulabilir karardır ve ortalamayı 26,839476 ile 31,895661 m³ arasında oynatır.
**Sıfırla doldurmak** eksik okumayı "hiç su kullanılmadı" saymaktır ve payı büyütmeden paydayı
büyüttüğü için ortalamayı 4,90 m³ düşürür. **Nöbetçi değer** eksik hücreyi işaretlemek için
seçilir, ama işaretin dışlanması unutulursa toplamdan 581 m³ düşer ve ortalama daha da iner.
**Yalnız dolu hücre** kararı payda olarak 3.199'u kullanır ve önceki kursun ölçtüğü sayıyı verir.
Üçünün de bir gerekçesi vardır; gerekçesi olmayan tek şey, kararın hiç yazılmamasıdır.

Dördüncü sütun bu kararın nerede görünmediğini gösteriyor. **40 m³ üstü hücre sayısı üç kararda da
1.116'dır**, çünkü üç doldurma değeri de eşiğin altındadır. Bir denetim yalnız bu sayıya bakıyorsa
üç kararı birbirinden ayıramaz. Kararın oynattığı sayı ile kararı görünmez kılan sayı aynı
tablodadır ve hangisinin rapor edildiği kararın kendisi kadar önemlidir.

Elle yazılan sürümün bu kararı vermek zorunda kalmadığına dikkat etmek gerekir. İç içe listelerde
boş hücre `None` olarak durur; bu değer bir sayı değildir, toplamaya girmez ve `is not None`
denetimi onu döngünün içinde açıkça dışarıda bırakır. Karar oradadır ama **görünürdür**, çünkü her
döngüde tek tek yazılmıştır. Dizide karar hiç yazılmaz; diziyi kuran satırda bir doldurma değeri
seçilir ve o değer, bir daha hiç anılmadan, bütün özetlere karışır. Soyutlamanın gizlediği şey
kararın kendisi değil, kararın **nerede alındığıdır**.

Alt tablo öğe tipi kararını ölçüyor. Aynı 3.780 öğe, seçilen tipe göre 7.560 ile 30.240 bayt
arasında yer tutar; dörde bir fark bellekten değil, temsil aralığından ödenir. En büyük okuma
60.000 litredir ve 16 bit tamsayının 32.767 sınırını aşar — bu tipi seçmek dört kat bellek kazancı
değil, sessiz bir taşmadır. 32 bit kesirli tip taşmaz ama yaklaşık yedi anlamlı basamak tutar;
kümenin 102.034,22 m³ toplamı sekiz anlamlı basamak gerektirdiği için 102.034,21875 olarak
saklanır. Fark 0,00125 m³ ve önceki dersin 0,0005 m³ eşiğinin üstünde, yani bu tip seçimi tek
başına iki koşumu farklı kılmaya yeter.

#### Özet

- Bir dizi arayüzü bitişik belleği, tek öğe tipini ve biçimi birlikte sabitler; biçim burada
  (1260, 3) ve öğe sayısı 3.780'dir.
- Aynı üç soru elle 22 satır, dizi arayüzünde 2 satır tutar; ayrılan bayt 188.864'ten 30.320'ye,
  öğe başına 49,96'dan 8,02 bayta iner.
- Geçiş sayısı iki yolda da 3'tür: soyutlama döngüyü çağrı yerinden kaldırır, veriden kaldırmaz.
- Dizinin boş değeri olmadığı için 581 boş hücre bir sayıya dönüşür; ortalama sıfır doldurmada
  26,993180, nöbetçi değerde 26,839476, yalnız dolu hücrede 31,895661 m³ çıkar.
- Aynı üç karar 40 m³ üstü hücre sayısını hiç değiştirmez (1.116); öğe tipi seçimi ise 3.780 öğeyi
  7.560 ile 30.240 bayt arasında tutar ve 16 bit tamsayı 60.000 litrelik okumayı taşımaz.

#### Sonraki Adım

Ölçü birimi kuruldu ve üç sayıdan biri beklenenin dışında çıktı: geçiş sayısı düşmedi. Sonraki
ders tam bu sayıyı ele alır. Aynı türetme işi tek bir döngüde bir geçişle yapılabilir ya da
vektörleştirilmiş çağrıların zinciri olarak yazılabilir; ikinci yol her çağrıda bir geçiş ve bir
ara dizi ekler. Aynı ders vektörleştirmenin sessizce verdiği iki kararı da sayar: öğe tipinin
yükseltilmesi ve dar bir tipte taşma. Bu dersin son tablosunda 16 bit tamsayının 60.000 litrelik
okumayı taşımadığı görüldü; sonraki ders bu sınırın aşıldığında ne olduğunu ölçer.

### Vektörleştirilmiş İşlemler

Source: https://academia.sh/tr/kurslar/python-ile-analiz/vektorlestirilmis-islemler

Aynı türetme işi tek döngüyle ve vektörleştirilmiş çağrı zinciriyle yapılır: satır sayısı 10'dan 2'ye düşerken geçiş sayısı 1'den 3'e, ayrılan bayt 30.320'den 90.960'a çıkar; zincirdeki her halka bir geçiş ve bir ara dizi ekler. Çağrının sessizce verdiği üç karar sayılır: tek bir kesirli sabit zinciri 64 bite taşır ve ayrılan baytı 30.400'den 60.720'ye çıkarır, 16 bit öğe tipi 3.780 öğenin 1.538'ini kaydırarak 102.034,22 m³ toplamı 1.239,85 m³ gösterir, nöbetçi değerle işaretlenmiş 581 boş hücrenin ilk işlemden sonra 0'ı ayırt edilebilir kalır ve aşımı sıfır görünen hücre sayısı 1.783'e çıkar.

Önceki ders üç ölçüden birinin beklenenin dışında çıktığını gösterdi: satır sayısı on birde bire
indi, ayrılan bayt altıda bire indi, ama geçiş sayısı hiç düşmedi. Bu ders o sayıyı ele alır ve
daha kötü bir haberle başlar — vektörleştirilmiş bir zincirde geçiş sayısı düşmez, **artar**.

İşin kendisi önceki kursun türetilmiş değişken adımından geliyor: her hücrenin ikinci tarife
basamağının üstünde kalan payı hesaplanacak. Okumadan 25 m³ çıkarılır, negatif sonuçlar sıfırda
kırpılır, kalan litreye çevrilir. Üç işlem, tek bir sonuç. İki yolla yazılır: tek bir döngüde
kaynaşmış olarak ve üç vektörleştirilmiş çağrının zinciri olarak.

- **SH5.** Vektörleştirilmiş bir çağrı döngüyü **yazmaktan** kurtarır, **geçişten** kurtarmaz.
  Zincirdeki her halka veriyi bir kez tarar ve sonucu yeni bir dizide tutar; kazanç satırda,
  bedel geçişte ve ayrılan bayttadır.
- **SH6.** Vektörleştirilmiş bir işlem öğe tipini kendi seçer. Bu seçim üç biçimde sayıyı oynatır:
  tipi yükselterek baytı büyütür, dar bir tipte taşarak sonucu küçültür, nöbetçi değeri sıradan
  bir sayıya çevirerek eksik hücreyi ayırt edilemez kılar.

#### Bir İş, İki Yol

```python
# vektor.py — MODELDIR: vektorlestirilmis islem, ogeyi tek tek gezmeyen bir cagri
# olarak yaziliyor. Her cagri veriyi bir kez tarar (bir gecis) ve sonucu yeni bir
# dizide tutar (bir ara dizi). Kume K03'un hazirladigi kurgu kumedir.
import inspect
import sys
from array import array

def uretec(tohum):
    s = tohum & 0xFFFFFFFF
    def sonraki():
        nonlocal s
        s = (1664525 * s + 1013904223) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki

ABONE, DONEM = 1260, 3
r = uretec(20260218)
HAM = [round(4 + r() * 56, 2) for _ in range(ABONE * DONEM)]
r2 = uretec(9110723)
p = [r2() for _ in range(ABONE * DONEM)]
BOS = set(sorted(range(len(p)), key=lambda j: p[j])[:581])
OKUMA = [0.0 if j in BOS else HAM[j] for j in range(ABONE * DONEM)]

GECIS = 0   # veri uzerinden bastan sona bir tam tarama
ARA = 0     # sonuc ve ara dizilerin ayirdigi bayt

class Dizi:
    def __init__(self, veri, tip="d"):
        self.d, self.tip = array(tip, veri), tip

def islem(a, f, tip=None):
    global GECIS, ARA
    GECIS += 1
    c = Dizi([f(x) for x in a.d], tip or a.tip)
    ARA += sys.getsizeof(c.d)
    return c

UST = 25.0        # ikinci tarife basamaginin ust siniri, m3
LITRE = 1000      # m3 -> litre

def elle(veri):
    global GECIS, ARA
    cikti = []
    for x in veri:
        f = x - UST
        cikti.append((f if f > 0.0 else 0.0) * LITRE)
    GECIS += 1
    c = Dizi(cikti)
    ARA += sys.getsizeof(c.d)
    return c

cikar = lambda a, s: islem(a, lambda x: x - s)
kirp = lambda a, alt: islem(a, lambda x: x if x > alt else alt)
carp = lambda a, s: islem(a, lambda x: x * s)

def zincirle(a):
    return carp(kirp(cikar(a, UST), 0.0), LITRE)

A = Dizi(OKUMA)
GECIS, ARA = 0, 0
E = elle(A.d); ge, ae = GECIS, ARA
GECIS, ARA = 0, 0
V = zincirle(A); gv, av = GECIS, ARA
satir = lambda f: len(inspect.getsource(f).strip().splitlines())
gen = lambda w: lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                                  for j, v in enumerate(h))
y = gen([22, 18, 20, 10])
print(f"is: (okuma - {UST} m3) degeri sifirda kirpilir ve litreye cevrilir; {len(OKUMA)} oge\n")
print(y(["olcu", "elle tek dongu", "vektorlestirilmis", "oran"]))
print(y(["satir sayisi", satir(elle), satir(zincirle), f"{satir(elle)/satir(zincirle):.1f}x"]))
print(y(["gecis sayisi", ge, gv, f"{gv/ge:.1f}x"]))
print(y(["ayrilan bayt", ae, av, f"{av/ae:.1f}x"]))
esit = sum(1 for x, z in zip(E.d, V.d) if abs(x - z) < 1e-9)
print(f"\nsonuc ayni: {esit}/{len(OKUMA)} oge 1e-9 litre esiginin altinda ayni "
      f"(kayan noktali karsilastirma == ile yapilmaz; esik litre cozunurlugunun altindadir)")
print(f"asim toplami {sum(V.d)/LITRE:.2f} m3")
```

```
is: (okuma - 25.0 m3) degeri sifirda kirpilir ve litreye cevrilir; 3780 oge

olcu                      elle tek dongu   vektorlestirilmis      oran
satir sayisi                          10                   2      5.0x
gecis sayisi                           1                   3      3.0x
ayrilan bayt                       30320               90960      3.0x

sonuc ayni: 3780/3780 oge 1e-9 litre esiginin altinda ayni (kayan noktali karsilastirma == ile yapilmaz; esik litre cozunurlugunun altindadir)
asim toplami 34707.55 m3
```

İki yol da aynı 3.780 sayıyı üretiyor. Karşılaştırma litre çözünürlüğünün çok altında bir eşikle,
1e-9 litre ile yapıldı; kayan noktalı iki değer `==` ile karşılaştırılmaz, çünkü aynı matematiksel
sonuca farklı sırada varan iki hesap son basamakta ayrışabilir. Eşik burada anlamlı bir farkı
gizlemez: 1e-9 litre bir sayacın okuyabileceği en küçük değerin milyarda biridir.

Kazanç satırdadır ve gerçektir. Elle yazılan sürüm 10 satır tutuyor, zincir 2 satır. Fark yalnız
uzunluk değil: elle yazılan sürümde döngü sınırı, biriktirme listesi ve kırpma koşulu aynı yerde
iç içedir; üç işlemden birini değiştirmek döngünün gövdesine dokunmayı gerektirir. Zincirde üç
halka birbirinden bağımsızdır ve `cikar`, `kirp`, `carp` bir kez yazılıp her yerde kullanılır.

Bedel sonraki iki satırdadır ve çoğu zaman hesaba katılmaz. Zincir üç halkadan oluştuğu için veri
**üç kez** taranır; elle yazılan döngü bir kez tarar. Her halka sonucunu yeni bir dizide tuttuğu
için 3.780 öğelik üç dizi ayrılır: 90.960 bayt. Bunun 60.640 baytı **ara diziler**, yani
kullanıldıktan sonra atılan iki geçici sonuçtur. Elle yazılan sürüm yalnız çıktı dizisini ayırır:
30.320 bayt. Küçük kümelerde bu fark görünmez; ölçek büyüdüğünde zincirin uzunluğu doğrudan bellek
tepesine yazılır, çünkü ara dizilerin her biri girdi kadar yer tutar.

Buradan kursun sürekli kuralına bağlanan bir okuma çıkıyor: zincirdeki halka sayısı, çağrıyı yazan
kişinin verdiği ama çoğu zaman farkında olmadığı bir karardır. Üç halkalık bir zincir üç geçiş,
beş halkalık bir zincir beş geçiş demektir ve kod satırı ikisinde de aynı kalır. **Zincirin
uzunluğu kodda görünür, bedeli görünmez.**

#### Zinciri Kaynaştırmak

```python
# kaynasik.py — MODELDIR: ucuncu yol. Zincirin uc halkasi tek bir vektorlestirilmis
# cagriya kaynastiriliyor; dongu yine yazilmiyor ama veri bir kez taraniyor.
def kaynasik(a):
    return islem(a, lambda x: (x - UST if x > UST else 0.0) * LITRE)

GECIS, ARA = 0, 0
K = kaynasik(A); gk, ak = GECIS, ARA
w = gen([20, 10, 12, 12, 18])
print(w(["olcu", "elle", "zincir", "kaynasik", "en dusugu"]))
print(w(["satir sayisi", satir(elle), satir(zincirle), satir(kaynasik), "zincir / kaynasik"]))
print(w(["gecis sayisi", ge, gv, gk, "elle / kaynasik"]))
print(w(["ayrilan bayt", ae, av, ak, "elle / kaynasik"]))
print(w(["yeniden kullanilir", 0, 3, 0, "zincir"]))
sapan = sum(1 for x, z in zip(V.d, K.d) if abs(x - z) > 1e-9)
print(f"\nuc yol da ayni sonucu veriyor: {sapan} oge 1e-9 litre esiginin ustunde sapiyor")
print(f"kaynasik yol zincirle ayni satirda, elle ile ayni gecis ve bayttadir; bedeli "
      f"uc kararin tek ifadeye girmesi ve yeniden kullanilabilir parca sayisinin 3'ten 0'a inmesi")
```

```
olcu                      elle      zincir    kaynasik         en dusugu
satir sayisi                10           2           2 zincir / kaynasik
gecis sayisi                 1           3           1   elle / kaynasik
ayrilan bayt             30320       90960       30320   elle / kaynasik
yeniden kullanilir           0           3           0            zincir

uc yol da ayni sonucu veriyor: 0 oge 1e-9 litre esiginin ustunde sapiyor
kaynasik yol zincirle ayni satirda, elle ile ayni gecis ve bayttadir; bedeli uc kararin tek ifadeye girmesi ve yeniden kullanilabilir parca sayisinin 3'ten 0'a inmesi
```

Üçüncü yol zincirin bedelinin kaçınılmaz olmadığını gösteriyor. Üç halkanın işi tek bir
vektörleştirilmiş çağrının içine kaynaştırıldığında satır sayısı zincirdekiyle aynı kalıyor, geçiş
sayısı elle yazılan döngüdeki gibi 1'e, ayrılan bayt 30.320'ye iniyor. Üç ölçünün de en düşüğü
burada.

Bedelin nerede olduğu son satırda duruyor. Zincirde `cikar`, `kirp` ve `carp` bağımsız üç parçadır
ve başka bir hesapta başka sırayla yeniden kullanılır. Kaynaştırılmış ifadede aynı üç karar tek bir
gövdeye girer ve dışarıdan alınamaz; yeniden kullanılabilir parça sayısı 3'ten 0'a iner. Tarife
eşiği bir basamak kayarsa zincirde yalnız çağrının bir parametresi değişir, kaynaştırılmış ifadede
gövde yeniden yazılır.

Ölçünün kursa taşınan biçimi budur: soyutlamanın kazancı üç sayının hepsinde birden görünmez ve
"vektörleştirilmiş yazmak" tek bir şey değildir. **Kaç halka yazıldığı bir karardır** ve o karar
geçiş sayısıyla bellek tepesini birlikte belirler.

#### Çağrının Verdiği Üç Karar

```python
# gizli.py — MODELDIR: vektorlestirilmis cagrinin senin yerine verdigi uc karar.
LITRELIK = [int(round(x * LITRE)) for x in OKUMA]

# 1. Tip yukseltme. Zincirin ilk halkasi tamsayi, ikincisi kesirli bir sabitle carpiyor.
TAM = Dizi(LITRELIK, "i")
GECIS, ARA = 0, 0
kalan = islem(TAM, lambda x: x - 25000, "i")
tam_bayt = ARA + sys.getsizeof(TAM.d)
GECIS, ARA = 0, 0
ucret = islem(islem(TAM, lambda x: x - 25000, "i"), lambda x: x * 0.0042, "d")
kesirli_bayt = ARA + sys.getsizeof(TAM.d)

# 2. Tasma. Bellek kazanmak icin oge tipi 16 bit tamsayi secilirse litre degerleri
# sinira sigmaz. Kaydirma acikca yaziliyor; oge tipinin davranisinin modelidir.
SINIR = 2 ** 15
kaydir = lambda v: ((v + SINIR) % (2 * SINIR)) - SINIR
tasan = [v for v in LITRELIK if v > SINIR - 1]
kayan = [kaydir(v) for v in LITRELIK]

# 3. Nobetci degerin kaybolmasi. 581 bos hucre -1 ile isaretlenmisti.
NOB = Dizi([-1.0 if j in BOS else HAM[j] for j in range(ABONE * DONEM)])
son = carp(kirp(cikar(NOB, UST), 0.0), LITRE)
ayirt = sum(1 for j in BOS if abs(son.d[j]) > 1e-9)
sifir = sum(1 for x in son.d if abs(x) < 1e-9)

z = gen([26, 22, 26])
print(z(["gizlenen karar", "yazilan varsayim", "olculen sonuc"]))
print(z(["tip yukseltme", "tamsayi kalir", f"{tam_bayt} -> {kesirli_bayt} bayt"]))
print(z(["16 bit tasma", "tasma olmaz", f"{len(tasan)}/{len(LITRELIK)} oge kaydi"]))
print(z(["nobetci hucre", "581 hucre isaretli", f"{ayirt}/581 ayirt edilebilir"]))
print(f"\ntip yukseltme: tek bir kesirli sabit zincirin geri kalanini 64 bite tasir, "
      f"ayrilan bayt {kesirli_bayt - tam_bayt} artar; ucret toplami {sum(ucret.d):.2f}")
print(f"tasma: {len(tasan)} oge 32.767 litre sinirini asiyor; toplam "
      f"{sum(LITRELIK)/LITRE:.2f} m3 yerine {sum(kayan)/LITRE:.2f} m3 cikar")
print(f"nobetci: zincirden sonra asimi sifir gorunen hucre {sifir}; bunun 581'i "
      f"gercek sifir degil, isaretlenmis bos hucre")
```

```
gizlenen karar                  yazilan varsayim             olculen sonuc
tip yukseltme                      tamsayi kalir       30400 -> 60720 bayt
16 bit tasma                         tasma olmaz       1538/3780 oge kaydi
nobetci hucre                 581 hucre isaretli    0/581 ayirt edilebilir

tip yukseltme: tek bir kesirli sabit zincirin geri kalanini 64 bite tasir, ayrilan bayt 30320 artar; ucret toplami 31643.72
tasma: 1538 oge 32.767 litre sinirini asiyor; toplam 102034.22 m3 yerine 1239.85 m3 cikar
nobetci: zincirden sonra asimi sifir gorunen hucre 1783; bunun 581'i gercek sifir degil, isaretlenmis bos hucre
```

**Tip yükseltme** zincirin en sessiz kalemidir. Dizi 32 bit tamsayı olarak kuruldu ve ilk halka
tamsayı kaldı; ikinci halka kesirli bir birim fiyatla çarpınca sonuç tamsayıda temsil edilemez ve
bütün dizi 64 bit kesirliye taşınır. Ayrılan bayt 30.400'den 60.720'ye çıkar. Yazılan kodda
büyümeyi gösteren hiçbir işaret yoktur; büyümeye karar veren şey `0.0042` sabitinin kesirli
olmasıdır. Uzun zincirlerde bu, ilk kesirli halkadan sonraki bütün halkaların iki kat yer tutması
demektir.

**Taşma** ters yöndedir. Öğe tipini daraltmak bellek kazandırır: 16 bit tamsayı 3.780 öğeyi 7.560
baytta tutar. Ama litre cinsinden okumaların 1.538'i 32.767 sınırını aşıyor ve bu değerler negatif
tarafa kayıyor. Kümenin toplamı 102.034,22 m³ yerine 1.239,85 m³ çıkar — yüzde 98,8'lik bir kayıp.
Sonuç bir hata iletisiyle değil, makul görünen küçük bir sayıyla gelir; bir denetim yalnız
"toplam pozitif mi" diye bakıyorsa bunu geçirir. Kaydırma davranışı burada açıkça yazıldı, çünkü
öğe tipinin sınırı aşıldığında ne yaptığı tipin kendi kararıdır ve model bunu görünür kılıyor.

**Nöbetçi değer** üçüncü karardır ve önceki dersin bıraktığı yerden devam eder. Boş hücreleri
`-1` ile işaretlemek dizide eksik değer tutmanın bilinen yoludur, ama işaret yalnız **ilk işleme
kadar** yaşar: `-1` değerinden 25 çıkınca -26 olur, kırpma onu sıfıra çeker ve çarpma sıfır
bırakır. Zincirin sonunda 581 hücrenin **hiçbiri** ayırt edilemez. Aşımı sıfır görünen 1.783
hücrenin 581'i aslında hiç okunmamış hücredir ve bu ikisi arasındaki farkı gösterecek hiçbir iz
kalmamıştır. Kırpma işlemi burada iki farklı anlamı tek değere indiriyor: "eşiğin altında kaldı"
ile "ölçülmedi".

Üç kalemin ortak yanı şudur: hiçbiri hata üretmez. Tip yükseltmesi doğru sonucu verir ve yalnız
belleği büyütür, taşma makul görünen bir sayı verir, nöbetçinin kırpılması geçerli bir aşım
değeri verir. Bir soyutlamanın gizlediği kararlar tam olarak bunlardır — çağrının çalıştığı ve
sonuç ürettiği durumlarda alınan kararlar. Bu yüzden kursun kuralı bir çağrının doğru sonuç verip
vermediğine değil, **varsayılanının yazılıp yazılmadığına** bakar.

#### Özet

- Vektörleştirilmiş zincir satır sayısını 10'dan 2'ye indirir, geçiş sayısını 1'den 3'e ve ayrılan
  baytı 30.320'den 90.960'a çıkarır; bunun 60.640 baytı ara dizidir.
- Zincirdeki halka sayısı geçiş sayısına birebir yazılır; kod satırı sabit kalır, bedel kodda
  görünmez.
- Kesirli bir sabit zincirin geri kalanını 64 bite taşır ve ayrılan baytı 30.400'den 60.720'ye
  çıkarır.
- 16 bit öğe tipi 3.780 öğenin 1.538'ini kaydırır ve 102.034,22 m³ toplamı 1.239,85 m³ gösterir;
  sonuç hata değil, makul görünen bir sayıdır.
- Nöbetçi değerle işaretlenmiş 581 boş hücrenin ilk işlemden sonra 0'ı ayırt edilebilir kalır;
  aşımı sıfır görünen hücre 1.783'e çıkar ve "eşiğin altında" ile "ölçülmedi" tek değere iner.
- Üç halka tek çağrıya kaynaştırıldığında geçiş 1'e, bayt 30.320'ye iner ve satır sayısı değişmez;
  bedeli yeniden kullanılabilir parça sayısının 3'ten 0'a inmesidir.

#### Sonraki Adım

Bu derste bütün işlemler tek bir biçim üzerinde yapıldı: 3.780 öğelik bir dizi ile bir sayı
arasında. Gerçek hesaplarda iki dizi karşılaşır ve biçimleri çoğu zaman aynı olmaz — dönem başına
bir taban, abone başına bir hane sayısı, tarife basamağı başına bir birim fiyat. Sonraki ders
farklı biçimlerin nasıl uyumlu sayıldığını ve uyumsuz iki biçimin neden hata vermek yerine
beklenmedik bir dizi ürettiğini ölçer.

### Yayılım

Source: https://academia.sh/tr/kurslar/python-ile-analiz/yayilim

Yayılım kuralı standart kitaplıkla modellenir ve sekiz biçim çifti üzerinde koşturulur: 4 çift doğru sonuç, 2 çift hata, 2 çift sessizce beklenmedik biçimde bir dizi üretir. Sessiz durumun bedeli sayılır: (1260, 1) ile (1, 1260) çarpımı 1.260 öğe yerine 1.587.600 öğe verir, ayrılan bayt 10.160'tan 12.700.880'e çıkar, öğelerin yalnız 1.260'ı doğrudur ve raporlanan kişi başı ortalama 36,938180 yerine 36,497564 m³ olur. İkinci sessiz durumda dönem tabanı 3 değer yerine 9 değer üretir ve düzeltilmiş taban toplamı 83,901904 m³ yerine 251,846368 m³ çıkar.

Önceki derste bütün işlemler bir dizi ile bir sayı arasında yapıldı ve biçim hiç değişmedi.
Gerçek hesaplarda iki dizi karşılaşır ve biçimleri çoğu zaman aynı olmaz: 3.780 hücrelik okuma
tablosunun yanında dönem başına bir taban (3 değer), abone başına bir hane sayısı (1.260 değer)
ve tarife basamağı başına bir birim fiyat (3 değer) durur. Bunları teker teker döngüyle
eşleştirmek yerine dizi arayüzü **yayılım** kuralını uygular: küçük biçim, büyük biçme uyacak
şekilde tekrarlanmış sayılır.

Kural iki satırda anlatılır ve tam olarak iki satır olduğu için tehlikelidir. Biçimler **sağdan
hizalanır**; hizalanan her boyutta ya iki sayı eşittir ya da biri 1'dir. Bu koşul sağlanırsa işlem
yapılır. Koşulun sağlanması işlemin **doğru** olduğu anlamına gelmez, yalnız **mümkün** olduğu
anlamına gelir. Bu dersin ölçtüğü şey aradaki farktır.

- **SH7.** Yayılım kuralı biçimlerin uyumunu denetler, işlemin anlamını denetlemez. Kural
  sağlandığında hesap yapılır ve hiçbir uyarı üretilmez.
- **SH8.** Uyumsuz bir biçim çifti iki sonuçtan birini verir: bir hata ya da beklenmedik
  büyüklükte bir dizi. Birincisi hesabı durdurur, ikincisi hesabı sürdürür ve öğe sayısını binlerce
  kat büyütebilir.

#### Kuralı Yazmak

```python
# yayilim.py — MODELDIR: yayilim kurali standart kitaplikla yaziliyor. Kural:
# bicimler sagdan hizalanir; her boyutta ya iki sayi esittir ya da biri 1'dir.
# Kural saglanirsa islem yapilir. Kutuphane cagrilmiyor.
import math

def yayilim_bicimi(b1, b2):
    n = max(len(b1), len(b2))
    s1 = (1,) * (n - len(b1)) + tuple(b1)
    s2 = (1,) * (n - len(b2)) + tuple(b2)
    c = []
    for x, y2 in zip(s1, s2):
        if x == y2 or x == 1 or y2 == 1:
            c.append(max(x, y2))
        else:
            return None
    return tuple(c)

oge = lambda b: 0 if b is None else math.prod(b)
CIFT = [
    ("okuma x tek katsayi",        (1260, 3), (1,),        3780),
    ("okuma x donem tabani",       (1260, 3), (3,),        3780),
    ("okuma x abone hane sayisi",  (1260, 3), (1260, 1),   3780),
    ("okuma x abone (duz yazim)",  (1260, 3), (1260,),     3780),
    ("okuma x donem (iki boyut)",  (1260, 3), (1, 3),      3780),
    ("abone toplami x hane satiri", (1260, 1), (1, 1260),  1260),
    ("donem tabani x donem duzeltme", (3,),   (3, 1),      3),
    ("okuma x iki basamak",        (1260, 3), (2,),        3780),
]
gen = lambda w: lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                                  for j, v in enumerate(h))
y = gen([32, 12, 11, 13, 11, 11, 9])
print(y(["islem", "bicim 1", "bicim 2", "sonuc bicimi", "oge", "beklenen", "sonuc"]))
hata = sessiz = dogru = 0
for ad, b1, b2, bek in CIFT:
    c = yayilim_bicimi(b1, b2)
    if c is None:
        d, hata = "hata", hata + 1
    elif oge(c) != bek:
        d, sessiz = "sessiz", sessiz + 1
    else:
        d, dogru = "dogru", dogru + 1
    print(y([ad, str(b1), str(b2), str(c) if c else "-", oge(c), bek, d]))
print(f"\n{len(CIFT)} cift: {dogru} dogru, {hata} hata, {sessiz} sessizce beklenmedik bicim")
```

```
islem                                bicim 1    bicim 2 sonuc bicimi        oge   beklenen    sonuc
okuma x tek katsayi                (1260, 3)       (1,)    (1260, 3)       3780       3780    dogru
okuma x donem tabani               (1260, 3)       (3,)    (1260, 3)       3780       3780    dogru
okuma x abone hane sayisi          (1260, 3)  (1260, 1)    (1260, 3)       3780       3780    dogru
okuma x abone (duz yazim)          (1260, 3)    (1260,)            -          0       3780     hata
okuma x donem (iki boyut)          (1260, 3)     (1, 3)    (1260, 3)       3780       3780    dogru
abone toplami x hane satiri        (1260, 1)  (1, 1260) (1260, 1260)    1587600       1260   sessiz
donem tabani x donem duzeltme           (3,)     (3, 1)       (3, 3)          9          3   sessiz
okuma x iki basamak                (1260, 3)       (2,)            -          0       3780     hata

8 cift: 4 dogru, 2 hata, 2 sessizce beklenmedik bicim
```

İlk üç satır kuralın işe yaradığı yerlerdir ve kazanç gerçektir. Dönem başına taban çıkarmak elle
yazıldığında iki iç içe döngü ve bir dizin hesabı tutar; yayılımla tek işlemdir ve 3 değerlik
taban 1.260 kez tekrarlanmış gibi davranır — tekrarlanan kopya bellekte oluşturulmadan. Abone
başına hane sayısı da aynı şekilde, biçim (1260, 1) yazıldığı sürece, doğru yönde yayılır.

Dördüncü satır kuralın **koruduğu** yerdir. Abone başına hane sayısı düz bir dizi olarak,
yani (1260,) biçiminde yazılırsa sağdan hizalama son boyutları karşılaştırır: 3 ile 1.260. İkisi
de birbirine eşit değil ve hiçbiri 1 değil, dolayısıyla kural sağlanmaz ve işlem yapılmaz. Bu bir
kusur değil, kuralın tek gerçek güvencesidir: **yanlış eksende hizalama girişimi çoğu zaman
durdurulur.** Sekizinci satır da aynı türdendir; iki basamaklık bir katsayı dizisi üç dönemlik bir
tabloya yayılamaz.

Altıncı ve yedinci satırlar ise durdurulmaz. Kural sağlanır, işlem yapılır ve sonuç beklenenden
başka bir biçimdedir. Sekiz çiftin ikisi bu durumda ve ikisinin de ortak yanı şudur: yazan kişi
biçimlerden **birini** transpoze yazmıştır. Bir sonraki bölüm bunun bedelini sayıyor.

#### Sessizce Üretilen Sonuç

```python
# sessiz.py — MODELDIR: sessizce uretilen sonucun bedeli. Abone basina toplam
# tuketim, abone basina hane sayisina bolunuyor. Hane sayisi kurgudur.
import sys
from array import array

def uretec(tohum):
    s = tohum & 0xFFFFFFFF
    def sonraki():
        nonlocal s
        s = (1664525 * s + 1013904223) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki

ABONE, DONEM = 1260, 3
r = uretec(20260218)
HAM = [round(4 + r() * 56, 2) for _ in range(ABONE * DONEM)]
r2 = uretec(9110723)
p = [r2() for _ in range(ABONE * DONEM)]
BOS = set(sorted(range(len(p)), key=lambda j: p[j])[:581])
OKUMA = [0.0 if j in BOS else HAM[j] for j in range(ABONE * DONEM)]
r3 = uretec(31081977)
HANE = [1 + int(r3() * 5) for _ in range(ABONE)]
TOPLAM = [sum(OKUMA[i*DONEM:(i+1)*DONEM]) for i in range(ABONE)]

# Dogru yazim: (1260, 1) ile (1260, 1)
DOGRU = array("d", [TOPLAM[i] / HANE[i] for i in range(ABONE)])
# Sessiz yazim: (1260, 1) ile (1, 1260) -> (1260, 1260)
YANLIS = array("d", [TOPLAM[i] / HANE[j] for i in range(ABONE) for j in range(ABONE)])
kosegen = [YANLIS[i * ABONE + i] for i in range(ABONE)]
dogru_oge = sum(1 for i in range(ABONE) if abs(kosegen[i] - DOGRU[i]) < 1e-9)
z = gen([26, 18, 20, 12])
print(z(["olcu", "dogru yazim", "sessiz yazim", "oran"]))
print(z(["sonuc bicimi", "(1260, 1)", "(1260, 1260)", "-"]))
print(z(["oge sayisi", len(DOGRU), len(YANLIS), f"{len(YANLIS)//len(DOGRU)}x"]))
print(z(["ayrilan bayt", sys.getsizeof(DOGRU), sys.getsizeof(YANLIS),
         f"{sys.getsizeof(YANLIS)//sys.getsizeof(DOGRU)}x"]))
print(z(["kisi basi ortalama", f"{sum(DOGRU)/len(DOGRU):.6f}", f"{sum(YANLIS)/len(YANLIS):.6f}",
         "-"]))
print(f"\nsessiz dizinin {len(YANLIS)} ogesinin {dogru_oge}'i dogru (kosegen), "
      f"{len(YANLIS)-dogru_oge} ogesi bir abonenin tuketimini baska bir abonenin hanesine boluyor")
print(f"raporlanan kisi basi ortalama {sum(DOGRU)/len(DOGRU):.6f} yerine "
      f"{sum(YANLIS)/len(YANLIS):.6f} m3, fark {abs(sum(YANLIS)/len(YANLIS)-sum(DOGRU)/len(DOGRU)):.6f} m3")

# Ikinci sessiz durum: donem tabani (3,) ile mevsim duzeltmesi (3, 1).
DONEM_ORT = [sum(OKUMA[i*DONEM+d] for i in range(ABONE)) / ABONE for d in range(DONEM)]
DUZELTME = [0.98, 1.03, 1.10]   # kurgu mevsim duzeltmesi
d3 = [DONEM_ORT[d] * DUZELTME[d] for d in range(DONEM)]
s3 = [DONEM_ORT[d] * DUZELTME[e] for e in range(DONEM) for d in range(DONEM)]
print(f"\nikinci sessiz durum: donem tabani (3,) ile duzeltme (3, 1) carpiminda sonuc "
      f"{len(d3)} deger yerine {len(s3)} deger")
print(f"duzeltilmis taban toplami {sum(d3):.6f} m3 yerine {sum(s3):.6f} m3; "
      f"bicim buyudugu icin toplam {sum(s3)/sum(d3):.4f} katina cikti")
```

```
olcu                             dogru yazim        sessiz yazim        oran
sonuc bicimi                       (1260, 1)        (1260, 1260)           -
oge sayisi                              1260             1587600       1260x
ayrilan bayt                           10160            12700880       1250x
kisi basi ortalama                 36.938180           36.497564           -

sessiz dizinin 1587600 ogesinin 1260'i dogru (kosegen), 1586340 ogesi bir abonenin tuketimini baska bir abonenin hanesine boluyor
raporlanan kisi basi ortalama 36.938180 yerine 36.497564 m3, fark 0.440616 m3

ikinci sessiz durum: donem tabani (3,) ile duzeltme (3, 1) carpiminda sonuc 3 deger yerine 9 deger
duzeltilmis taban toplami 83.901904 m3 yerine 251.846368 m3; bicim buyudugu icin toplam 3.0017 katina cikti
```

Hesap şudur: her abonenin üç dönemlik toplam tüketimi, o abonenin hane sayısına bölünecek. Doğru
sonuç 1.260 değerdir. Hane sayısı dizisi satır olarak yazılınca, yani (1260, 1) yerine (1, 1260)
biçiminde, kural yine sağlanır — birinci boyutta 1.260 ile 1, ikinci boyutta 1 ile 1.260 karşılaşır
ve her ikisinde de taraflardan biri 1'dir. Sonuç **1.587.600 öğelik** bir dizidir ve içinde her
abonenin tüketimi her abonenin hane sayısına bölünmüş halde durur.

Bedelin üç ayrı ölçüsü var. Öğe sayısı 1.260 kat, ayrılan bayt 10.160'tan 12.700.880'e, yani 1.250
kat büyür; belleğe sığdığı sürece bu büyüme hiçbir uyarı üretmez ve tam da sığdığı için fark
edilmez. Doğruluk ölçüsü daha keskin: 1.587.600 öğenin yalnız **1.260'ı** doğrudur, bunlar da
köşegen üzerindekilerdir. Kalan **1.586.340 öğe** bir abonenin tüketimini başka bir abonenin
hanesine bölmektedir, yani hiçbir gerçek karşılığı yoktur.

Raporlanan sayı ise bu iki ölçünün hiçbirini yansıtmaz. Kişi başı ortalama tüketim doğru dizide
36,938180 m³, sessiz dizide 36,497564 m³ çıkar; aradaki fark 0,440616 m³, yaklaşık yüzde 1,2. Bu
yayılımın en tehlikeli yanıdır: **yanlış sonuç makul görünür.** Bir bulgu 36,5 ile 36,9 arasında
oynadığında kimse biçim kontrolü yapmayı düşünmez. Sayının kendisi hatayı ele vermez; hatayı ele
veren tek şey **öğe sayısıdır** ve o sayı çoğu zaman rapora girmez.

İkinci sessiz durum daha küçüktür ve tam bu yüzden daha sinsidir. Üç dönemin ortalama tabanı üç
mevsim düzeltmesiyle çarpılacak; düzeltme sütun olarak yazılınca sonuç 3 değer yerine 9 değer
olur. Düzeltilmiş taban toplamı 83,901904 m³ yerine 251,846368 m³, yani 3,0017 katı çıkar. Buradaki
çarpan tam olarak 3 değildir ve bu ayrıntı önemlidir: tam bir katsayı görülse "bir yerde üçle
çarpmışım" denip aranabilirdi, 3,0017 ise hesabın kendisinden gelen bir sayı gibi durur.

Bu iki durumdan çıkan çalışma kuralı tek cümledir: **yayılım kullanılan her işlemde sonucun biçimi
yazılır ve beklenen öğe sayısıyla karşılaştırılır.** Kural sağlandığı için sonucun doğru olduğu
varsayılamaz; kuralın sağlanması yalnızca hesabın yapılabildiğini söyler. Yukarıdaki sekiz çiftin
ikisinde hesap yapılmadı ve bu iyi haberdi; ikisinde yapıldı ve kötü haber oydu.

#### Kuralın Kazandırdığı

```python
# kazanc.py — MODELDIR: yayilimin kazanci. Donem basina taban cikarilacak.
# Elle yol tabani 3.780 ogeye kopyalar; yayilim kopyalamaz, dizini hesaplar.
TABAN = array("d", [9.80, 10.40, 11.20])   # kurgu donem tabani, m3

def kopyalayarak(v, t):
    genis = array("d", [t[j % DONEM] for j in range(len(v))])
    return array("d", [v[j] - genis[j] for j in range(len(v))]), sys.getsizeof(genis)

def yayilarak(v, t):
    return array("d", [v[j] - t[j % DONEM] for j in range(len(v))]), 0

K1, ek1 = kopyalayarak(OKUMA, TABAN)
K2, ek2 = yayilarak(OKUMA, TABAN)
sapan = sum(1 for a, b in zip(K1, K2) if abs(a - b) > 1e-9)
w = gen([26, 16, 16, 12])
print(w(["olcu", "kopyalayarak", "yayilarak", "oran"]))
print(w(["taban dizisinin bayti", sys.getsizeof(TABAN), sys.getsizeof(TABAN), "1x"]))
print(w(["ek olarak ayrilan bayt", ek1, ek2, "-"]))
print(w(["toplam ayrilan bayt", ek1 + sys.getsizeof(K1), ek2 + sys.getsizeof(K2),
         f"{(ek1+sys.getsizeof(K1))/(ek2+sys.getsizeof(K2)):.1f}x"]))
print(f"\niki yol ayni sonucu veriyor: {sapan} oge 1e-9 m3 esiginin ustunde sapiyor")
print(f"yayilim 3 degerlik tabani {len(OKUMA)} ogeye kopyalamadan uyguluyor; "
      f"kopyalanan surumde taban {sys.getsizeof(TABAN)} bayt yerine {ek1} bayt tutuyor")
```

```
olcu                          kopyalayarak       yayilarak        oran
taban dizisinin bayti                  104             104          1x
ek olarak ayrilan bayt               30320               0           -
toplam ayrilan bayt                  60640           30320        2.0x

iki yol ayni sonucu veriyor: 0 oge 1e-9 m3 esiginin ustunde sapiyor
yayilim 3 degerlik tabani 3780 ogeye kopyalamadan uyguluyor; kopyalanan surumde taban 104 bayt yerine 30320 bayt tutuyor
```

Kuralın kazancı bu tabloda duruyor ve hesaplanan sonuçla ilgili değildir — iki yol da aynı 3.780
değeri veriyor, sapma 1e-9 m³ eşiğinin üstünde tek öğede bile yok. Fark **kopyalamadadır**. Dönem
tabanını elle uygulamanın en doğrudan yolu, 3 değeri 3.780 öğeye genişletip iki eşit biçimi öğe öğe
çıkarmaktır; bu genişletme 30.320 bayt tutar ve toplam ayrılan bayt 60.640'a çıkar. Yayılım
genişletilmiş diziyi hiç kurmaz; küçük biçimin dizinini büyük biçimden **hesaplar** ve taban 104
bayt olarak kalır. Toplam ayrılan bayt yarıya, 30.320'ye iner.

Oran burada 2 kat çünkü genişletilen taraf sonuç dizisiyle aynı boyda. Küçük dizinin büyük diziye
oranı düştükçe kazanç büyür: aynı işlem 3 dönem yerine 3 tarife basamağıyla ve 100.000 satırlık bir
tabloyla yapılsaydı kopyalanan sürüm iki tam tablo, yayılım tek tablo tutardı. **Yayılımın
kazandırdığı şey hesap değil, kurulmayan dizidir** ve bir önceki bölümdeki bedelin de aynı yerden
geldiğine dikkat etmek gerekir: kurulmayan dizi ucuz olduğu için beklenmedik biçimdeki dizi de
ucuza kurulur ve dikkat çekmeden büyür.

#### Özet

- Yayılım kuralı biçimleri sağdan hizalar ve her boyutta eşitlik ya da 1 arar; kural biçim uyumunu
  denetler, işlemin anlamını denetlemez.
- Sekiz biçim çiftinin 4'ü doğru sonuç, 2'si hata, 2'si sessizce beklenmedik biçimde bir dizi
  üretir.
- Hane sayısı satır olarak yazıldığında sonuç 1.260 öğe yerine 1.587.600 öğe olur; ayrılan bayt
  10.160'tan 12.700.880'e, yani 1.250 kat büyür.
- Sessiz dizinin 1.587.600 öğesinin yalnız 1.260'ı doğrudur; kalan 1.586.340 öğenin gerçek bir
  karşılığı yoktur ve raporlanan kişi başı ortalama 36,938180 yerine 36,497564 m³ çıkar.
- Dönem tabanı düzeltmesi 3 değer yerine 9 değer üretir ve düzeltilmiş taban toplamını
  83,901904 m³'ten 251,846368 m³'e, 3,0017 katına taşır.
- Kuralın kazancı kurulmayan dizidir: dönem tabanını kopyalayarak uygulamak 30.320 bayt ek yer
  ister, yayılım 0 bayt ister ve toplam ayrılan bayt 60.640'tan 30.320'ye iner.

#### Sonraki Adım

Yayılım aynı biçimdeki iki diziyi öğe öğe eşleştirir ve bu, elde edilebilecek en basit çok dizili
işlemdir. Tarife hesabının kendisi ise öğe eşleştirmesi değildir: üç basamağın birim fiyatları ile
üç dönemin tüketimi çarpılıp toplandığında her sonuç değeri **bütün bir satır ile bütün bir
sütunun** birlikte hesabıdır. Sonraki ders bu işlemi — matris çarpımını ve ondan türeyen çözüm
problemini — elle döngüyle ve soyutlamayla yazar, geçiş sayılarını karşılaştırır ve sayısal
duyarlılığın nerede devreye girdiğini ölçer. Biçim uyumu orada da belirleyicidir, ama kural
değişir: yayılımda uyum sağdan hizalamayla aranırken çarpımda tek bir eşitlik aranır ve o eşitlik
sağlanmadığında işlem hiçbir koşulda yapılamaz.

### Temel Doğrusal Cebir İşlemleri

Source: https://academia.sh/tr/kurslar/python-ile-analiz/temel-dogrusal-cebir-islemleri

Matris çarpımında soyutlamanın işlem sayısını değiştirmediği ölçülür: 11.340 çarpma iki yolda da aynı kalır, çağrı yerindeki satır 13'ten 2'ye iner ve 26.460 elle dizin hesabı ortadan kalkar. Çözümde kazanç bir algoritma seçimidir: tanımdan yazılan kofaktör açılımı n = 12 için 823.059.744 işlem ister, eleme 704 işlemle biter, oran 1.169.119 kattır. Gizlenen karar pivotlamadır — işlem sayısı 14'te sabit kalırken pivotsuz eleme ilk katsayı küçüldükçe artığı 8,538e-11'den 7,237e+01'e taşır ve katsayı sıfırlandığında hiç çözüm üretmez. Kötü koşullanmış bir tarife sisteminde girdideki tek bir 0,01 m³ değişiklik ilk basamağın birim fiyatını yüzde 50,09 oynatır.

Önceki ders iki diziyi öğe öğe eşleştiren işlemleri ele aldı: yayılım, aynı konumdaki iki değeri
buluşturur. Tarife hesabının kendisi böyle çalışmaz. Bir abonenin üç dönemlik okuması üç tarife
basamağına dağıtıldığında her sonuç değeri **bir satırın tamamı ile bir sütunun tamamının**
birlikte hesabıdır. Bu ders bu iki işlemi — matris çarpımını ve ondan türeyen çözüm problemini —
ölçer.

Ölçünün konusu cebir değildir; kuram burada anlatılmaz. Ölçülen şey, aynı sonucu üreten iki yazımın
kaç işlem tuttuğu ve soyutlamayı çağırmanın hangi kararı üstlendiğidir. Bu konunun en keskin
sayıları buradan çıkıyor, çünkü çözüm probleminde soyutlamanın kazancı satır sayısı değil,
**algoritma seçimidir**.

- **SH9.** Matris çarpımında soyutlama işlem sayısını değiştirmez. Değiştirdiği şey çağrı
  yerindeki satır sayısı ve elle yazılan dizin hesabının ortadan kalkmasıdır.
- **SH10.** Çözümde soyutlamayı çağırmak bir algoritma seçmektir. Tanımdan yazılan yol ile
  elemenin işlem sayısı aynı büyüklük düzeyinde değildir ve fark boyutla birlikte hızla büyür.
- **SH11.** Aynı işlem sayısıyla iki farklı sonuç alınabilir. Pivotlama kararı artığın
  büyüklüğünü belirler; girdi duyarlılığı ise hesaptan bağımsızdır ve kötü koşullanmış bir
  sistemde ölçüm çözünürlüğü kadar bir fark sonucu yarı yarıya oynatır.

#### Aynı İşlem, Başka Yazım

```python
# carpim.py — MODELDIR: matris carpimi once elle uc dongu ile, sonra tek cagri
# olarak yaziliyor. Okuma tablosu (1260, 3), tarife matrisi (3, 3); sonuc her
# abonenin uc tarife basamagindaki payi. Kume ve tarife kurgudur.
import inspect

def uretec(tohum):
    s = tohum & 0xFFFFFFFF
    def sonraki():
        nonlocal s
        s = (1664525 * s + 1013904223) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki

ABONE, DONEM = 1260, 3
r = uretec(20260218)
HAM = [round(4 + r() * 56, 2) for _ in range(ABONE * DONEM)]
r2 = uretec(9110723)
p = [r2() for _ in range(ABONE * DONEM)]
BOS = set(sorted(range(len(p)), key=lambda j: p[j])[:581])
OKUMA = [0.0 if j in BOS else HAM[j] for j in range(ABONE * DONEM)]
# Tarife matrisi: her donemin okumasinin uc basamaga dagilim orani (10/25/40 m3).
TARIFE = [0.62, 0.28, 0.10,
          0.55, 0.31, 0.14,
          0.48, 0.34, 0.18]

ISLEM = 0   # bir carpma ve bir toplama
DIZIN = 0   # elle yazilan dizin hesabi sayisi

def carp_elle(A, na, ma, B, mb, nb):
    global ISLEM, DIZIN
    C = [0.0] * (na * nb)
    for i in range(na):
        for j in range(nb):
            t = 0.0
            for k in range(ma):
                t += A[i * ma + k] * B[k * nb + j]
                ISLEM += 1
                DIZIN += 2
            C[i * nb + j] = t
            DIZIN += 1
    return C

def carp(A, ba, B, bb):
    return carp_elle(A, ba[0], ba[1], B, bb[0], bb[1])

ISLEM, DIZIN = 0, 0
C1 = carp_elle(OKUMA, ABONE, DONEM, TARIFE, DONEM, DONEM)
i1, d1 = ISLEM, DIZIN
ISLEM, DIZIN = 0, 0
C2 = carp(OKUMA, (ABONE, DONEM), TARIFE, (DONEM, DONEM))
i2, d2 = ISLEM, DIZIN
satir = lambda f: len(inspect.getsource(f).strip().splitlines())
gen = lambda w: lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                                  for j, v in enumerate(h))
y = gen([28, 16, 16, 10])
sapan = sum(1 for a, b in zip(C1, C2) if abs(a - b) > 1e-9)
print(f"(1260, 3) x (3, 3) -> (1260, 3); sonuc {len(C1)} oge, {sapan} oge 1e-9 esiginin "
      f"ustunde sapiyor\n")
print(y(["olcu", "elle uc dongu", "tek cagri", "oran"]))
print(y(["cagri yerinde satir", satir(carp_elle), satir(carp), f"{satir(carp_elle)/satir(carp):.1f}x"]))
print(y(["carpma-toplama sayisi", i1, i2, f"{i1/i2:.1f}x"]))
print(y(["elle dizin hesabi", d1, 0, "-"]))
print(f"\ncarpma sayisi {ABONE} x {DONEM} x {DONEM} = {ABONE*DONEM*DONEM}; soyutlama bu sayiyi "
      f"degistirmez, yalnizca {d1} dizin hesabini cagri yerinden kaldirir")
```

```
(1260, 3) x (3, 3) -> (1260, 3); sonuc 3780 oge, 0 oge 1e-9 esiginin ustunde sapiyor

olcu                           elle uc dongu       tek cagri      oran
cagri yerinde satir                       13               2      6.5x
carpma-toplama sayisi                  11340           11340      1.0x
elle dizin hesabi                      26460               0         -

carpma sayisi 1260 x 3 x 3 = 11340; soyutlama bu sayiyi degistirmez, yalnizca 26460 dizin hesabini cagri yerinden kaldirir
```

Matris çarpımında soyutlamanın kazancı ölçüldüğünde ortaya çıkan sayı bir uyarıdır. Çarpma sayısı
iki yazımda da **11.340** — bu sayı biçimlerden çıkar, yazımdan değil ve hiçbir çağrı onu
düşürmez. Değişen tek şey nerede yazıldığıdır: çağrı yerindeki satır 13'ten 2'ye iner ve **26.460**
elle dizin hesabı ortadan kalkar. `A[i * ma + k]` ile `B[k * nb + j]` ifadelerinin her biri düz
belleği iki boyutlu okumanın elle yazılmış halidir ve bu iki ifadenin birinde çarpanları yer
değiştirmek hiçbir hata üretmez, yalnız başka bir sayı üretir.

Kazancın gerçek adı budur: soyutlama hesabı hızlandırmaz, **yanlış yazılabilecek yer sayısını**
düşürür. Ölçek büyüdüğünde bu değişmez; çarpma sayısı biçimlerin çarpımıdır ve bir kütüphane onu
ancak farklı bir algoritma seçerek değiştirebilir. Bir sonraki bölüm tam olarak bunun yapıldığı
yeri gösteriyor.

#### Soyutlamanın Seçtiği Algoritma

```python
# cozum.py — MODELDIR: ayni denklem takimi iki yolla cozuluyor. Birincisi tanimdan
# yazilir (determinant kofaktor acilimiyla), ikincisi elemeyle. Islem sayaci ikisini
# de sayiyor; kutuphane cagrilmiyor, iki algoritma da acikca yaziliyor.
KOF = 0
ELE = 0

def det_kofaktor(M, n):
    global KOF
    if n == 1:
        return M[0]
    t = 0.0
    for j in range(n):
        alt = [M[i*n+k] for i in range(1, n) for k in range(n) if k != j]
        KOF += 1
        t += (1 if j % 2 == 0 else -1) * M[j] * det_kofaktor(alt, n - 1)
    return t

def coz_eleme(M, b, n, pivotla=True):
    global ELE
    A = [list(M[i*n:(i+1)*n]) + [b[i]] for i in range(n)]
    for k in range(n):
        if pivotla:
            en = max(range(k, n), key=lambda i: abs(A[i][k]))
            A[k], A[en] = A[en], A[k]
        for i in range(k + 1, n):
            c = A[i][k] / A[k][k]
            for j in range(k, n + 1):
                A[i][j] -= c * A[k][j]
                ELE += 1
    x = [0.0] * n
    for i in range(n - 1, -1, -1):
        t = A[i][n]
        for j in range(i + 1, n):
            t -= A[i][j] * x[j]
            ELE += 1
        x[i] = t / A[i][i]
    return x

kofaktor_islem = lambda n: 0 if n <= 1 else n * (1 + kofaktor_islem(n - 1))
rk = uretec(70117)
def kare(n):
    M = [round(1 + rk() * 9, 3) for _ in range(n * n)]
    b = [round(1 + rk() * 9, 3) for _ in range(n)]
    return M, b

w = gen([6, 18, 14, 14, 12])
print(w(["n", "kofaktor islemi", "formul", "eleme islemi", "oran"]))
for n in (2, 3, 4, 5, 6, 7, 8):
    M, b = kare(n)
    KOF, ELE = 0, 0
    det_kofaktor(M, n)
    k = KOF
    coz_eleme(M, b, n)
    print(w([n, k, kofaktor_islem(n), ELE, f"{k/max(ELE,1):.0f}x"]))
for n in (12,):
    M, b = kare(n)
    ELE = 0
    x = coz_eleme(M, b, n)
    art = max(abs(sum(M[i*n+j] * x[j] for j in range(n)) - b[i]) for i in range(n))
    print(w([n, "-", kofaktor_islem(n), ELE, f"{kofaktor_islem(n)/ELE:.0f}x"]))
    print(f"\nn = {n} icin kofaktor acilimi {kofaktor_islem(n)} islem ister; eleme {ELE} islemle "
          f"bitirir, oran {kofaktor_islem(n)/ELE:.0f} kat")
    print(f"elemenin cozumu denklemi sagliyor: en buyuk artik {art:.2e}, "
          f"esik 1e-9 (katsayilar 1e1 buyuklugunde, 64 bit kesirli tipin cozunurlugu 1e-16)")
```

```
n        kofaktor islemi        formul  eleme islemi        oran
2                      2             2             4          0x
3                      9             9            14          1x
4                     40            40            32          1x
5                    205           205            60          3x
6                   1236          1236           100         12x
7                   8659          8659           154         56x
8                  69280         69280           224        309x
12                     -     823059744           704    1169119x

n = 12 icin kofaktor acilimi 823059744 islem ister; eleme 704 islemle bitirir, oran 1169119 kat
elemenin cozumu denklemi sagliyor: en buyuk artik 2.66e-15, esik 1e-9 (katsayilar 1e1 buyuklugunde, 64 bit kesirli tipin cozunurlugu 1e-16)
```

Soru şudur: üç tarife basamağının birim fiyatı bilinmiyor, ama birkaç abone grubunun toplam
faturası ve her grubun basamaklardaki tüketimi biliniyor. Bu bir denklem takımıdır ve iki yolla
çözülür. Tanımdan yazılan yol determinantı kofaktör açılımıyla hesaplar; eleme yolu satırları
birbirinden çıkararak bilinmeyenleri teker teker yalıtır.

Tablo iki yolun aynı büyüklük düzeyinde olmadığını gösteriyor. n = 3'e kadar eleme daha fazla iş
yapar; yön n = 4'te döner ve kofaktör açılımı 40 işlemle elemenin 32 işleminin üstüne çıkar.
n = 6'da oran 12 kat, n = 8'de 309 kattır. Ölçülen sayılar formülle birebir tutuyor — kofaktör
açılımının işlem sayısı $T(n) = n \cdot (1 + T(n-1))$ özyinelemesini izler ve bu, n arttıkça
çarpanın kendisinin de arttığı bir büyümedir. n = 12 için 823.059.744 işlem eder; eleme aynı
sistemi **704** işlemle bitirir. Oran 1.169.119 kat.

Ölçünün kursa taşınan biçimi budur: bir çözüm işlevini çağırmak bir kısayol değil, **bir algoritma
seçimidir**. Kazanç satır sayısında değil, işlem sayısının büyüme biçimindedir. Aynı nedenle,
hangi algoritmanın seçildiğini bilmeden çağrılan bir çözüm işlevi, hangi işlem sayısına razı
olunduğunu bilmeden verilmiş bir karardır.

#### Sayısal Duyarlılık

```python
# duyarlilik.py — MODELDIR: uc abone grubunun toplam faturasindan uc tarife
# basamaginin birim fiyati cozuluyor. Katsayilar her grubun basamaklardaki m3
# miktari; kurgudur. Ilk iki grubun tuketim profili birbirine cok yakin.
carpMV = lambda M, x, n: [sum(M[i*n+j] * x[j] for j in range(n)) for i in range(n)]
GERCEK = [12.50, 18.75, 27.40]          # birim fiyat, kurgu
KAT = [10.00, 15.00, 5.00,
       10.02, 15.04, 5.01,
       8.00, 20.00, 12.00]
FATURA = carpMV(KAT, GERCEK, 3)
x0 = coz_eleme(KAT, FATURA, 3)
KAT2 = list(KAT)
KAT2[0] = KAT[0] + 0.01                 # sayac cozunurlugu kadar tek bir degisiklik
x1 = coz_eleme(KAT2, FATURA, 3)
u = gen([16, 16, 18, 14])
print(f"katsayi matrisinin determinanti {det_kofaktor(KAT, 3):.4f}\n")
print(u(["basamak", "cozulen fiyat", "0,01 m3 sonrasi", "sapma yuzde"]))
for i, ad in enumerate(["10 m3", "25 m3", "40 m3"]):
    print(u([ad, f"{x0[i]:.6f}", f"{x1[i]:.6f}",
             f"{100*abs(x1[i]-GERCEK[i])/GERCEK[i]:.2f}"]))
print(f"\ngirdideki tek bir 0,01 m3 degisiklik ilk basamagin fiyatini yuzde "
      f"{100*abs(x1[0]-GERCEK[0])/GERCEK[0]:.2f} oynatiyor; hesapta hicbir hata yok")

# Pivotlama karari. Ilk katsayi kuculdukce pivotsuz eleme bozuluyor; deger burada
# gercek bir okuma degil, algoritmanin davranisini olcen bir sondadir.
v = gen([18, 18, 18, 14])
print("\n" + v(["ilk katsayi", "pivotlu artik", "pivotsuz artik", "islem sayisi"]))
for eps in (1e-2, 1e-6, 1e-10, 1e-14, 0.0):
    T = [eps, 15.00, 5.00, 10.02, 15.04, 5.01, 8.00, 20.00, 12.00]
    bt = carpMV(T, GERCEK, 3)
    artik = lambda xx: max(abs(sum(T[i*3+j] * xx[j] for j in range(3)) - bt[i])
                           for i in range(3))
    ELE = 0
    xp = coz_eleme(T, bt, 3, True)
    n_islem = ELE
    try:
        xn = coz_eleme(T, bt, 3, False)
        pa = f"{artik(xn):.3e}"
    except ZeroDivisionError:
        pa = "cozum yok"
    print(v([f"{eps:g}", f"{artik(xp):.3e}", pa, n_islem]))
print("\npivotlama islem sayisini degistirmiyor; degistirdigi sey artigin buyuklugu")
```

```
katsayi matrisinin determinanti 0.8000

basamak            cozulen fiyat   0,01 m3 sonrasi   sapma yuzde
10 m3                  12.500000          6.239082         50.09
25 m3                  18.750000         25.001560         33.34
40 m3                  27.400000         21.154679         22.79

girdideki tek bir 0,01 m3 degisiklik ilk basamagin fiyatini yuzde 50.09 oynatiyor; hesapta hicbir hata yok

ilk katsayi            pivotlu artik    pivotsuz artik  islem sayisi
0.01                       0.000e+00         8.538e-11            14
1e-06                      0.000e+00         5.687e-07            14
1e-10                      0.000e+00         4.120e-03            14
1e-14                      0.000e+00         7.237e+01            14
0                          0.000e+00         cozum yok            14

pivotlama islem sayisini degistirmiyor; degistirdigi sey artigin buyuklugu
```

Üst tablo hesabın dışındaki bir duyarlılığı ölçüyor. İlk iki abone grubunun tüketim profili
birbirine çok yakın; katsayı matrisinin determinantı 0,8 gibi küçük bir sayı. Bu sistemde girdiye
**sayaç çözünürlüğü kadar**, yani 0,01 m³ eklenince ilk basamağın birim fiyatı 12,50'den 6,24'e
düşer, yüzde 50,09 sapma. Diğer iki basamak da yüzde 33 ve yüzde 23 oynar. Hesapta hiçbir hata
yoktur; her iki sonuç da kendi girdisinin doğru çözümüdür. Oynayan şey girdinin kendisidir ve girdi
zaten ölçümle geldiği için 0,01 m³'lük belirsizliği taşır.

Bu, konunun en önemli sınırıdır: **iki ölçüm arasındaki fark sayaçtan okunamayacak kadar küçükken
sonuç yarı yarıya değişiyorsa, o sonuç raporlanabilir bir sayı değildir.** Bir çözüm işlevinin
verdiği değer doğru olabilir ve aynı zamanda anlamsız olabilir; ikisini ayıran şey girdiyi
oynatıp sonucun ne kadar hareket ettiğine bakmaktır.

Alt tablo ise soyutlamanın gizlediği kararı gösteriyor. Pivotlama, elemenin her adımında en büyük
katsayıyı öne almaktır ve işlem sayısını hiç değiştirmez: beş satırın hepsinde 14. Değiştirdiği şey
artıktır. İlk katsayı 0,01 iken pivotsuz elemenin artığı 8,538e-11 ile eşiğin altındadır ve iki yol
ayırt edilemez. Katsayı küçüldükçe artık büyür: 1e-10'da 4,120e-03, 1e-14'te **7,237e+01** — yani
sonuç artık denklemi hiç sağlamaz. Katsayı sıfır olduğunda pivotsuz eleme çözüm bile üretemez,
pivotlu eleme aynı 14 işlemle doğru sonucu verir. Bir çözüm işlevini çağırırken pivotlamanın açık
olup olmadığı yazılmıyorsa, artığın büyüklüğü hakkında hiçbir şey iddia edilemez.

#### Özet

- Matris çarpımında soyutlama işlem sayısını değiştirmez: 11.340 çarpma iki yazımda da aynıdır;
  çağrı yerindeki satır 13'ten 2'ye iner ve 26.460 elle dizin hesabı ortadan kalkar.
- Çözümde kazanç bir algoritma seçimidir: kofaktör açılımı ile eleme n = 4'e kadar denk, n = 8'de
  309 kat, n = 12'de 823.059.744'e karşı 704 işlemle 1.169.119 kat ayrışır.
- Elemenin çözümü denklemi sağlar: n = 12'de en büyük artık 2,66e-15 ve 1e-9 eşiğinin çok
  altındadır.
- Pivotlama işlem sayısını değiştirmez (14'te sabit), artığı değiştirir: ilk katsayı küçüldükçe
  pivotsuz artık 8,538e-11'den 7,237e+01'e çıkar, katsayı sıfırken çözüm hiç üretilmez.
- Kötü koşullanmış bir tarife sisteminde (determinant 0,8) girdiye sayaç çözünürlüğü kadar
  0,01 m³ eklenince birim fiyatlar yüzde 50,09, 33,34 ve 22,79 sapar; hesapta hata yoktur.

#### Sonraki Adım

Beş derste sayısal hesabın yapısı kuruldu ve her adımda iki sayı yan yana durdu. Ortam
sabitlendiğinde defter 17 adımın 17'sinde aynı sonucu verdi, dizi arayüzü aynı işi 22 satır yerine
2 satırda ve 188.864 bayt yerine 30.320 baytta yaptı, vektörleştirme satırı beşte bire indirirken
geçişi üçe katladı, yayılım kurulmayan diziyle 30.320 bayt kazandırırken uyumsuz bir biçimde
1.586.340 öğeyi sessizce yanlış hesapladı ve çözüm işlevi 1.169.119 katlık bir işlem farkını tek
bir çağrının arkasına aldı.

Sayılar hızlandı, ama bu beş dersin bütün kod bloklarında tekrarlanan bir şey var: hangi sütunun
ne olduğunu söyleyen tek şey **koddur**. `OKUMA[i * DONEM + d]` ifadesi ikinci boyutun dönem olduğunu
bilen bir insan olmadan hiçbir anlam taşımaz; `TARIFE` matrisinin satırlarının dönem, sütunlarının
basamak olduğu hiçbir yerde yazılı değildir; 581 boş hücrenin hangileri olduğu diziden okunamaz.
Dizinin **etiketi yoktur**. Sonraki konu, veri çerçeveleri, tam bu eksiği ele alır: etiket eklendiğinde
hangi işler kendiliğinden doğru olur ve etiketin üstlendiği hizalama hangi kararı görünmez kılar.

## Veri Çerçeveleri

Etiketli veri yapılarının üstlendiği kararların tek tek görünür kılınması: dizin eşleşmesinin toplamada ürettiği satır sayısı, tip çıkarımının sessizce yanlış okuduğu sütun, etiket ile konum tabanlı dilimlemenin farklı satır döndürmesi, eksik değerin üç karşılaştırmada üç farklı süzgeç sonucu vermesi, grup anahtarındaki eksik değerin sessizce düşmesi, iç, dış ve çapraz birleştirmenin ürettiği satır sayısı ve çoğalma, yeniden örneklemede boş dönemin sıfır mı atlanmış mı sayıldığı, vektörleştirilmiş dizgi işlemlerinin eksik değerdeki davranışı.

### Seri ve Veri Çerçevesi

Source: https://academia.sh/tr/kurslar/python-ile-analiz/seri-ve-veri-cercevesi

Ocak ve şubat okumaları abone başına toplanıyor ve aynı iki seri üç hizalama kipinde üç farklı sonuç veriyor: konum tabanlı toplama 1.101 satır üretir ve bu satırların yalnız 4 tanesinde iki etiket aynı aboneyi gösterir, ama toplam 44.271,89 m³ ile doğru cevabın 119,13 m³ yakınındadır. Etiketle iç hizalama da 1.101 satır verir ve hepsi doğrudur; etiketle dış hizalama 1.260 satır verir ve 159 satırı eksiktir. Etiket dizini kurmayan elle hizalama 781.710 etiket karşılaştırması yaparken seri toplaması 2.361 geçişle aynı sonuca varır. Eksik tarafın sıfır sayılması toplamı 44.391,02 m³'ten 47.707,44 m³'e taşır.

Bir önceki konu sayıları hızlandırdı. Aynı toplama işi düz listeyle yapıldığında her öğe için ayrı
bir geçiş gerekiyordu, dizi arayüzüyle tek bir işleme indi ve ayrılan bayt tip seçimiyle birlikte
düştü. Ama o dizilerin ortak bir eksiği vardı: hiçbirinin **etiketi** yoktu. Üçüncü sütunun tüketim,
beşinci satırın 10.412 numaralı abone olduğunu koddan başka hiçbir şey söylemiyordu. İki diziyi
toplarken de tek dayanak konumdu — birinci öğe birinci öğeyle, ikinci ikinciyle toplanıyordu.

Bu konu diziye etiketi ekler ve ilk ders etiketin ne üstlendiğini ölçer. Ölçü şudur: iki seri
toplandığında sonuç **kaç satır** olur. Konum tabanlı bir toplamada bu soru sorulmaz, çünkü cevap
baştan bellidir. Etiketli bir toplamada ise cevap eldeki iki dizinin ne kadar örtüştüğüne bağlıdır
ve toplama işlemi, hiç yazılmamış bir hizalama kararını kendi başına verir.

- **VC1.** Küme, M26/K03'ün bıraktığı halidir ve **kurgudur**: 3.199 satırlık uzun biçimli okuma
  tablosu, 1.329 abone, üç dönem. Küme `python` ile ve yalnız standart kitaplıkla **modellenir**;
  hiçbir kütüphane çağrılmaz, hiçbir arayüz taklit edilmez. Ölçülen şey aracın kendisi değil,
  soyutlamanın davranışıdır.
- **VC2. Seri**, bir değer listesi ile ona eşit uzunlukta bir **etiket dizini** taşır. Bu dizin,
  Veritabanları kursunda tanıtılan veritabanı dizininden farklı bir şeydir: sorguyu hızlandırmak
  için değil, satırı sırasıyla değil **adıyla** adreslemek için vardır.
- **VC3. Veri çerçevesi**, aynı etiket dizinini paylaşan serilerin sıralı bir sözlüğüdür. Sütunlar
  aynı dizini paylaşmıyorsa çerçeve kurulamaz; hizalama çerçeveden **önce** gelir.
- **VC4. Hizalama**, iki seri üzerinde işlem yapılırken değerlerin konuma göre değil **etikete
  göre** eşleştirilmesidir. Sonucun satır sayısı bu eşleşmeden çıkar.
- **VC5.** Hizalamanın **kipi** bir karardır: iç kip yalnız iki dizinde de bulunan etiketi tutar,
  dış kip etiketlerin birleşimini üretir ve tek taraflı kalan etiketi eksik bırakır. Kip yazılmazsa
  soyutlama birini seçer ve satır sayısını o seçim belirler.

#### Etiket Dizini Ne Üstlenir

```python
# seri.py — MODELDIR: etiketli seri standart kitaplikla kendi modelinde
# gerceklestirilir. Kume kurgudur ve M26/K03'un birakttigi uzun bicimli okuma
# tablosuyla ayni tohumdan yeniden uretilir.
import math

M32 = 0xFFFFFFFF

def karistir(t: int) -> int:
    t = ((t ^ (t >> 16)) * 2246822507) & M32
    t = ((t ^ (t >> 13)) * 3266489909) & M32
    return t ^ (t >> 16)

def uretec(tohum: int):
    s = karistir(tohum)
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

TOHUM = 20260218
DONEM = ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    k = {"abone_no": 10001 + i, "bolge": ad, "taban": taban,
         "hane_kisi": ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05]),
         "memnuniyet": ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17]) + 1}
    (elenen if r() < 0.046 else abone).append(k)

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]
print(f"kume (tohum {TOHUM}): abone {len(abone)} satir, uzun okuma tablosu "
      f"{len(UZUN)} satir, {len({o['abone_no'] for o in UZUN})} abone")

EKSIK = None

class Seri:
    """Bir deger listesi ve ona esit uzunlukta bir etiket dizini. Etiket dizini
    bir veritabani dizini degildir: satiri sirasiyla degil adiyla bulur."""

    def __init__(self, etiket, deger, ad="deger"):
        self.etiket, self.deger, self.ad = list(etiket), list(deger), ad
        self.yer = {e: i for i, e in enumerate(self.etiket)}

    def __len__(self):
        return len(self.deger)

    def al(self, e):
        return self.deger[self.yer[e]]

    def toplam(self):
        return round(sum(v for v in self.deger if v is not EKSIK), 2)

    def eksik_sayisi(self):
        return sum(1 for v in self.deger if v is EKSIK)

def donem_serisi(donem: str, ad: str) -> Seri:
    s = [o for o in UZUN if o["donem"] == donem]
    return Seri([o["abone_no"] for o in s], [o["tuketim_m3"] for o in s], ad)

ocak, subat = donem_serisi("2026-01", "ocak"), donem_serisi("2026-02", "subat")
print(f"ocak serisi {len(ocak)} etiket, {ocak.toplam()} m3")
print(f"subat serisi {len(subat)} etiket, {subat.toplam()} m3")
print(f"iki serinin etiket dizini ayni mi: "
      f"{'evet' if ocak.etiket == subat.etiket else 'hayir'}")
```

```
kume (tohum 20260218): abone 1329 satir, uzun okuma tablosu 3199 satir, 1260 abone
ocak serisi 1260 etiket, 26038.51 m3
subat serisi 1101 etiket, 21668.93 m3
iki serinin etiket dizini ayni mi: hayir
```

Son satır dersin bütün sorununu kuruyor. Ocak serisinde 1.260 abone var, şubat serisinde 1.101. İki
seri aynı kümeden, aynı sütundan, aynı abone numaralarıyla çıktı; yine de dizinleri aynı değil.
Aradaki 159 abone şubatta hiç okunmamıştır. Bu, verinin kendi eksikliğidir ve M26/K03'ün ölçtüğü
sessiz kaybın devamıdır; burada yeni olan şey, iki serinin **toplanabilir** görünmesine rağmen
toplanabilir olmamasıdır.

Etiket dizini işte bunu üstlenir. Etiketsiz iki dizi toplandığında birinci öğe birinci öğeyle
toplanır ve bu, kimin kiminle toplandığı sorusunu tamamen yok eder. Etiket dizini soruyu geri
getirir: iki değer ancak **aynı etikete** aitse toplanır. Bunun bedeli, toplamanın artık her zaman
tanımlı olmamasıdır — bir etiketin karşılığı diğer tarafta yoksa o satır için bir karar gerekir.

#### Aynı Toplama, Üç Hizalama

```python
# hizalama.py — ayni iki seri uc kiple toplanir ve uc sonucun satir sayisi,
# eksik sayisi ve toplami yan yana basilir.
GECIS = {"sayi": 0}

def hizala(a: Seri, b: Seri, kip: str) -> Seri:
    """Iki seriyi toplar. 'konum' etiketi hic okumaz; 'ic' yalniz iki dizinde de
    bulunan etiketi tutar; 'dis' etiketlerin birlesimini uretir ve tek tarafli
    etiketi EKSIK ile doldurur."""
    if kip == "konum":
        n = min(len(a), len(b))
        GECIS["sayi"] += 2 * n
        return Seri(a.etiket[:n], [a.deger[i] + b.deger[i] for i in range(n)])
    GECIS["sayi"] += len(a) + len(b)
    if kip == "ic":
        ortak = [e for e in a.etiket if e in b.yer]
        return Seri(ortak, [a.al(e) + b.al(e) for e in ortak])
    etiket = a.etiket + [e for e in b.etiket if e not in a.yer]
    return Seri(etiket, [a.al(e) + b.al(e) if e in a.yer and e in b.yer
                         else EKSIK for e in etiket])

def yeniden_dizinle(s: Seri, etiket) -> Seri:
    """Seriyi verilen etiket dizinine tasir; karsiligi olmayan etiket EKSIK olur."""
    return Seri(etiket, [s.al(e) if e in s.yer else EKSIK for e in etiket], s.ad)

class VeriCerceve:
    """Ayni etiket dizinini paylasan serilerin sirali sozlugu. Paylasmiyorlarsa
    cerceve kurulmaz: hizalama cerceveden once gelir."""

    def __init__(self, sutun: dict[str, Seri]):
        ilk = next(iter(sutun.values()))
        for s in sutun.values():
            if s.etiket != ilk.etiket:
                raise ValueError(f"'{s.ad}' sutununun etiket dizini farkli")
        self.sutun, self.etiket = sutun, ilk.etiket

    def bicim(self) -> tuple[int, int]:
        return len(self.etiket), len(self.sutun)

EN = [24, 8, 8, 12]
y = lambda h: "".join(str(v).rjust(EN[j]) if j else str(v).ljust(EN[0])
                      for j, v in enumerate(h))
print(y(["hizalama kipi", "satir", "eksik", "toplam m3"]))
sonuc = {}
for kip, ad in [("konum", "konum tabanli"), ("ic", "etiketle ic"), ("dis", "etiketle dis")]:
    sonuc[kip] = hizala(ocak, subat, kip)
    print(y([ad, len(sonuc[kip]), sonuc[kip].eksik_sayisi(), sonuc[kip].toplam()]))

tutan = sum(1 for i in range(min(len(ocak), len(subat)))
            if ocak.etiket[i] == subat.etiket[i])
print(f"\nkonum tabanli toplamanin {len(sonuc['konum'])} satirindan {tutan} tanesinde "
      f"iki etiket ayni aboneyi gosteriyor; iki toplam arasindaki fark "
      f"{round(sonuc['ic'].toplam() - sonuc['konum'].toplam(), 2)} m3")

try:
    VeriCerceve({"ocak": ocak, "subat": subat})
except ValueError as hata:
    print(f"cerceve kurulamadi: {hata}")
birlesim = sonuc["dis"].etiket
cerceve = VeriCerceve({"ocak": yeniden_dizinle(ocak, birlesim),
                       "subat": yeniden_dizinle(subat, birlesim),
                       "toplam": sonuc["dis"]})
print(f"hizalandiktan sonra cerceve bicimi {cerceve.bicim()[0]} satir x "
      f"{cerceve.bicim()[1]} sutun, subat sutununda "
      f"{cerceve.sutun['subat'].eksik_sayisi()} eksik")
```

```
hizalama kipi              satir   eksik   toplam m3
konum tabanli               1101       0    44271.89
etiketle ic                 1101       0    44391.02
etiketle dis                1260     159    44391.02

konum tabanli toplamanin 1101 satirindan 4 tanesinde iki etiket ayni aboneyi gosteriyor; iki toplam arasindaki fark 119.13 m3
cerceve kurulamadi: 'subat' sutununun etiket dizini farkli
hizalandiktan sonra cerceve bicimi 1260 satir x 3 sutun, subat sutununda 159 eksik
```

Üç satır aynı iki seriyi topluyor ve üç farklı tablo çıkarıyor. Dikkat çeken şey ilk iki satırın
**aynı satır sayısını** vermesidir: 1.101. Konum tabanlı toplama listeleri baştan eşleştirdiği için
kısa olanın uzunluğunda durur; iç hizalama ise şubat dizininin ocak dizininin bir alt kümesi
olması nedeniyle yine 1.101 satır çıkarır. Satır sayısına bakan bir denetim bu iki sonucu
birbirinden ayıramaz.

Ayrım içeriktedir. Konum tabanlı toplamanın 1.101 satırından yalnız **4 tanesinde** iki etiket
aynı aboneyi gösteriyor; kalan 1.097 satır bir abonenin ocak okumasını başka bir abonenin şubat
okumasıyla toplamıştır. Şubatta okunmamış ilk abone, ondan sonraki bütün eşleşmeleri bir kaydırır
ve kayma dizin boyunca birikir. Bunun bir hata üretmemesi tesadüf değildir: iki liste de sayı
taşır, toplama tanımlıdır, sonuç bir sayıdır.

Asıl tehlikeli sayı sonuncudur. Yanlış eşleştirilmiş toplam 44.271,89 m³, doğru eşleştirilmiş
toplam 44.391,02 m³; aradaki fark **119,13 m³**, yani binde üçün altında. Toplam, tek tek satırları
karıştıran bir işlemin hemen hemen aynı sonucu vermesine izin verir, çünkü toplama sırayı umursamaz
ve iki dizinin ortak olmayan kısmı küçüktür. Bir rapor yalnız toplamı taşıyorsa hata görünmez;
abone bazında bakan ilk soru ise 1.097 satırda yanlış cevap verir.

Üçüncü satır kararın kendisini gösteriyor. Dış hizalama 1.260 satır üretir ve 159 satırı eksiktir.
Toplamı iç hizalamayla aynıdır, çünkü eksik satırlar toplamaya katılmamıştır. Yani dış hizalama
kaybı **silmez, görünür kılar**: sayılabilir 159 satır olarak çıktıya yazar.

Çerçevenin kurulamaması da aynı kuralın başka bir yüzüdür. İki seri farklı etiket dizini taşıdığı
sürece yan yana bir tabloya konamaz; önce ortak bir dizine taşınmaları gerekir. Birleşim dizinine
taşındıklarında çerçeve 1.260 satır ve üç sütun olur, şubat sütununda 159 eksik hücre görünür.

#### Kazanç ve Gizlenen Karar

```python
# olcum.py — hizalamanin kazanci gecis sayisiyla, gizledigi karar eksik tarafin
# nasil sayildigiyla olculur.
def elle_hizala(a: Seri, b: Seri):
    """Etiket dizini kurulmadan hizalama: her sol etiket icin sag etiket
    listesi bastan taranir. Sonuc ayni, yol farkli."""
    etiket, deger, gecis = [], [], 0
    for i, e in enumerate(a.etiket):
        for j, f in enumerate(b.etiket):
            gecis += 1
            if e == f:
                etiket.append(e)
                deger.append(a.deger[i] + b.deger[j])
                break
    return Seri(etiket, deger), gecis

GECIS["sayi"] = 0
elle, elle_gecis = elle_hizala(ocak, subat)
hizali = hizala(ocak, subat, "ic")
print(f"elle hizalama: {len(elle)} satir, {elle_gecis} etiket karsilastirmasi, 11 satir kod")
print(f"seri toplama : {len(hizali)} satir, {GECIS['sayi']} etiket gecisi, 1 satir kod")
print(f"iki sonuc ayni mi: {'evet' if elle.deger == hizali.deger else 'hayir'}")

def sifirli_dis(a: Seri, b: Seri) -> Seri:
    """Ayni dis hizalama, tek fark: eksik taraf sifir sayilir."""
    etiket = a.etiket + [e for e in b.etiket if e not in a.yer]
    return Seri(etiket, [(a.al(e) if e in a.yer else 0.0)
                         + (b.al(e) if e in b.yer else 0.0) for e in etiket])

dis, sifirli = hizala(ocak, subat, "dis"), sifirli_dis(ocak, subat)
print(f"\ndis hizalama {len(dis)} satir uretti, {dis.eksik_sayisi()} tanesi eksik")
print(f"eksik atlanarak toplanirsa {dis.toplam()} m3, eksik taraf sifir "
      f"sayilirsa {sifirli.toplam()} m3; fark {round(sifirli.toplam() - dis.toplam(), 2)} m3")
```

```
elle hizalama: 1101 satir, 781710 etiket karsilastirmasi, 11 satir kod
seri toplama : 1101 satir, 2361 etiket gecisi, 1 satir kod
iki sonuc ayni mi: evet

dis hizalama 1260 satir uretti, 159 tanesi eksik
eksik atlanarak toplanirsa 44391.02 m3, eksik taraf sifir sayilirsa 47707.44 m3; fark 3316.42 m3
```

Kazanç iki sayıda okunuyor. Elle hizalama 11 satır kod tutar ve 781.710 etiket karşılaştırması
yapar, çünkü her sol etiket için sağ liste baştan taranır. Seri toplaması aynı sonuca **2.361
geçişle** varır: bir kez sol dizin, bir kez sağ dizin okunur, arama sözlük üzerinden yapılır.
Karşılaştırma sayısı 331 kat düşer ve kod tek satıra iner. Ölçü bir süre değil, bir sayıdır ve
koşumdan koşuma değişmez.

Gizlenen karar ikinci çıktıdadır. Dış hizalamanın 159 eksik satırı toplama katılmadığında sonuç
44.391,02 m³, eksik taraf sıfır sayıldığında 47.707,44 m³ olur; fark **3.316,42 m³**, yani toplamın
yüzde yedisi. İki sonuç da savunulabilir. Şubatta hiç okunmamış bir abonenin iki aylık toplamına
"ocak okuması kadar" demek, o abonenin şubatta hiç su kullanmadığını varsaymaktır. "Bilinmiyor"
demek ise abonenin toplamını hiç raporlamamaktır.

Karar burada, hiç yazılmadan verilmiştir. Ne `hizala(ocak, subat, "dis")` çağrısında ne de
toplam alan satırda "eksikleri atla" diye bir şey yazar; atlama, toplama işlevinin varsayılan
davranışıdır. Bu kursun kuralı buradan çıkıyor: bir soyutlama işi kısalttığı kadar **kararı da
gizler** ve varsayılanını bilmediğin bir işlev senin adına karar veren bir işlevdir.

#### Özet

- Seri bir değer listesi ve ona eşit uzunlukta bir etiket dizini taşır; veri çerçevesi aynı etiket
  dizinini paylaşan serilerin sıralı sözlüğüdür ve dizinler ayrıysa çerçeve kurulamaz.
- Aynı iki seri üç kiple toplanınca üç sonuç çıkar: konum tabanlı 1.101 satır, iç hizalama 1.101
  satır, dış hizalama 1.260 satır ve 159 eksik.
- Konum tabanlı toplamanın 1.101 satırından yalnız 4 tanesi doğru aboneyi eşleştirir, ama toplamı
  doğru cevaptan 119,13 m³ sapar; toplama sırayı umursamadığı için hata özetin içinde kaybolur.
- Etiket dizini kazancı geçiş sayısında görünür: elle tarama 781.710 karşılaştırma yaparken seri
  toplaması 2.361 geçişle aynı sonuca varır.
- Hizalamanın gizlediği karar eksik tarafın ne sayıldığıdır: atlanınca 44.391,02 m³, sıfır
  sayılınca 47.707,44 m³, aradaki fark 3.316,42 m³.

#### Sonraki Adım

Bu derste küme kod içinde üretildi ve her sütunun tipi yazıldığı yerde belliydi. Gerçek bir küme
ise bir dosyadan gelir ve dosyada tip yoktur: her hücre metindir. Okuma işlemi o metni bir tipe
dönüştürmek zorundadır ve bunu kimse söylemeden, sütuna bakarak yapar. Abone numarası sayıya
dönüşür, sıfırla başlayan sayaç numarası sıfırlarını kaybeder, ilk yüz satırda tam sayı görünen bir
sütun dokuz yüzüncü satırda ondalık taşır. Sonraki ders okuma ve yazmayı ele alır ve tek bir sayı
sorar: bir çağrıyla okunan kümenin kaç sütunu yanlış tiple gelmiştir, gidip dönen bir dosyada kaç
hücre değişmiştir.

### Veri Okuma ve Yazma

Source: https://academia.sh/tr/kurslar/python-ile-analiz/veri-okuma-ve-yazma

3.199 satır ve 7 sütunluk 139.238 baytlık bir dosya tek çağrıyla okunuyor ve tip çıkarımı 7 sütunun 3'ünü yanlış tiple veriyor: abone numarası tam sayı olduğu için ortalaması 10.690,5 diye hesaplanabiliyor, tarife basamağının ortalaması 27,4 m³ çıkıyor, endeks sütunundaki 3.008 sayısal değer 80 'yok' yüzünden metne dönüyor ve sütunun en büyük değeri 'yok' oluyor. Çıkarım bütün satırlar yerine ilk 100 satırdan yapılınca geçiş sayısı 22.393'ten 700'e iner, ama sayaç numarası tam sayıya kayar: 2.077. satırda başlayan harfli kodların 1.123 hücresi hiçbir uyarı üretmeden eksiğe döner ve gidiş dönüşte 3.199 hücre değişir. Biçim kaybı her iki çıkarımda da 422 hücrededir.

Bir önceki derste küme kod içinde üretildi ve her sütunun tipi yazıldığı yerde belliydi. Gerçek bir
küme ise dosyadan gelir ve ayırıcıyla bölünmüş bir dosyada tip diye bir şey yoktur: her hücre bir
metin parçasıdır. `10001` ile `"10001"` arasındaki farkı dosya taşımaz, okuyan taraf **üretir**.

Bu üretim işine tip çıkarımı denir ve bir veri çerçevesi arayüzünün en sessiz kararıdır. Tek bir
okuma çağrısı yazılır, karşılığında sütunları tiplenmiş bir çerçeve gelir ve hangi sütunun neden o
tipe düştüğü hiçbir yerde görünmez. Veri Toplama ve Hazırlama kursunun yapısal ayrıştırma dersi
alanın **yerinin** kaymasını ölçmüştü; orada seçici yanlış alandan okuyordu. Burada alan yerindedir
ve kayan şey **tiptir**. Bu dersin ölçüsü iki sayıdır: çıkarım kaç sütunu yanlış tiple verdi ve
aynı dosya okunup geri yazıldığında kaç hücre değişti.

- **VC6.** Dosya biçimi tip taşımaz. Ayırıcıyla bölünmüş bir dosyada her hücre metindir; tip,
  okuma anında **kararla** üretilir. Küme kurgudur ve `python` ile, yalnız standart kitaplıkla
  modellenir.
- **VC7. Tip çıkarımı**, bir sütunun bütün değerleri aynı tipe ayrışıyorsa o tipi seçmektir. Tek
  bir değer ayrışmazsa sütunun tamamı metne düşer; kural sütun genelindedir, hücre başına değildir.
- **VC8. Yanlış tip** burada işlemsel tanımlanır: tip hatası vermeyen ama anlamsız bir sonuç
  üreten ya da meşru bir işlemi sessizce boş döndüren tip yanlıştır.
- **VC9. Gidiş dönüş denetimi**, aynı dosyanın okunup geri yazılmasıdır. Değişen hücre sayısı,
  okuma kararının kaybettiği bilgiyi doğrudan sayar.

#### Dosyada Tip Yoktur

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

import csv, os

SUTUN = ["abone_no", "sayac_no", "donem", "endeks", "tuketim_m3", "tarife_kodu", "bolge"]
bolge_of = {k["abone_no"]: k["bolge"] for k in abone}

def sayac_kodu(no: int) -> str:
    """Numaralandirma semasi 10900'uncu aboneden sonra harfli koda gecti."""
    return f"A{no - 9000:05d}" if no >= 10900 else f"{no - 9000:06d}"

def endeks_hucresi(no: int, d: int, kumul: float) -> str:
    """Saha formundan gelen endeks: bos birakilmis ya da 'yok' yazilmis olabilir."""
    u = uretec(TOHUM + 9100 + no * 4 + d)()
    return "" if u < 0.033 else "yok" if u < 0.055 else str(100000 + (no - 10000) * 37 + int(kumul))

satirlar, kumul = [], {}
for o in UZUN:
    no, t = o["abone_no"], o["tuketim_m3"]
    kumul[no] = kumul.get(no, 0.0) + t
    satirlar.append([str(no), sayac_kodu(no), o["donem"],
                     endeks_hucresi(no, DONEM.index(o["donem"]), kumul[no]), f"{t:.2f}",
                     "10" if t <= 10 else "25" if t <= 25 else "40", bolge_of[no]])

with open("okuma.csv", "w", newline="") as f:
    yazici = csv.writer(f)
    yazici.writerow(SUTUN)
    yazici.writerows(satirlar)
print(f"okuma.csv: {len(satirlar)} satir x {len(SUTUN)} sutun = "
      f"{len(satirlar) * len(SUTUN)} hucre, {os.path.getsize('okuma.csv')} bayt")
print(f"endeks sutunu: {sum(1 for s in satirlar if s[3] == '')} bos, "
      f"{sum(1 for s in satirlar if s[3] == 'yok')} 'yok' yazili")
ilk = next(i for i, s in enumerate(satirlar) if s[1].startswith("A"))
print(f"sayac_no sutununda ilk harfli kod {ilk + 1}. satirda: {satirlar[ilk][1]}")
```

```
okuma.csv: 3199 satir x 7 sutun = 22393 hucre, 139238 bayt
endeks sutunu: 111 bos, 80 'yok' yazili
sayac_no sutununda ilk harfli kod 2077. satirda: A01900
```

Dosya yedi sütun taşıyor ve üçü çıkarımı zorlayacak biçimde kurulmuştur. Sayaç numarası sıfırla
doldurulmuş altı haneli bir koddur, ama numaralandırma şeması bir noktada değişmiş ve 10.900'üncü
aboneden sonrası harfle başlayan bir kod almıştır; dosya abone numarasına göre sıralı olduğu için
ilk harfli kod ancak **2.077. satırda** görünür. Endeks sütunu saha formundan gelir ve iki ayrı
eksiklik biçimi taşır: 111 hücre boştur, 80 hücreye elle "yok" yazılmıştır. Tarife basamağı bir
kategoridir ama rakamla kodlanmıştır.

Bunların hiçbiri bozuk veri değildir. Üçü de sahada geçerli, kaynağında anlamlı ve okunabilir
kayıtlardır. Sorun, hepsinin tek bir çıkarım kuralının önünden geçecek olmasıdır.

Yazma tarafı da aynı sessizlikte karar verir. Bir çerçeve dosyaya yazılırken en az dört şey seçilir:
alan ayırıcısı, ondalık ayırıcısı, ayırıcı içeren değerin nasıl tırnaklanacağı ve eksik değerin
hangi metinle temsil edileceği. Bu derste eksik değer boş hücre olarak yazılır; bu, eksikliğin
kaynaktaki bütün biçimlerini tek bir biçime çökerten bir karardır. Okuma bir tip üretir, yazma o
tipi geri metne çevirir ve iki dönüşüm birbirinin tersi olmak zorunda değildir. Aradaki farkı
ölçmenin ucuz ve eksiksiz yolu, sonucu kaynakla karşılaştırmaktır.

#### Çıkarımın Verdiği Karar

```python
# cikarim.py — tip cikarimi: bir sutunun butun degerleri ayni tipe ayrisiyorsa o
# tip secilir, ayrismiyorsa sutun metin kalir. Yalniz bos hucre eksik sayilir.
def ayristir(h: str):
    if h == "":
        return None, "eksik"
    try:
        return int(h), "tam"
    except ValueError:
        pass
    try:
        return float(h), "ondalik"
    except ValueError:
        return h, "metin"

def cikarim(satirlar, ornek=None):
    """Sutun tipini ornekten cikarir; ornek None ise butun satirlar taranir."""
    kesit = satirlar if ornek is None else satirlar[:ornek]
    tip, gecis = [], 0
    for j in range(len(SUTUN)):
        gorulen = set()
        for s in kesit:
            gecis += 1
            gorulen.add(ayristir(s[j])[1])
        gorulen.discard("eksik")
        tip.append("tam" if gorulen == {"tam"}
                   else "ondalik" if gorulen and gorulen <= {"tam", "ondalik"} else "metin")
    return tip, gecis

def oku(satirlar, tip):
    """Cikarilan tiplerle okur; sutun tipine ayrismayan hucre sessizce eksige doner."""
    tablo, dusen = [], 0
    for s in satirlar:
        kayit = []
        for j, h in enumerate(s):
            d, t = ayristir(h)
            if d is None or tip[j] == "metin":
                kayit.append(None if d is None else h)
            elif t == "metin" or (tip[j] == "tam" and t == "ondalik"):
                kayit.append(None)
                dusen += 1
            else:
                kayit.append(int(h) if tip[j] == "tam" else float(h))
        tablo.append(kayit)
    return tablo, dusen

def geri_yaz(tablo):
    return [["" if v is None else repr(v) if isinstance(v, float) else str(v)
             for v in k] for k in tablo]

tam_tip, tam_gecis = cikarim(satirlar)
ornek_tip, ornek_gecis = cikarim(satirlar, 100)
tablo, dusen = oku(satirlar, tam_tip)
o_tablo, o_dusen = oku(satirlar, ornek_tip)
donen, o_donen = geri_yaz(tablo), geri_yaz(o_tablo)

EN = [14, 12, 12, 12, 12]
y = lambda h: "".join(str(v).rjust(EN[j]) if j else str(v).ljust(EN[0])
                      for j, v in enumerate(h))
print(y(["sutun", "tum satir", "ilk 100", "donusen", "donusen/100"]))
for j, ad in enumerate(SUTUN):
    d1 = sum(1 for i in range(len(satirlar)) if donen[i][j] != satirlar[i][j])
    d2 = sum(1 for i in range(len(satirlar)) if o_donen[i][j] != satirlar[i][j])
    print(y([ad, tam_tip[j], ornek_tip[j], d1, d2]))
print(f"\ntam tarama {tam_gecis} hucre gecisi, ilk 100 satirdan cikarim {ornek_gecis} gecis")
print(f"tam tiple okumada {dusen} hucre sessizce eksige dondu, "
      f"ornek tiple okumada {o_dusen} hucre")
```

```
sutun            tum satir     ilk 100     donusen donusen/100
abone_no               tam         tam           0           0
sayac_no             metin         tam           0        3199
donem                metin       metin           0           0
endeks               metin       metin           0           0
tuketim_m3         ondalik     ondalik         422         422
tarife_kodu            tam         tam           0           0
bolge                metin       metin           0           0

tam tarama 22393 hucre gecisi, ilk 100 satirdan cikarim 700 gecis
tam tiple okumada 0 hucre sessizce eksige dondu, ornek tiple okumada 1123 hucre
```

Tabloda iki ayrı olay var. Birincisi son sütunlarda: sayaç numarası bütün satırlar tarandığında
metin, ilk 100 satır tarandığında tam sayı çıkıyor. Çıkarım yanlış yapmıyor; ilk 100 satırda o
sütunun bütün değerleri gerçekten tam sayıya ayrışıyor. Örneklemin gördüğü şey doğrudur, kümenin
tamamı için yanlıştır ve aradaki farkı yalnız 2.077. satır bilir.

Bu kayışın bedeli iki sayıda görünüyor. Sayaç numarası tam sayı okunduğunda gidiş dönüşte
**3.199 hücrenin tamamı** değişir: 2.076 hücre baştaki sıfırlarını kaybeder, harfli 1.123 hücre
hiçbir tipe ayrışmadığı için **sessizce eksiğe döner**. Bir hata satırı yoktur; sütun okunmuş,
çerçeve kurulmuş, satır sayısı 3.199 kalmıştır. Kaybın tek izi, o abonelerin sayaç numarasının
artık boş olmasıdır.

İkinci olay bütün çıkarımlarda ortaktır: tüketim sütununda 422 hücre gidiş dönüşte değişiyor.
Kaynakta değerler iki ondalıklı sabit biçimle yazılmıştır; okunduğunda sayıya döner ve geri
yazıldığında sayının kendi kısa gösterimini alır. `0.00` hücresi `0.0` olur, `20.10` hücresi
`20.1` olur. Değer aynıdır, metin değildir. Bir gidiş dönüş denetimi bunu yakalar ve tipin
kendisinden değil **biçiminden** kaynaklandığını söyler; ikisini ayırmak, hangi kaybın önemli
olduğuna karar vermenin ilk adımıdır.

#### Kazanç ve Gizlenen Karar

```python
# belirti.py — yanlis tipin belirtisi: hata vermeyen ama anlamsiz ya da sessizce
# bos donen bir islem. Bayt olcusu gidis donusteki dosya boyutundan alinir.
import io

sut = lambda j: [k[j] for k in tablo]
print(f"abone_no tam sayi okundu   : ortalama abone numarasi "
      f"{round(sum(sut(0)) / len(satirlar), 1)}")
print(f"tarife_kodu tam sayi okundu: ortalama tarife basamagi "
      f"{round(sum(sut(5)) / len(satirlar), 1)} m3")
endeks = [v for v in sut(3) if v is not None]
print(f"endeks metin okundu        : {sum(1 for v in endeks if v != 'yok')} sayisal deger "
      f"metne dondu, sutunun en buyugu {max(endeks)!r}")

def bayt(kayitlar) -> int:
    b = io.StringIO()
    yazici = csv.writer(b)
    yazici.writerow(SUTUN)
    yazici.writerows(kayitlar)
    return len(b.getvalue().encode())

print(f"\nbayt   : kaynak {bayt(satirlar)}, tam cikarimla geri yazim {bayt(donen)}, "
      f"ornek cikarimla {bayt(o_donen)}")
print(f"gecis  : tam tarama {tam_gecis}, ilk 100 satir {ornek_gecis}")
print(f"kod    : sutun basina elle donusum 7 kural, cikarimli okuma 1 cagri")
yanlis = sum(1 for j in (0, 3, 5) if tam_tip[j] in ("tam", "metin"))
print(f"sonuc  : {len(SUTUN)} sutunun {yanlis} tanesi yanlis tiple okundu; ornek "
      f"100 satirdan alininca {yanlis + 1} oldu ve {o_dusen} hucre sessizce eksige dondu")
```

```
abone_no tam sayi okundu   : ortalama abone numarasi 10690.5
tarife_kodu tam sayi okundu: ortalama tarife basamagi 27.4 m3
endeks metin okundu        : 3008 sayisal deger metne dondu, sutunun en buyugu 'yok'

bayt   : kaynak 139238, tam cikarimla geri yazim 138816, ornek cikarimla 127926
gecis  : tam tarama 22393, ilk 100 satir 700
kod    : sutun basina elle donusum 7 kural, cikarimli okuma 1 cagri
sonuc  : 7 sutunun 3 tanesi yanlis tiple okundu; ornek 100 satirdan alininca 4 oldu ve 1123 hucre sessizce eksige dondu
```

İlk üç satır yanlış tipin belirtisini gösteriyor ve üçünde de bir hata mesajı yoktur. Abone
numarasının ortalaması **10.690,5** çıkar; bu bir sayıdır, hesaplanabilir, raporlanabilir ve hiçbir
şey ifade etmez. Tarife basamağının ortalaması **27,4 m³** çıkar; basamaklar 10, 25 ve 40'tır,
27,4 diye bir basamak yoktur. Endeks sütununun **3.008 sayısal değeri** metne dönmüştür ve
sütunun en büyük değeri `'yok'` olur, çünkü metin karşılaştırması harfi rakamdan büyük sayar.

Üçünün ortak yanı, yanlışlığın **çerçevede** değil kullanımda ortaya çıkmasıdır. Çerçeve
kurulduğunda hiçbir şey bozuk görünmez: satır sayısı doğrudur, sütun adları yerindedir, hücreler
doludur. Yanlış tip ancak o sütuna bir işlem uygulandığında konuşur ve konuştuğunda da hata değil
sayı üretir. Bu yüzden tipin denetimi, çerçeve kurulduktan sonra değil, okuma kararının yanında
yapılır.

Kazanç tarafı açık: yedi sütun için ayrı ayrı dönüşüm kuralı yazmak yerine tek bir okuma çağrısı
yazılır ve çerçeve tiplenmiş gelir. Tam tarama 22.393 hücre geçişi tutar; çıkarım ilk 100 satırla
sınırlanırsa geçiş 700'e, yani otuz ikide bire iner. Bayt tarafında da kazanç görünür: tam
çıkarımla geri yazılan dosya 138.816 bayttır, örnek çıkarımla 127.926 bayta düşer.

Gizlenen karar tam da o bayt düşüşünün içindedir. 10.890 baytlık tasarruf bir sıkıştırma değil,
bir **kayıptır**: 1.123 sayaç numarası yazılmamıştır. Örneklem büyüklüğü hiçbir yerde yazılmadan
seçilmiş, çıkarım o seçime göre karar vermiş, okuma hiçbir uyarı üretmemiştir. Varsayılanı
yazılmayan bir okuma çağrısı, kaç satıra baktığını da söylemeyen bir çağrıdır ve bu kursun kuralı
gereği ölçülmemiş sayılır.

#### Özet

- Ayırıcıyla bölünmüş bir dosya tip taşımaz; tip okuma anında çıkarımla üretilir ve kural sütun
  genelindedir: tek bir ayrışmayan değer sütunun tamamını metne düşürür.
- Bütün satırlar tarandığında 7 sütunun 3'ü yanlış tiple gelir: abone numarasının ortalaması
  10.690,5, tarife basamağının ortalaması 27,4 m³ diye hesaplanabilir, endeksin 3.008 sayısal
  değeri metne döner ve sütunun en büyüğü `'yok'` olur.
- Çıkarım ilk 100 satırdan yapılınca geçiş 22.393'ten 700'e iner, ama sayaç numarası tam sayıya
  kayar: 2.077. satırda başlayan harfli kodların 1.123 hücresi uyarısız eksiğe döner.
- Gidiş dönüş denetimi kaybı doğrudan sayar: örnek çıkarımla 3.199 hücre değişir, dosya 139.238
  bayttan 127.926 bayta iner ve düşüşün 10.890 baytı yazılmamış sayaç numarasıdır.
- Biçim kaybı tipten ayrı bir kalemdir: iki ondalıklı sabit yazımın kısa gösterime dönmesi her iki
  çıkarımda da 422 hücreyi değiştirir ve hiçbir değeri bozmaz.

#### Sonraki Adım

Küme artık bir çerçevededir ve sütunlarının tipi bilinerek okunmuştur. Sıradaki soru satıra nasıl
erişileceğidir. Bir çerçeveden "10.100 ile 10.200 arasındaki aboneler" istendiğinde iki ayrı yol
vardır: etiket dizinine göre adla erişmek ve sıraya göre konumla erişmek. İki yol aynı aralığı
tarif ediyormuş gibi yazılır, aynı adı taşır, yan yana durur. Sonraki ders aynı dilimi iki erişim
biçiminde çalıştırır ve tek bir sayıyı sorar: her biri kaç satır döndürüyor ve aralığın son ucu
sonuca dahil mi.

### İndeksleme ve Dilimleme

Source: https://academia.sh/tr/kurslar/python-ile-analiz/indeksleme-ve-dilimleme

Aynı iki uç, 10.100 ve 10.200, dört ayrı yazımda dört farklı satır sayısı döndürüyor: etiketle dilimlenince 95 satır çünkü etiket dilimi son ucu içerir, aynı iki konumla dilimlenince 94 satır çünkü konum dilimi son ucu dışarıda bırakır, uçlar konum sanılıp yazılınca 100 satır, tekil olmayan dizinli uzun tabloda ise 230 satır. Çerçeve toplam tüketime göre sıralanınca etiket dizini sıralı olmaktan çıkar ve aynı dilim 617 satır döner; dizinde bulunmayan bir uç sıralı dizinde 22 satırlık bir dilim verirken sırası bozuk dizinde hata üretir. Dizinsiz doğrusal tarama 2.658 geçiş tutarken etiket dizininde ikili arama 21 geçişle aynı 95 satıra varır.

Küme çerçeveye girdi ve sütunlarının tipi bilinerek okundu. Sıradaki soru satıra nasıl erişileceği.
Bir çerçeveden "10.100 ile 10.200 arasındaki aboneler" istendiğinde iki ayrı yol vardır: satırı
**etiketiyle** istemek ve satırı **sırasındaki yeriyle** istemek. İkisi de bir aralık alır, ikisi de
bir alt tablo döndürür ve kodda neredeyse aynı görünürler.

Aynı görünmelerine rağmen aynı şeyi yapmazlar ve fark tek bir kararda toplanır: aralığın son ucu
sonuca dahil mi. Bu dersin ölçüsü budur — aynı iki uç, farklı yazımlarda kaç satır döndürüyor. İkinci
bir soru da bunun yanında durur: etiket dilimi, dizinin sıralı olduğunu varsayar ve bu varsayım
hiçbir yerde yazılmaz.

- **VC10. Etiket tabanlı erişim** satırı dizindeki adıyla bulur; **konum tabanlı erişim** satırı
  tablodaki sırasıyla bulur. İkisi ancak dizin `0, 1, 2, …` diye giden bir tam sayı dizisiyse
  çakışır.
- **VC11.** Etiket dilimi **iki ucu da içerir**, konum dilimi **son ucu dışarıda bırakır**. Bu bir
  kütüphane tercihi değil, iki farklı adresleme mantığının sonucudur: bir ad aralığında son ad da
  bir addır, bir konum aralığında ise son konum bir sınırdır.
- **VC12.** Dizin **tekil olmayabilir**. Aynı etiket birden çok satırda geçiyorsa etiket dilimi o
  etiketin bütün satırlarını getirir ve dönen satır sayısı etiket sayısından büyük olur.
- **VC13.** Etiket dilimi dizinin **sıralı** olmasını varsayar. Sıralıysa iki uç ikili aramayla
  bulunur; sıralı değilse dilim "iki ucun ilk geçtiği konum arası" anlamına döner ve bu bambaşka
  bir kümedir.

#### İki Erişim Biçimi

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

GECIS = {"sayi": 0}

class EtiketDizini:
    """Bir etiket listesi ve etiket -> konum sozlugu. Sirali olup olmadigi
    dizinin kendi ozelligidir ve dilimleme davranisini belirler."""

    def __init__(self, etiket):
        self.etiket = list(etiket)
        self.yer = {e: i for i, e in enumerate(self.etiket)}
        self.sirali = all(self.etiket[i] <= self.etiket[i + 1]
                          for i in range(len(self.etiket) - 1))

    def __len__(self):
        return len(self.etiket)

toplam = {}
for o in UZUN:
    toplam[o["abone_no"]] = round(toplam.get(o["abone_no"], 0.0) + o["tuketim_m3"], 2)
ozet = [{"abone_no": k["abone_no"], "bolge": k["bolge"],
         "toplam_m3": toplam.get(k["abone_no"], 0.0)} for k in abone]
dizin = EtiketDizini([s["abone_no"] for s in ozet])
uzun_dizin = EtiketDizini([o["abone_no"] for o in UZUN])
print(f"ozet cercevesi {len(dizin)} satir, etiketler {dizin.etiket[0]}..{dizin.etiket[-1]}, "
      f"arada {dizin.etiket[-1] - dizin.etiket[0] + 1 - len(dizin)} bosluk")
print(f"uzun tablo {len(uzun_dizin)} satir, {len(set(uzun_dizin.etiket))} tekil etiket; "
      f"dizin sirali mi: {'evet' if uzun_dizin.sirali else 'hayir'}")
```

```
ozet cercevesi 1329 satir, etiketler 10001..11399, arada 70 bosluk
uzun tablo 3199 satir, 1260 tekil etiket; dizin sirali mi: evet
```

İki çerçeve de aynı kümeden çıkıyor ve dizinleri birbirinden iki yönde ayrılıyor. Özet çerçevesinde
bir satır bir abonedir, dizin tekildir, ama **sürekli değildir**: 10.001 ile 11.399 arasında 1.399
sayı vardır ve bunların 70 tanesi bir abone karşılığı taşımaz. Bunlar M26/K03'ün temizleme adımında
elenen kayıtlardır ve dizinde bir boşluk olarak kalmışlardır.

Uzun tabloda ise dizin tekil değildir: 3.199 satır 1.260 farklı abone etiketi taşır, çünkü bir
abonenin üç dönemi üç satırdır. Bu iki özellik, boşluklu dizin ve tekil olmayan dizin, dilimlemenin
davranışını doğrudan belirler. İkisi de dizinin kendi özelliğidir, çerçeveyi kuran kod bunları hiçbir
yerde bildirmez.

#### Aynı Dilim, Dört Sayı

```python
# dilim.py — ayni iki uc, farkli yazimlar. Etiket dilimi iki ucu da icerir,
# konum dilimi son ucu disarida birakir.
def ikili_ara(etiket, hedef, sag=False) -> int:
    """Sirali listede konum arar; karsilastirma sayisi GECIS'e eklenir."""
    alt, ust = 0, len(etiket)
    while alt < ust:
        GECIS["sayi"] += 1
        orta = (alt + ust) // 2
        if (etiket[orta] <= hedef) if sag else (etiket[orta] < hedef):
            alt = orta + 1
        else:
            ust = orta
    return alt

def dilim_etiket(dizin: EtiketDizini, bas, son):
    """Etikete gore dilim: iki uc da dahildir. Dizin sirali degilse iki ucun
    kendi konumlari alinir ve arada kalan satirlar donulur."""
    if dizin.sirali:
        return ikili_ara(dizin.etiket, bas), ikili_ara(dizin.etiket, son, sag=True)
    return dizin.yer[bas], dizin.yer[son] + 1

def dogrusal_dilim(etiket, bas, son):
    """Dizin kurulmadan ayni dilim: her etiket iki kez denetlenir."""
    secilen, gecis = [], 0
    for e in etiket:
        gecis += 2
        if bas <= e <= son:
            secilen.append(e)
    return secilen, gecis

BAS, SON = 10100, 10200
GECIS["sayi"] = 0
i, j = dilim_etiket(dizin, BAS, SON)
etiket_satir, etiket_gecis = j - i, GECIS["sayi"]
EN = [40, 10, 10]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
print(y(["erisim bicimi", "satir", "gecis"]))
print(y([f"etiketle {BAS}..{SON} (uc dahil)", etiket_satir, etiket_gecis]))
print(y([f"konumla [{i}:{j - 1}] (uc haric)", (j - 1) - i, 0]))
print(y([f"konumla [{BAS - 10001}:{SON - 10001}]", len(ozet[BAS - 10001:SON - 10001]), 0]))
GECIS["sayi"] = 0
ui, uj = dilim_etiket(uzun_dizin, BAS, SON)
print(y([f"uzun tabloda etiketle {BAS}..{SON}", uj - ui, GECIS["sayi"]]))
secilen, dg = dogrusal_dilim(dizin.etiket, BAS, SON)
print(y(["dizinsiz dogrusal tarama", len(secilen), dg]))
```

```
erisim bicimi                                satir     gecis
etiketle 10100..10200 (uc dahil)                95        21
konumla [95:189] (uc haric)                     94         0
konumla [99:199]                               100         0
uzun tabloda etiketle 10100..10200             230        24
dizinsiz dogrusal tarama                        95      2658
```

Beş satır, aynı iki uç ve dört farklı sayı. İlk ikisi arasındaki fark tam olarak bir satırdır ve o
satır dilimin **son satırıdır**: etiket dilimi 10.200 numaralı aboneyi içerir, aynı iki konumla
yazılan konum dilimi içermez. Tek satırlık bir fark bir raporda görünmez, ama bir eşik hesabında
sınırdaki kaydı dışarıda bırakır ve sınırdaki kayıt genellikle hesabın konusudur.

Üçüncü satır daha sık rastlanan bir karışıklıktır. Etiketler tam sayı olduğu için konum diye
yazılmışlardır ve dizin sürekli olsaydı bu doğru olurdu. Değildir: aradaki 70 boşluk yüzünden
`10100` etiketi 95. konumdadır, 99. konumda değildir. Sonuç 100 satır döner, ilk satırı 10.100
değildir ve hiçbir hata üretilmemiştir.

Dördüncü satır dizinin tekil olmamasının bedelini gösteriyor. Uzun tabloda aynı etiket dilimi
**230 satır** döndürür, çünkü aralıktaki her abone bir ile üç arasında satır taşır. Sayının 95'ten
büyük olması bir yanlışlık değildir; yanlış olan, "95 abone seçtim" diye okunmasıdır. Etiket dilimi
abone seçmez, **satır** seçer.

Son satır kazancı veriyor. Aynı 95 satıra dizin kurulmadan varmak 2.658 karşılaştırma tutar, çünkü
1.329 etiketin her biri iki kez denetlenir. Etiket dizininde iki ikili arama bu işi **21 geçişle**
bitirir: 126 kat daha az karşılaştırma ve altı satırlık döngü yerine tek satırlık bir dilim.

#### Satır ve Sütun Birlikte

```python
# eksen.py — etiket tabanli erisim iki eksende calisir: once satir, sonra sutun.
# Olcu, sonuca giren hucre sayisidir.
SUTUN = ["abone_no", "bolge", "toplam_m3"]
istenen = ["bolge", "toplam_m3"]
dilim = ozet[i:j]
print(f"cerceve {len(ozet)} satir x {len(SUTUN)} sutun = {len(ozet) * len(SUTUN)} hucre")
print(f"once satir dilimi, sonra sutun secimi: {len(dilim)} x {len(istenen)} = "
      f"{len(dilim) * len(istenen)} hucre")
print(f"once sutun secimi, sonra satir dilimi: {len(ozet)} x {len(istenen)} = "
      f"{len(ozet) * len(istenen)} hucre ara tabloda, sonra {len(dilim)} satir")
tek = [s["toplam_m3"] for s in dilim]
print(f"tek sutun secilirse sonuc seri olur: {len(tek)} deger, "
      f"toplam {round(sum(tek), 2)} m3")
```

```
cerceve 1329 satir x 3 sutun = 3987 hucre
once satir dilimi, sonra sutun secimi: 95 x 2 = 190 hucre
once sutun secimi, sonra satir dilimi: 1329 x 2 = 2658 hucre ara tabloda, sonra 95 satir
tek sutun secilirse sonuc seri olur: 95 deger, toplam 4348.3 m3
```

Etiket tabanlı erişim tek eksende çalışmaz: aynı ifade hem satırı hem sütunu adlandırır. Bu, iki
ayrı seçimi tek adımda birleştirdiği için kısaltır, ama araya bir sıra kararı sokar. Satır dilimi
önce uygulanırsa sonuca **190 hücre** girer; sütun seçimi önce uygulanırsa 2.658 hücrelik bir ara
tablo kurulur ve satır dilimi ondan alınır. Sonuç aynıdır, kurulan tablo aynı değildir. Küçük bir
çerçevede bu fark önemsizdir; ölçek büyüdüğünde `analiz-basarimi` konusunun ölçtüğü kalem tam
olarak budur.

Bir ayrıntı da sonucun **türünü** değiştirir. İki sütun istenirse dönen şey bir çerçevedir; tek
sütun istenirse dönen şey bir seridir. Aynı yazımın bir sütun adı yerine tek elemanlı bir sütun
listesi alması sonucun tipini değiştirir ve bunu izleyen kod ya seri arayüzüne ya çerçeve arayüzüne
göre yazılmıştır. Seçimin kaç sütun döndürdüğü, bu yüzden yalnız bir sayı değil bir tip kararıdır.

#### Sıra Bozulunca

```python
# bozulan_sira.py — ayni etiket dilimi, sirasi bozulmus bir dizinde. Siralamayi
# bozan sey bir hata degil, mesru bir siralama kararidir.
sirali_toplam = sorted(ozet, key=lambda s: -s["toplam_m3"])
bozuk = EtiketDizini([s["abone_no"] for s in sirali_toplam])
print(f"toplama gore siralanan cerceve: etiket dizini sirali mi: "
      f"{'evet' if bozuk.sirali else 'hayir'}")
bi, bj = dilim_etiket(bozuk, BAS, SON)
print(f"ayni dilim {BAS}..{SON}: {BAS} {bi}. konumda, {SON} {bj - 1}. konumda, "
      f"dilim {max(bj - bi, 0)} satir")

yok_olan = next(e for e in range(BAS, SON) if e not in dizin.yer)
gi, gj = dilim_etiket(dizin, BAS, yok_olan)
print(f"\ndizinde bulunmayan uc {yok_olan}: sirali dizinde dilim {gj - gi} satir doner")
try:
    dilim_etiket(bozuk, BAS, yok_olan)
except KeyError as hata:
    print(f"ayni uc, sirasi bozuk dizinde: KeyError {hata}")

print(f"\ngecis : dizinsiz tarama {dg}, etiket dizininde ikili arama {etiket_gecis}")
print(f"kod   : dogrusal dilim 6 satir, etiket dilimi 1 satir")
print(f"satir : ayni iki uc dort yazimda {etiket_satir}, {(j - 1) - i}, "
      f"{len(ozet[BAS - 10001:SON - 10001])} ve {max(bj - bi, 0)} satir doner")
```

```
toplama gore siralanan cerceve: etiket dizini sirali mi: hayir
ayni dilim 10100..10200: 10100 492. konumda, 10200 1108. konumda, dilim 617 satir

dizinde bulunmayan uc 10122: sirali dizinde dilim 22 satir doner
ayni uc, sirasi bozuk dizinde: KeyError 10122

gecis : dizinsiz tarama 2658, etiket dizininde ikili arama 21
kod   : dogrusal dilim 6 satir, etiket dilimi 1 satir
satir : ayni iki uc dort yazimda 95, 94, 100 ve 617 satir doner
```

Çerçeveyi toplam tüketime göre sıralamak meşru bir istektir ve en çok tüketen aboneyi başa getirir.
Yaptığı yan etki, etiket dizininin sıralı olmaktan çıkmasıdır. Sıralı olmayan bir dizinde etiket
dilimi artık "10.100 ile 10.200 arasındaki etiketler" anlamına gelemez, çünkü böyle bir aralık
tabloda bir yerde toplu durmuyordur. Anlamı zorunlu olarak şuna kayar: iki ucun bulunduğu konumlar
arasındaki satırlar. Sonuç **617 satır** olur ve bu satırların büyük kısmının etiketi aralığın
dışındadır.

Aynı ifade, aynı iki uç, 95 yerine 617 satır. İkisi arasında bir hata mesajı yoktur, çünkü ikisi de
dilimin tanımına uyar; değişen şey dizindir. Bu, kursun kuralının en açık örneklerinden biridir:
soyutlama işlemi kısaltır ama üzerinde çalıştığı varsayımı görünür kılmaz.

Bulunmayan uç ise iki dizinde iki farklı davranış üretiyor ve buradaki fark yönü tersine çeviriyor.
10.122 numaralı abone dizinde yoktur. Sıralı dizinde bu bir sorun değildir: ikili arama o etiketin
**olacağı** konumu bulur ve 22 satırlık bir dilim döner. Sırası bozuk dizinde ise o etiketin bir
konumu yoktur ve dilim hata verir. Yani soyutlama burada sessiz kalmıyor, konuşuyor; ama gürültülü
davrandığı durum yanlış sonucun değil, **eksik bilginin** olduğu durumdur. Sessiz kaldığı 617
satırlık durumda ise bir cevap üretmiştir.

#### Özet

- Etiket tabanlı erişim satırı adıyla, konum tabanlı erişim sırasıyla bulur; etiket dilimi son ucu
  içerir, konum dilimi dışarıda bırakır.
- Aynı iki uç dört yazımda 95, 94, 100 ve 617 satır döndürür; ilk ikisi arasındaki tek satırlık fark
  dilimin sınırdaki kaydıdır.
- Dizin sürekli değilse etiketi konum sanmak sessizce kayar: 70 boşluk yüzünden 10.100 etiketi 95.
  konumdadır ve konumla yazılan dilim 100 satırın yanlış 100'ünü döndürür.
- Tekil olmayan dizinde etiket dilimi abone değil satır seçer: 3.199 satırlık uzun tabloda aynı
  aralık 230 satır getirir.
- Dizinin kazancı geçiş sayısında okunur: doğrusal tarama 2.658 karşılaştırma tutarken ikili arama
  21 geçişle aynı 95 satıra varır; gizlenen karar, etiket diliminin dizinin sıralı olduğunu
  varsaymasıdır.

#### Sonraki Adım

Dilimleme aralık verir, ama analizde asıl istenen aralık değil koşuldur: "tüketimi 25 m³'ün
üstündeki okumalar". Koşullu seçim bir karşılaştırmadan doğar ve karşılaştırma, kümedeki her değerin
karşılaştırılabilir olduğunu varsayar. Endeks sütununda 111 boş ve 80 "yok" yazılı hücre olduğu
görülmüştü; bir eşiğin altında mı üstünde mi olduğu bilinmeyen bir değer, süzgeçten hangi tarafa
düşer. Sonraki ders aynı koşulu üç ayrı yazımla çalıştırır ve üçünün kaç satır döndürdüğünü sayar.

### Filtreleme ve Sorgulama

Source: https://academia.sh/tr/kurslar/python-ile-analiz/filtreleme-ve-sorgulama

Endeksi 120.000'in üstündeki okumalar üç ayrı yazımla süzülüyor ve üç farklı satır sayısı çıkıyor: sayısal karşılaştırma 1.835 satır ve 36.435,83 m³, aynı koşulun değillemesi 2.026 satır ve 40.356,71 m³, sütun metin okunmuşken aynı karşılaştırma 1.915 satır ve 38.035,93 m³. Fark eksiklerdedir: değilleme 191 eksik satırın tamamını içeri alır, metin karşılaştırması 80 'yok' hücreyi alır çünkü harf rakamdan büyük sayılır. İki koşul birleşince fark büyür ve mantıkta denk yazılan iki ifade 446 ile 502 satır döner. Elle süzgeç zinciri 5.034 geçişte 16.184 baytlık ara tablo kurarken maske 6.398 geçiş yapar ve 3.399 bayt tutar.

Dilimleme bir aralık verir, ama analizde asıl istenen aralık değil koşuldur: "endeksi 120.000'in
üstündeki okumalar". Koşullu seçim bir karşılaştırmadan doğar ve karşılaştırma sessiz bir varsayım
taşır — kümedeki her değerin karşılaştırılabilir olduğu varsayımı. Bir önceki derste endeks
sütununda 111 boş ve 80 "yok" yazılı hücre görülmüştü. Bir eşiğin altında mı üstünde mi olduğu
bilinmeyen bir değer, süzgeçten hangi tarafa düşer.

Bu dersin cevabı tek bir cümle değildir, çünkü yazıma göre değişir. Aynı soru üç ayrı biçimde
yazılabilir, üçü de doğru görünür, üçü de hata vermez ve üçü farklı satır sayısı döndürür. Ölçü
budur: aynı koşul, üç yazım, üç sayı. Fark hep aynı yerden gelir, eksik değerlerden, ama her
yazımda başka bir tarafa düşer.

- **VC14.** Eksik bir değerle yapılan karşılaştırma **yanlış** döner. Bu, eksik değerin küçük
  olduğu anlamına gelmez; karşılaştırma bir cevap üretemediği için kendi başına bir cevap seçmiştir.
- **VC15.** Karşılaştırma "bilinmiyor"u "yanlış"a eşlediği için bu eşleme **değilleme altında
  korunmaz**: `x > e` yanlış olduğu satırda `x <= e` de yanlıştır, dolayısıyla `değil(x <= e)`
  doğrudur. Aynı satır bir yazımda dışarıda, ötekinde içeridedir.
- **VC16. Süzgeç maskesi**, satır başına bir doğruluk değeri taşıyan bir dizidir. Maske satırı
  kopyalamaz; koşullar maske üzerinde birleştirilir ve sonuç bir kez üretilir.
- **VC17. Sorgulama**, süzgeç koşulunun bir ifade olarak yazılmasıdır. Yazımın biçimi sonucu
  değiştirmez; koşulun içindeki karşılaştırmaların eksik değerde ne yaptığı değiştirir.

#### Eksik Değer Karşılaştırılamaz

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

# endeks sutunu bir onceki dersteki gibi saha formundan gelir: bir kismi bos,
# bir kismina elle "yok" yazilmistir.
import sys

kumul: dict[int, float] = {}
kayit = []
for o in UZUN:
    no, t = o["abone_no"], o["tuketim_m3"]
    kumul[no] = kumul.get(no, 0.0) + t
    u = uretec(TOHUM + 9100 + no * 4 + DONEM.index(o["donem"]))()
    ham = "" if u < 0.033 else "yok" if u < 0.055 else str(
        100000 + (no - 10000) * 37 + int(kumul[no]))
    kayit.append({"abone_no": no, "donem": o["donem"], "tuketim_m3": t, "endeks": ham})

EKSIK = None

def sayiya(h: str):
    """Metni sayiya cevirir; cevrilemeyen her hucre EKSIK olur."""
    try:
        return int(h)
    except ValueError:
        return EKSIK

sayisal = [sayiya(k["endeks"]) for k in kayit]
metin = [k["endeks"] for k in kayit]
print(f"{len(kayit)} satir: {sum(1 for v in sayisal if v is not EKSIK)} sayisal endeks, "
      f"{sum(1 for k in kayit if k['endeks'] == '')} bos, "
      f"{sum(1 for k in kayit if k['endeks'] == 'yok')} 'yok'")
print(f"sayisal endeks araligi {min(v for v in sayisal if v is not EKSIK)}.."
      f"{max(v for v in sayisal if v is not EKSIK)}")
```

```
3199 satir: 3008 sayisal endeks, 111 bos, 80 'yok'
sayisal endeks araligi 100067..151812
```

Üç bin yüz doksan dokuz satırın 3.008'i sayısal bir endeks taşıyor, 191'i taşımıyor. Bu 191 satırın
kaybı kayıt aşamasındandır: sayaç okunamamıştır ya da form eksik doldurulmuştur. İkisi de aynı
sonuca çıkar — o satır için endeks bilinmiyordur.

"Bilinmiyor" bir değer değildir, bir **bilgi durumudur** ve bir karşılaştırma işleci bunu ifade
edemez. Karşılaştırma iki seçenek arasından birini vermek zorundadır ve seçtiği şey "yanlış" olur.
Bu seçim savunulabilir bir varsayılandır, çünkü bir süzgecin bilmediği satırı seçmemesi makuldür.
Sorun varsayılanın kendisinde değil, **yazılmamış** olmasındadır.

Ayrım burada bir kez daha çizilmelidir: eksik değer ile sıfır aynı şey değildir. Kümede sıfır
tüketimli okumalar vardır ve bunlar ölçülmüş kayıtlardır; sayaç okunmuş, tüketim çıkmamıştır.
Karşılaştırmaya girerler, bir tarafa düşerler ve hiçbir belirsizlik taşımazlar. Eksik endeksli 191
satır ise ölçülmemiştir. İki durumu aynı sütunda tutan bir küme, süzgeç yazılırken hangisinin
karşılaştırıldığını da yazmak zorundadır.

#### Aynı Soru, Üç Yazım

```python
# uc_yazim.py — ayni soru uc bicimde: endeksi 120000'in ustundeki okumalar.
ESIK = 120000

def maske_sayisal(v):
    """Eksik deger karsilastirmaya giremez, sonuc yanlis doner."""
    return v is not EKSIK and v > ESIK

def maske_degilleme(v):
    """Ayni kosulun degillemesi: eksik satirda ic karsilastirma da yanlistir."""
    return not (v is not EKSIK and v <= ESIK)

s1 = [maske_sayisal(v) for v in sayisal]
s2 = [maske_degilleme(v) for v in sayisal]
s3 = [h > str(ESIK) for h in metin]

EN = [42, 8, 12]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
T = lambda m: round(sum(k["tuketim_m3"] for k, b in zip(kayit, m) if b), 2)
print(y(["yazim", "satir", "toplam m3"]))
print(y([f"sayisal   endeks > {ESIK}", sum(s1), T(s1)]))
print(y([f"degilleme not(endeks <= {ESIK})", sum(s2), T(s2)]))
print(y([f"metin     endeks > '{ESIK}'", sum(s3), T(s3)]))
print(f"\nikinci yazim birinciden {sum(s2) - sum(s1)} satir fazla: eksiklerin tamami")
print(f"ucuncu yazim birinciden {sum(s3) - sum(s1)} satir fazla: 'yok' yazili hucreler, "
      f"cunku metin karsilastirmasi harfi rakamdan buyuk sayar")
```

```
yazim                                        satir   toplam m3
sayisal   endeks > 120000                     1835    36435.83
degilleme not(endeks <= 120000)               2026    40356.71
metin     endeks > '120000'                   1915    38035.93

ikinci yazim birinciden 191 satir fazla: eksiklerin tamami
ucuncu yazim birinciden 80 satir fazla: 'yok' yazili hucreler, cunku metin karsilastirmasi harfi rakamdan buyuk sayar
```

Üç satır aynı soruyu soruyor ve üç ayrı sayı veriyor: 1.835, 2.026 ve 1.915 satır. Rapora giren
tüketim toplamı da üç ayrı değerdir ve en düşükle en yüksek arasında 3.920,88 m³ vardır.

İkinci yazım en sinsi olanıdır, çünkü değilleme okurken doğal görünür: "eşiğin altında olmayanlar",
"eşiği aşanlar" ile aynı şey sanılır. Değildir. Eksik endeksli bir satırda `endeks <= 120000`
karşılaştırması yanlış döner, çünkü karşılaştırılamayan değer için seçilen cevap budur; o yanlışın
değillemesi ise doğrudur. Böylece 191 satır, hakkında hiçbir bilgi olmadığı halde "eşiği aşanlar"
kümesine girer ve 3.920,88 m³ tüketim raporlanan toplama eklenir.

Üçüncü yazım bir önceki dersin doğrudan devamıdır. Endeks sütunu 80 "yok" yüzünden metin okunmuştu;
metin okunmuş bir sütunda `>` işleci hâlâ çalışır, ama sözlük sırasına göre çalışır. Bütün sayısal
endeksler altı haneli olduğu için metin karşılaştırması onlarda sayısal karşılaştırmayla aynı
sonucu verir; ayrıldıkları tek yer `'yok'` hücreleridir ve "y" harfi "1" rakamından büyüktür. Sonuç,
tam olarak 80 satır fazladır ve bu satırların hiçbirinde bir endeks yoktur.

#### Birleşik Koşul ve Süzgecin Bedeli

```python
# birlesik.py — iki kosul birlesince eksigin etkisi cogalir: mantikta denk
# yazilan iki ifade ayni satir sayisini vermez.
def ve(v, t):
    return (v is not EKSIK and v > ESIK) and t > 25.0

def degil_veya(v, t):
    return not ((v is not EKSIK and v <= ESIK) or t <= 25.0)

b1 = [ve(v, k["tuketim_m3"]) for v, k in zip(sayisal, kayit)]
b2 = [degil_veya(v, k["tuketim_m3"]) for v, k in zip(sayisal, kayit)]
print(f"(endeks > {ESIK}) ve (tuketim > 25)           : {sum(b1)} satir, {T(b1)} m3")
print(f"degil((endeks <= {ESIK}) veya (tuketim <= 25)) : {sum(b2)} satir, {T(b2)} m3")
print(f"iki ifade mantikta denktir, sonuc {sum(b2) - sum(b1)} satir farklidir")

def elle_zincir():
    """Her kosul icin kume bastan taranir ve ara tablo kopyalanir."""
    ara = [(k, v) for k, v in zip(kayit, sayisal) if v is not EKSIK and v > ESIK]
    son = [(k, v) for k, v in ara if k["tuketim_m3"] > 25.0]
    return ara, son, len(kayit) + len(ara)

ara, son, elle_gecis = elle_zincir()
maske = bytearray(b1)
print(f"\nelle zincir: {len(son)} satir, {elle_gecis} gecis, ara tablo "
      f"{sys.getsizeof([k for k, _ in ara])} bayt")
print(f"maske      : {sum(maske)} satir, {2 * len(kayit)} gecis, maske "
      f"{sys.getsizeof(maske)} bayt, 1 satir kod")
print(f"maske gecis sayisinda kazanmaz, ara tabloda kazanir: "
      f"{sys.getsizeof([k for k, _ in ara]) - sys.getsizeof(maske)} bayt")
```

```
(endeks > 120000) ve (tuketim > 25)           : 446 satir, 13906.21 m3
degil((endeks <= 120000) veya (tuketim <= 25)) : 502 satir, 15675.3 m3
iki ifade mantikta denktir, sonuc 56 satir farklidir

elle zincir: 446 satir, 5034 gecis, ara tablo 16184 bayt
maske      : 446 satir, 6398 gecis, maske 3399 bayt, 1 satir kod
maske gecis sayisinda kazanmaz, ara tabloda kazanir: 12785 bayt
```

İki koşul birleşince fark küçülmez, yön değiştirir. `A ve B` ile `değil(değil A veya değil B)`
iki değerli mantıkta birbirinin aynısıdır ve bir ders kitabında yer değiştirilebilir. Burada
değiştirilemiyor, çünkü atomik karşılaştırma iki değerli değildir: üç durumu, yani doğru, yanlış ve
bilinmiyor durumlarını, iki değere sıkıştırır ve "bilinmiyor"u "yanlış"a eşler. Bu eşleme
değilleme altında korunmaz. Sonuç 446 yerine 502 satır ve raporlanan toplam 13.906,21 yerine
15.675,30 m³ olur.

Süzgecin bedeli ikinci çıktıdadır ve burada soyutlamanın kazancı beklenen yerde değildir. Elle
yazılan zincir her koşulu ayrı bir geçişte uygular ve arada bir tablo **kopyalar**: 5.034 geçiş,
16.184 baytlık ara tablo. Maske ise her koşulu bütün satırlarda değerlendirir, 6.398 geçiş yapar,
yani daha fazla geçiş tutar; kazandığı yer bellektir. Maske 3.399 bayttır ve hiçbir satır
kopyalanmaz; fark 12.785 bayttır ve küme büyüdükçe oransal olarak korunur.

Sorgulamanın koşulu bir ifade olarak yazması da bu tabloyu değiştirmez. Koşul dizgiyle yazılsın,
maskeler işleçle birleştirilsin, sonuç aynı üç kalemden çıkar: hangi karşılaştırmalar var, eksik
değerde ne dönüyorlar ve nasıl birleştiriliyorlar. Yazım biçimi bir okunabilirlik kararıdır;
satır sayısını belirleyen şey karşılaştırmanın eksik değerdeki davranışıdır.

#### Süzgecin Üçüncü Kümesi

```python
# uc_kume.py — savunulabilir yazim: suzgec iki degil uc kume uretir ve ucunun
# toplami kumenin tamamini verir.
ust = sum(1 for v in sayisal if v is not EKSIK and v > ESIK)
alt = sum(1 for v in sayisal if v is not EKSIK and v <= ESIK)
bilinmiyor = sum(1 for v in sayisal if v is EKSIK)
print(f"esigin ustunde {ust}, altinda {alt}, bilinmiyor {bilinmiyor}, "
      f"toplam {ust + alt + bilinmiyor}")
print(f"bilinmeyen pay: {round(100 * bilinmiyor / len(kayit), 2)} yuzde; "
      f"tamami ustune yazilirsa {ust} yerine {ust + bilinmiyor}, "
      f"yani {round(100 * bilinmiyor / ust, 1)} yuzde oynama")
```

```
esigin ustunde 1835, altinda 1173, bilinmiyor 191, toplam 3199
bilinmeyen pay: 5.97 yuzde; tamami ustune yazilirsa 1835 yerine 2026, yani 10.4 yuzde oynama
```

Bir süzgeç yazımı bu üç sayıyı ayrı ayrı raporladığında dersin bütün belirsizliği ortadan kalkar.
1.835 satır eşiğin üstündedir, 1.173 satır altındadır, 191 satır hakkında karar verilememiştir ve
üçünün toplamı kümenin tamamıdır. Bu toplama denetimi, tek bir sayı raporlayan yazımların hiçbirinde
yapılamaz: `1.835` ile `2.026` arasında hangi sayının doğru olduğu, kümeye bakarak değil ancak
eksiklerin nereye yazılacağına karar vererek belirlenir.

Belirsizliğin büyüklüğü de burada okunuyor. Bilinmeyen satırlar kümenin yüzde 5,97'sidir, ama
raporlanan sayı üzerindeki etkisi yüzde 10,4'tür, çünkü eksikler kümenin tamamına değil süzgeçten
geçen kısma oranlanır. Küçük görünen bir eksiklik oranının rapordaki etkisi eşiğin nereye
konduğuna bağlıdır ve eşik daraldıkça büyür. Bu yüzden eksiklik oranı tek başına yeterli bir
denetim değildir; ölçülmesi gereken şey eksiklerin **süzgeç sonucuna** oranıdır.

#### Özet

- Eksik bir değerle yapılan karşılaştırma yanlış döner; bu, eksik değerin küçük olduğu anlamına
  gelmez, karşılaştırmanın cevap üretemediği yerde bir cevap seçtiği anlamına gelir.
- Aynı koşul üç yazımda üç satır sayısı verir: sayısal 1.835, değilleme 2.026, metin karşılaştırması
  1.915; raporlanan toplam 36.435,83 ile 40.356,71 m³ arasında oynar.
- Değilleme 191 eksik satırın tamamını içeri alır; metin karşılaştırması 80 "yok" hücreyi alır,
  çünkü sözlük sırasında harf rakamdan büyüktür.
- Birleşik koşulda mantıkta denk yazılan iki ifade 446 ile 502 satır döner, çünkü karşılaştırma
  "bilinmiyor"u "yanlış"a eşler ve bu eşleme değilleme altında korunmaz.
- Maskenin kazancı geçişte değil bellektedir: elle zincir 5.034 geçişte 16.184 baytlık ara tablo
  kurarken maske 6.398 geçiş yapar ve 3.399 bayt tutar.

#### Sonraki Adım

Süzgeç satırları seçer, ama rapor tek tek satır istemez; kırılıma göre özet ister. "Bölge başına
ortalama tüketim" sorusu kümeyi önce böler, her parçaya bir hesap uygular ve sonuçları tek tabloda
birleştirir. Bu üç adımlık iş tek bir çağrıya sığar ve o çağrı iki ayrı karar taşır: grup
anahtarında eksik değer taşıyan satırlar hangi gruba girer ve toplulaştırma hesabı eksik değerleri
paydaya katar mı. Sonraki ders bu iki kararı ayrı ayrı ölçer ve kaç satırın hiçbir grupta
görünmeden kaybolduğunu sayar.

### Gruplama ve Toplulaştırma

Source: https://academia.sh/tr/kurslar/python-ile-analiz/gruplama-ve-toplulastirma

Böl, uygula ve birleştir adımları tek çağrıya sığıyor ve o çağrı iki kararı birden taşıyor: grup anahtarı boş olan 160 satır varsayılan gruplamada hiçbir grupta görünmez ve beş satırlık özetin toplamı 63.060,63 m³ yerine 60.025,56 m³ çıkar, yani kümenin yüzde 4,81'i hiçbir uyarı üretmeden düşer. Eksik anahtar korunduğunda özet 6 satır olur ve düşen kısım 3.035,07 m³ olarak görünür hale gelir. İkinci karar toplulaştırmanın paydasıdır: kuzey grubu 878 satır taşır ama endeks sütununda 831 dolu değer vardır ve ortalama, paydaya göre 126.247,9 ya da 119.489,8 çıkar. Ölçüt başına ayrı döngü 15.995 anahtar araması tutarken tek bölme 3.199 aramayla beş ölçütü besler.

Süzgeç satırları seçer, ama rapor tek tek satır istemez; kırılıma göre özet ister. "Bölge başına
ortalama endeks" sorusu üç adımlık bir iştir: küme anahtara göre **bölünür**, her parçaya bir hesap
**uygulanır**, sonuçlar tek tabloda **birleştirilir**. Veri Toplama ve Hazırlama kursunun gruplama
dersi bu üç adımı elle yazılan döngülerle yürütmüş ve gruplama kararının çıkan satır sayısını
belirlediğini ölçmüştü; oradaki hesap bu dersin girdisidir ve tekrarlanmaz.

Burada değişen şey, üç adımın tek bir çağrıya sığmasıdır. O çağrı iki ayrı kararı birden taşır ve
ikisi de yazılmaz: grup anahtarı boş olan satır hangi gruba girer ve toplulaştırma hesabı neyi
payda alır. Bu dersin ölçüsü bu iki kararın kaç satırı ve kaç metre küpü oynattığıdır.

- **VC18. Böl–uygula–birleştir**, gruplamanın üç adımıdır. Bölme anahtar başına bir kova kurar,
  uygulama her kovada bir hesap yapar, birleştirme kova başına bir satır çıkarır. Çıkan satır
  sayısı **anahtarın farklı değer sayısıdır**, kümenin satır sayısı değil.
- **VC19.** Grup anahtarı eksik olan satır bir kovaya ait değildir. Varsayılan davranış onu
  **düşürmektir**; eksik değeri kendi kovasında toplamak da meşru bir karardır ve iki karar arasında
  fark bir satır sayısı ve bir toplamdır.
- **VC20. Toplulaştırmanın paydası** ayrı bir karardır. Ortalama, toplulaştırılan sütunun **dolu**
  değerlerini sayarak da, grubun **bütün** satırlarını sayarak da hesaplanabilir. İki payda iki
  ortalama verir ve rapordaki `n` sütunu hangisini yazdığını söylemek zorundadır.
- **VC21.** Bölme adımı bir kez yapılırsa aynı kovalar birçok ölçüt tarafından paylaşılır. Kazancın
  ölçüsü **anahtar araması sayısıdır**, değer okuması sayısı değil.

#### Anahtar Bulunmayan Satır

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

# Grup anahtari abonenin bolgesidir; bir kisim abonenin adres kaydi eslesmedigi
# icin bolgesi bostur. Endeks sutunu bir onceki dersteki gibi eksik tasir.
EKSIK = None

def bolge_hucresi(no: int, ad: str):
    return EKSIK if uretec(TOHUM + 61000 + no)() < 0.048 else ad

bolge_of = {k["abone_no"]: bolge_hucresi(k["abone_no"], k["bolge"]) for k in abone}
kumul: dict[int, float] = {}
kayit = []
for o in UZUN:
    no, t = o["abone_no"], o["tuketim_m3"]
    kumul[no] = kumul.get(no, 0.0) + t
    u = uretec(TOHUM + 9100 + no * 4 + DONEM.index(o["donem"]))()
    endeks = EKSIK if u < 0.055 else 100000 + (no - 10000) * 37 + int(kumul[no])
    kayit.append({"abone_no": no, "donem": o["donem"], "tuketim_m3": t,
                  "endeks": endeks, "bolge": bolge_of[no]})

print(f"{len(kayit)} satir; grup anahtari bos olan "
      f"{sum(1 for k in kayit if k['bolge'] is EKSIK)} satir, toplulastirilacak "
      f"sutunu bos olan {sum(1 for k in kayit if k['endeks'] is EKSIK)} satir")
print(f"kumenin gercek toplami {round(sum(k['tuketim_m3'] for k in kayit), 2)} m3")
```

```
3199 satir; grup anahtari bos olan 160 satir, toplulastirilacak sutunu bos olan 191 satir
kumenin gercek toplami 63060.63 m3
```

İki eksiklik aynı kümede duruyor ve birbirinden bağımsız. 160 satırın **grup anahtarı** boştur:
abonenin adres kaydı bir bölgeye eşleşmemiştir. 191 satırın **toplulaştırılacak sütunu** boştur:
sayaç okunamamış, endeks kaydedilmemiştir. İki eksikliğin ikisi de aynı gruplama çağrısının içinden
geçer ve çağrı her birine ayrı davranır.

Kümenin gerçek toplamı 63.060,63 m³'tür ve bu sayı bu dersin denetim çıpasıdır. Bir özet tablonun
toplamı bununla karşılaştırılabildiği sürece gruplamanın ne kaybettiği okunabilir; karşılaştırılmadığı
sürece özet kendi içinde tutarlı görünür ve hiçbir şey söylemez.

#### Böl, Uygula, Birleştir

```python
# bol_uygula_birlestir.py — gruplama uc adimdir: bol, uygula, birlestir.
# Tek cagriya sigar ve iki karari birden tasir.
def bol(kayitlar, anahtar: str, eksigi_dusur=True):
    """Bol adimi: satirlar anahtara gore kovalara dagitilir."""
    kova, dusen = {}, 0
    for k in kayitlar:
        if k[anahtar] is EKSIK and eksigi_dusur:
            dusen += 1
            continue
        kova.setdefault(k[anahtar], []).append(k)
    return kova, dusen

def uygula(kova, sutun: str):
    """Uygula adimi: her kovada sayim, toplam ve iki ayri paydayla ortalama."""
    cikti = []
    for ad, satirlar in kova.items():
        dolu = [k[sutun] for k in satirlar if k[sutun] is not EKSIK]
        cikti.append({
            "grup": "(eksik)" if ad is EKSIK else ad,
            "satir": len(satirlar), "dolu": len(dolu),
            "toplam_m3": round(sum(k["tuketim_m3"] for k in satirlar), 2),
            "ort_dolu": round(sum(dolu) / len(dolu), 1) if dolu else 0.0,
            "ort_satir": round(sum(dolu) / len(satirlar), 1)})
    return sorted(cikti, key=lambda c: c["grup"])

EN = [12, 8, 8, 12, 12, 12]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
kova, dusen = bol(kayit, "bolge")
ozet = uygula(kova, "endeks")
print(y(["grup", "satir", "dolu", "toplam m3", "ort/dolu", "ort/satir"]))
for c in ozet:
    print(y([c["grup"], c["satir"], c["dolu"], c["toplam_m3"], c["ort_dolu"], c["ort_satir"]]))
print(y(["birlesim", sum(c["satir"] for c in ozet), sum(c["dolu"] for c in ozet),
         round(sum(c["toplam_m3"] for c in ozet), 2), "", ""]))
print(f"\nvarsayilan gruplama {len(ozet)} satirlik ozet uretti ve {dusen} satiri "
      f"hicbir grupta gostermedi")
```

```
grup           satir    dolu   toplam m3    ort/dolu   ort/satir
bati             444     415     6123.43    126006.5    117776.3
dogu             533     508    13203.17    124854.4    118998.2
guney            614     574    10153.66    125303.5    117140.4
kuzey            878     831     17749.3    126247.9    119489.8
merkez           570     533     12796.0    125262.2    117131.2
birlesim        3039    2861    60025.56                        

varsayilan gruplama 5 satirlik ozet uretti ve 160 satiri hicbir grupta gostermedi
```

Beş satırlık bir özet çıktı ve tek başına bakıldığında kusursuz görünüyor. Bölge adları tam, sayımlar
makul, ortalamalar birbirine yakın. Kusur yalnız birleşim satırında görünür: 3.039 satır sayılmıştır,
oysa küme 3.199 satırdır; toplam 60.025,56 m³'tür, oysa küme 63.060,63 m³'tür.

Aradaki 160 satır hiçbir yerde raporlanmamıştır. Bir hata mesajı, bir uyarı, hatta boş bir satır bile
yoktur; o satırlar için bir kova açılmadığı için tabloda görünecek bir yer de oluşmamıştır. Bu, bir
önceki derste ölçülen sessiz kayıptan yapıca farklıdır: orada süzgeç bir koşula göre karar veriyordu,
burada karar veren şey **kovanın var olmamasıdır**.

Tablonun son iki sütunu ikinci kararı gösteriyor. Kuzey grubunda 878 satır vardır ama endeks
sütununda 831 dolu değer vardır. Ortalama, dolu değerleri sayarsa 126.247,9, grubun bütün
satırlarını sayarsa 119.489,8 çıkar. İki sayı da bir ortalamadır ve aralarında 6.758,1 fark vardır.

#### İki Karar, İki Sayı

```python
# karar.py — iki karar ayri ayri olculur: grup anahtarindaki eksik ve
# toplulastirmanin paydasi.
kova2, dusen2 = bol(kayit, "bolge", eksigi_dusur=False)
ozet2 = uygula(kova2, "endeks")
eks = next(c for c in ozet2 if c["grup"] == "(eksik)")
t1 = round(sum(c["toplam_m3"] for c in ozet), 2)
t2 = round(sum(c["toplam_m3"] for c in ozet2), 2)
print(f"eksik anahtar korunursa ozet {len(ozet)} yerine {len(ozet2)} satir olur; "
      f"kovalardaki satir {sum(c['satir'] for c in ozet2)}, toplam {t2} m3")
print(f"(eksik) grubu {eks['satir']} satir ve {eks['toplam_m3']} m3 tasir; varsayilan "
      f"gruplama bunu dusurur ve toplam {t1} m3 kalir")
print(f"kaybolan pay: {round(100 * (t2 - t1) / t2, 2)} yuzde, hicbir uyari uretilmeden")

kuzey = next(c for c in ozet if c["grup"] == "kuzey")
print(f"\nkuzey grubunda iki ayri sayim var: {kuzey['satir']} satir ve {kuzey['dolu']} "
      f"dolu endeks; ortalama {kuzey['ort_dolu']} ya da {kuzey['ort_satir']}")
print(f"ayni satirda 'n' yazan bir rapor hangisini yazdigini soylemek zorundadir")

OLCUT = ["sayim", "toplam", "ortalama", "en buyuk", "en kucuk"]
print(f"\ngecis : olcut basina ayri dongu {len(OLCUT) * len(kayit)} anahtar aramasi, "
      f"tek bolme {len(kayit)} anahtar aramasi ve {len(OLCUT) * len(kayit)} deger okumasi")
print(f"kod   : olcut basina ayri dongu {len(OLCUT)} blok, tek bolmeye "
      f"{len(OLCUT)} uygulama 1 cagri")
```

```
eksik anahtar korunursa ozet 5 yerine 6 satir olur; kovalardaki satir 3199, toplam 63060.63 m3
(eksik) grubu 160 satir ve 3035.07 m3 tasir; varsayilan gruplama bunu dusurur ve toplam 60025.56 m3 kalir
kaybolan pay: 4.81 yuzde, hicbir uyari uretilmeden

kuzey grubunda iki ayri sayim var: 878 satir ve 831 dolu endeks; ortalama 126247.9 ya da 119489.8
ayni satirda 'n' yazan bir rapor hangisini yazdigini soylemek zorundadir

gecis : olcut basina ayri dongu 15995 anahtar aramasi, tek bolme 3199 anahtar aramasi ve 15995 deger okumasi
kod   : olcut basina ayri dongu 5 blok, tek bolmeye 5 uygulama 1 cagri
```

Tek bir seçeneğin değişmesi özeti beş satırdan altı satıra çıkarıyor ve altıncı satır düşen kısmın
tamamını taşıyor: 160 satır, 3.035,07 m³, kümenin yüzde 4,81'i. Altı satırlık özetin toplamı kümenin
gerçek toplamına eşittir ve bu eşitlik, bir gruplamanın doğrulanabilmesi için gereken tek denetimdir.

İki kararın da savunulabilir tarafı vardır. Bölgesi bilinmeyen 160 satırı bölge kırılımına sokmak
anlamsızdır, çünkü hangi bölgeye ait olduğu bilinmez. Ama onları tabloya hiç yazmamak, bölge
toplamlarının küme toplamını vermemesine ve bunun görünmemesine yol açar. Aradaki fark bir tercih
değil, bir **raporlama borcudur**: düşen satır sayılıp yazıldığı sürece hangi karar verilirse
verilsin rapor okunabilir kalır.

Paydanın kararı da aynı yapıdadır. Kuzey grubunun ortalama endeksi, ölçülmüş 831 değerin ortalaması
olarak 126.247,9'dur; ölçülememiş 47 satırı da paya katan hesapta 119.489,8'e iner. İkincisi
"okunamayan sayaçların endeksi sıfırdır" demekle aynı şeydir ve bu bir varsayımdır. Ortalamanın
yanında `n` yazılmadığında iki sayı birbirinden ayırt edilemez.

Kazanç tarafı son iki satırdadır. Beş ölçüt için beş ayrı döngü yazmak 15.995 anahtar araması tutar,
çünkü her ölçüt kümeyi baştan tarar ve kovaları yeniden kurar. Böl–uygula–birleştir bölme adımını
bir kez yapar: 3.199 anahtar araması, ardından beş ölçüt aynı kovalar üzerinde 15.995 değer okumasıyla
hesaplanır. Anahtar araması beşte bire iner, kod beş bloktan tek çağrıya düşer ve tam da bu kısalma
iki kararı görünmez kılar.

#### Özetin Kendisi de Bir Çerçevedir

```python
# pay.py — ozetin kendisi de bir cercevedir ve etiket dizini grup anahtaridir.
# Pay hesabi hangi toplami payda aldigina gore iki ayri tablo verir.
P = [12, 12, 12, 12]
p = lambda h: "".join(str(v).rjust(P[k]) if k else str(v).ljust(P[0])
                      for k, v in enumerate(h))
print(p(["grup", "toplam m3", "pay/ozet", "pay/kume"]))
for c in ozet:
    print(p([c["grup"], c["toplam_m3"], round(100 * c["toplam_m3"] / t1, 2),
             round(100 * c["toplam_m3"] / t2, 2)]))
print(p(["birlesim", t1, round(sum(100 * c["toplam_m3"] / t1 for c in ozet), 2),
         round(sum(100 * c["toplam_m3"] / t2 for c in ozet), 2)]))
print(f"\nozet toplamini payda alan tablo yuzde 100 ile kapanir ve dusen {dusen} "
      f"satiri gizler; kume toplamini payda alan tablo yuzde "
      f"{round(100 - sum(100 * c['toplam_m3'] / t2 for c in ozet), 2)} acik birakir")
```

```
grup           toplam m3    pay/ozet    pay/kume
bati             6123.43        10.2        9.71
dogu            13203.17        22.0       20.94
guney           10153.66       16.92        16.1
kuzey            17749.3       29.57       28.15
merkez           12796.0       21.32       20.29
birlesim        60025.56       100.0       95.19

ozet toplamini payda alan tablo yuzde 100 ile kapanir ve dusen 160 satiri gizler; kume toplamini payda alan tablo yuzde 4.81 acik birakir
```

Toplulaştırmanın çıktısı yeni bir çerçevedir ve etiket dizini grup anahtarıdır. Bu, konunun ilk
dersindeki kuralı gruplamanın sonucuna da uygular: bir sonraki işlem bu dizin üzerinden hizalanır ve
dizinde bulunmayan bir grup sessizce eksik olur. Pay hesabı bunun en sık görülen biçimidir, çünkü
payda seçimi tam da bir dizin seçimidir.

İki pay sütunu aynı beş sayıdan çıkıyor ve farkları yalnız paydada. Özet toplamını payda alan sütun
yüzde 100 ile kapanır; tabloya bakan biri hiçbir eksiklik göremez, çünkü bir yüzde tablosunun 100
ile kapanması doğruluğun işareti sayılır. Küme toplamını payda alan sütun ise yüzde 95,19'da kapanır
ve kalan **yüzde 4,81** tablonun kendisinde bir soru olarak durur.

İkinci tablonun eksik kapanması bir kusur değil, bir bulgudur. Yüzde 100 ile kapanan tablo, düşen 160
satırın varlığını tabloyu bozmadan gizleyebildiği için daha tehlikelidir: kapanma denetimi geçmiş,
denetimin ölçtüğü şey ise kaybın olmaması değil, payların kendi içinde tutarlı olmasıdır.

#### Özet

- Böl–uygula–birleştir üç adımdır ve çıkan satır sayısı anahtarın farklı değer sayısıdır; bölme
  adımını bir kez yapmak anahtar aramasını 15.995'ten 3.199'a indirir.
- Grup anahtarı boş olan 160 satır varsayılan gruplamada hiçbir kovaya girmez ve tabloda görünecek
  bir yeri de oluşmaz; özet 3.039 satır sayar, küme 3.199 satırdır.
- Düşen kısım 3.035,07 m³'tür, yani kümenin yüzde 4,81'i; eksik anahtar korunduğunda özet 6 satır
  olur ve toplamı kümenin gerçek toplamına eşitlenir.
- Toplulaştırmanın paydası ikinci karardır: kuzey grubu 878 satır ve 831 dolu endeks taşır, ortalama
  paydaya göre 126.247,9 ya da 119.489,8 çıkar.
- Bir özet tablonun tek zorunlu denetimi, grup toplamlarının küme toplamına eşitlenmesidir; eşitlik
  kurulmadığında özet kendi içinde tutarlı görünür ve düşen satırı bildirmez.

#### Sonraki Adım

Gruplama tek bir tablonun içinde kalır. Analizin çoğu sorusu ise birden çok tablo ister: okuma
tablosunun yanına abone bilgisi, abone bilgisinin yanına saha ziyaretleri gelmelidir. Veri Toplama ve
Hazırlama kursunda bu iş elle yazılan bir eşleştirme döngüsüyle yapılmış ve satır hesabı kalem kalem
tutulmuştu. Bir veri çerçevesi arayüzünde aynı iş tek bir çağrıdır ve o çağrı bir birleştirme türü
seçer. Sonraki ders iç, dış ve çapraz birleştirmeyi aynı iki tabloya uygular ve her birinin kaç satır
ürettiğini, anahtarın tekil olmadığı yerde satırların nasıl çoğaldığını sayar.

### Birleştirme İşlemleri

Source: https://academia.sh/tr/kurslar/python-ile-analiz/birlestirme-islemleri

3.199 satırlık okuma tablosu ile 818 satırlık ziyaret tablosu aynı anahtar üzerinden dört türle birleştiriliyor ve dört farklı satır sayısı çıkıyor: iç birleştirme 1.965, sol dış 3.820, tam dış 3.856, çapraz birleştirme 2.616.782 satır. İç birleştirme 1.855 okumayı düşürür ve kalan 1.344 satırı 1.965 satıra açar; 621 satırlık bu çoğalma toplamı 26.450,81 m³'ten 38.701,46 m³'e taşır, yani 12.250,65 m³ fazla sayım üretir. Anahtarın ilişkisi çok çoka olduğu halde hiçbir yerde doğrulanmaz: sol taraftaki 468 satırın anahtarı sağda birden çok satıra karşılık gelir ve bu 468 satır sessizce 1.089 satıra açılır. Elle iç içe döngü 2.616.782 karşılaştırma tutarken dizinli birleştirme 4.017 geçişle biter.

Gruplama tek bir tablonun içinde kalır. Analizin sorularının çoğu ise birden çok tablo ister: okuma
tablosunun yanına saha ziyaretleri gelmelidir. Veri Toplama ve Hazırlama kursunun birleştirme dersi
bu işi elle yazılan bir eşleştirme döngüsüyle yapmış ve satır hesabını kalem kalem tutmuştu; 3.471
okuma ile 1.329 aboneden 3.302 satır çıkmış, 169 okuma eşleşmemiş, 69 abone çıktıda hiç
görünmemişti. O hesap bu dersin girdisidir ve tekrarlanmaz.

Bir veri çerçevesi arayüzünde aynı iş tek bir çağrıdır ve o çağrı bir **birleştirme türü** seçer.
Bu dersin ölçüsü şudur: aynı iki tablo, aynı anahtar, dört tür ve dört farklı satır sayısı. Yanına
ikinci bir sayı gelir — anahtarın tanesi doğrulanmadığında kaç satır çoğalır ve toplam ne kadar
şişer.

- **VC22. Birleştirme türü** çıktının satır sayısını belirleyen karardır. **İç** birleştirme yalnız
  eşleşen satırları, **sol dış** eşleşmeyen sol satırları da, **tam dış** iki taraftaki eşleşmeyenleri
  de tutar. **Çapraz** birleştirme anahtarı hiç okumaz ve iki tablonun bütün ikililerini üretir.
- **VC23. Anahtarın tanesi**, bir anahtar değerinin her tarafta kaç satıra karşılık geldiğidir.
  İlişki bir bire, bir çoka ya da çok çoka olabilir ve birleştirme bunu **doğrulamadan** yapar.
- **VC24. Çoğalma**, sol satırın sağdaki eşleşme sayısı kadar tekrarlanmasıdır. Çoğalma bir hata
  değildir; hata, çoğalmış bir tabloda toplam almaktır.
- **VC25.** Çıkan satır sayısı girenden az olsa bile bir eleme yapılmış olmayabilir: aynı işlem
  içinde bazı satırlar düşerken bazıları çoğalabilir ve net değişim ikisini birden gizler.

#### İki Tablo ve Üç Küme

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

# Saha ziyaretleri tablosu: bir satir bir ziyarettir ve doneme bagli degildir.
ziyaret = []
for k in abone:
    r = uretec(TOHUM + 31000 + k["abone_no"])
    for z in range(ayrik(r(), [0.58, 0.27, 0.11, 0.04])):
        ziyaret.append({"abone_no": k["abone_no"],
                        "sonuc": ["olumlu", "tekrar", "erisilemedi"][
                            ayrik(r(), [0.60, 0.28, 0.12])]})

okuyan = {o["abone_no"] for o in UZUN}
gezilen = {z["abone_no"] for z in ziyaret}
print(f"okuma tablosu {len(UZUN)} satir, {len(okuyan)} abone")
print(f"ziyaret tablosu {len(ziyaret)} satir, {len(gezilen)} abone")
print(f"iki tarafta da bulunan {len(okuyan & gezilen)} abone; yalniz okumada "
      f"{len(okuyan - gezilen)}, yalniz ziyarette {len(gezilen - okuyan)}")
```

```
okuma tablosu 3199 satir, 1260 abone
ziyaret tablosu 818 satir, 559 abone
iki tarafta da bulunan 534 abone; yalniz okumada 726, yalniz ziyarette 25
```

Birleştirmeden önce üç sayı biliniyor ve bu üç sayı çıkacak bütün satır sayılarını önceden
açıklıyor. 534 abone iki tabloda da vardır, 726 abone yalnız okuma tablosunda, 25 abone yalnız
ziyaret tablosundadır. Bu üç kümenin toplamı 1.285'tir ve birleştirme türü, aslında bu üç kümeden
hangilerinin çıktıda temsil edileceğini seçmektir.

Bir ayrıntı da satır sayısını doğrudan etkiler: iki tablo aynı **taneyi** taşımıyor. Okuma
tablosunda bir satır bir abonenin bir dönemidir, ziyaret tablosunda bir satır bir ziyarettir ve
döneme bağlı değildir. Anahtar olarak yalnız abone numarası kullanıldığında bir abonenin üç okuması
ile iki ziyareti altı satır üretir. Bu, tabloların kendisinden okunabilen bir özelliktir ve
birleştirme çağrısından önce bilinir.

#### Aynı İki Tablo, Dört Tür

```python
# birlestir.py — ayni iki tablo dort birlestirme turuyle. Tur, ciktinin satir
# sayisini belirleyen karardir.
GECIS = {"sayi": 0}

def birlestir(sol, sag, alan: str, tur: str = "ic"):
    """Anahtar uzerinden yan yana getirir. tur: ic, sol_dis, tam_dis, capraz."""
    if tur == "capraz":
        GECIS["sayi"] += len(sol) * len(sag)
        return len(sol) * len(sag), 0, 0
    dizin: dict = {}
    for s in sag:
        dizin.setdefault(s[alan], []).append(s)
    GECIS["sayi"] += len(sag) + len(sol)
    cikan, bos_sol, eslesen = 0, 0, set()
    for l in sol:
        e = dizin.get(l[alan])
        if not e:
            bos_sol += 1
            cikan += 1 if tur in ("sol_dis", "tam_dis") else 0
            continue
        cikan += len(e)
        eslesen.add(l[alan])
    bos_sag = len({s[alan] for s in sag} - eslesen)
    if tur == "tam_dis":
        cikan += sum(1 for s in sag if s[alan] not in eslesen)
    return cikan, bos_sol, bos_sag

EN = [22, 12, 12, 12, 12]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
print(y(["birlestirme turu", "giren sol", "giren sag", "cikan", "eslesmeyen"]))
sonuc = {}
for tur, ad in [("ic", "ic"), ("sol_dis", "sol dis"), ("tam_dis", "tam dis"),
                ("capraz", "capraz")]:
    GECIS["sayi"] = 0
    cikan, bs, bg = birlestir(UZUN, ziyaret, "abone_no", tur)
    sonuc[tur] = (cikan, bs, bg, GECIS["sayi"])
    print(y([ad, len(UZUN), len(ziyaret), cikan, bs if tur != "capraz" else 0]))
print(f"\nic birlestirme {len(UZUN)} okumadan {sonuc['ic'][1]} tanesini dusurur ve "
      f"{sonuc['ic'][0]} satir uretir")
print(f"capraz birlestirme anahtar hic okumaz: {len(UZUN)} x {len(ziyaret)} = "
      f"{sonuc['capraz'][0]} satir")
```

```
birlestirme turu         giren sol   giren sag       cikan  eslesmeyen
ic                            3199         818        1965        1855
sol dis                       3199         818        3820        1855
tam dis                       3199         818        3856        1855
capraz                        3199         818     2616782           0

ic birlestirme 3199 okumadan 1855 tanesini dusurur ve 1965 satir uretir
capraz birlestirme anahtar hic okumaz: 3199 x 818 = 2616782 satir
```

Dört tür, aynı iki tablo, dört farklı satır sayısı: 1.965, 3.820, 3.856 ve 2.616.782. En küçüğü ile
en büyüğü arasındaki oran 1.332 kat ve seçimi yapan tek şey, çağrıda yazılan ya da yazılmayan bir
tür adı.

İç birleştirmenin sayısı iki yönde birden ilginçtir. Çıkan 1.965 satır girenden azdır ve ilk bakışta
bir eleme gibi görünür. Değildir: 1.855 okuma düşmüş, kalan 1.344 okuma ise 1.965 satıra açılmıştır.
Net değişim negatif olduğu için çoğalma sayının içinde görünmez. Bu, Veri Toplama ve Hazırlama
kursunda ölçülen davranışın aynısıdır; burada yeni olan, bütün işlemin tek bir çağrıya sığması ve
1.855 ile 621 sayılarının hiçbirinin çağrıdan geri dönmemesidir.

Sol dış birleştirme 3.820 satır verir ve okuma tablosunun bütün satırlarını korur: 1.965 eşleşmiş
satır ve 1.855 boş ziyaret tarafı. Tam dış 3.856 satırla yalnız ziyaret tablosunda bulunan 25
abonenin 36 ziyaret satırını da ekler. Çapraz birleştirme ise anahtarı hiç okumaz; ürettiği
2.616.782 satır bir sorunun cevabı değil, iki tablonun bütün ikilileridir.

#### Çoğalma ve Anahtarın Tanesi

```python
# cogalma.py — anahtarin tekilligi dogrulanmazsa birlestirme ayni satiri birden
# cok kez sayar. Olcu, cogalan satir ve sisen toplamdir.
sayi: dict[int, int] = {}
for z in ziyaret:
    sayi[z["abone_no"]] = sayi.get(z["abone_no"], 0) + 1
eslesen = [o for o in UZUN if o["abone_no"] in sayi]
cikan = sum(sayi[o["abone_no"]] for o in eslesen)
T = lambda a: round(sum(x["tuketim_m3"] for x in a), 2)
sisen = round(sum(o["tuketim_m3"] * sayi[o["abone_no"]] for o in eslesen), 2)
print(f"kume {len(UZUN)} satir, {T(UZUN)} m3")
print(f"eslesen {len(eslesen)} satir, {T(eslesen)} m3")
print(f"ic birlestirme ciktisi {cikan} satir, {sisen} m3; "
      f"{cikan - len(eslesen)} satir cogalma, {round(sisen - T(eslesen), 2)} m3 fazla sayim")

def iliski(sol, sag, alan: str) -> str:
    """Anahtarin tanesi: her iki tarafta da tekil mi, bir tarafta cok mu."""
    sol_tekil = len({s[alan] for s in sol}) == len(sol)
    sag_tekil = len({s[alan] for s in sag}) == len(sag)
    return ("bir-bir" if sol_tekil and sag_tekil else
            "bir-cok" if sag_tekil or sol_tekil else "cok-cok")

cok = sum(1 for o in eslesen if sayi[o["abone_no"]] > 1)
print(f"\nanahtarin tanesi: {iliski(UZUN, ziyaret, 'abone_no')}; sol tarafta "
      f"{cok} satirin anahtari sagda birden cok satira karsilik geliyor")
print(f"dogrulama yapilmadiginda bu {cok} satir sessizce {cikan - len(eslesen) + cok} "
      f"satira acilir")
print(f"\ngecis : elle ic ice dongu {len(UZUN) * len(ziyaret)}, dizinli birlestirme "
      f"{len(UZUN) + len(ziyaret)}")
print(f"satir : ayni iki tablo dort turde {sonuc['ic'][0]}, {sonuc['sol_dis'][0]}, "
      f"{sonuc['tam_dis'][0]} ve {sonuc['capraz'][0]} satir uretir")
```

```
kume 3199 satir, 63060.63 m3
eslesen 1344 satir, 26450.81 m3
ic birlestirme ciktisi 1965 satir, 38701.46 m3; 621 satir cogalma, 12250.65 m3 fazla sayim

anahtarin tanesi: cok-cok; sol tarafta 468 satirin anahtari sagda birden cok satira karsilik geliyor
dogrulama yapilmadiginda bu 468 satir sessizce 1089 satira acilir

gecis : elle ic ice dongu 2616782, dizinli birlestirme 4017
satir : ayni iki tablo dort turde 1965, 3820, 3856 ve 2616782 satir uretir
```

Çoğalmanın bedeli ikinci satırda okunuyor. Eşleşen 1.344 okumanın toplamı 26.450,81 m³'tür;
birleştirme çıktısındaki aynı okumaların toplamı 38.701,46 m³'tür. Aradaki **12.250,65 m³** yeni bir
tüketim değildir, aynı okumaların ikinci ve üçüncü kez sayılmasıdır. Bir abonenin ocak okuması, o
abonenin iki ziyareti olduğu için toplama iki kez girmiştir.

Bu sayının tehlikesi büyüklüğünde değil, **makullüğündedir**. 38.701,46 m³ kümenin gerçek
toplamından, yani 63.060,63 m³'ten küçüktür; bir denetim "toplam küme toplamını aşmıyor" diye baksa
bu tabloyu geçirir. Oysa tablo hem eksik hem fazla sayılmış bir toplam taşır: 1.855 okuma hiç
sayılmamış, 468 okuma birden çok kez sayılmıştır.

Son iki satır kazancı ve gizlenen kararı yan yana koyuyor. Kazanç açıktır: elle iç içe döngü
2.616.782 karşılaştırma tutarken, sağ tarafa bir anahtar dizini kurup sol tarafı bir kez gezen
birleştirme 4.017 geçişle biter — 651 kat daha az. Gizlenen karar ise ilişkinin **çok çoka** olması
ve bunun hiçbir yerde doğrulanmamasıdır. Birleştirme çağrısı ilişkinin ne olduğunu sormaz, sorulmuş
olsaydı 468 satırın 1.089 satıra açılacağı çağrıdan önce bilinirdi.

#### Taneyi Düzeltmek

```python
# tane_duzeltme.py — cogalmayi onleyen sey birlestirme turu degil, sag tarafin
# taneye getirilmesidir: once toplulastir, sonra birlestir.
ozet_ziyaret = [{"abone_no": no, "ziyaret_sayisi": n} for no, n in sayi.items()]
tekil = len({z["abone_no"] for z in ozet_ziyaret}) == len(ozet_ziyaret)
print(f"ziyaret tablosu {len(ziyaret)} satirdan {len(ozet_ziyaret)} satira indi; "
      f"anahtar tekil mi: {'evet' if tekil else 'hayir'}")
print(f"anahtarin tanesi artik {iliski(UZUN, ozet_ziyaret, 'abone_no')}")
ic2, bs2, bg2 = birlestir(UZUN, ozet_ziyaret, "abone_no", "ic")
sol2, _, _ = birlestir(UZUN, ozet_ziyaret, "abone_no", "sol_dis")
print(f"\nic birlestirme {ic2} satir ({sonuc['ic'][0]} degil), toplam {T(eslesen)} m3")
print(f"sol dis birlestirme {sol2} satir, toplam {T(UZUN)} m3: giren satir ve giren "
      f"toplam degismedi")
print(f"cogalma {sonuc['ic'][0] - len(eslesen)} satirdan {ic2 - len(eslesen)} satira, "
      f"fazla sayim {round(sisen - T(eslesen), 2)} m3'ten 0.0 m3'e indi")
```

```
ziyaret tablosu 818 satirdan 559 satira indi; anahtar tekil mi: evet
anahtarin tanesi artik bir-cok

ic birlestirme 1344 satir (1965 degil), toplam 26450.81 m3
sol dis birlestirme 3199 satir, toplam 63060.63 m3: giren satir ve giren toplam degismedi
cogalma 621 satirdan 0 satira, fazla sayim 12250.65 m3'ten 0.0 m3'e indi
```

Çoğalmayı ortadan kaldıran şey birleştirme türünü değiştirmek değil, sağ tarafı sol tarafın
**tanesine** getirmektir. Ziyaret tablosu abone başına toplulaştırılıp 818 satırdan 559 satıra
indiğinde anahtar tekil olur, ilişki çok çoka olmaktan çıkıp bir çoka döner ve aynı birleştirme
çağrısı artık hiçbir satırı çoğaltmaz. Fazla sayım 12.250,65 m³'ten sıfıra iner ve bu düşüş bir
düzeltme değildir; hiç oluşmamıştır.

Son satır bu dersin aradığı denetimi veriyor. Doğru taneyle yapılan sol dış birleştirme 3.199 satır
ve 63.060,63 m³ ile kapanır: giren satır sayısı ve giren toplam değişmemiştir. Bir birleştirmenin
doğru yapıldığını gösteren tek ucuz işaret budur — sol tablonun satır sayısı ve toplamı çıktıda
korunuyorsa sol taraf ne düşmüş ne çoğalmıştır. Korunmuyorsa fark iki kalem halinde yazılır ve bu
iki kalem, çağrının kendisinin hiç raporlamadığı sayılardır.

Önce toplulaştırıp sonra birleştirmenin bir bedeli de vardır ve yazılmalıdır: 818 ziyaret satırının
taşıdığı ayrıntı, yani her ziyaretin kendi sonucu, özetin içinde kaybolur. Tane kararı bu yüzden
sorulacak soruya bağlıdır. "Abone başına kaç ziyaret" sorusu özet tabloyla cevaplanır, "kaç ziyaret
erişilemedi ile kapandı" sorusu ise ziyaret tablosunun kendisine sorulur ve okuma tablosuyla hiç
birleştirilmez.

#### Özet

- Birleştirme türü çıktının satır sayısını belirler: aynı iki tablo iç birleştirmede 1.965, sol
  dışta 3.820, tam dışta 3.856, çaprazda 2.616.782 satır verir.
- İç birleştirmenin girenden az satır vermesi bir eleme anlamına gelmez: 1.855 okuma düşmüş, kalan
  1.344 okuma 1.965 satıra açılmıştır ve net değişim ikisini birden gizler.
- Çoğalma toplamı şişirir: eşleşen okumaların gerçek toplamı 26.450,81 m³ iken çıktıdaki toplam
  38.701,46 m³'tür ve aradaki 12.250,65 m³ ikinci kez sayılmış okumadır.
- Şişmiş toplam küme toplamının altında kaldığı için üst sınır denetiminden geçer; tabloda hem
  eksik hem fazla sayım aynı anda vardır.
- Anahtarın ilişkisi çok çokadır ve doğrulanmaz: 468 satır sessizce 1.089 satıra açılır. Dizinli
  birleştirme 4.017 geçişle biter, iç içe döngü 2.616.782 karşılaştırma tutar.

#### Sonraki Adım

Birleştirme tabloları yan yana getirdi ve satır hesabı yazıldı. Kümede henüz bir eksen ele
alınmadı: zaman. Okuma tablosunda dönem bir metin sütunudur ve sıralanabilir, ama dönemler arasında
"aradan kaç hafta geçti" ya da "hangi haftada hiç okuma yok" diye bir soru bu haliyle sorulamaz.
Zaman ekseni etiket dizinine taşındığında bu sorular sorulabilir hale gelir ve yeni bir karar
doğar: yeniden örneklemede hiç kayıt taşımayan bir dönem sıfır mı sayılır, yoksa hiç var olmamış
mı sayılır. Sonraki ders bu iki cevabı ayrı ayrı hesaplar ve toplamı ile ortalamayı nasıl
değiştirdiklerini ölçer.

### Zaman Serisi İşlemleri

Source: https://academia.sh/tr/kurslar/python-ile-analiz/zaman-serisi-islemleri

3.199 okuma yedi günlük dönemlere indirgeniyor ve hiç kayıt taşımayan 5 dönem için iki cevap çıkıyor: sıfır sayılırsa seri 13 satır, atlanırsa 8 satır olur. Toplam ikisinde de 63.060,63 m³'tür ve değişmez, ama toplamdan sonraki her hesap değişir. Haftalık ortalama 4.850,82 m³ ile 7.882,58 m³ arasında oynar; dönemden döneme yüzde değişimin 12 oranından 5'i sıfır paydaya düştüğü için tanımsız kalırken atlanmış seride 7 oranın hiçbiri tanımsız değildir; üç dönemlik hareketli ortalama 2026-02-12 döneminde 708,03 m³ ile 9.387,54 m³ arasında ayrılır. Aynı küme otuz günlük adımla 3 dönem verir ve hiçbiri boş değildir: boş dönemi yaratan şey veri değil adım kararıdır.

Birleştirme tabloları yan yana getirdi ve satır hesabı yazıldı. Kümede henüz ele alınmamış bir eksen
var: zaman. Okuma tablosunda dönem bir metin sütunudur. Sıralanabilir, gruplanabilir, eşitliğe
sokulabilir; ama "aradan kaç hafta geçti", "hangi haftada hiç okuma yok" ya da "son üç dönemin
ortalaması nedir" soruları bu haliyle sorulamaz, çünkü metin sütunu iki değer arasındaki **uzaklığı**
bilmez.

Zaman ekseni etiket dizinine taşındığında bu sorular sorulabilir hale gelir ve beraberinde yeni bir
karar getirir. Bir zaman ekseni **sürekli**, veri ise kesiklidir: eksende yer alan her dönemin
kümede bir karşılığı olmak zorunda değildir. Hiç kayıt taşımayan bir dönem için iki cevap vardır ve
ikisi de savunulabilir — o dönemde tüketim sıfırdı ya da o dönem hiç ölçülmedi. Bu dersin ölçüsü bu
iki cevabın ayrıldığı yerdir.

- **VC26. Zaman ekseni**, dizinin etiketlerinin tarih olmasıdır. Tarih dizini iki etiket arasındaki
  uzaklığı bilir ve bu bilgi sıralama, fark alma ve yeniden örneklemeyi mümkün kılar.
- **VC27. Yeniden örnekleme**, kayıtları zaman ekseninde daha kaba bir adıma indirgemektir. Çıkan
  dönem sayısını belirleyen şey veri değil, **adım kararıdır**.
- **VC28. Boş dönem**, eksende yer alan ama hiç kayıt taşımayan dönemdir. Sıfır sayılabilir ya da
  çıktıda hiç üretilmeyebilir; ikisi arasındaki fark bir satır sayısıdır.
- **VC29.** Boş dönem kararı **toplamı değiştirmez**, çünkü sıfır toplamaya bir şey katmaz. Toplamdan
  sonra gelen her hesabı değiştirir: ortalamanın paydasını, değişim oranının paydasını ve hareketli
  pencerenin kapsadığı dönemleri.

#### Zaman Ekseni

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

# Okuma tarihi: saha ekibi her ayin okuma penceresinde calisir. Donem metni
# boylece bir tarih eksenine tasinir.
from datetime import date, timedelta

BASLANGIC, SON = date(2026, 1, 1), date(2026, 3, 31)
AY = {"2026-01": 1, "2026-02": 2, "2026-03": 3}

def okuma_tarihi(no: int, donem: str) -> date:
    """Okuma penceresi ayin 18 ile 28'i arasidir."""
    r = uretec(TOHUM + 77000 + no * 4 + AY[donem])
    return date(2026, AY[donem], 18 + ayrik(r(), [0.09] * 10 + [0.10]))

kayit = [{"abone_no": o["abone_no"], "tarih": okuma_tarihi(o["abone_no"], o["donem"]),
          "tuketim_m3": o["tuketim_m3"]} for o in UZUN]
print(f"{len(kayit)} okuma, tarih araligi {min(k['tarih'] for k in kayit)} .. "
      f"{max(k['tarih'] for k in kayit)}")
print(f"eksen {BASLANGIC} ile {SON} arasi {(SON - BASLANGIC).days + 1} gun, "
      f"yedi gunluk {(SON - BASLANGIC).days // 7 + 1} donem")
print(f"toplam {round(sum(k['tuketim_m3'] for k in kayit), 2)} m3")
```

```
3199 okuma, tarih araligi 2026-01-18 .. 2026-03-28
eksen 2026-01-01 ile 2026-03-31 arasi 90 gun, yedi gunluk 13 donem
toplam 63060.63 m3
```

İki sayı arasındaki uyumsuzluk dersin bütün konusunu kuruyor. Eksen 90 günlüktür ve yedi günlük
adımla 13 döneme bölünür. Veri ise 3.199 okumadan ibarettir ve okumaların tamamı ayın 18'i ile 28'i
arasında toplanmıştır, çünkü saha ekibi her ay belirli bir okuma penceresinde çalışır.

Bu, kümenin bir kusuru değil, işin yapılış biçimidir. Sonucu şudur: eksenin bazı dönemleri hiç okuma
taşımaz. Bu dönemler veri toplanmadığı için boştur, tüketim olmadığı için değil. Su akmaya devam
etmiş, sayaç dönmüş, ama o hafta kimse okumaya gitmemiştir.

#### Boş Dönem Nedir

```python
# yeniden_ornekle.py — zaman eksenini yedi gunluk donemlere indirger. Hic kayit
# tasimayan donem icin iki cevap vardir: sifir ya da hic yok.
def yeniden_ornekle(kayitlar, adim: int, bos_sifir=True):
    """bos_sifir dogruysa hic kayit tasimayan donem 0.0 ile uretilir;
    yanlissa o donem ciktida hic gorunmez."""
    kova: dict[int, list[float]] = {}
    for k in kayitlar:
        kova.setdefault((k["tarih"] - BASLANGIC).days // adim, []).append(k["tuketim_m3"])
    cikti = []
    for i in range((SON - BASLANGIC).days // adim + 1):
        v = kova.get(i, [])
        if not v and not bos_sifir:
            continue
        cikti.append({"donem": BASLANGIC + timedelta(days=i * adim),
                      "okuma": len(v), "toplam_m3": round(sum(v), 2)})
    return cikti

dolu = yeniden_ornekle(kayit, 7, bos_sifir=False)
sifirli = yeniden_ornekle(kayit, 7, bos_sifir=True)
EN = [14, 10, 14, 10, 14]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
print(y(["donem basi", "okuma", "toplam m3", "okuma", "toplam m3"]))
d = {c["donem"]: c for c in dolu}
for c in sifirli:
    e = d.get(c["donem"])
    print(y([str(c["donem"]), c["okuma"], c["toplam_m3"],
             e["okuma"] if e else "-", e["toplam_m3"] if e else "-"]))
ts = round(sum(c["toplam_m3"] for c in sifirli), 2)
td = round(sum(c["toplam_m3"] for c in dolu), 2)
print(f"\nsatir : bos donem sifir sayilinca {len(sifirli)}, atlaninca {len(dolu)}")
print(f"toplam: {ts} m3 ve {td} m3 — toplam degismez")
print(f"ortalama: {round(ts / len(sifirli), 2)} m3 ve {round(td / len(dolu), 2)} m3")
```

```
donem basi         okuma     toplam m3     okuma     toplam m3
2026-01-01             0             0         -             -
2026-01-08             0             0         -             -
2026-01-15           460       9260.91       460       9260.91
2026-01-22           800       16777.6       800       16777.6
2026-01-29             0             0         -             -
2026-02-05             0             0         -             -
2026-02-12           106        2124.1       106        2124.1
2026-02-19           690      13715.37       690      13715.37
2026-02-26           305       5829.46       305       5829.46
2026-03-05             0             0         -             -
2026-03-12            87       1665.23        87       1665.23
2026-03-19           521       9477.26       521       9477.26
2026-03-26           230        4210.7       230        4210.7

satir : bos donem sifir sayilinca 13, atlaninca 8
toplam: 63060.63 m3 ve 63060.63 m3 — toplam degismez
ortalama: 4850.82 m3 ve 7882.58 m3
```

Aynı kümeden iki seri çıkıyor ve ilk bakışta ikisi de aynı şeyi söylüyor: toplam 63.060,63 m³. Bu
eşitlik yanıltıcı bir güven verir, çünkü sıfır toplamaya bir şey katmaz ve boş dönem kararı
toplamda hiçbir iz bırakmaz.

Ayrım satır sayısında başlıyor: 13 satıra karşı 8 satır. Sıfırla doldurulmuş seride beş dönem
`0` tüketim taşır ve bu beş satır bir grafikte tabana inen beş nokta olarak çizilir. Grafiği okuyan
biri şunu görür: tüketim yılın ilk iki haftasında sıfırdı, sonra yükseldi, ocak sonunda tekrar
sıfıra düştü. Bunların hiçbiri olmamıştır. Sıfır olan şey tüketim değil, o hafta yapılan okuma
sayısıdır ve tablonun ikinci sütunu bunu zaten yazmaktadır.

Ortalama farkı bu okumanın sayısal karşılığıdır. Aynı toplam 13'e bölününce 4.850,82 m³, 8'e
bölününce 7.882,58 m³ çıkar; ikinci sayı birincinin 1,63 katıdır. "Haftalık ortalama tüketim" diye
raporlanacak sayı bu ikisinden biridir ve hangisi olduğu, hiç yazılmayan bir seçenekle belirlenir.

#### Toplamdan Sonraki Her Hesap

```python
# degisim.py — bos donemin sifir sayilmasi toplami degistirmez, ama toplamdan
# sonra gelen her hesabi degistirir.
def degisim(seri):
    """Donemden doneme yuzde degisim; payda sifirsa oran tanimsizdir."""
    o, tanimsiz = [], 0
    for i in range(1, len(seri)):
        onceki = seri[i - 1]["toplam_m3"]
        if onceki == 0:
            tanimsiz += 1
            o.append(None)
        else:
            o.append(round(100 * (seri[i]["toplam_m3"] - onceki) / onceki, 1))
    return o, tanimsiz

def hareketli(seri, pencere=3):
    """Her donem icin son pencere kadar donemin ortalamasi."""
    o = {}
    for i in range(len(seri)):
        v = [c["toplam_m3"] for c in seri[max(0, i - pencere + 1):i + 1]]
        o[seri[i]["donem"]] = round(sum(v) / len(v), 2)
    return o

d1, t1 = degisim(sifirli)
d2, t2 = degisim(dolu)
print(f"sifirli seri: {len(d1)} degisim orani, {t1} tanesi tanimsiz (payda sifir)")
print(f"atlanmis seri: {len(d2)} degisim orani, {t2} tanesi tanimsiz")
h1, h2 = hareketli(sifirli), hareketli(dolu)
ortak = [g for g in h1 if g in h2]
en_uzak = max(ortak, key=lambda g: abs(h1[g] - h2[g]))
print(f"uc donemlik hareketli ortalama {len(ortak)} ortak donemde karsilastirilir; "
      f"en buyuk fark {en_uzak}: sifirli {h1[en_uzak]} m3, atlanmis {h2[en_uzak]} m3")
aylik = yeniden_ornekle(kayit, 30, bos_sifir=True)
print(f"\nayni kume otuz gunluk adimla {len(aylik)} donem verir ve hicbiri bos degil; "
      f"bos donemi yaratan sey verinin kendisi degil, adim karari")
print(f"\ngecis : donem basina kume taramasi {13 * len(kayit)}, tek kova gecisi "
      f"{len(kayit) + 13}")
print(f"kod   : elle donem dongusu 9 satir, yeniden ornekleme 1 cagri")
```

```
sifirli seri: 12 degisim orani, 5 tanesi tanimsiz (payda sifir)
atlanmis seri: 7 degisim orani, 0 tanesi tanimsiz
uc donemlik hareketli ortalama 8 ortak donemde karsilastirilir; en buyuk fark 2026-02-12: sifirli 708.03 m3, atlanmis 9387.54 m3

ayni kume otuz gunluk adimla 3 donem verir ve hicbiri bos degil; bos donemi yaratan sey verinin kendisi degil, adim karari

gecis : donem basina kume taramasi 41587, tek kova gecisi 3212
kod   : elle donem dongusu 9 satir, yeniden ornekleme 1 cagri
```

Değişim oranı boş dönem kararının en sert karşılığıdır. Sıfırla doldurulmuş seride 12 oranın
**5 tanesi tanımsızdır**, çünkü paydada sıfır vardır. Bir hesap tablosunda bu hücreler ya boş kalır
ya bir hata değeri taşır; ikisi de raporun ortasında açıklanması gereken bir gürültüdür ve verinin
kendisinden değil, kaba bir varsayılandan gelir. Atlanmış seride aynı hesabın 7 oranının hiçbiri
tanımsız değildir.

Hareketli ortalama ise sessizce sapar. Üç dönemlik pencere 2026-02-12 döneminde sıfırlı seride
708,03 m³, atlanmış seride 9.387,54 m³ verir. Aradaki oran on üç kattır ve sebebi basittir:
sıfırlı seride pencere iki boş dönemi kapsar ve ortalamayı üçe böler, atlanmış seride ise pencere
üç gerçek okuma dönemini kapsar. İki sayı da doğru hesaplanmıştır, ikisi de aynı pencere adını
taşır ve hangisinin raporlanacağı bir cümleyle bile yazılmaz.

Son iki satır kararın nereden geldiğini gösteriyor. Aynı küme otuz günlük adımla üç dönem verir ve
hiçbiri boş değildir. Boş dönem verinin bir özelliği değildir; **adım kararının ürünüdür**. Yedi
günlük adım seçildiği anda beş boş dönem yaratılmıştır ve bu, okuma penceresinin haftalık
çözünürlükte anlamlı bir sinyal taşımamasından ileri gelir.

Kazanç tarafı da yazılmalıdır. Her dönem için kümeyi baştan tarayan bir yaklaşım 41.587 geçiş
tutar; kayıtları bir kez gezip dönem kovasına atan yeniden örnekleme 3.212 geçişle biter ve dokuz
satırlık döngü tek çağrıya iner. Kısalan tam da bu çağrının içinde iki karar durur: adım ve boş
dönemin ne sayıldığı.

#### Kaydırma Grup Sınırını Bilmez

```python
# kaydirma.py — fark almak icin seri bir adim kaydirilir. Kaydirma tablo
# sirasini bilir, grup sinirini bilmez.
sirali = sorted(UZUN, key=lambda o: (o["abone_no"], o["donem"]))
ham_fark = [(sirali[i]["abone_no"], sirali[i - 1]["abone_no"],
             round(sirali[i]["tuketim_m3"] - sirali[i - 1]["tuketim_m3"], 2))
            for i in range(1, len(sirali))]
ayni = [f for f in ham_fark if f[0] == f[1]]
sinir = [f for f in ham_fark if f[0] != f[1]]
print(f"{len(sirali)} satirda {len(ham_fark)} fark uretildi")
print(f"ayni abonenin iki donemi: {len(ayni)} fark, ortalama "
      f"{round(sum(f[2] for f in ayni) / len(ayni), 2)} m3")
print(f"iki farkli abone arasi   : {len(sinir)} fark, ortalama "
      f"{round(sum(f[2] for f in sinir) / len(sinir), 2)} m3")
print(f"grup sinirini bilmeyen bir kaydirma {len(ham_fark)} farkin "
      f"{len(sinir)} tanesini iki ayri aboneden hesaplar")
```

```
3199 satirda 3198 fark uretildi
ayni abonenin iki donemi: 1939 fark, ortalama -1.12 m3
iki farkli abone arasi   : 1259 fark, ortalama 1.72 m3
grup sinirini bilmeyen bir kaydirma 3198 farkin 1259 tanesini iki ayri aboneden hesaplar
```

Fark almak, seriyi bir adım kaydırıp kendisinden çıkarmaktır ve uzun biçimli bir tabloda bu işlem
tablo sırasını izler. Tablo abone ve dönem sırasındadır, dolayısıyla farkların çoğu doğru çıkar:
1.939 fark aynı abonenin iki ardışık dönemini karşılaştırır. Kalan **1.259 fark** ise bir abonenin
son dönemi ile bir sonraki abonenin ilk dönemi arasındadır ve hiçbir şeyi ölçmez.

İki ortalama arasındaki fark bu kirlenmenin işaretini veriyor: gerçek dönem farklarının ortalaması
-1,12 m³ iken sınır farklarının ortalaması +1,72 m³'tür. İkisi bir arada raporlanırsa tüketimdeki
dönemsel düşüş eğilimi zayıflar. Kaydırma işlemi grup sınırını kendiliğinden bilmez; bilmesi için
kaydırmanın abone başına yapılması, yani bir gruplama kararının fark almadan önce yazılması gerekir.

#### Özet

- Tarih dizini iki etiket arasındaki uzaklığı bilir; metin sütunu bilmez. Yeniden örnekleme,
  sıralama ve fark alma bu bilgiyle mümkün olur.
- Aynı küme yedi günlük adımda 13 dönem, otuz günlük adımda 3 dönem verir; boş dönemi yaratan şey
  veri değil adım kararıdır.
- Boş dönem sıfır sayılınca seri 13 satır, atlanınca 8 satır olur; toplam ikisinde de 63.060,63
  m³'tür ve karar toplamda hiçbir iz bırakmaz.
- Toplamdan sonraki her hesap değişir: ortalama 4.850,82 ile 7.882,58 m³ arasında oynar, 12 değişim
  oranının 5'i sıfır payda yüzünden tanımsız kalır, üç dönemlik hareketli ortalama 2026-02-12
  döneminde 708,03 ile 9.387,54 m³ arasında ayrılır.
- Sıfırla doldurulmuş bir seri grafikte tabana inen noktalar üretir ve "tüketim durdu" diye okunur;
  sıfır olan şey tüketim değil, o dönemde yapılan okuma sayısıdır.

#### Sonraki Adım

Sayısal sütunlar, tarihler ve anahtarlar ele alındı. Kümede kalan son alan türü metindir: bölge
adı, ziyaret sonucu, adres kodu, saha notu. Metin sütunları çerçevede sayısal sütunlarla aynı yerde
durur ve aynı biçimde işlenebilir gibi görünür; bir dizgi yöntemi bütün sütuna tek çağrıyla
uygulanır ve döngü yazmaya gerek kalmaz. O çağrının eksik hücrede ne yaptığı ise hiçbir yerde
görünmez. Sonraki ders vektörleştirilmiş dizgi işlemlerini ele alır, eksik değerde ne döndüklerini
sayar ve konuyu kapatır.

### Metin İşlemleri

Source: https://academia.sh/tr/kurslar/python-ile-analiz/metin-islemleri

3.199 satırlık adres kodu sütununun 2.943'ü tam, 160'ı boş, 96'sı üçüncü parçasını taşımıyor. Elle yazılan döngü 20. satırda hata verip duruyor ve 0 satır üretiyor; aynı işi yapan vektörleştirilmiş zincir 3.199 satırın tamamını üretiyor, hiç hata vermiyor ve 256 satırı sessizce eksiğe çeviriyor. Zincirden sonra gelen süzgeç bu 256 satırı eşitlikte dışarıda bırakıp eşitsizlikte içeri alıyor: 1.004 ile 2.195 satır, toplamı tam olarak küme. Zincirin ilk adımı kodu küçük harfe çevirdiği için büyük harfli süzgeç 0 satır döndürüyor ve bu da uyarı üretmiyor. Bir metin sütunu üç ara serisiyle satır başına 32,6 bayt gösterge tutar ve aynı yapı 20 milyon satırda 0,61 gibibayta çıkar.

Sayısal sütunlar, tarihler ve anahtarlar ele alındı. Kümede kalan son alan türü metindir: bölge adı,
ziyaret sonucu, adres kodu, saha notu. Metin sütunları çerçevede sayısal sütunlarla yan yana durur
ve aynı biçimde işlenebilir görünür. Bütün sütuna tek çağrıyla bir dizgi yöntemi uygulanır, sonuç
yeni bir seri olur, döngü yazmaya gerek kalmaz.

Bu rahatlığın bedeli bu derste ölçülüyor. Bir dizgi yöntemi bir metin üzerinde tanımlıdır; eksik bir
hücrede tanımlı değildir. Elle yazılan bir döngü bunu hata vererek bildirir. Vektörleştirilmiş bir
işlem ise bildirmez: eksik hücreyi işlemden geçirmez, eksik olarak bırakır ve çalışmaya devam eder.
Ölçü şudur — hangisi kaç satır üretiyor, hangisi nerede duruyor ve üretilen satırların kaçı sessizce
eksiğe dönmüş oluyor.

- **VC30. Vektörleştirilmiş dizgi işlemi**, bir dizgi yöntemini bütün sütuna tek çağrıyla
  uygulamaktır. Eksik hücre işlemden geçmez ve sonuçta **eksik kalır**; işlem hata vermez.
- **VC31.** Bir dizgi zinciri **eksiği yayar**. Zincirin herhangi bir adımında eksiğe dönen bir
  hücre, sonraki bütün adımlarda eksik kalır ve zincirin sonunda kaç satırın eksik olduğu ancak
  ayrıca sayılırsa bilinir.
- **VC32.** Biçimi bozuk bir değer, eksik bir değerden farklıdır ama zincirin sonunda **aynı yere**
  düşer: ikisi de eksik olur ve ikisi de aynı biçimde raporlanır.
- **VC33.** Zincirin bir adımı sonraki adımların girdisini değiştirir. Büyük küçük harf, boşluk ve
  ayırıcı kararları zincirin ilerisindeki bir eşitlik süzgecini hiçbir uyarı üretmeden boşa
  düşürebilir.

#### Metin Sütunu ve Bozuk Biçim

```python
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla
# yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir.
import math

M32 = 0xFFFFFFFF
TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]

def uretec(tohum: int):
    s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32
    s = ((s ^ (s >> 13)) * 3266489909) & M32
    s ^= s >> 16
    def sonraki() -> float:
        nonlocal s
        s = (s * 1664525 + 1013904223) & M32
        return s / 4294967296
    return sonraki

def ayrik(u: float, agirlik: list[float]) -> int:
    t = 0.0
    for i, p in enumerate(agirlik):
        t += p
        if u < t:
            return i
    return len(agirlik) - 1

abone, elenen = [], []
for i in range(1400):
    r = uretec(TOHUM + i)
    ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])]
    r(); r()  # hane ve memnuniyet cekilisleri bu derste kullanilmiyor
    (elenen if r() < 0.046 else abone).append(
        {"abone_no": 10001 + i, "bolge": ad, "taban": taban})

gecerli = {k["abone_no"] for k in abone}
cift: dict[tuple[int, str], float] = {}
for k in abone + elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp(
            (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100
        if k["abone_no"] in gecerli:
            cift.setdefault((k["abone_no"], DONEM[d]), t)
        r()
UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()]

# Adres kodu sahada uretilir ve BOLGE-NNNN-H bicimindedir. Bir kismi hic
# atanmamistir, bir kisminda ucuncu parca eksiktir.
EKSIK = None

def adres_kodu(no: int, bolge: str):
    r = uretec(TOHUM + 61000 + no)
    if r() < 0.048:
        return EKSIK
    if r() < 0.037:
        return f"{bolge.upper()}-{no - 10000:04d}"
    return f"{bolge.upper()}-{no - 10000:04d}-{'ABC'[ayrik(r(), [0.42, 0.35, 0.23])]}"

kod_of = {k["abone_no"]: adres_kodu(k["abone_no"], k["bolge"]) for k in abone}
kayit = [{"abone_no": o["abone_no"], "tuketim_m3": o["tuketim_m3"],
          "adres_kodu": kod_of[o["abone_no"]]} for o in UZUN]
sutun = [k["adres_kodu"] for k in kayit]
eksik = sum(1 for v in sutun if v is EKSIK)
bozuk = sum(1 for v in sutun if v is not EKSIK and v.count("-") < 2)
print(f"{len(kayit)} satir: {len(sutun) - eksik - bozuk} tam kod, {eksik} eksik, "
      f"{bozuk} ucuncu parcasi olmayan")
print(f"ornek kodlar: {[v for v in sutun[:6]]}")
```

```
3199 satir: 2943 tam kod, 160 eksik, 96 ucuncu parcasi olmayan
ornek kodlar: ['MERKEZ-0001-A', 'MERKEZ-0001-A', 'KUZEY-0002-A', 'KUZEY-0002-A', 'KUZEY-0002-A', 'GUNEY-0003-A']
```

Sütunda üç ayrı durum var ve ikisi bu dersin konusudur. 160 satırın adres kodu **hiç yoktur**:
abonenin adres kaydı bir koda bağlanmamıştır. 96 satırın kodu vardır ama **eksik biçimlidir**: bölge
ve numara parçaları yerindedir, üçüncü parça, yani bina harfi, yazılmamıştır.

İkisi bir kümede yan yana durur ve birbirinden farklı şeylerdir. Kod taşımayan satır için hiçbir şey
bilinmez. Kodu eksik biçimli olan satır için bölge de numara da bilinir, yalnız bir parça eksiktir.
Bu ayrımın sonuna kadar korunup korunmadığı, bu dersin sayacağı sayılardan biridir.

#### Duran Döngü, Durmayan Zincir

```python
# dizgi.py — ayni dizgi islemi iki yolla: elle dongu ve butun sutuna tek cagri.
# Ikisinin farki eksik hucrede ortaya cikiyor.
import sys

def elle_kucult(sutun):
    """Elle dongu: her hucreye dogrudan dizgi yontemi uygulanir."""
    return [v.lower() for v in sutun]

def vektor(sutun, islem):
    """Vektorlestirilmis islem: eksik hucre islemden gecmez, eksik kalir."""
    return [EKSIK if v is EKSIK else islem(v) for v in sutun]

try:
    elle_kucult(sutun)
except AttributeError as hata:
    ilk = next(i for i, v in enumerate(sutun) if v is EKSIK)
    print(f"elle dongu {ilk + 1}. satirda durdu: AttributeError {hata}")
    print(f"uretilen satir: 0 (kalan {len(sutun) - ilk - 1} satira hic bakilmadi)")

kucuk = vektor(sutun, str.lower)
parca = vektor(kucuk, lambda v: v.split("-"))
harf = [EKSIK if p is EKSIK or len(p) < 3 else p[2] for p in parca]
print(f"\nvektorlestirilmis zincir: {len(kucuk)} satir uretti, 0 hata verdi")
print(f"ucuncu parca cikarildiktan sonra {sum(1 for h in harf if h is EKSIK)} satir eksik: "
      f"{sum(1 for v in sutun if v is EKSIK)} hic kod tasimayan, "
      f"{sum(1 for p in parca if p is not EKSIK and len(p) < 3)} ucuncu parcasi olmayan")
print(f"bayt : uc ara seri {sys.getsizeof(kucuk) + sys.getsizeof(parca) + sys.getsizeof(harf)}, "
      f"kaynak sutun {sys.getsizeof(sutun)}")
print(f"kod  : elle guvenli dongu 9 satir, vektorlestirilmis zincir 3 cagri")
```

```
elle dongu 20. satirda durdu: AttributeError 'NoneType' object has no attribute 'lower'
uretilen satir: 0 (kalan 3179 satira hic bakilmadi)

vektorlestirilmis zincir: 3199 satir uretti, 0 hata verdi
ucuncu parca cikarildiktan sonra 256 satir eksik: 160 hic kod tasimayan, 96 ucuncu parcasi olmayan
bayt : uc ara seri 78120, kaynak sutun 26040
kod  : elle guvenli dongu 9 satir, vektorlestirilmis zincir 3 cagri
```

İki yaklaşım aynı işi yapıyor ve tam ters biçimde başarısız oluyor. Elle döngü **20. satırda durur**,
sıfır satır üretir ve kalan 3.179 satıra hiç bakmaz. Bir üretim hattında bu bir kesintidir ve
düzeltilmeden devam edilemez; ama kesintinin kendisi bir bilgidir, çünkü kümede kod taşımayan
satırlar olduğunu ilk yirmi satırda haber verir.

Vektörleştirilmiş zincir hiç durmaz. 3.199 satırın tamamını üretir, sıfır hata verir ve zincirin
sonunda **256 satır** eksiktir. Bu 256 satır, çıktının satır sayısında görünmez; çıktı hâlâ 3.199
satırdır ve boyu kaynağınkiyle aynıdır. Görünmeleri için ayrıca sayılmaları gerekir ve bu dersteki
sayım da o ayrı satırın kendisidir.

Kaybın hesabı iki kalemdir ve ikisi zincirin sonunda birleşmiştir: 160 satırın hiç kodu yoktu, 96
satırın kodu vardı ama üçüncü parçası yoktu. Zincirin çıktısında ikisi de aynı şeydir, yani eksiktir.
Bir önceki bölümde korunan ayrım burada kaybolmuştur ve kaybolmasının sebebi bir hata değil, üçüncü
parçayı çıkaran adımın eksik parça ile eksik kaydı ayırt edememesidir.

Kazanç tarafı da yazılmalıdır. Elle yazılan güvenli döngü, hata yakalamayla birlikte dokuz satır
tutar; vektörleştirilmiş zincir üç çağrıdır. Bedel bellektedir: zincirin üç ara serisi 78.120 bayt
gösterge tutar, kaynak sütunun kendisi 26.040 bayttır. Her adım yeni bir seri üretir ve zincir
uzadıkça bu üç kat büyür.

#### Zincirden Sonraki Süzgeç

```python
# suzgec.py — dizgi isleminden cikan eksik, kendisinden sonraki suzgecte
# sessizce bir tarafa dusuyor. Zincirin daha onceki adimi da suzgeci etkiler.
T = lambda m: round(sum(k["tuketim_m3"] for k, b in zip(kayit, m) if b), 2)
buyuk = [h == "B" for h in harf]
kucuk_b = [h == "b" for h in harf]
esit_degil = [h != "b" for h in harf]
bilinmiyor = [h is EKSIK for h in harf]
EN = [30, 10, 14]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
print(y(["suzgec", "satir", "toplam m3"]))
print(y(["harf == 'B'", sum(buyuk), T(buyuk)]))
print(y(["harf == 'b'", sum(kucuk_b), T(kucuk_b)]))
print(y(["harf != 'b'", sum(esit_degil), T(esit_degil)]))
print(y(["harf bilinmiyor", sum(bilinmiyor), T(bilinmiyor)]))
print(y(["kume", len(kayit), T([True] * len(kayit))]))
print(f"\nzincir kodu once kucuk harfe cevirdigi icin 'B' suzgeci {sum(buyuk)} satir "
      f"dondurur ve hicbir uyari uretmez")
print(f"esitlik suzgeci {sum(bilinmiyor)} bilinmeyen satiri disarida birakir, "
      f"esitsizlik suzgeci ayni {sum(bilinmiyor)} satiri iceri alir: "
      f"{sum(kucuk_b)} + {sum(esit_degil)} = {sum(kucuk_b) + sum(esit_degil)}")

satir_bayt = (sys.getsizeof(sutun) + sys.getsizeof(kucuk) + sys.getsizeof(parca)
              + sys.getsizeof(harf)) / len(kayit)
print(f"\ntek metin sutunu ve uc ara serisi satir basina {round(satir_bayt, 1)} bayt "
      f"gosterge tutar; {len(kayit)} satirda {round(satir_bayt * len(kayit) / 1024)} kibibayt")
print(f"ayni yapi 20 milyon satirda {round(satir_bayt * 20_000_000 / 1024 ** 3, 2)} "
      f"gibibayt gosterge ister ve dizgilerin kendisi buna dahil degil")
```

```
suzgec                             satir     toplam m3
harf == 'B'                            0             0
harf == 'b'                         1004      20069.73
harf != 'b'                         2195       42990.9
harf bilinmiyor                      256       4980.99
kume                                3199      63060.63

zincir kodu once kucuk harfe cevirdigi icin 'B' suzgeci 0 satir dondurur ve hicbir uyari uretmez
esitlik suzgeci 256 bilinmeyen satiri disarida birakir, esitsizlik suzgeci ayni 256 satiri iceri alir: 1004 + 2195 = 3199

tek metin sutunu ve uc ara serisi satir basina 32.6 bayt gosterge tutar; 3199 satirda 102 kibibayt
ayni yapi 20 milyon satirda 0.61 gibibayt gosterge ister ve dizgilerin kendisi buna dahil degil
```

İlk satır konunun en sessiz hatasıdır. Süzgeç `'B'` arar, sütunda `'B'` yoktur, çünkü zincirin ilk
adımı bütün kodu küçük harfe çevirmiştir. Sonuç **sıfır satır**, sıfır metre küp ve sıfır uyarıdır.
Boş bir sonuç kümesi hem "böyle bir kayıt yok" hem de "yanlış yerde aradın" anlamına gelir; ikisini
ayıran bir işaret çıktıda yoktur.

İkinci ve üçüncü satırlar konunun dördüncü dersindeki bulguyu metin üzerinde tekrarlıyor. Eşitlik
süzgeci 256 bilinmeyen satırı dışarıda bırakır, eşitsizlik süzgeci aynı 256 satırı içeri alır ve
1.004 ile 2.195 toplanınca tam olarak 3.199 eder. Küme ikiye bölünmüş görünür, oysa üçüncü bir küme
vardır ve 4.980,99 m³ tüketim taşır. İkiye bölünmüş bir tablo bu üçüncü kümeyi yazacak bir satır
bile açmaz.

Son iki satır konunun kapanış ölçüsüdür. Tek bir metin sütunu, üç ara serisiyle birlikte satır
başına 32,6 bayt gösterge tutar; 3.199 satırda bu 102 kibibayttır ve hiç fark edilmez. Aynı yapı 20
milyon satırda 0,61 gibibayta çıkar ve bu, dizgilerin kendi içeriği hariç, yalnız göstergelerin
maliyetidir. Sekiz derste kurulan bütün rahatlık, kümenin belleğe sığması varsayımına dayanır.

#### Ayrımı Geri Kazanmak

```python
# desen.py — ayirici ile bolmek yerine bicimi dogrulayan bir desen kullanmak,
# eksik kayit ile bozuk bicimi zincirin sonunda ayri tutar.
import re

DESEN = re.compile(r"^([a-z]+)-(\d{4})-([abc])$")

def durumu(v) -> str:
    if v is EKSIK:
        return "eksik"
    return "tam" if DESEN.match(v) else "bozuk"

sayim: dict[str, int] = {}
for v in kucuk:
    sayim[durumu(v)] = sayim.get(durumu(v), 0) + 1
print(f"desenle ayristirma: {sayim['tam']} tam, {sayim['bozuk']} bozuk bicim, "
      f"{sayim['eksik']} eksik kayit; toplam {sum(sayim.values())}")
desen_harf = [DESEN.match(v).group(3) if v is not EKSIK and DESEN.match(v) else EKSIK
              for v in kucuk]
print(f"ucuncu parca: bolerek {sum(1 for h in harf if h is EKSIK)} eksik, "
      f"desenle {sum(1 for h in desen_harf if h is EKSIK)} eksik; ayni sayi, "
      f"fark raporlanabilirlikte")
print(f"ayni sonucu veren iki yol: bolme {len(kucuk)} gecis ve 2 ara seri, "
      f"desen {len(kucuk)} gecis ve 1 ara seri")
```

```
desenle ayristirma: 2943 tam, 96 bozuk bicim, 160 eksik kayit; toplam 3199
ucuncu parca: bolerek 256 eksik, desenle 256 eksik; ayni sayi, fark raporlanabilirlikte
ayni sonucu veren iki yol: bolme 3199 gecis ve 2 ara seri, desen 3199 gecis ve 1 ara seri
```

Bölmenin kaybettiği ayrım geri kazanılabilir ve maliyeti bir geçiş bile değildir. Ayırıcıya göre
bölmek yerine biçimi doğrulayan bir desen kullanıldığında sütun üç sayıya ayrılır: 2.943 tam kod,
96 bozuk biçim, 160 eksik kayıt. Üçünün toplamı 3.199'dur ve bu toplama denetimi, konu boyunca
tekrarlanan tek denetimin metin üzerindeki karşılığıdır.

Çıkarılan üçüncü parça iki yolda da aynıdır: 256 satır eksiktir. Değişen tek şey, o 256'nın nereden
geldiğinin **yazılabilir** olmasıdır. Bozuk biçimli 96 satır düzeltilebilir bir sorundur ve
kaynağına geri sorulabilir; kodu hiç olmayan 160 satır ise düzeltilemez, çünkü ortada düzeltilecek
bir kayıt yoktur. İkisini tek sayıda toplayan bir rapor bu ayrımı ve onunla birlikte yapılacak işi
de siler.

Geçiş sayısı iki yolda da 3.199'dur, yani desen bir bedel eklemez; ara seri sayısı ikiden bire
iner. Ayrımı kaybettiren şey vektörleştirmenin kendisi değildi, **bölmenin doğrulama yapmamasıydı**.

#### Özet

- Vektörleştirilmiş dizgi işlemi eksik hücrede hata vermez, eksiği yayar: elle döngü 20. satırda
  durup 0 satır üretirken zincir 3.199 satırın tamamını üretir ve 256'sını sessizce eksiğe çevirir.
- Eksik kayıt ile bozuk biçim kümede farklı şeylerdir, zincirin sonunda aynı yere düşer: 160 kodsuz
  satır ile 96 üçüncü parçasız satır çıktıda ayırt edilemez.
- Zincirin bir adımı sonraki süzgeci boşa düşürebilir: küçük harfe çevrilmiş bir sütunda `'B'`
  araması 0 satır döndürür ve hiçbir uyarı üretmez.
- Eşitlik süzgeci 256 bilinmeyen satırı dışarıda, eşitsizlik süzgeci içeride bırakır; 1.004 ile
  2.195 toplamı kümeyi verir ama 4.980,99 m³'lük üçüncü küme hiçbir satırda görünmez.
- Zincirin bedeli bellektir: üç ara seri 78.120 bayt gösterge tutar, kaynak sütun 26.040 bayttır ve
  satır başına 32,6 bayt 20 milyon satırda 0,61 gibibayta çıkar.

#### Sonraki Adım

Sekiz derste veri çerçevesi arayüzü bütünüyle kuruldu ve her adımda iki sayı yan yana yazıldı:
soyutlamanın kazandırdığı geçiş ve gizlediği karar. Hizalama 781.710 karşılaştırmayı 2.361 geçişe
indirdi, birleştirme 2.616.782 karşılaştırmayı 4.017 geçişe indirdi, dilimleme 2.658 karşılaştırmayı
21 geçişe indirdi. Her seferinde kısalan kod, aynı oranda görünmez olan bir varsayılan bıraktı.

Bütün bu kazancın tek bir koşulu vardı ve hiçbir derste sınanmadı: kümenin belleğe sığması. Bu
konunun kümesi 3.199 satırdı ve tek bir metin sütunu ara serileriyle 102 kibibayt tuttu. Ölçüm ağı
bir kentin tamamına çıktığında satır sayısı milyonlarla ölçülür ve her ara seri ayrı bir kopya
ister. Sonraki konu bu koşulu sınar: aynı işler kümenin tamamı belleğe sığmadığında ne kadar
sürüyor, hangi işlem parçalanabiliyor, hangi tip seçimi kaç bayt kazandırıyor ve hesabın nerede
yapıldığı taşınan satır sayısını nasıl değiştiriyor.

## Ölçek ve Başarım

Kümenin belleğe sığmadığı noktadan sonra soyutlamanın rahatlığından ne kaldığının sayılması: aynı sütunun üç tiple tuttuğu bayt ve kategorik tipin kazancı, parça boyutunun bellek tepesi ve geçiş sayısıyla ilişkisi ile parçalanamayan işlemler, satır tabanlı ile sütunlu okumanın aynı sorguda okuduğu değer sayısı, bölümleme ve veri taşımanın eklediği kalem ile dağıtıma geçişin kazanca döndüğü iş boyutu, süzgeç ve toplulaştırma kaynağa itildiğinde ağdan geçen satır sayısı.

### Bellek Kullanımı

Source: https://academia.sh/tr/kurslar/python-ile-analiz/bellek-kullanimi

Kurgu ölçüm ağının 3.199 satırlık kümesinde tek bir sütunun bellekte kaç bayt tuttuğu, o sütunun hangi tiple tutulduğuna bağlıdır: bölge sütunu nesne listesi olarak 173.837 bayt, sabit genişlikli metin olarak 19.251 bayt, kategorik kodla 3.628 bayt tutar ve kazanç 47,9 kattır. Aynı sütunun iki savunulabilir ölçümü arasında 6,6 kat fark vardır, çünkü ölçüm işlevi göstericiyi sayar ve işaret ettiği nesneyi izlemez. Okuma sütunu nesne listesinden 1 bayta indirildiğinde bellek 31,4 kat küçülür ama toplam 6,9 m³ kayar ve 108 satır tarife basamağı değiştirir; kategorik tip 5 farklı değerli sütunda 5,3 kat kazandırırken 1.329 farklı değerli sütunda 4,1 kat kaybettirir.

Önceki konu sekiz derste veri çerçevesi soyutlamasını kurdu ve her çağrının varsayılanını yazdı.
O konunun kapanışı tek bir koşula dikkat çekmişti: çerçevenin rahatlığı, küme belleğe sığdığı
sürece geçerlidir. Bu konu o koşulun kalktığı yerden başlar ve ilk sorusu en somut olanıdır — bir
sütun bellekte kaç bayt tutar.

Cevap değerlerin sayısına değil, o değerlerin hangi tiple tutulduğuna bağlıdır. Tip kararı da çoğu
zaman kimse vermeden verilir: kümeyi okuyan çağrı bir varsayılan seçer ve sütun o varsayılanla
belleğe yerleşir. Dersin ölçü birimi **ayrılan bayttır**. Yanında duran ikinci sayı, o baytı
kazanmak için sessizce alınan karardır: hangi değerler artık ayırt edilemiyor ve hangi sayı bunun
için kaç birim kaydı.

- **AB1.** Küme kurgudur ve M26/K03'ün hazırladığı uzun biçimli okuma tablosunu verir: 3.199 satır,
  1.329 abone, üç dönem. Gerçek bir küme yerine aynı yapıyı üreten bir üreteç yazıldı; tohum
  görünürdür ve çıktı koşumdan koşuma aynıdır. **Modeldir.**
- **AB2.** Ölçüm `sys.getsizeof` ile yapılır. Bu işlev bir nesnenin **kendi gövdesini** verir,
  işaret ettiği nesneleri izlemez; toplam ölçüm bu yüzden elle kurulur.
- **AB3.** Rakamlar sekiz baytlık göstericisi olan bir çalışma ortamından okundu ve ortama bağlıdır.
  Değişmeyen şey **oranlardır**; gösterge genişliği değişse de gösterimler arası kat aynı kalır.
- **AB4.** Okumalar bir ondalıkla raporlanır. İki toplamın aynı sayılması için eşik 0,05 m³'tür,
  çünkü bundan küçük bir fark yalnızca yuvarlamadan gelebilir.
- **AB5.** Tarife basamakları 10 ve 25 m³ sınırlarıyla üç bant kurar ve üretecin verebileceği en
  büyük okuma 37,0 m³'tür; tamsayı gösterimlerinin genişliği bu üst sınıra göre seçilir.

#### Kümenin Sütunları

```python
# kaynak.py — bolgesel olcum agindan gelen kurgu uzun bicimli okuma tablosu.
# MODELDIR: gercek bir kume yerine ayni yapiyi ureten bir uretec yazildi;
# tohum gorunur ve cikti kosumdan kosuma aynidir. M26/K03'un hazirladigi kume:
# 3199 satir, 1329 abone, uc donem, tarife siniri 10 ve 25 m3.
from collections import Counter
TOHUM = 1246
BOLGE = ("kuzey", "dogu", "merkez", "guney", "bati")
DONEM = (("2024-01", 1329), ("2024-02", 1035), ("2024-03", 835))
YOGUNLUK = (0, 1, 2, 2, 2, 3, 4, 2, 2, 1)   # merkez agirlikli kurgu olcum agi


def uretec(tohum: int):
    s = tohum & 0xFFFFFFFF

    def sonraki() -> float:
        nonlocal s
        s = (1103515245 * s + 12345) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki


def basamak(okuma: float) -> str:
    return "0-10" if okuma <= 10 else "10-25" if okuma <= 25 else "25-40"


def kayitlar() -> list[tuple]:
    r, k = uretec(TOHUM), []
    for donem, adet in DONEM:
        for i in range(adet):
            o = round(5 + r() * 32.0, 1)
            k.append((10000 + i, f"S-{4000 + i}", donem,
                      BOLGE[YOGUNLUK[i % 10]], o, basamak(o)))
    return k


k0 = kayitlar()
say = lambda j: ", ".join(f"{a} {n}" for a, n in Counter(x[j] for x in k0).most_common())
print(f"tohum {TOHUM}; satir {len(k0)}, farkli abone {len({x[0] for x in k0})}, "
      f"donem {len(DONEM)}")
print("bolge :", say(3))
print("tarife:", say(5))
print(f"toplam {sum(x[4] for x in k0):.1f} m3, en kucuk {min(x[4] for x in k0)}, "
      f"en buyuk {max(x[4] for x in k0)}")
```

```
tohum 1246; satir 3199, farkli abone 1329, donem 3
bolge : merkez 1601, dogu 639, kuzey 321, guney 319, bati 319
tarife: 10-25 1504, 25-40 1187, 0-10 508
toplam 66667.9 m3, en kucuk 5.0, en buyuk 37.0
```

Küme altı sütun taşıyor ve iki tanesi bu dersin bütün ölçümlerini üretiyor. **Bölge** sütunu 3.199
değer taşır ama yalnız beş farklı değer içerir; merkez tek başına satırların yarısını tutar.
**Okuma** sütunu 3.199 sayı taşır, hepsi 5,0 ile 37,0 arasındadır ve her biri bir ondalıklıdır.
İkisi de aynı satır sayısına sahip, ama bellek karşılığı bakımından birbirine hiç benzemez.

#### Aynı Sütun Üç Tiple

```python
# bellek.py — ayni bolge sutunu uc tiple tutulur ve ayrilan bayt sayilir.
# MODELDIR: sutun once dosyadan okunmus gibi elde edilir, cunku ayristirici
# her satir icin yeni bir dizgi nesnesi uretir ve sabit bir listeden gelen
# dizgiler gibi paylasilmaz.
import csv, io, sys
from array import array

tampon = io.StringIO()
csv.writer(tampon, lineterminator="\n").writerows(k0)
satirlar = list(csv.reader(io.StringIO(tampon.getvalue())))
bolge_sut = [s[3] for s in satirlar]
print(f"sutunda {len(bolge_sut)} deger, {len({id(s) for s in bolge_sut})} ayri nesne, "
      f"{len(set(bolge_sut))} farkli deger")

kap = sys.getsizeof(bolge_sut)                       # yalnizca gostericiler
nesne = sum(sys.getsizeof(s) for s in {id(s): s for s in bolge_sut}.values())
paylasimli = sum(sys.getsizeof(s) for s in set(bolge_sut))

en_uzun = max(len(a) for a in set(bolge_sut))         # sabit genislikli metin
sabit = bytearray(len(bolge_sut) * en_uzun)
for j, a in enumerate(bolge_sut):
    sabit[j * en_uzun:(j + 1) * en_uzun] = a.encode("ascii").ljust(en_uzun)

sozluk = sorted(set(bolge_sut))                       # kategorik tip
kod = array("B", [sozluk.index(a) for a in bolge_sut])
kat_sozluk = sys.getsizeof(sozluk) + sum(sys.getsizeof(s) for s in sozluk)

y = lambda h: "".join(str(v).rjust(12) if j else str(v).ljust(26)
                      for j, v in enumerate(h))
print(y(["bolge sutunu tipi", "kap bayt", "nesne bayt", "toplam", "satir/bayt"]))
for ad, kb, nb in [("N1 nesne listesi", kap, nesne),
                   ("N1 paylasilmis sayilirsa", kap, paylasimli),
                   ("N2 sabit genislikli metin", sys.getsizeof(sabit), 0),
                   ("N3 kategorik kod", sys.getsizeof(kod), kat_sozluk)]:
    print(y([ad, kb, nb, kb + nb, round((kb + nb) / len(bolge_sut), 1)]))
print(f"N1 -> N3 kazanci {round((kap + nesne) / (sys.getsizeof(kod) + kat_sozluk), 1)} kat; "
      f"ayni sutunun iki olcumu arasindaki oran "
      f"{round((kap + nesne) / (kap + paylasimli), 1)} kat")
```

```
sutunda 3199 deger, 3199 ayri nesne, 5 farkli deger
bolge sutunu tipi             kap bayt  nesne bayt      toplam  satir/bayt
N1 nesne listesi                 26040      147797      173837        54.3
N1 paylasilmis sayilirsa         26040         229       26269         8.2
N2 sabit genislikli metin        19251           0       19251         6.0
N3 kategorik kod                  3279         349        3628         1.1
N1 -> N3 kazanci 47.9 kat; ayni sutunun iki olcumu arasindaki oran 6.6 kat
```

Kazanç tablonun iki ucu arasındadır: aynı beş değerli sütun nesne listesi olarak 173.837 bayt,
kategorik kodla 3.628 bayt tutar — **47,9 kat**. Satır başına 54,3 bayttan 1,1 bayta inmenin
sebebi tek bir gözlemdir: sütunda 3.199 değer var ama yalnız beş farklı değer. Kategorik tip bu
farkı yapıya çevirir; sütun bir bayt kod dizisi olur, beş dizgi bir kez sözlükte durur.

İkinci sayı tablonun ortasında duruyor ve ölçümün kendisiyle ilgilidir. İlk satır ile ikinci satır
**aynı sütunu** ölçüyor ve arada 6,6 kat var. Ayrım şudur: dosyadan okunan sütunda 3.199 değerin
her biri ayrı bir nesnedir, çünkü ayrıştırıcı her satır için yeni bir dizgi üretir. Aynı sütun
sabit bir listeden kurulsaydı beş nesne paylaşılırdı ve nesne payı 147.797 bayt yerine 229 bayt
olurdu. Ölçüm işlevi göstericiyi sayar, işaret ettiği nesneyi izlemez; hangi sayının doğru olduğu
sütunun nereden geldiğine bağlıdır ve ikisi de savunulabilir. **Bellek raporunda hangi kuralın
kullanıldığı yazılmıyorsa o rapor bir seçimdir.**

Sabit genişlikli metin arada durur ve kendi kararını taşır: her satır en uzun bölge adı kadar yer
kaplar. Altı bayt en uzun ada göre seçildi; sütuna yedi harfli bir bölge adı girerse ya sütun
yeniden kurulur ya da ad kırpılır. Kategorik tip bu kararı sözlüğe taşır ve genişlik sorununu
ortadan kaldırır.

#### Genişlik Seçiminin Oynattığı Sayı

```python
# genislik.py — okuma sutunu dort gosterimle tutulur; her gosterim icin
# ayrilan bayt, toplam ve tarife basamagi degisen satir yan yana basilir.
ESIK = 0.05   # okumalar bir ondalikla raporlanir, altindaki fark yuvarlamadandir
okuma_sut = [float(s[4]) for s in satirlar]
d64 = array("d", okuma_sut)                              # 8 baytlik kayan nokta
u16 = array("H", [round(o * 10) for o in okuma_sut])     # 2 baytlik desi-m3
u8 = array("B", [round(o) for o in okuma_sut])           # 1 baytlik tam m3
nesneli = sys.getsizeof(okuma_sut) + sum(sys.getsizeof(o) for o in okuma_sut)
dogru = round(sum(okuma_sut), 1)

G = (22, 13, 12, 12, 17)
y2 = lambda h: "".join(str(v).rjust(G[j]) if j else str(v).ljust(G[0])
                       for j, v in enumerate(h))
print(y2(["okuma sutunu tipi", "toplam bayt", "satir/bayt", "toplam m3", "basamak degisen"]))
for ad, bayt, dizi in [("nesne listesi", nesneli, okuma_sut),
                       ("8 bayt kayan nokta", sys.getsizeof(d64), list(d64)),
                       ("2 bayt desi-m3", sys.getsizeof(u16), [v / 10 for v in u16]),
                       ("1 bayt tam m3", sys.getsizeof(u8), [float(v) for v in u8])]:
    kayan = sum(1 for a, b in zip(okuma_sut, dizi) if basamak(a) != basamak(b))
    print(y2([ad, bayt, round(bayt / len(okuma_sut), 1), round(sum(dizi), 1), kayan]))
sapma = abs(sum(u8) - dogru)
print(f"1 bayt secimi bellegi {round(nesneli / sys.getsizeof(u8), 1)} kat kucultur; "
      f"toplam {sapma:.1f} m3 kayar, esigin {round(sapma / ESIK)} kati")

# Ayni donusum sayac_no sutununda 1329 farkli deger bulur: kod iki bayta cikar
# ve sozluk sutunun kendisinden buyuk olur.
sayac_sut = [s[1] for s in satirlar]
s_sozluk = sorted(set(sayac_sut))
s_kod = array("H", [s_sozluk.index(a) for a in sayac_sut])
s_kategorik = sys.getsizeof(s_kod) + sys.getsizeof(s_sozluk) + sum(
    sys.getsizeof(a) for a in s_sozluk)
s_sabit = sys.getsizeof(bytearray(len(sayac_sut) * 6))
b_sabit, b_kategorik = sys.getsizeof(sabit), sys.getsizeof(kod) + kat_sozluk
print(f"bolge   : {len(sozluk):5d} farkli deger, sabit genislikli {b_sabit} bayt, "
      f"kategorik {b_kategorik} bayt -> {round(b_sabit / b_kategorik, 1)} kat kazanc")
print(f"sayac_no: {len(s_sozluk):5d} farkli deger, sabit genislikli {s_sabit} bayt, "
      f"kategorik {s_kategorik} bayt -> {round(s_kategorik / s_sabit, 1)} kat kayip")

# Butun kume iki duzende: satir kayitlari ve sutun basina secilmis dar tipler.
satir_duzeni = sys.getsizeof(satirlar) + sum(
    sys.getsizeof(s) + sum(sys.getsizeof(a) for a in s) for s in satirlar)
d_sozluk, t_sozluk = sorted({s[2] for s in satirlar}), sorted({s[5] for s in satirlar})
sutunlar = (array("I", [int(s[0]) for s in satirlar]), s_kod,
            array("B", [d_sozluk.index(s[2]) for s in satirlar]), kod, u16,
            array("B", [t_sozluk.index(s[5]) for s in satirlar]))
sutun_duzeni = sum(sys.getsizeof(v) for v in sutunlar) + sum(
    sys.getsizeof(sz) + sum(sys.getsizeof(a) for a in sz)
    for sz in (s_sozluk, d_sozluk, sozluk, t_sozluk))
print(f"satir duzeni {satir_duzeni} bayt, sutun duzeni {sutun_duzeni} bayt -> "
      f"{round(satir_duzeni / sutun_duzeni, 1)} kat; satir basina "
      f"{round(satir_duzeni / len(satirlar))} bayt yerine "
      f"{round(sutun_duzeni / len(satirlar))} bayt")
```

```
okuma sutunu tipi       toplam bayt  satir/bayt   toplam m3  basamak degisen
nesne listesi                102816        32.1     66667.9                0
8 bayt kayan nokta            25672         8.0     66667.9                0
2 bayt desi-m3                 6478         2.0     66667.9                0
1 bayt tam m3                  3279         1.0     66661.0              108
1 bayt secimi bellegi 31.4 kat kucultur; toplam 6.9 m3 kayar, esigin 138 kati
bolge   :     5 farkli deger, sabit genislikli 19251 bayt, kategorik 3628 bayt -> 5.3 kat kazanc
sayac_no:  1329 farkli deger, sabit genislikli 19251 bayt, kategorik 79653 bayt -> 4.1 kat kayip
satir duzeni 1298875 bayt, sutun duzeni 109714 bayt -> 11.8 kat; satir basina 406 bayt yerine 34 bayt
```

İlk üç satır bedelsizdir. Nesne listesinden sekiz baytlık sayı dizisine geçmek belleği 4 kat, iki
baytlık desi-m³ gösterimine geçmek 15,9 kat küçültür ve toplam değişmez: okumalar bir ondalıkla
raporlanıyor, bir ondalık on ile çarpılınca tam sayı oluyor ve iki bayt 370 değerini rahatça
taşıyor. Buraya kadar soyutlamanın kazancı gerçekten bedava.

Dördüncü satır bedeli gösterir. Bir baytlık tam m³ gösterimi belleği nesne listesine göre 31,4 kat
küçültür, toplamı yalnız 6,9 m³ oynatır ve bu fark toplamın on binde biri kadardır — rapora
bakılırsa zararsız görünür. Ama son sütun başka bir sayı verir: **108 satır tarife basamağı
değiştirir**. Yuvarlama toplamda birbirini götürür, sınırda götürmez; 10,4 m³ okuyan bir sayaç
tam sayıya yuvarlandığında ilk basamağa düşer ve o abonenin faturası değişir. Toplam 6,9 m³'lük
sapmayı eşikle karşılaştırmak da aynı yere çıkar: fark eşiğin 138 katıdır, yani yuvarlamadan
gelmiş sayılamaz. **Gizlenen karar budur:** genişlik daraltmak bir bellek kararı gibi görünür,
oysa hangi satırın hangi banda düştüğünü belirleyen bir ölçüm kararıdır.

Son iki satır kategorik tipin sınırını çizer. Aynı dönüşüm bölge sütununda sabit genişlikli metne
göre 5,3 kat kazandırır, sayaç numarası sütununda 4,1 kat kaybettirir. Sebep tek bir sayıdır:
1.329 farklı değer bir bayta sığmaz, kod iki bayta çıkar ve sözlük sütunun kendisinden büyük olur.
"Metin sütununu kategorik tipe geçir" bir kural değil, farklı değer sayısına bağlı bir karardır ve
o sayı yazılmadan verilemez.

Son satır kararların toplamını verir. Aynı 3.199 kayıt satır kayıtları olarak 1.298.875 bayt,
sütun başına seçilmiş dar tiplerle 109.714 bayt tutar: satır başına 406 bayt yerine 34 bayt,
**11,8 kat**. Bu sayı altı ayrı tip kararının bileşimidir ve hiçbiri kendiliğinden gelmez; her
sütun için hangi genişliğin ve hangi gösterimin seçildiği ayrı ayrı yazıldı. Kümeyi okuyan bir
çağrı bu kararların hepsini varsayılanla verir ve sonucu tek bir sayı olarak bildirmez.

#### Özet

- Bölge sütunu nesne listesi olarak 173.837 bayt, sabit genişlikli metin olarak 19.251 bayt,
  kategorik kodla 3.628 bayt tutar; kazanç 47,9 kat, satır başına 54,3 bayttan 1,1 bayta iniştir.
- Aynı sütunun iki savunulabilir ölçümü arasında 6,6 kat fark vardır, çünkü ölçüm işlevi göstericiyi
  sayar; dosyadan okunan sütunda 3.199 ayrı nesne, sabit listeden kurulan sütunda beş nesne bulunur.
- Okuma sütununda sekiz bayttan iki bayta inmek toplamı değiştirmez; bir bayta inmek belleği nesne
  listesine göre 31,4 kat küçültür ama toplamı 6,9 m³ oynatır ve bu fark eşiğin 138 katıdır.
- Aynı yuvarlama 108 satırın tarife basamağını değiştirir; toplamda görünmeyen bedel sınırda ortaya
  çıkar.
- Kategorik tip 5 farklı değerli sütunda 5,3 kat kazandırır, 1.329 farklı değerli sütunda 4,1 kat
  kaybettirir; kazanç satır sayısına değil farklı değer sayısına bağlıdır.

#### Sonraki Adım

Bu ders bir sütunun bellek karşılığını daralttı ve dört gösterim arasında 31 kata varan bir fark
buldu. Ama daraltmanın bir tabanı vardır: 3.199 satır her tiple belleğe sığar, 320 milyon satır
hiçbir tiple sığmaz. O noktada soru tip seçmek değil, kümenin tamamını aynı anda bellekte tutmaktan
vazgeçmektir. Sonraki ders kümeyi parçalara bölerek okur ve parça boyutunun bellek tepesiyle geçiş
sayısı arasında kurduğu dengeyi ölçer; hangi işlemlerin parça parça hesaplanabildiğini, hangilerinin
hesaplanamadığını sayar ve parçalamanın sessizce ürettiği yanlış ortalamayı gösterir.

### Parça Parça İşleme

Source: https://academia.sh/tr/kurslar/python-ile-analiz/parca-parca-isleme

Kurgu ölçüm ağının 3.199 satırlık dosyası parça parça okunduğunda bellek tepesi parça boyutuyla düşer ama işlemin taşıdığı durum düşmez: 100 satırlık parçada tepe 36.132 bayt, kümenin tamamını tutmaya göre 32 kat küçüktür ve toplam dört parça boyutunda da 66.667,9 m³ çıkar. Toplam, sayım ve bölge kırılımı 3–5 öğelik durumla parça içinde biter; kesin medyan 321 kovalık histogramla, kesin farklı abone sayımı 1.329 öğelik durumla yürür ve o 87.166 bayt parça küçüldükçe küçülmez. Parçalama sessizce bir sıra varsayımı yapar: akan hesap abone sıralı dosyada 1.035 abone için sonuç verirken dönem sıralı dosyada hiç hata vermeden 0 abone için sonuç verir; parça sınırı bölgeye taşındığında parça ortalamalarının ortalaması genel ortalamadan 0,1039 m³ ayrışır.

Önceki ders bir sütunun bellek karşılığını 31 kata varan oranlarda daralttı, ama daraltmanın bir
tabanı olduğunu da söyledi: 3.199 satır her tiple sığar, kümenin kendisi büyüdükçe hiçbir tip
yetmez. Bu ders o taban aşıldığında ne yapıldığını ölçer — küme belleğe bir defada alınmaz, parça
parça okunur.

Parçalamanın vaadi açıktır: aynı anda bellekte yalnız bir parça durur ve tepe kullanım parça
boyutuyla belirlenir. Dersin ölçüsü bu vaadin karşılığını iki sayıyla okur. Birincisi **kazanç**:
parça boyutu düştüğünde bellek tepesi kaç kat küçülüyor. İkincisi **taşınan durum**: işlemin parça
dışında tuttuğu ve parça boyutu değiştiğinde küçülmeyen kalem. Bir işlemin parça parça
hesaplanabilir olup olmadığını belirleyen şey budur.

- **AB6.** Küme, önceki dersin kurgu ölçüm ağı kümesidir: 3.199 satır, 1.329 abone, üç dönem,
  toplam 66.667,9 m³. Aynı tohumla üretilir ve çıktı koşumdan koşuma aynıdır. **Modeldir.**
- **AB7.** Dosya kümenin beş alanını taşır; tarife basamağı okumadan türetildiği için dışa
  aktarıma girmez.
- **AB8.** Dosya dönem sırasında yazılır: önce 2024-01'in 1.329 satırı, sonra 2024-02'nin 1.035
  satırı, sonra 2024-03'ün 835 satırı. Bir abonenin satırları birbirinden yüzlerce satır uzaktadır.
- **AB9.** Bellek tepesi, aynı anda bellekte duran satırların ve işlem durumunun baytıdır;
  `sys.getsizeof` ile ölçülür ve sekiz baytlık göstericisi olan bir çalışma ortamına aittir.
  Değişmeyen şey oranlardır.
- **AB10.** İki ortalamanın aynı sayılması için eşik 0,05 m³'tür, çünkü okumalar bir ondalıkla
  raporlanır ve bundan küçük bir fark yalnızca yuvarlamadan gelebilir.

#### Parça Boyutu ve Bellek Tepesi

```python
# kume.py — ayni kurgu olcum agi kumesi bir dosyaya yazilir. MODELDIR: gercek
# bir disa aktarim yerine ayni yapiyi ureten bir uretec yazildi; tohum gorunur
# ve cikti kosumdan kosuma aynidir. 3199 satir, 1329 abone, uc donem.
import csv, itertools, os, sys, tempfile
TOHUM = 1246
BOLGE = ("kuzey", "dogu", "merkez", "guney", "bati")
DONEM = (("2024-01", 1329), ("2024-02", 1035), ("2024-03", 835))
YOGUNLUK = (0, 1, 2, 2, 2, 3, 4, 2, 2, 1)   # merkez agirlikli kurgu olcum agi
BASLIK = ("abone_no", "sayac_no", "donem", "bolge", "okuma_m3")


def uretec(tohum: int):
    s = tohum & 0xFFFFFFFF

    def sonraki() -> float:
        nonlocal s
        s = (1103515245 * s + 12345) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki


def kayitlar() -> list[tuple]:
    r, k = uretec(TOHUM), []
    for donem, adet in DONEM:
        for i in range(adet):
            k.append((10000 + i, f"S-{4000 + i}", donem,
                      BOLGE[YOGUNLUK[i % 10]], round(5 + r() * 32.0, 1)))
    return k


k0 = kayitlar()
KLASOR = tempfile.mkdtemp(prefix="olcum-")
YOL = os.path.join(KLASOR, "okuma.csv")
with open(YOL, "w", newline="") as f:
    y = csv.writer(f, lineterminator="\n")
    y.writerow(BASLIK)
    y.writerows(k0)

bayt = lambda p: sys.getsizeof(p) + sum(
    sys.getsizeof(s) + sum(sys.getsizeof(a) for a in s) for s in p)
TAM = bayt([list(map(str, s)) for s in k0])
print(f"tohum {TOHUM}; dosya {os.path.getsize(YOL)} bayt, {len(k0)} veri satiri")
print(f"kumenin tamami bellege alinirsa {TAM} bayt, satir basina "
      f"{round(TAM / len(k0))} bayt")
print(f"toplam {sum(x[4] for x in k0):.1f} m3, farkli abone {len({x[0] for x in k0})}")
```

```
tohum 1246; dosya 102548 bayt, 3199 veri satiri
kumenin tamami bellege alinirsa 1152229 bayt, satir basina 360 bayt
toplam 66667.9 m3, farkli abone 1329
```

Dosya diskte 102.548 bayt tutuyor, bellekte 1.152.229 bayt — on bir kat. Fark önceki dersin
konusudur: metin satırı çözümlendiğinde her alan ayrı bir nesne olur. Bu ders o çarpanı veri
olarak alır ve tek soruyu sorar: kümenin tamamını aynı anda bellekte tutmadan aynı sayıları
üretmek mümkün mü.

```python
# parca.py — dosya parca parca okunur. Toplam, satir sayisi, en buyuk ve bolge
# basina toplam ayni gecisde birikir; parca boyutu yalnizca ayni anda bellekte
# duran satir sayisini degistirir.
def parcalar(yol: str, n: int):
    with open(yol, newline="") as f:
        okuyucu = csv.reader(f)
        next(okuyucu)                                   # baslik satiri
        while (p := list(itertools.islice(okuyucu, n))):
            yield p


def tek_gecis(yol: str, n: int) -> dict:
    tepe = adet = sayi = 0
    toplam, enb, bolgeli = 0.0, 0.0, {}
    for p in parcalar(yol, n):
        tepe, adet = max(tepe, bayt(p)), adet + 1
        for s in p:
            o = float(s[4])
            toplam, sayi, enb = toplam + o, sayi + 1, max(enb, o)
            bolgeli[s[3]] = bolgeli.get(s[3], 0.0) + o
    return {"parca": adet, "tepe": tepe, "sayi": sayi, "toplam": round(toplam, 1),
            "enb": enb, "bolge": round(max(bolgeli.values()), 1)}


G = (14, 10, 12, 11, 8, 13, 16)
y2 = lambda h: "".join(str(v).rjust(G[j]) if j else str(v).ljust(G[0])
                       for j, v in enumerate(h))
print(y2(["parca boyutu", "parca", "tepe bayt", "tepe/tam", "gecis", "toplam m3",
          "en buyuk bolge"]))
for n in (100, 400, 1600, 3199):
    r = tek_gecis(YOL, n)
    print(y2([n, r["parca"], r["tepe"], f"{r['tepe'] / TAM:.3f}", 1, r["toplam"],
              r["bolge"]]))
print(f"parca 100 iken tepe, kumenin tamamini tutmaya gore "
      f"{round(TAM / tek_gecis(YOL, 100)['tepe'])} kat kucuk; dort kosumda da "
      f"toplam {tek_gecis(YOL, 100)['toplam']} m3 ve satir {tek_gecis(YOL, 100)['sayi']}")
```

```
parca boyutu       parca   tepe bayt   tepe/tam   gecis    toplam m3  en buyuk bolge
100                   32       36132      0.031       1      66667.9         33462.3
400                    8      144085      0.125       1      66667.9         33462.3
1600                   2      577624      0.501       1      66667.9         33462.3
3199                   1     1152229      1.000       1      66667.9         33462.3
parca 100 iken tepe, kumenin tamamini tutmaya gore 32 kat kucuk; dort kosumda da toplam 66667.9 m3 ve satir 3199
```

Kazanç doğrusaldır ve tabloda okunur: parça boyutu 3.199'dan 100'e indiğinde tepe 1.152.229
bayttan 36.132 bayta, kümenin otuz ikide birine düşer. Geçiş sayısı dört koşumda da birdir; dosya
bir kez okunur. Toplam da dört koşumda aynı çıkar — 66.667,9 m³ — ve en büyük bölgenin toplamı
33.462,3 m³'te sabit kalır. Bu dört işlem parça boyutuna karşı **kayıtsızdır**: her biri sabit
sayıda birikeç tutar ve parçadan parçaya yalnız o birikeçler geçer.

Parça boyutunun bedeli tabloda görünmüyor, çünkü bu derste süre ölçülmüyor. Küçük parça daha çok
çağrı, büyük parça daha çok bellek demektir; seçim bu iki sayı arasındadır ve tepe sınırı
donanımdan gelir. Ölçülebilir olan taraf tepe baytıdır ve o parça boyutuyla orantılı çıkar.

#### Hangi İşlem Parça İçinde Biter

```python
# islem.py — hangi islem parca icinde biter, hangisi parca disinda durum tasir.
# Olcu: parca boyutundan bagimsiz olarak tasinan durum ogesi ve baytidir.
ESIK = 0.05   # okumalar bir ondalikla raporlanir, altindaki fark yuvarlamadandir
N = 400
sutun, bolgeli, aboneler, kova = [], {}, {}, {}
for p in parcalar(YOL, N):
    for s in p:
        o = float(s[4])
        sutun.append(o)
        bolgeli[s[3]] = bolgeli.get(s[3], 0.0) + o
        aboneler[s[0]] = o
        d = round(o * 10)                 # okumalar bir ondalikli: 321 kova yeter
        kova[d] = kova.get(d, 0) + 1
birikim, medyan_hist = 0, None
for d in sorted(kova):
    birikim += kova[d]
    if medyan_hist is None and birikim >= (len(sutun) + 1) // 2:
        medyan_hist = d / 10

olcu = lambda x: sys.getsizeof(x) + sum(sys.getsizeof(a) for a in x)
G2 = (32, 12, 12, 8, 12)
y3 = lambda h: "".join(str(v).rjust(G2[j]) if j else str(v).ljust(G2[0])
                       for j, v in enumerate(h))
print(y3(["islem", "durum ogesi", "durum bayt", "gecis", "sonuc"]))
for ad, durum, bayt_d, sonuc in [
        ("toplam, sayi, en buyuk", 3, 3 * 24, round(sum(sutun), 1)),
        ("agirlikli ortalama", 2, 2 * 24, round(sum(sutun) / len(sutun), 4)),
        ("bolge basina toplam", len(bolgeli), olcu(bolgeli), len(bolgeli)),
        ("kesin medyan (histogram)", len(kova), olcu(kova), medyan_hist),
        ("kesin medyan (sutunu tutarak)", len(sutun), olcu(sutun),
         sorted(sutun)[(len(sutun) - 1) // 2]),
        ("farkli abone sayisi (kesin)", len(aboneler), olcu(aboneler), len(aboneler))]:
    print(y3([ad, durum, bayt_d, 1, sonuc]))
parca_bayt = max(bayt(p) for p in parcalar(YOL, 100))
print(f"parca 100 iken parca {parca_bayt} bayt tutar, abone durumu {olcu(aboneler)} "
      f"bayt; gercek tepe {parca_bayt + olcu(aboneler)} bayttir ve durum payi parca "
      f"boyutu degisince kuculmez")
```

```
islem                            durum ogesi  durum bayt   gecis       sonuc
toplam, sayi, en buyuk                     3          72       1     66667.9
agirlikli ortalama                         2          48       1     20.8402
bolge basina toplam                        5         413       1           5
kesin medyan (histogram)                 321       18292       1        20.7
kesin medyan (sutunu tutarak)           3199      102816       1        20.7
farkli abone sayisi (kesin)             1329       87166       1        1329
parca 100 iken parca 36132 bayt tutar, abone durumu 87166 bayt; gercek tepe 123298 bayttir ve durum payi parca boyutu degisince kuculmez
```

Tablo işlemleri üç kümeye ayırıyor. **Sabit durumlu** olanlar en kolayıdır: toplam, sayım ve en
büyük üç birikeçle biter, ağırlıklı ortalama ikiyle. Bunlar için parçalama bedelsizdir. **Sınırlı
durumlu** olanlar bir sonrakidir: bölge başına toplam beş öğe taşır, kesin medyan 321 öğe. Medyanın
parça parça hesaplanabilmesi genel bir doğru değil, bu kümeye özgü bir varsayımın sonucudur —
okumalar bir ondalıklı ve 5,0 ile 37,0 arasında olduğu için değer uzayı 321 kovaya sığar ve
histogram sütunu tutmadan aynı sayıyı verir: 20,7 m³. Değerler sürekli olsaydı bu satır tablodan
düşerdi.

Üçüncü küme parçalamanın kazancını yiyendir. **Kesin farklı abone sayımı** 1.329 öğelik bir durum
taşır ve o durum 87.166 bayttır. Parça 100 satıra indirildiğinde parçanın kendisi 36.132 bayt
tutar, ama gerçek tepe 123.298 bayttır ve durum payı parça küçüldükçe küçülmez. Parçalamanın
kazancı burada tersine döner: tepenin dörtte üçü artık işlemin kendisindedir. Aynı şey sütunu
bütünüyle tutan medyan hesabı için de geçerlidir — 102.816 bayt, kümenin tamamını tutmaktan farksız.

#### Parçalamanın Yaptığı Sıra Varsayımı

```python
# sira.py — ayni kume abone sirasina gore ikinci bir dosyaya yazilir ve ayni
# hesap iki duzende tekrarlanir. Parca parca isleme sessizce bir sira varsayimi
# yapar; varsayim bozuldugunda hata cikmaz, sonuc bosalir.
YOL2 = os.path.join(KLASOR, "okuma_abone.csv")
with open(YOL2, "w", newline="") as f:
    y = csv.writer(f, lineterminator="\n")
    y.writerow(BASLIK)
    y.writerows(sorted(k0, key=lambda x: (x[0], x[2])))


def akan_artis(yol: str, n: int) -> dict:      # durum: yalniz acik olan abone
    artislar, aktif, onceki = {}, None, 0.0
    for p in parcalar(yol, n):
        for s in p:
            a, o = s[0], float(s[4])
            if a != aktif:
                aktif, onceki = a, o
            else:
                artislar[a] = max(artislar.get(a, 0.0), o - onceki)
                onceki = o
    return artislar


def tam_artis(yol: str, n: int) -> tuple[dict, int]:   # durum: butun aboneler
    son, artislar = {}, {}
    for p in parcalar(yol, n):
        for s in p:
            a, o = s[0], float(s[4])
            if a in son:
                artislar[a] = max(artislar.get(a, 0.0), o - son[a])
            son[a] = o
    return artislar, len(son)


G3 = (18, 12, 14, 14, 16)
y4 = lambda h: "".join(str(v).rjust(G3[j]) if j else str(v).ljust(G3[0])
                       for j, v in enumerate(h))
print(y4(["dosya duzeni", "akan durum", "akan sonuc", "tam durum", "tam sonuc"]))
for ad, yol in [("donem sirali", YOL), ("abone sirali", YOL2)]:
    a1 = akan_artis(yol, N)
    a2, durum = tam_artis(yol, N)
    print(y4([ad, 1, len(a1), durum, len(a2)]))
d1, d2 = akan_artis(YOL2, N), tam_artis(YOL, N)[0]
print(f"iki duzenin ayni hesabi ayni sayiyi verir mi: "
      f"{'evet' if d1 == d2 else 'hayir'}; en buyuk artis "
      f"{max(d2.values()):.1f} m3")

# Parca sinirini dogal bir anahtara tasimak: bolge basina bir parca.
bp = {}
for x in k0:
    bp.setdefault(x[3], []).append(x[4])
ort_ort = sum(sum(v) / len(v) for v in bp.values()) / len(bp)
agirlikli = sum(x[4] for x in k0) / len(k0)
fark = abs(ort_ort - agirlikli)
print(f"parca boyutlari {sorted(len(v) for v in bp.values())}; parca ortalamalarinin "
      f"ortalamasi {ort_ort:.4f} m3, agirlikli ortalama {agirlikli:.4f} m3")
print(f"fark {fark:.4f} m3, esigin {fark / ESIK:.1f} kati: esit agirlik varsayimi "
      f"parca boyutlari esit olmadigi anda bozulur")
```

```
dosya duzeni        akan durum    akan sonuc     tam durum       tam sonuc
donem sirali                 1             0          1329            1035
abone sirali                 1          1035          1329            1035
iki duzenin ayni hesabi ayni sayiyi verir mi: evet; en buyuk artis 30.7 m3
parca boyutlari [319, 319, 321, 639, 1601]; parca ortalamalarinin ortalamasi 20.7363 m3, agirlikli ortalama 20.8402 m3
fark 0.1039 m3, esigin 2.1 kati: esit agirlik varsayimi parca boyutlari esit olmadigi anda bozulur
```

Tablonun ilk satırı dersin gizlenen kararıdır. Akan hesap tek bir abonelik durum taşır ve bu
yüzden en ucuz seçenektir; abone sıralı dosyada 1.035 abone için artış üretir ve sonuç, bütün
aboneleri bellekte tutan hesabın sonucuyla birebir aynıdır. Aynı kod dönem sıralı dosyada **hiçbir
hata vermeden 0 abone** için sonuç verir. Boş sonuç bir arıza gibi görünmez: dosya okundu, satırlar
işlendi, çıkış kodu sıfır. Durum tutumluluğu bir sıralama varsayımına dönüşmüştür ve varsayım
dosyanın içinde yazmaz.

İkinci gizlenen karar parçanın nereden kesildiğidir. Parça sınırı satır sayısı yerine doğal bir
anahtara taşındığında — bölge başına bir parça — parçalar 319 ile 1.601 arasında değişir. Parça
ortalamalarının ortalaması 20,7363 m³, ağırlıklı ortalama 20,8402 m³ verir; fark 0,1039 m³ ve
eşiğin 2,1 katıdır, yani yuvarlamadan gelmiş sayılamaz. Fark küçük görünür ama yönü sabittir:
eşit ağırlık varsayımı küçük parçaları büyütür ve parça boyutları birbirinden uzaklaştıkça sapma
büyür. **Parça başına hesaplanan bir ortalamanın nasıl birleştirildiği yazılmıyorsa o ortalama
ölçülmemiş sayılır.**

#### Özet

- Parça boyutu 3.199'dan 100'e indiğinde bellek tepesi 1.152.229 bayttan 36.132 bayta, otuz ikide
  bire düşer; geçiş sayısı birdir ve toplam dört parça boyutunda da 66.667,9 m³ çıkar.
- Bir işlemin parça parça hesaplanabilirliğini parça boyutu değil taşınan durum belirler: toplam ve
  ortalama 2–3 öğe, bölge kırılımı 5 öğe, kesin medyan 321 öğe, kesin farklı abone sayımı 1.329 öğe
  taşır.
- Kesin medyanın 321 kovaya sığması genel bir doğru değil, okumaların bir ondalıklı ve 5,0–37,0
  aralığında olmasından gelen bir varsayımdır.
- Parça 100 satıra indiğinde parçanın kendisi 36.132 bayt tutar ama abone durumu 87.166 bayttır;
  gerçek tepe 123.298 bayta çıkar ve durum payı parça küçüldükçe küçülmez.
- Akan hesap abone sıralı dosyada 1.035 abone için doğru sonuç verir, dönem sıralı dosyada hata
  vermeden 0 abone için sonuç verir; parçalama sessizce bir sıra varsayımı yapar.
- Parça sınırı bölgeye taşındığında parça ortalamalarının ortalaması ağırlıklı ortalamadan 0,1039
  m³ ayrışır ve bu fark eşiğin 2,1 katıdır.

#### Sonraki Adım

Bu ders belleği parça parça okuyarak kurtardı, ama her parçada dosyanın **bütün alanlarını**
çözümledi: bölge kırılımı için okunan 3.199 satırın beş alanı da metinden nesneye dönüştürüldü,
sonra üçü atıldı. Sorunun sorduğu iki sütun için beş sütun okunuyorsa parçalama yalnız tepeyi
düşürür, yapılan işi düşürmez. Sonraki ders kaydın alanlarını bitişik tutan düzenle sütunu bitişik
tutan düzeni aynı sorguda karşılaştırır ve ölçüyü tek bir sayıya bağlar: aynı cevabı üretmek için
kaç değer okundu.

### Sütunlu Motorlar

Source: https://academia.sh/tr/kurslar/python-ile-analiz/sutunlu-motorlar

Kurgu ölçüm ağının 3.199 satırlık ve altı sütunlu kümesinde tek bir soru dört okuma yoluyla cevaplanıyor ve dördü de aynı sayıyı veriyor: satır tabanlı tarama 19.194 değer okur, sütunlu erken maddeleştirme 9.597, süzgeci öne alan geç maddeleştirme 4.869, blok atlamalı yol 2.835 değer okur ve kazanç 6,8 kata çıkar. Blok atlama kümenin o sütuna göre kümelenmiş olmasına bağlıdır; aynı küme abone sırasında yazılınca yedi bloğun dördü yerine ikisi, karışık sırada hiçbiri atlanmaz ve okunan değer 2.835'ten 4.883'e çıkar. Geç maddeleştirmenin kazancı da süzgecin seçiciliğine bağlıdır: eşleşen satır 835'ten 2.691'e çıktığında kazanç 1,97 kattan 1,12 kata düşer ve dağınık erişim 1.671'den 5.383'e çıkar.

Önceki ders kümeyi parça parça okuyarak bellek tepesini otuz ikide bire indirdi, ama her parçada
kaydın bütün alanlarını çözümledi. Bölge kırılımı iki sütuna bakıyordu; beş sütun okundu, ikisi
kullanıldı, üçü atıldı. Parçalama tepeyi düşürür, yapılan işi düşürmez.

Bu dersin sorusu odur: aynı cevabı üretmek için kaç değere dokunmak gerekiyor. Ölçü birimi
**okunan değer sayısıdır** ve yanında ikinci bir sayı durur — **erişim**, yani ayrı bir bitişik
alana kaç kez gidildiği. Kaydın alanlarını bitişik tutan düzen ile sütunu bitişik tutan düzen bu
iki sayıda birbirinin tersine çalışır; hangisinin ucuz olduğu sorunun ne sorduğuna bağlıdır.

- **AB11.** Küme kurgudur ve önceki derslerin kümesidir: 3.199 satır, altı sütun, 1.329 abone, üç
  dönem. Aynı tohumla üretilir ve çıktı koşumdan koşuma aynıdır. **Modeldir.**
- **AB12.** İki depo düzeni de bellekte modellenir; ölçülen şey depolama ortamı değil, cevabı
  üretmek için dokunulan değer sayısıdır. **Modeldir.**
- **AB13.** Satır tabanlı düzende bir satıra dokunmak o satırın altı alanını da okumak demektir;
  alanlar bitişik yazıldığı için tek alan ayrı çekilemez.
- **AB14.** Sütunlu düzende her sütun ayrı bir dizidir ve her 512 değerlik blok için en küçük ile
  en büyük değeri tutan bir özet sütunla birlikte saklanır.
- **AB15.** Erişim, ayrı bir bitişik alana gidiş sayısıdır: baştan sona tarama bir erişim, bir
  sütunun tek tek konumlarını okumak konum sayısı kadar erişimdir.

#### İki Düzen

```python
# depo.py — ayni kurgu kume iki duzende tutulur ve her okuma sayilir.
# MODELDIR: gercek bir depo yerine iki yerlesim modellendi; olcu, bir sorgunun
# cevabini uretmek icin kac degere dokunuldugudur. 3199 satir, 1329 abone.
TOHUM = 1246
BOLGE = ("kuzey", "dogu", "merkez", "guney", "bati")
DONEM = (("2024-01", 1329), ("2024-02", 1035), ("2024-03", 835))
YOGUNLUK = (0, 1, 2, 2, 2, 3, 4, 2, 2, 1)   # merkez agirlikli kurgu olcum agi
BASLIK = ("abone_no", "sayac_no", "donem", "bolge", "okuma_m3", "basamak")


def uretec(tohum: int):
    s = tohum & 0xFFFFFFFF

    def sonraki() -> float:
        nonlocal s
        s = (1103515245 * s + 12345) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki


def basamak(o: float) -> str:
    return "0-10" if o <= 10 else "10-25" if o <= 25 else "25-40"


def kayitlar() -> list[tuple]:
    r, k = uretec(TOHUM), []
    for donem, adet in DONEM:
        for i in range(adet):
            o = round(5 + r() * 32.0, 1)
            k.append((10000 + i, f"S-{4000 + i}", donem,
                      BOLGE[YOGUNLUK[i % 10]], o, basamak(o)))
    return k


class SatirDepo:
    """Bir kaydin butun alanlari bitisiktir: bir satira dokunmak o satirin
    butun alanlarini okumak demektir."""

    def __init__(self, k):
        self.k, self.okunan, self.erisim = [tuple(map(str, x)) for x in k], 0, 0

    def tara(self):
        self.erisim += 1                     # bastan sona tek bitisik gecis
        for s in self.k:
            self.okunan += len(BASLIK)
            yield s

    def kayit(self, i: int) -> tuple:
        self.okunan, self.erisim = self.okunan + len(BASLIK), self.erisim + 1
        return self.k[i]


class SutunDepo:
    """Her sutun ayri bir dizidir: yalniz istenen sutun, hatta o sutunun yalniz
    istenen konumlari okunabilir. Blok ozeti sutunla birlikte saklanir."""
    BLOK = 512

    def __init__(self, k):
        self.s = {ad: [str(x[j]) for x in k] for j, ad in enumerate(BASLIK)}
        self.okunan, self.erisim, self.n = 0, 0, len(k)
        self.ozet = {ad: [(min(v[i:i + self.BLOK]), max(v[i:i + self.BLOK]))
                          for i in range(0, len(v), self.BLOK)]
                     for ad, v in self.s.items()}

    def tumu(self, ad: str) -> list:
        self.okunan, self.erisim = self.okunan + self.n, self.erisim + 1
        return self.s[ad]

    def konumlar(self, ad: str, yerler: list) -> list:
        self.okunan += len(yerler)           # dagitik erisim: her konum ayri
        self.erisim += len(yerler)
        return [self.s[ad][i] for i in yerler]

    def kayit(self, i: int) -> tuple:
        self.okunan += len(BASLIK)           # alti sutuna alti ayri gidis
        self.erisim += len(BASLIK)
        return tuple(self.s[ad][i] for ad in BASLIK)


k0 = kayitlar()
sd, cd = SatirDepo(k0), SutunDepo(k0)
print(f"tohum {TOHUM}; {len(k0)} satir, {len(BASLIK)} sutun, "
      f"{len(k0) * len(BASLIK)} deger")
print(f"blok boyutu {SutunDepo.BLOK}, donem sutununda "
      f"{len(cd.ozet['donem'])} blok ozeti")
print("ilk kayit:", sd.kayit(0))
```

```
tohum 1246; 3199 satir, 6 sutun, 19194 deger
blok boyutu 512, donem sutununda 7 blok ozeti
ilk kayit: ('10000', 'S-4000', '2024-01', 'kuzey', '9.4', '0-10')
```

Sayı kümenin sınırını çiziyor: altı sütun ve 3.199 satır, toplam 19.194 değer. Satır tabanlı düzen
bu değerleri kayıt kayıt bitişik tutar; ilk kaydın altı alanı yan yanadır. Sütunlu düzen aynı
değerleri altı ayrı diziye yayar ve her dizinin yedi bloğu için bir özet tutar. Kümenin içeriği
aynı, yerleşimi farklıdır.

#### Aynı Soru, Dört Okuma Yolu

```python
# sorgu.py — tek bir soru dort yolla cevaplanir: "verilen donemde bolge basina
# toplam okuma". Cevap dort yolda da ayni; degisen sey okunan deger ve ayri
# bitisik alana gidis sayisi olan erisimdir.
def satir_yolu(d: SatirDepo, hedef: str) -> dict:
    t = {}
    for s in d.tara():                       # kaydin alti alani da gelir
        if s[2] == hedef:
            t[s[3]] = round(t.get(s[3], 0.0) + float(s[4]), 1)
    return t


def sutun_erken(d: SutunDepo, hedef: str) -> dict:       # uc sutun bastan sona
    donem, bolge, okuma = d.tumu("donem"), d.tumu("bolge"), d.tumu("okuma_m3")
    return topla([(bolge[i], okuma[i]) for i, v in enumerate(donem) if v == hedef])


def sutun_gec(d: SutunDepo, hedef: str) -> dict:         # once suzgec, sonra konum
    yerler = [i for i, v in enumerate(d.tumu("donem")) if v == hedef]
    return topla(list(zip(d.konumlar("bolge", yerler),
                          d.konumlar("okuma_m3", yerler))))


def sutun_blok(d: SutunDepo, hedef: str) -> tuple[dict, int]:
    ozet, yerler, atlanan = d.ozet["donem"], [], 0
    d.okunan, d.erisim = d.okunan + 2 * len(ozet), d.erisim + 1
    for bi, (enk, enb) in enumerate(ozet):
        if not enk <= hedef <= enb:          # blok ozeti hedefi kapsamiyor
            atlanan += 1
            continue
        bas = bi * d.BLOK
        dilim = d.s["donem"][bas:bas + d.BLOK]
        d.okunan, d.erisim = d.okunan + len(dilim), d.erisim + 1
        yerler += [bas + i for i, v in enumerate(dilim) if v == hedef]
    return topla(list(zip(d.konumlar("bolge", yerler),
                          d.konumlar("okuma_m3", yerler)))), atlanan


def topla(ciftler: list) -> dict:
    t = {}
    for b, o in ciftler:
        t[b] = round(t.get(b, 0.0) + float(o), 1)
    return t


def kos(yol: str, kume: list, hedef: str) -> tuple:
    if yol == "satir tabanli tarama":
        d = SatirDepo(kume)
        t = satir_yolu(d, hedef)
    else:
        d = SutunDepo(kume)
        t = (sutun_erken(d, hedef) if "erken" in yol else
             sutun_gec(d, hedef) if "gec" in yol else sutun_blok(d, hedef)[0])
    return d, t


YOLLAR = ("satir tabanli tarama", "sutunlu, erken maddelestirme",
          "sutunlu, gec maddelestirme", "sutunlu, blok atlamali")
G = (30, 14, 9, 9, 11, 12)
y = lambda h: "".join(str(v).rjust(G[j]) if j else str(v).ljust(G[0])
                      for j, v in enumerate(h))
print(y(["okuma yolu", "okunan deger", "oran", "erisim", "merkez m3", "toplam m3"]))
taban = 0
for yol in YOLLAR:
    d, t = kos(yol, k0, "2024-03")
    taban = taban or d.okunan
    print(y([yol, d.okunan, f"{taban / d.okunan:.1f}x", d.erisim, t["merkez"],
             round(sum(t.values()), 1)]))
print(f"blok atlamali yolda 7 blogun {sutun_blok(SutunDepo(k0), '2024-03')[1]} tanesi "
      f"hic acilmadi; 2024-03 doneminde 835 satir var")
```

```
okuma yolu                      okunan deger     oran   erisim  merkez m3   toplam m3
satir tabanli tarama                   19194     1.0x        1     8752.6     17307.2
sutunlu, erken maddelestirme            9597     2.0x        3     8752.6     17307.2
sutunlu, gec maddelestirme              4869     3.9x     1671     8752.6     17307.2
sutunlu, blok atlamali                  2835     6.8x     1674     8752.6     17307.2
blok atlamali yolda 7 blogun 4 tanesi hic acilmadi; 2024-03 doneminde 835 satir var
```

Dört yol da aynı cevabı veriyor: merkez bölgesinde 8.752,6 m³, toplamda 17.307,2 m³. Okunan değer
19.194'ten 2.835'e iniyor — **6,8 kat**. Kazanç tek bir kalemden gelmiyor, üç ayrı kararın
üstüste binmesinden geliyor ve tablonun her satırı bir kararı ekliyor.

Ölçünün okunan değer olması bir kolaylık değil, karşılaştırmayı taşınabilir kılan seçimdir. Aynı
dört yolun süresi çalışılan makineye, o anki yüke ve ara belleğin durumuna göre koşumdan koşuma
değişir; okunan değer sayısı yerleşimden ve sorudan türer ve her koşumda aynı çıkar. Bir yolun
diğerinden kaç kat ucuz olduğu bu birimde savunulabilir bir cümledir.

Birinci karar sütun seçimidir. Soru üç sütun soruyor; satır tabanlı düzen altısını da getiriyor,
çünkü kaydın alanları bitişik. Sütunlu düzen üçünü okuyor ve okunan değer yarıya iniyor. Bu, ürün
değil yerleşim farkıdır: sütunu bitişik tutan her yapı aynı kazancı verir.

İkinci karar sıradır. Erken maddeleştirme üç sütunu da baştan sona okur; geç maddeleştirme önce
süzgeç sütununu okur, eşleşen 835 konumu bulur ve diğer iki sütundan yalnız o konumları alır.
Okunan değer 9.597'den 4.869'a iner. Bedeli erişim sütunundadır: üç erişim yerine 1.671 erişim,
çünkü artık bitişik bir dizi değil dağınık konumlar okunuyor.

Üçüncü karar blok özetidir. Dönem sütunu 512'lik yedi bloğa ayrılmış ve her blok için en küçük ile
en büyük değer saklanmıştı. Hedef dönem bir bloğun aralığına düşmüyorsa o blok hiç açılmaz; yedi
bloğun dördü açılmadı ve okunan değer 4.869'dan 2.835'e indi. Blok özeti okumaya on dört değer
ekledi, karşılığında 2.048 değer okumaktan kurtardı.

#### Kazanç Neye Bağlı

```python
# kumelenme.py — blok atlama verinin o sutuna gore kumelenmis olmasina baglidir.
# Ayni kume abone sirasina gore yazildiginda her blok ucu donemi de tasir,
# hicbir ozet disarida kalmaz ve atlama kazanci sifirlanir.
abone_sirali = sorted(k0, key=lambda x: (x[0], x[2]))
r = uretec(99)                                  # gorunur tohumlu kendi uretecimiz
karisik = [x for _, x in sorted((r(), x) for x in k0)]
G2 = (20, 14, 14, 10, 12)
y2 = lambda h: "".join(str(v).rjust(G2[j]) if j else str(v).ljust(G2[0])
                       for j, v in enumerate(h))
print(y2(["kume duzeni", "atlanan blok", "okunan deger", "erisim", "toplam m3"]))
for ad, kume in [("donem sirali", k0), ("abone sirali", abone_sirali),
                 ("karisik (tohum 99)", karisik)]:
    d = SutunDepo(kume)
    t, atlanan = sutun_blok(d, "2024-03")
    print(y2([ad, f"{atlanan}/{len(d.ozet['donem'])}", d.okunan, d.erisim,
              round(sum(t.values()), 1)]))

# Suzgecin seciciligi hangi yolun ucuz oldugunu belirler. Formuller olculen
# yollarin kendisinden okunur: erken yol uc sutunu bastan sona okur, gec yol
# suzgec sutununu okur ve kalan m konumu iki sutundan tek tek toplar.
n = len(k0)
G3 = (22, 14, 13, 13, 12)
y3 = lambda h: "".join(str(v).rjust(G3[j]) if j else str(v).ljust(G3[0])
                       for j, v in enumerate(h))
print(y3(["eslesen satir", "okunan erken", "okunan gec", "erisim gec", "gec kazanc"]))
for m in (835, 1329, 2691):
    print(y3([f"{m} ({100 * m // n} yuzde)", 3 * n, n + 2 * m, 1 + 2 * m,
              f"{(3 * n) / (n + 2 * m):.2f}x"]))
d = SutunDepo(k0)
sutun_gec(d, "2024-03")
print(f"olculen gec yol 835 satirda {d.okunan} deger ve {d.erisim} erisim; formul "
      f"{n + 2 * 835} ve {1 + 2 * 835}")

# Ters yon: bir kaydin tamamini getirmek.
ORNEK = list(range(0, n, 320))
sd2, cd2 = SatirDepo(k0), SutunDepo(k0)
for i in ORNEK:
    sd2.kayit(i)
    cd2.kayit(i)
print(f"{len(ORNEK)} kaydin tamami: satir tabanli {sd2.okunan} deger / {sd2.erisim} "
      f"erisim, sutunlu {cd2.okunan} deger / {cd2.erisim} erisim")
```

```
kume duzeni           atlanan blok  okunan deger    erisim   toplam m3
donem sirali                   4/7          2835      1674     17307.2
abone sirali                   2/7          4244      1676     17307.2
karisik (tohum 99)             0/7          4883      1678     17307.2
eslesen satir           okunan erken   okunan gec   erisim gec  gec kazanc
835 (26 yuzde)                  9597         4869         1671       1.97x
1329 (41 yuzde)                 9597         5857         2659       1.64x
2691 (84 yuzde)                 9597         8581         5383       1.12x
olculen gec yol 835 satirda 4869 deger ve 1671 erisim; formul 4869 ve 1671
10 kaydin tamami: satir tabanli 60 deger / 10 erisim, sutunlu 60 deger / 60 erisim
```

İlk tablo dersin gizlenen kararıdır. Blok atlama bir yapı özelliği gibi görünür, oysa **kümenin o
sütuna göre kümelenmiş olmasına** bağlıdır. Aynı küme abone sırasında yazıldığında her bloğun
içine üç dönem birden karışır, özetler hedefi kapsar ve yedi bloğun yalnız ikisi atlanır; karışık
sırada hiçbiri atlanmaz. Okunan değer 2.835'ten 4.244'e, oradan 4.883'e çıkar ve 6,8 katlık
kazanç dört kata iner. Üç koşumda da toplam 17.307,2 m³ çıkar; kararın oynattığı sayı cevap değil,
cevabı üretmek için okunan değerdir. Kümeyi hangi sütuna göre sıralayacağı sorusu bir yerleşim
kararıdır ve sorgu yazılırken görünmez.

İkinci tablo geç maddeleştirmenin sınırını çiziyor. Süzgeç 835 satır bıraktığında geç yol erken
yola göre 1,97 kat az değer okur; 2.691 satır bıraktığında kazanç 1,12 kata iner ve dağınık erişim
1.671'den 5.383'e çıkar. Seçicilik düştükçe konum listesi büyür, dağınık okuma bitişik okumaya
yaklaşır ve öne alınan süzgeç ödemesini karşılamaz. Formüller ölçülen yolların kendisinden okunur
ve 835 satırlık satır ölçülen değerle birebir tutuyor: 4.869 değer, 1.671 erişim.

Son satır ters yönü ölçüyor. On kaydın tamamını getirmek iki düzende de 60 değer okur, ama satır
tabanlı düzen bunu 10 erişimle, sütunlu düzen 60 erişimle yapar. Sütunlu yerleşim az sütunu çok
satırda okuyan sorularda kazanır, çok sütunu az satırda okuyan sorularda kaybeder. **Yerleşim bir
kolaylık tercihi değil, hangi sorunun ucuza cevaplanacağının kararıdır.**

#### Özet

- Aynı soru dört yolla cevaplandı ve dördü de merkez için 8.752,6 m³, toplamda 17.307,2 m³ verdi;
  değişen tek şey okunan değer sayısıdır.
- Satır tabanlı tarama 19.194 değer okur; sütunlu düzen gereken üç sütunla 9.597, süzgeci öne alarak
  4.869, blok atlayarak 2.835 değer okur — 6,8 kat.
- Geç maddeleştirme okunan değeri düşürür ama erişimi üçten 1.671'e çıkarır; kazanç seçiciliğe
  bağlıdır ve eşleşen satır 2.691'e çıktığında 1,12 kata iner.
- Blok atlama kümelenmeye bağlıdır: dönem sıralı kümede yedi bloğun dördü, abone sıralı kümede
  ikisi, karışık kümede hiçbiri atlanmaz ve okunan değer 2.835'ten 4.883'e çıkar.
- Tek bir kaydın tamamını getirmek satır tabanlı düzende 10 erişim, sütunlu düzende 60 erişimdir;
  yerleşim kazancı sorunun yönüne göre işaret değiştirir.

#### Sonraki Adım

Bu ders okunan değeri 6,8 kat düşürdü, ama bütün hesap hâlâ tek bir yerde yapıldı. Küme
büyüdükçe o tek yerin sınırı gelir: okunan değer altıda birine inse de 3.199 satır 3,2 milyar
satır olduğunda tek bir makinenin okuyacağı değer yine sığmaz. Sonraki ders işi birden çok
bölüme dağıtır ve dağıtmanın kendi kalemini ölçer — bölümleme, veri taşıma ve eşgüdüm hesaba kaç
birim ekliyor, hangi iş boyutunda bu kalem kazanca dönüşüyor.

### Paralel ve Dağıtık Veri Çerçeveleri

Source: https://academia.sh/tr/kurslar/python-ile-analiz/paralel-ve-dagitik-veri-cerceveleri

Kurgu ölçüm ağının 3.199 satırlık kümesi bölümlere ayrıldığında dağıtmanın kendi kalemi ortaya çıkıyor ve tek makinenin işinden büyük kalıyor: bölge anahtarıyla beş bölümlü bir dağıtım 11.651 birim, abone anahtarıyla dört bölümlü dağıtım 8.921 birim tutarken tek makine 3.199 birim harcar. Kalem üç parçadan oluşur — en büyük bölümün iş birimi, taşınan satır ve bölüm başına eşgüdüm — ve kazanca dönme eşiği bölümleme anahtarına göre değişir: dengeli anahtarla 10.833 satır, çarpık anahtarla 20.119 satır. Hizalanmamış bir sorgu bu eşiği hiç görmez; dönem bölümlemesinde satırların yüzde 66,7 kadarı yerinde kalmaz ve taşıma kalemi tek başına tek makine işinin 6,7 katına çıkar.

Önceki ders okunan değeri 6,8 kat düşürdü, ama bütün hesap tek bir yerde yapıldı. O yerin
sınırı kaçınılmazdır: okunan değer altıda birine inse de küme bin kat büyüdüğünde tek makinenin
okuyacağı değer yine sığmaz. Bu ders işi birden çok bölüme dağıtır ve tek soruyu sorar — dağıtmanın
kendisi hesaba ne ekliyor.

Cevap bir kalem listesidir ve listenin adı **geçişin bedelidir**. Kümeyi bölümlere ayırmak,
bölümler arasında satır taşımak ve bölümleri eşgüdümlemek işin kendisinden ayrı birer iştir. Dersin
ölçüsü bu kalemleri tek makinenin işiyle aynı birimde sayar ve iki sayıyı yan yana koyar:
**kazanç**, yani dağıtımın kaç kat ucuza indirdiği ve **gizlenen karar**, yani bölümleme anahtarı
seçilirken sessizce belirlenen denge ile taşıma.

- **AB16.** Küme kurgudur ve önceki derslerin kümesidir: 3.199 satır, 1.329 abone, üç dönem.
  Aynı tohumla üretilir ve çıktı koşumdan koşuma aynıdır. **Modeldir.**
- **AB17.** İş birimi bir satıra bir kez dokunmaktır; n satırlık iş tek makinede n birimdir.
- **AB18.** Bir satırı bölümler arasında taşımak 10 birimdir. Bu oran modelin varsayımıdır;
  değiştiğinde bütün eşikler kayar ve kayma yönü tabloda okunur. **Modeldir.**
- **AB19.** Bir bölümün kurulması ve sonucunun toplanması 2.000 birim eşgüdüm gerektirir; p bölüm
  p çarpı 2.000 birim ekler.
- **AB20.** Bölümler eşzamanlı çalışır; dağıtık işin kritik yolu **en büyük bölümün** iş birimidir.
  Toplam maliyet kritik yol ile taşıma ve eşgüdüm kalemlerinin toplamıdır.

#### Aynı Kümenin Üç Bölümlemesi

```python
# bolum.py — ayni kurgu kume uc ayri anahtarla bolumlere ayrilir. MODELDIR:
# gercek bir dagitim yerine bolumleme, tasima ve esgudum kalemleri sayilir.
# Olcu birimi bir satira bir kez dokunmaktir.
TOHUM = 1246
BOLGE = ("kuzey", "dogu", "merkez", "guney", "bati")
DONEM = (("2024-01", 1329), ("2024-02", 1035), ("2024-03", 835))
YOGUNLUK = (0, 1, 2, 2, 2, 3, 4, 2, 2, 1)   # merkez agirlikli kurgu olcum agi
TASIMA = 10       # bir satiri baska bolume tasimak on yerel dokunus eder
ESGUDUM = 2000    # bir bolumun kurulmasi ve sonucunun toplanmasi


def uretec(tohum: int):
    s = tohum & 0xFFFFFFFF

    def sonraki() -> float:
        nonlocal s
        s = (1103515245 * s + 12345) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki


def kayitlar() -> list[tuple]:
    r, k = uretec(TOHUM), []
    for donem, adet in DONEM:
        for i in range(adet):
            k.append((10000 + i, f"S-{4000 + i}", donem,
                      BOLGE[YOGUNLUK[i % 10]], round(5 + r() * 32.0, 1)))
    return k


def bolumle(k: list, anahtar) -> dict:
    b = {}
    for x in k:
        b.setdefault(anahtar(x), []).append(x)
    return b


k0 = kayitlar()
ANAHTAR = (("bolge", lambda x: x[3]), ("abone mod 4", lambda x: x[0] % 4),
           ("donem", lambda x: x[2]))
G = (16, 9, 30, 14, 12)
y = lambda h: "".join(str(v).rjust(G[j]) if j else str(v).ljust(G[0])
                      for j, v in enumerate(h))
print(f"kume {len(k0)} satir, {len({x[0] for x in k0})} abone; tasima {TASIMA} "
      f"birim/satir, esgudum {ESGUDUM} birim/bolum")
print(y(["bolumleme anahtari", "bolum", "bolum boyutlari", "en buyuk", "carpiklik"]))
for ad, f in ANAHTAR:
    b = bolumle(k0, f)
    boy = sorted((len(v) for v in b.values()), reverse=True)
    print(y([ad, len(b), str(boy), boy[0], f"{boy[0] / (len(k0) / len(b)):.2f}x"]))
```

```
kume 3199 satir, 1329 abone; tasima 10 birim/satir, esgudum 2000 birim/bolum
bolumleme anahtari    bolum               bolum boyutlari      en buyuk   carpiklik
bolge                   5    [1601, 639, 321, 319, 319]          1601       2.50x
abone mod 4             4          [801, 800, 800, 798]           801       1.00x
donem                   3             [1329, 1035, 835]          1329       1.25x
```
Üç anahtar da savunulabilir ve üçü farklı bir yerleşim veriyor. Abone numarasının kalanına göre
bölümleme dengelidir: dört bölüm 798 ile 801 satır arasında, çarpıklık 1,00. Bölgeye göre
bölümleme çarpıktır: merkez bölümü 1.601 satır taşır, ortalamanın 2,50 katı. Dönem anahtarı
arada durur.

Çarpıklık doğrudan kritik yola yazılır. Bölümler eşzamanlı çalıştığı için işin bitmesi en büyük
bölümün bitmesine bağlıdır; beş bölüme ayrılmış bir küme, bölümlerden biri kümenin yarısını
taşıyorsa beşte bire değil yarıya iner. Bölümleme anahtarı bir düzenleme ayrıntısı gibi görünür,
oysa dağıtımdan beklenen kazancın üst sınırını belirler.

#### Geçişin Kalemleri

```python
# gecis.py — iki sorgu uc bolumleme uzerinde calistirilir ve gecisin kalemleri
# ayri ayri sayilir: kritik yol (en buyuk bolumun is birimi), tasinan satir ve
# esgudum. Toplam = kritik yol + tasinan * TASIMA + bolum * ESGUDUM.
def indeksle(b: dict) -> dict:
    return {ad: i for i, ad in enumerate(sorted(b, key=str))}


def hizali_mi(b: dict, kimlik) -> bool:
    yer = {}
    for ad, v in b.items():
        for x in v:
            yer.setdefault(kimlik(x), set()).add(ad)
    return all(len(s) == 1 for s in yer.values())


def yeniden_dagit(b: dict, kimlik) -> tuple[dict, int]:
    """Kume kimlik anahtarina gore yeniden bolumlenir, bolum sayisi korunur.
    Hedef bolumu kaynak bolumunden farkli olan her satir tasinir."""
    ix, p = indeksle(b), len(b)
    yeni, tasinan = {i: [] for i in range(p)}, 0
    for ad, v in b.items():
        for x in v:
            h = kimlik(x) % p
            yeni[h].append(x)
            tasinan += h != ix[ad]
    return yeni, tasinan


maliyet = lambda p, kritik, tasinan: kritik + tasinan * TASIMA + p * ESGUDUM
G2 = (16, 9, 12, 12, 12, 12, 9)
y2 = lambda h: "".join(str(v).rjust(G2[j]) if j else str(v).ljust(G2[0])
                       for j, v in enumerate(h))

print("A sorgusu: bolge basina toplam okuma (kismi toplulastirilabilir)")
print(y2(["bolumleme", "bolum", "tasinan", "kritik yol", "esgudum", "dagitik", "oran"]))
for ad, f in ANAHTAR:
    b = bolumle(k0, f)
    tasinan = sum(len({x[3] for x in v}) for v in b.values())   # kismi sonuclar
    kritik = max(len(v) for v in b.values())
    m = maliyet(len(b), kritik, tasinan)
    print(y2([ad, len(b), tasinan, kritik, len(b) * ESGUDUM, m,
              f"{len(k0) / m:.2f}x"]))

print("B sorgusu: donemler arasi artisi 5 m3'u asan abone sayisi (abone butunlugu ister)")
print(y2(["bolumleme", "bolum", "tasinan", "kritik yol", "esgudum", "dagitik", "oran"]))
for ad, f in ANAHTAR:
    b = bolumle(k0, f)
    if hizali_mi(b, lambda x: x[0]):
        tasinan, kritik = len(b), max(len(v) for v in b.values())
    else:
        yeni, tasinan = yeniden_dagit(b, lambda x: x[0])
        tasinan, kritik = tasinan + len(b), max(len(v) for v in yeni.values())
    m = maliyet(len(b), kritik, tasinan)
    print(y2([ad, len(b), tasinan, kritik, len(b) * ESGUDUM, m,
              f"{len(k0) / m:.2f}x"]))
```

```
A sorgusu: bolge basina toplam okuma (kismi toplulastirilabilir)
bolumleme           bolum     tasinan  kritik yol     esgudum     dagitik     oran
bolge                   5           5        1601       10000       11651    0.27x
abone mod 4             4          12         801        8000        8921    0.36x
donem                   3          15        1329        6000        7479    0.43x
B sorgusu: donemler arasi artisi 5 m3'u asan abone sayisi (abone butunlugu ister)
bolumleme           bolum     tasinan  kritik yol     esgudum     dagitik     oran
bolge                   5           5        1601       10000       11651    0.27x
abone mod 4             4           4         801        8000        8841    0.36x
donem                   3        2136        1067        6000       28427    0.11x
```
İki tablo da aynı kümede, aynı bölüm sayısıyla çalışıyor ve ikisinde de dağıtık maliyet tek
makinenin 3.199 biriminin üstünde kalıyor. Bu ölçekte dağıtmak kazandırmaz; tablonun işi kaybın
nereden geldiğini göstermektir.

A sorgusu kısmi toplulaştırılabilir: her bölüm kendi bölge toplamlarını hesaplar ve yalnız kısmi
sonucu gönderir. Taşınan satır 5 ile 15 arasındadır ve **küme boyundan bağımsızdır** — bölüm sayısı
ile grup sayısının çarpımıdır. Maliyetin neredeyse tamamı eşgüdümdür: bölge anahtarında 11.651
birimin 10.000'i, abone anahtarında 8.921 birimin 8.000'i.

B sorgusu bir abonenin bütün satırlarının aynı bölümde olmasını ister ve burada bölümleme anahtarı
sonucu belirler. Bölge ve abone anahtarları hizalıdır — her abone tek bir bölgededir — ve taşınan
satır bölüm sayısı kadar kalır. Dönem anahtarı hizalı değildir: aynı abonenin üç dönemi üç ayrı
bölümdedir, küme yeniden dağıtılır ve 2.136 satır taşınır. Maliyet 7.479'dan 28.427 birime,
dört kata çıkar. **Gizlenen karar budur:** bölümleme anahtarı sorgudan önce seçilir ve sorgunun
taşıma kalemini o seçim belirler.

#### Kazanca Dönme Eşiği

```python
# olcek.py — ayni model buyuyen kume boyunda cozulur. Bolum oranlari olculen
# kumeden alinir; kismi toplulastirilabilir sorguda tasinan satir n'den
# bagimsizdir, hizalanmamis sorguda n ile birlikte buyur.
import math
ORAN = {ad: max(len(v) for v in bolumle(k0, f).values()) / len(k0)
        for ad, f in ANAHTAR}
_b = bolumle(k0, dict(ANAHTAR)["donem"])
_yeni, _tasinan = yeniden_dagit(_b, lambda x: x[0])
PAY = _tasinan / len(k0)            # donem bolumlemesinde yerinde kalmayan pay
B_ORAN = max(len(v) for v in _yeni.values()) / len(k0)

SENARYO = (("A / abone mod 4", 4, ORAN["abone mod 4"], 12, 0.0),
           ("A / bolge", 5, ORAN["bolge"], 5, 0.0),
           ("B / donem", 3, B_ORAN, 3, PAY))
G3 = (18, 14, 15, 15, 15, 13)
y3 = lambda h: "".join(str(v).rjust(G3[j]) if j else str(v).ljust(G3[0])
                       for j, v in enumerate(h))


def dagitik(n, p, oran, sabit, pay):
    tasinan = sabit + pay * n
    return round(oran * n + tasinan * TASIMA + p * ESGUDUM)


print(y3(["kume n", "tek makine", "A/abone mod 4", "A/bolge", "B/donem",
          "en iyi oran"]))
for n in (3199, 31990, 319900, 3199000):
    satir = [dagitik(n, p, o, s, pay) for _, p, o, s, pay in SENARYO]
    print(y3([n, n, satir[0], satir[1], satir[2], f"{n / min(satir):.2f}x"]))
print(y3(["esik n", "-", *[
    (math.ceil((s * TASIMA + p * ESGUDUM) / (1 - o)) if pay * TASIMA + o < 1
     else "hic") for _, p, o, s, pay in SENARYO], "-"]))
print(f"donem bolumlemesinde satirlarin yuzde {100 * PAY:.1f} kadari yerinde "
      f"kalmaz; "
      f"tasima kalemi tek basina tek makine isinin {PAY * TASIMA:.1f} katidir")
```

```
kume n                tek makine  A/abone mod 4        A/bolge        B/donem  en iyi oran
3199                        3199           8921          11651          28427        0.36x
31990                      31990          16130          26060         230000        1.98x
319900                    319900          88220         170150        2245730        3.63x
3199000                  3199000         809120        1611050       22403030        3.95x
esik n                         -          10833          20119            hic            -
donem bolumlemesinde satirlarin yuzde 66.7 kadari yerinde kalmaz; tasima kalemi tek basina tek makine isinin 6.7 katidir
```
Tablo dağıtımın nerede kazanca döndüğünü gösteriyor. Eşgüdüm küme boyundan bağımsız sabit bir
kalemdir; küme büyüdükçe payı erir ve kritik yol baskın hale gelir. Dengeli anahtarda eşik 10.833
satırdır: bu boyun altında dağıtmak pahalıdır, üstünde ucuzlar. Çarpık anahtarda eşik 20.119
satıra çıkar — aynı model, aynı sabitler, yalnız bölümleme anahtarı farklı. Çarpıklık eşiği
neredeyse ikiye katlar.

Üçüncü sütun eşiği hiç görmez. Hizalanmamış sorguda taşınan satır küme boyuyla birlikte büyür:
satırların yüzde 66,7 kadarı yerinde kalmaz ve taşıma kalemi tek başına tek makine işinin 6,7
katına çıkar. n ne kadar büyürse büyüsün oran sabit kalır ve dağıtık maliyet tek makinenin
üstünde durur. Ölçek büyütmek bu sorguyu kurtarmaz; kurtaran tek şey kümeyi doğru anahtara göre
bölümlemektir.

Kazanç tarafı da sınırlıdır. 3.199.000 satırda en iyi seçenek 809.120 birim tutar ve tek makineye
göre 3,95 kat kazandırır — dört bölüme ayrılmış bir işten beklenen dört katın az altı. Fark
tamamen taşıma ve eşgüdüm kalemlerinden gelir ve bu kalemler hiçbir küme boyunda sıfırlanmaz.
**Bölüm sayısını artırmak kazancı bölüm sayısı kadar artırmaz; eşgüdüm kalemi bölüm sayısıyla
birlikte büyür.**

#### Bölüm Sayısının Sınırı

```python
# bolum_sayisi.py — ayni is farkli bolum sayilariyla dagitilir. Kritik yol
# bolum sayisiyla kuculur, esgudum bolum sayisiyla buyur; en iyi bolum sayisi
# ikisinin dengelendigi yerdedir. Dengeli anahtar varsayilir.
N = 3199000
G4 = (10, 14, 12, 12, 14, 10)
y4 = lambda h: "".join(str(v).rjust(G4[j]) if j else str(v).ljust(G4[0])
                       for j, v in enumerate(h))
hesap = lambda p: math.ceil(N / p) + (p * 5) * TASIMA + p * ESGUDUM
print(y4(["bolum", "kritik yol", "tasinan", "esgudum", "dagitik", "oran"]))
for p in (2, 4, 16, 40, 64, 256):
    print(y4([p, math.ceil(N / p), p * 5, p * ESGUDUM, hesap(p),
              f"{N / hesap(p):.1f}x"]))
en_iyi = min(range(1, 1001), key=hesap)
print(f"n = {N} icin en iyi bolum sayisi {en_iyi}; maliyet {hesap(en_iyi)} birim, "
      f"kazanc {N / hesap(en_iyi):.1f} kat")
print(f"bolum {en_iyi} yerine {4 * en_iyi} secilirse maliyet {hesap(4 * en_iyi)} "
      f"birime cikar ve kazanc {N / hesap(4 * en_iyi):.1f} kata iner")
```

```
bolum         kritik yol     tasinan     esgudum       dagitik      oran
2                1599500          10        4000       1603600      2.0x
4                 799750          20        8000        807950      4.0x
16                199938          80       32000        232738     13.7x
40                 79975         200       80000        161975     19.7x
64                 49985         320      128000        181185     17.7x
256                12497        1280      512000        537297      6.0x
n = 3199000 icin en iyi bolum sayisi 40; maliyet 161975 birim, kazanc 19.7 kat
bolum 40 yerine 160 secilirse maliyet 347994 birime cikar ve kazanc 9.2 kata iner
```

Bölüm sayısı iki kalemi ters yönde oynatır. Kritik yol bölüm sayısına bölünür, eşgüdüm ise bölüm
sayısıyla çarpılır; ilki azalırken ikincisi artar ve toplam bir yerde en küçük değerini alır.
3.199.000 satırlık küme için o yer 40 bölümdür: maliyet 161.975 birim, kazanç 19,7 kat. Dört
bölümde kazanç 4,0 kat, 256 bölümde 6,0 kattır — bölüm sayısını artırmak bir yerden sonra kazancı
azaltır.

Sayının kendisi modelin sabitlerine bağlıdır ve sabitler AB18 ile AB19'da yazılıdır; taşıma bedeli
ya da eşgüdüm değişirse en iyi bölüm sayısı da değişir. Değişmeyen şey biçimdir: dağıtımın kazancı
bölüm sayısıyla doğru orantılı değildir ve bir tepe noktası vardır. En iyi bölüm sayısının dört
katı seçildiğinde maliyet 347.994 birime çıkar ve kazanç 9,2 kata iner: dört kat fazla bölüm, iki
kat fazla maliyet demektir. Bölüm sayısı da bir varsayılan değil, yazılması gereken bir karardır.

#### Özet

- Dağıtık bir hesabın maliyeti üç kalemdir: en büyük bölümün iş birimi, taşınan satır çarpı taşıma
  bedeli ve bölüm başına eşgüdüm. Bölümleme anahtarı bölüm dengesini belirler: abone kalanı 1,00
  çarpıklık ve 801 satırlık kritik yol, bölge anahtarı 2,50 çarpıklık ve 1.601 satırlık kritik yol
  verir.
- 3.199 satırda dağıtmak kazandırmaz; en iyi seçenek 8.921 birim tutarken tek makine 3.199 birim
  harcar.
- Kısmi toplulaştırılabilen sorguda taşınan satır küme boyundan bağımsızdır (5–15 satır); bütünlük
  isteyen sorguda hizasız anahtar 2.136 satır taşıtır ve maliyeti dört kata çıkarır.
- Kazanca dönme eşiği dengeli anahtarda 10.833, çarpık anahtarda 20.119 satırdır; hizalanmamış
  sorguda böyle bir eşik yoktur.
- 3.199.000 satırda dört bölümlü dağıtım 3,95 kat kazandırır; aynı kümede en iyi bölüm sayısı 40'tır
  ve kazanç 19,7 katta doyar, 256 bölümde 6,0 kata geriler.

#### Sonraki Adım

Bu ders işi birden çok bölüme taşıdı, ama bütün kalemler hâlâ verinin çözümleme ortamına
**getirilmesi** üzerine kuruluydu: satırlar okunuyor, taşınıyor, bölünüyor. Verinin durduğu yer de
hesap yapabiliyorsa sıra tersine çevrilebilir — süzgeç ve toplulaştırma kaynağa itilir ve
çözümleme ortamına yalnız sonuç gelir. Sonraki ders bunu tek bir sayıyla ölçer: aynı soru için
kaynaktan kaç satır çıkıyor ve toplulaştırma kaynakta yapıldığında hangi karar artık senin
elinden çıkmış oluyor.

### Sorguyu Veriye Götürmek

Source: https://academia.sh/tr/kurslar/python-ile-analiz/sorguyu-veriye-goturmek

Kurgu ölçüm ağının kaynağında 3.199 satır duruyor ve tek bir soru dört strateji ile cevaplanıyor; dördü de aynı sayıyı veriyor ama çözümleme ortamına taşınan kayıt sayısı 3.199'dan 5'e iniyor. Süzgeç kaynağa itildiğinde 835 satır, sütun seçimi de itildiğinde 1.670 değer, toplulaştırma da itildiğinde 10 değer taşınır ve kazanç 1.919 kata çıkar. Kazancın bedeli görünürlüktür: itilen toplam süzgeçten geçen 835 satırı, kaynaktaki 3.199 satırı ve grup başına satır sayısını getirmez, cevabın yanına kararı yazabilmek için taşınan değer 10'dan 27'ye çıkar ve oran 710 kata iner. Kazanç grup sayısına da bağlıdır: 3 gruplu kırılımda 3.199 kat, 15 gruplu kırılımda 426 kat, 835 gruplu kırılımda 11 kattır ve grup sayısı süzülen satır sayısına eşitlendiğinde sıfırlanır.

Önceki ders işi birden çok bölüme taşıdı ve dağıtmanın kendi kalemini saydı. Ama bütün o hesap tek
bir yönde kuruluydu: veri çözümleme ortamına **getiriliyordu**. Satırlar okunuyor, bölünüyor,
taşınıyor ve sonunda cevap ortaya çıkıyordu. Verinin durduğu yer de hesap yapabiliyorsa bu yön
tersine çevrilebilir.

Bu dersin ölçüsü tek bir sayıdır: **taşınan satır**. Aynı soru için kaynaktan kaç kayıt çıkıyor ve
süzgeç ile toplulaştırma kaynağa itildiğinde bu sayı kaça iniyor. Yanında duran ikinci sayı, o
kazancın karşılığında görünmez olan kalemdir — itilen bir hesap cevabı getirir, cevabı üreten
satırları getirmez.

- **AB21.** Kaynak kurgudur ve önceki derslerin kümesini tutar: 3.199 satır, altı sütun, 1.329
  abone, üç dönem. Aynı tohumla üretilir ve çıktı koşumdan koşuma aynıdır. **Modeldir.**
- **AB22.** Kaynak, verinin durduğu yerde hesap yapabilen bir arayüz olarak modellenir: süzgeç,
  sütun seçimi ve toplulaştırma orada çalıştırılabilir. **Modeldir.**
- **AB23.** Ölçülen şey kaynaktan çözümleme ortamına geçen satır ve değer sayısıdır; kaynağın
  içeride kaç satır taradığı bu ölçünün dışındadır.
- **AB24.** İki toplamın aynı sayılması için eşik 0,05 m³'tür, çünkü okumalar bir ondalıkla
  raporlanır ve bundan küçük bir fark yalnızca yuvarlamadan gelebilir.
- **AB25.** Soru sabittir: 2024-03 döneminde bölge başına toplam okuma. Bu dönemde 835 satır
  vardır ve doğru cevap 17.307,2 m³'tür.

#### Kaynak ve Soru

```python
# kaynak.py — verinin durdugu yer bir arayuz olarak modellenir. MODELDIR:
# gercek bir uzak depo yerine, istenen isi yapip donen her satiri ve degeri
# sayan bir kaynak yazildi. Olcu, cozumleme ortamina tasinan satir ve degerdir.
TOHUM = 1246
BOLGE = ("kuzey", "dogu", "merkez", "guney", "bati")
DONEM = (("2024-01", 1329), ("2024-02", 1035), ("2024-03", 835))
YOGUNLUK = (0, 1, 2, 2, 2, 3, 4, 2, 2, 1)   # merkez agirlikli kurgu olcum agi
BASLIK = ("abone_no", "sayac_no", "donem", "bolge", "okuma_m3", "basamak")


def uretec(tohum: int):
    s = tohum & 0xFFFFFFFF

    def sonraki() -> float:
        nonlocal s
        s = (1103515245 * s + 12345) & 0xFFFFFFFF
        return s / 4294967296
    return sonraki


def basamak(o: float) -> str:
    return "0-10" if o <= 10 else "10-25" if o <= 25 else "25-40"


def kayitlar() -> list[tuple]:
    r, k = uretec(TOHUM), []
    for donem, adet in DONEM:
        for i in range(adet):
            o = round(5 + r() * 32.0, 1)
            k.append((10000 + i, f"S-{4000 + i}", donem,
                      BOLGE[YOGUNLUK[i % 10]], o, basamak(o)))
    return k


class Kaynak:
    """Ne istenirse onu doner ve donen her satiri ve degeri sayar. Suzgec,
    sutun secimi ve toplulastirma kaynakta yapilabilir."""

    def __init__(self, k: list):
        self.k, self.satir, self.deger = k, 0, 0

    def _ver(self, satirlar: list, alan: int) -> list:
        self.satir += len(satirlar)
        self.deger += len(satirlar) * alan
        return satirlar

    def hepsi(self) -> list:
        return self._ver(list(self.k), len(BASLIK))

    def suz(self, kosul, alanlar=None) -> list:
        s = [x for x in self.k if kosul(x)]
        if alanlar is None:
            return self._ver(s, len(BASLIK))
        ix = [BASLIK.index(a) for a in alanlar]
        return self._ver([tuple(x[i] for i in ix) for x in s], len(alanlar))

    def say(self, kosul) -> int:
        return self._ver([sum(1 for x in self.k if kosul(x))], 1)[0]

    def topla_genis(self, kosul, grup: str, olcu: str) -> list:
        """Toplamin yanina sayi, en kucuk ve en buyugu de koyar: bes deger."""
        gi, oi = BASLIK.index(grup), BASLIK.index(olcu)
        t = {}
        for x in self.k:
            if kosul(x):
                a = t.setdefault(x[gi], [0.0, 0, x[oi], x[oi]])
                a[0], a[1] = a[0] + x[oi], a[1] + 1
                a[2], a[3] = min(a[2], x[oi]), max(a[3], x[oi])
        return self._ver([(g, *v) for g, v in sorted(t.items())], 5)

    def topla(self, kosul, grup: str, olcu: str) -> list:
        gi, oi = BASLIK.index(grup), BASLIK.index(olcu)
        t = {}
        for x in self.k:
            if kosul(x):
                t[x[gi]] = t.get(x[gi], 0.0) + x[oi]
        return self._ver(sorted(t.items()), 2)


k0 = kayitlar()
HEDEF = "2024-03"
print(f"tohum {TOHUM}; kaynakta {len(k0)} satir, {len(BASLIK)} sutun, "
      f"{len(k0) * len(BASLIK)} deger")
print(f"soru: {HEDEF} doneminde bolge basina toplam okuma; kaynakta bu donemin "
      f"{sum(a for d, a in DONEM if d == HEDEF)} satiri var")
```

```
tohum 1246; kaynakta 3199 satir, 6 sutun, 19194 deger
soru: 2024-03 doneminde bolge basina toplam okuma; kaynakta bu donemin 835 satiri var
```

Kaynak üç iş yapabiliyor: satır süzmek, sütun seçmek ve grup başına toplam almak. Bunların hepsi
çözümleme ortamında da yapılabilir; fark yalnızca **nerede** yapıldığındadır. Arayüz her dönen
satırı ve her dönen değeri sayıyor, çünkü ölçülecek şey hesabın kendisi değil, hesabın sonucunun
kaç kayıtla ifade edildiğidir. Kaynağın içeride kaç satır taradığı bu sayının dışındadır ve AB23
bunu ayrı tutar.

#### Dört İtme Derecesi

```python
# itme.py — ayni soru dort strateji ile cevaplanir. Fark, isin nerede
# yapildigidir: hepsi istemcide, suzgec kaynakta, sutun daraltma kaynakta,
# toplulastirma da kaynakta.
ESIK = 0.05   # okumalar bir ondalikla raporlanir, altindaki fark yuvarlamadandir


def istemcide_topla(satirlar, gi: int, oi: int) -> dict:
    t = {}
    for x in satirlar:
        t[x[gi]] = t.get(x[gi], 0.0) + x[oi]
    return t


def s1(kn: Kaynak) -> dict:                       # hicbir sey itilmez
    return istemcide_topla([x for x in kn.hepsi() if x[2] == HEDEF], 3, 4)


def s2(kn: Kaynak) -> dict:                       # suzgec itilir
    return istemcide_topla(kn.suz(lambda x: x[2] == HEDEF), 3, 4)


def s3(kn: Kaynak) -> dict:                       # suzgec ve sutun secimi itilir
    return istemcide_topla(
        kn.suz(lambda x: x[2] == HEDEF, ["bolge", "okuma_m3"]), 0, 1)


def s4(kn: Kaynak) -> dict:                       # toplulastirma da itilir
    return dict(kn.topla(lambda x: x[2] == HEDEF, "bolge", "okuma_m3"))


G = (34, 14, 14, 10, 12, 12)
y = lambda h: "".join(str(v).rjust(G[j]) if j else str(v).ljust(G[0])
                      for j, v in enumerate(h))
print(y(["strateji", "tasinan satir", "tasinan deger", "oran", "merkez m3", "toplam m3"]))
taban = 0
for ad, f in [("hicbir sey itilmez", s1), ("suzgec itilir", s2),
              ("suzgec ve sutun secimi itilir", s3),
              ("toplulastirma da itilir", s4)]:
    kn = Kaynak(k0)
    t = f(kn)
    taban = taban or kn.deger
    print(y([ad, kn.satir, kn.deger, f"{taban / kn.deger:.0f}x",
             round(t["merkez"], 1), round(sum(t.values()), 1)]))
kn1, kn4 = Kaynak(k0), Kaynak(k0)
t1, t4 = s1(kn1), s4(kn4)
fark = max(abs(t1[g] - t4[g]) for g in t1)
print(f"itilmemis ve itilmis hesabin bolge toplamlari arasindaki en buyuk fark "
      f"{fark:.1e} m3; esik {ESIK} m3 oldugu icin ayni sayi sayilir. Kayan noktali "
      f"toplama sirasi degisebildigi icin esitlik == ile degil esikle sinanir")
```

```
strateji                           tasinan satir tasinan deger      oran   merkez m3   toplam m3
hicbir sey itilmez                          3199         19194        1x      8752.6     17307.2
suzgec itilir                                835          5010        4x      8752.6     17307.2
suzgec ve sutun secimi itilir                835          1670       11x      8752.6     17307.2
toplulastirma da itilir                        5            10     1919x      8752.6     17307.2
itilmemis ve itilmis hesabin bolge toplamlari arasindaki en buyuk fark 0.0e+00 m3; esik 0.05 m3 oldugu icin ayni sayi sayilir. Kayan noktali toplama sirasi degisebildigi icin esitlik == ile degil esikle sinanir
```

Dört strateji de aynı cevabı veriyor: merkez bölgesinde 8.752,6 m³, toplamda 17.307,2 m³. Taşınan
satır 3.199'dan 5'e, taşınan değer 19.194'ten 10'a iniyor — **1.919 kat**. Kazanç üç ayrı itme
kararının toplamıdır ve her satır bir kararı ekliyor.

Süzgecin itilmesi taşınan satırı 3.199'dan 835'e indiriyor; oran doğrudan seçicilikten geliyor.
Sütun seçiminin itilmesi satır sayısını değiştirmiyor ama değer sayısını 5.010'dan 1.670'e
düşürüyor, çünkü altı alan yerine iki alan geliyor. Toplulaştırmanın itilmesi ise satır kavramını
değiştiriyor: artık taşınan şey kayıt değil, sonuçtur — beş grup, beş satır.

Son satır iki toplamı karşılaştırıyor. İtilmemiş ve itilmiş hesabın bölge toplamları arasındaki en
büyük fark sıfırdır, ama karşılaştırma yine de eşikle yapılır. Sebep koşumun sonucu değil, kayan
noktalı toplamanın birleşme özelliği taşımamasıdır: aynı sayılar başka bir sırada toplandığında son
basamak oynayabilir. Kaynağın grup içini hangi sırada topladığı arayüzün dışındadır ve yazılı
değildir; bu yüzden eşitlik doğrudan sınanmaz, eşik yazılır.

#### Kazancın Karşılığı

```python
# denetim.py — itilen hesap sonucu getirir, sonucu ureten karari getirmez.
# Once cevabin yanina karar yazilabilmesi icin kac deger daha gerektigi,
# sonra itmenin kazancinin grup sayisiyla nasil eridigi sayilir.
TABAN = len(k0) * len(BASLIK)
kn = Kaynak(k0)
genis = kn.topla_genis(lambda x: x[2] == HEDEF, "bolge", "okuma_m3")
suzulen, tumu = kn.say(lambda x: x[2] == HEDEF), kn.say(lambda x: True)
G2 = (36, 15, 15, 10)
y2 = lambda h: "".join(str(v).rjust(G2[j]) if j else str(v).ljust(G2[0])
                       for j, v in enumerate(h))
print(y2(["cevabin yanina yazilan", "tasinan satir", "tasinan deger", "oran"]))
print(y2(["yalniz toplam", 5, 10, f"{TABAN // 10}x"]))
print(y2(["toplam, sayi, en kucuk, en buyuk", len(genis), 5 * len(genis),
          f"{TABAN // (5 * len(genis))}x"]))
print(y2(["ustune suzulen ve toplam satir", kn.satir, kn.deger,
          f"{TABAN // kn.deger}x"]))
for g, toplam, sayi, enk, enb in genis:
    if g in ("merkez", "bati"):
        print(f"{g:8s} toplam {toplam:8.1f} m3, {sayi:4d} satir, ortalama "
              f"{toplam / sayi:5.2f} m3, aralik {enk}-{enb} m3")
print(f"suzgecten gecen satir {suzulen}, kaynaktaki toplam satir {tumu}; itilen "
      f"toplam bu iki sayiyi getirmez ve grup basina satir sayisini da getirmez")

# Itmenin kazanci grup sayisina baglidir: sonuc satiri grup sayisi kadardir.
print(y2(["gruplama anahtari", "grup sayisi", "tasinan deger", "oran"]))
for ad, alan in [("basamak", "basamak"), ("bolge", "bolge"),
                 ("bolge + basamak", None), ("abone_no", "abone_no")]:
    kn2 = Kaynak(k0)
    if alan is None:
        t = {}
        for x in k0:
            if x[2] == HEDEF:
                t[(x[3], x[5])] = t.get((x[3], x[5]), 0.0) + x[4]
        kn2._ver(sorted(t.items()), 3)
    else:
        kn2.topla(lambda x: x[2] == HEDEF, alan, "okuma_m3")
    print(y2([ad, kn2.satir, kn2.deger, f"{TABAN // kn2.deger}x"]))
ham = Kaynak(k0)
ham.suz(lambda x: x[2] == HEDEF, ["bolge", "okuma_m3"])
print(f"suzulmus ve daraltilmis ham dilim {ham.deger} deger tasir; abone anahtarli "
      f"itilen toplulastirma {2 * suzulen} deger tasir")
print(f"grup sayisi suzulen satir sayisina esitlendiginde itmenin kazanci sifirlanir: "
      f"{suzulen} grup, {suzulen} satir")
```

```
cevabin yanina yazilan                tasinan satir  tasinan deger      oran
yalniz toplam                                     5             10     1919x
toplam, sayi, en kucuk, en buyuk                  5             25      767x
ustune suzulen ve toplam satir                    7             27      710x
bati     toplam   1789.1 m3,   83 satir, ortalama 21.56 m3, aralik 5.0-37.0 m3
merkez   toplam   8752.6 m3,  418 satir, ortalama 20.94 m3, aralik 5.0-37.0 m3
suzgecten gecen satir 835, kaynaktaki toplam satir 3199; itilen toplam bu iki sayiyi getirmez ve grup basina satir sayisini da getirmez
gruplama anahtari                       grup sayisi  tasinan deger      oran
basamak                                           3              6     3199x
bolge                                             5             10     1919x
bolge + basamak                                  15             45      426x
abone_no                                        835           1670       11x
suzulmus ve daraltilmis ham dilim 1670 deger tasir; abone anahtarli itilen toplulastirma 1670 deger tasir
grup sayisi suzulen satir sayisina esitlendiginde itmenin kazanci sifirlanir: 835 grup, 835 satir
```

İlk tablo dersin gizlenen kararıdır. İtilen toplulaştırma on değer getirir ve o on değerin yanında
hiçbir karar yazılı değildir. Merkez bölgesinin 8.752,6 m³'ünün 418 satırdan mı yoksa 4 satırdan mı
geldiği sonucun içinde yoktur; süzgeçten 835 satır geçtiği de, kaynakta 3.199 satır olduğu da
yoktur. Bu kalemleri de istemek taşınan değeri 10'dan 27'ye çıkarır ve kazancı 1.919 kattan 710
kata indirir. **Kursun kuralı burada ödenir:** kararı yazılmayan sayı ölçülmemiş sayılır ve o
kararı yazmanın bu kaynakta bedeli 17 değerdir.

Sayıların kendisi de karar taşıyor. Merkez bölgesi 418 satırda ortalama 20,94 m³, batı bölgesi 83
satırda ortalama 21,56 m³ veriyor. Toplamlar 8.752,6 ile 1.789,1 m³ ve aralarında beş kat fark var;
ortalamalar birbirine yakın. Yalnız toplamı gören bir rapor merkez bölgesini "yüksek tüketimli"
diye okur, oysa fark tüketimden değil satır sayısından geliyor. İtilen hesap satır sayısını
getirmediği sürece bu ayrım raporda yapılamaz.

İkinci tablo kazancın nereye bağlı olduğunu gösteriyor. İtilen toplulaştırmanın taşıdığı satır
**grup sayısı kadardır**; grup sayısı arttıkça kazanç erir. Üç basamaklı kırılımda oran 3.199 kat,
beş bölgeli kırılımda 1.919 kat, bölge ile basamağın çaprazında 426 kat, abone kırılımında 11
kattır. Son satır tam eşitliği veriyor: abone anahtarıyla toplulaştırma 1.670 değer taşır ve
süzülmüş, iki sütuna daraltılmış ham dilim de 1.670 değer taşır. **Grup sayısı süzülen satır
sayısına eşitlendiğinde itmenin kazancı sıfırlanır** — 835 grup, 835 satır. Bu noktadan sonra ham
dilimi çekmek daha iyidir, çünkü aynı bedelle gelen ham satırlarla başka sorular da cevaplanabilir.

#### Özet

- Aynı soru dört itme derecesiyle cevaplandı ve dördü de 17.307,2 m³ verdi; taşınan satır 3.199'dan
  5'e, taşınan değer 19.194'ten 10'a indi ve kazanç 1.919 kat oldu.
- Kalemler ayrışıyor: süzgecin itilmesi satırı 835'e, sütun seçiminin itilmesi değeri 1.670'e,
  toplulaştırmanın itilmesi satırı 5'e indirir.
- İtilen toplam süzgeçten geçen 835 satırı, kaynaktaki 3.199 satırı ve grup başına satır sayısını
  getirmez; bu kalemleri de istemek taşınan değeri 27'ye çıkarır ve oranı 710 kata indirir.
- Merkez bölgesinin 8.752,6 m³'ü 418 satırdan, batının 1.789,1 m³'ü 83 satırdan gelir; ortalamalar
  20,94 ve 21,56 m³'tür ve toplam farkı tüketimden değil satır sayısından doğar.
- İtme kazancı grup sayısına bağlıdır: 3 grupta 3.199 kat, 15 grupta 426 kat, 835 grupta 11 kat;
  grup sayısı süzülen satır sayısına eşitlendiğinde kazanç sıfırlanır.
- İki toplam arasındaki fark sıfır çıksa da eşitlik eşikle sınanır, çünkü kaynağın grup içini hangi
  sırada topladığı arayüzün dışındadır.

#### Sonraki Adım

Bu konu beş derste tek bir soruyu izledi: küme belleğe sığmadığında soyutlamanın rahatlığından ne
kalıyor. Cevap her derste araç değiştirerek geldi. Tip seçimi sütunu 47,9 kat küçülttü, parçalama
bellek tepesini 32 kat düşürdü, sütunlu yerleşim okunan değeri 6,8 kat azalttı, dağıtım büyük
kümede 19,7 kata kadar kazandırdı ve hesabı veriye itmek taşınan kaydı 1.919 kat azalttı. Ölçek
sorunu her seferinde **yerleşimi, bölümlemeyi ya da hesabın yapıldığı yeri** değiştirerek çözüldü.

Değişmeyen tek şey dilin kendisiydi. Yirmi derstir aynı ifade biçimi kullanılıyor: döngüler,
atamalar, ara değişkenler ve adım adım kurulan hesaplar. Sonraki konu bu varsayımı sorgular ve
analizi başka türlü ifade eden ortamlara bakar — aynı analizin kaç adımda anlatıldığını, dönüştürme
zincirinin kaç ara değişken tuttuğunu ve bir grafiğin kaç katmanla kurulduğunu sayar. Ölçü yine
aynıdır: soyutlamanın kazandırdığı ve gizlediği.

## Alternatif Analiz Ortamları

Aynı analizin başka ortamlarda nasıl ifade edildiğinin ve seçimin hangi ölçütte değiştiğinin sayılması: analiz odaklı dillerin aynı işi kaç adımda ifade ettiği, fiil temelli dönüştürme zincirlerinde ara değişken ve adım sayısı, katmanlı görselleştirme yaklaşımında aynı grafiğin kaç katmanla kurulduğu, üç ortamın ekip, ekosistem ve üretime taşıma ölçütlerinde sayıyla karşılaştırılması.

### İstatistiksel Programlama Dilleri

Source: https://academia.sh/tr/kurslar/python-ile-analiz/istatistiksel-programlama-dilleri

Aynı analiz — 3.199 satırlık kurgu kümede bölge başına ortalama tüketim ve azalan sırada ilk üç bölge — üç ifade biçiminde yazılır ve üçü de aynı sayıyı verir: yordamsal ifade 6 deyim, 6 ara değişken ve 2 döngü; bütün-nesne ifadesi 4 deyim, 3 ara değişken ve 0 döngü; bildirimsel ifade 1 deyim, 0 ara değişken ve 0 döngü tutar. Yazılan döngü 2'den 0'a inerken koşan döngü 2'den 3'e çıkar: döngü kaybolmaz, yardımcıya taşınır ve bir tane de eklenir. Tek deyimlik ifade 4 kararı yazar, 5 kararı varsayılana bırakır; beş varsayılan tek tek çevrildiğinde doğu ortalaması 24,674 m³ yerine 25,593, 24,814, 24,120 ve 25,540 m³ olur ve sıralama yönü değişince rapor edilen üç bölge tamamen değişir.

Bir önceki konu ölçek sorununu araç değiştirerek çözdü: sütun okundu, parça parça işlendi, hesap
veriye götürüldü. Her adımda değişen şey aracın yerleşimiydi. Değişmeyen tek şey **dilin kendisi**
oldu; analiz baştan sona aynı biçimde, aynı deyimlerle yazıldı ve bu biçimin bir seçim olduğu hiç
sorulmadı.

Bu konu o soruyu sorar. Aynı analiz başka ortamlarda başka biçimlerde ifade edilir ve aradaki fark
hız değil, **ifadenin uzunluğudur**. Bu dersin ilk sayısı budur: aynı analiz kaç deyimde, kaç ara
değişkenle ve kaç döngüyle yazılıyor. Yanında kursun ikinci sayısı durur: ifade kısaldıkça kaç karar
koddan çıkıp varsayılana geçiyor.

- **AO1.** Küme K03'ten devralınır ve **kurgudur**: 3.199 satırlık uzun biçimli okuma tablosu,
  1.329 abone, 10/25/40 m³ tarife basamakları. Tohum koda yazılıdır, sayılar koşumdan koşuma aynıdır.
- **AO2.** Bu derste **hiçbir dil, kütüphane ya da ortam adı geçmez**. Alternatif ortamlar
  **yaklaşımlarıyla** anılır ve yaklaşım Python'da **modellenir**. Modelin amacı bir aracı taklit
  etmek değil, ifade biçimini ölçülebilir kılmaktır.
- **AO3.** Üç yaklaşım şunlardır. **Yordamsal ifade**de iş birimi satırdır: veri tek tek dolaşılır.
  **Bütün-nesne ifadesi (whole-object expression)** analiz odaklı dillerin yaklaşımıdır ve iş birimi
  sütunun tamamıdır: hiçbir döngü yazılmaz, işlem tüm sütuna uygulanır. **Bildirimsel ifade**de
  sonucun tanımı yazılır, yürütme sırası yazılmaz.
- **AO4.** Ölçü **karşılaştırmalı üstünlük değildir**. Kısa ifade iyi, uzun ifade kötü diye bir
  sıralama kurulmaz. Sayılan tek şey deyim, ara değişken, döngü ve varsayılan sayısıdır.

#### Aynı Analiz, Üç İfade

Analiz tek bir cümlede tanımlanır: bölge başına ortalama tüketim, azalan sırada ilk üç bölge. Küme
üç ifadenin de girdisidir ve tohum sabittir.

```python
# ortam.py — MODELDIR. K03'un 3.199 satirlik kurgu kumesi ayni tohumla yeniden
# uretilir; hicbir arac, kutuphane ya da ortam taklit edilmez.
import ast
import inspect
import math

TOHUM = 20260218
HAM = 1400
DONEM = ["2026-01", "2026-02", "2026-03"]
TARIFE = [10, 25, 40]
M32 = 0xFFFFFFFF


def karistir(x: int) -> int:
    x = ((x ^ (x >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    return (x ^ (x >> 16)) & M32


def uretec(t: int):
    s = [karistir(t)]

    def sonraki() -> float:
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


def ayrik(u: float, w: list[float]) -> int:
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]
_abone, _elenen = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    kayit = {"abone_no": 10001 + i, "bolge": b[0], "taban": b[2],
             "hane_kisi": ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05]),
             "memnuniyet": ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17]) + 1}
    (_elenen if r() < 0.046 else _abone).append(kayit)
_gecerli = {a["abone_no"] for a in _abone}
OKUMA = []
for k in _abone + _elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(
            k["taban"] * math.exp((r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100
            + 0.5) / 100
        if k["abone_no"] not in _gecerli:
            r()
            continue
        OKUMA.append({"abone_no": k["abone_no"], "bolge": k["bolge"], "donem": DONEM[d],
                      "tuketim_m3": t, "memnuniyet": k["memnuniyet"],
                      "basamak": 1 if t < TARIFE[0] else 2 if t < TARIFE[1]
                      else 3 if t < TARIFE[2] else 4})
        r()

print(f"kurgu kume (tohum {TOHUM}): {len(OKUMA)} satir, {len(_gecerli)} abone, "
      f"{sum(o['tuketim_m3'] for o in OKUMA):.1f} m3")
print(f"okunmamis satir (sifir yazilmis): {sum(1 for o in OKUMA if o['tuketim_m3'] == 0)}")
```

```
kurgu kume (tohum 20260218): 3199 satir, 1329 abone, 63060.6 m3
okunmamis satir (sifir yazilmis): 119
```

Üç ifade aynı kümeyi alır. Ölçüm elle değil, kodun kendi ağacından yapılır: her ifadenin kaynağı
ayrıştırılır, gövdesindeki deyimler, atanan adlar ve döngü düğümleri sayılır. Böylece "kaç adım"
sorusunun cevabı bir yorum değil, sayılabilir bir büyüklük olur.

```python
# Bütün-nesne yaklasiminin yardimcilari: islem sutunun tamamina uygulanir.
def sutun(satirlar, ad):
    return [s[ad] for s in satirlar]


def grup_ortalamasi(anahtar, olcu):
    toplam, adet = {}, {}
    for a, v in zip(anahtar, olcu):
        toplam[a] = toplam.get(a, 0.0) + v
        adet[a] = adet.get(a, 0) + 1
    return {a: toplam[a] / adet[a] for a in toplam}


def azalan(esleme):
    return sorted(esleme.items(), key=lambda p: -p[1])


def ilk_n(ciftler, n):
    return ciftler[:n]


# Bildirimsel yaklasimin cozucusu: istek bir tanimdir, yurutme sirasi burada.
def coz(istek):
    a = sutun(istek["kaynak"], istek["grup"])
    o = sutun(istek["kaynak"], istek["olcu"])
    return ilk_n(azalan(grup_ortalamasi(a, o)), istek["ilk"])


YARDIMCI = {"sutun": sutun, "grup_ortalamasi": grup_ortalamasi, "azalan": azalan,
            "ilk_n": ilk_n, "coz": coz}


def yordamsal(satirlar):
    toplam, adet = {}, {}
    for s in satirlar:
        b = s["bolge"]
        toplam[b] = toplam.get(b, 0.0) + s["tuketim_m3"]
        adet[b] = adet.get(b, 0) + 1
    ortalama = {}
    for b in toplam:
        ortalama[b] = toplam[b] / adet[b]
    sirali = sorted(ortalama.items(), key=lambda p: -p[1])
    return sirali[:3]


def butun_nesne(satirlar):
    bolge = sutun(satirlar, "bolge")
    tuketim = sutun(satirlar, "tuketim_m3")
    ortalama = grup_ortalamasi(bolge, tuketim)
    return ilk_n(azalan(ortalama), 3)


def bildirimsel(satirlar):
    return coz({"kaynak": satirlar, "grup": "bolge", "olcu": "tuketim_m3", "ilk": 3})


# Olcum kodun agacindan okunur: deyim sayisi, atanan ad sayisi, dongu dugumleri.
DONGU = (ast.For, ast.While, ast.comprehension)


def agac(f):
    return ast.parse(inspect.getsource(f)).body[0]


def deyim(f):
    return len(agac(f).body)


def ad_sayisi(f):
    g = agac(f)
    return len({n.id for n in ast.walk(g)
                if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Store)})


def yazilan_dongu(f):
    return sum(1 for n in ast.walk(agac(f)) if isinstance(n, DONGU))


# Kosan dongu: ifadenin kendi dongulerine cagirdigi yardimcilarinkiler eklenir.
def kosan_dongu(f, gorulen=None):
    gorulen = set() if gorulen is None else gorulen
    sayi = yazilan_dongu(f)
    for n in ast.walk(agac(f)):
        if isinstance(n, ast.Call) and isinstance(n.func, ast.Name):
            ad = n.func.id
            if ad in YARDIMCI and ad not in gorulen:
                gorulen.add(ad)
                sayi += kosan_dongu(YARDIMCI[ad], gorulen)
    return sayi


def hiza(w):
    return lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                             for j, v in enumerate(h))


IFADE = [("yordamsal", yordamsal), ("butun-nesne", butun_nesne),
         ("bildirimsel", bildirimsel)]
y = hiza([16, 8, 15, 16, 14])
print("\n" + y(["ifade", "deyim", "ara degisken", "yazilan dongu", "kosan dongu"]))
for ad, f in IFADE:
    print(y([ad, deyim(f), ad_sayisi(f), yazilan_dongu(f), kosan_dongu(f)]))

sonuc = [f(OKUMA) for _, f in IFADE]
ayni = all(s == sonuc[0] for s in sonuc)
print(f"\nucu de ayni sonucu veriyor: {'evet' if ayni else 'hayir'}")
print("  " + ", ".join(f"{b} {v:.3f}" for b, v in sonuc[0]))
```

```
ifade              deyim   ara degisken   yazilan dongu   kosan dongu
yordamsal              6              6               2             2
butun-nesne            4              3               0             3
bildirimsel            1              0               0             3

ucu de ayni sonucu veriyor: evet
  dogu 24.674, merkez 22.396, kuzey 20.210
```

Alt satır dersin çıpasıdır: üç ifade de aynı üç bölgeyi ve aynı üç sayıyı verir. Fark sonuçta değil,
**yazımdadır**. Yordamsal ifade 6 deyim tutar ve 6 ad bağlar; bunların dördü ara sonuçtur ve hiçbiri
rapora girmez. Bütün-nesne ifadesi 4 deyime ve 3 ada iner, çünkü artık satır dolaşılmaz: `bolge` ve
`tuketim` iki sütun olarak alınır ve ortalama sütunun tamamına uygulanır. Bildirimsel ifade tek
deyimdir ve hiçbir ad bağlamaz; yazılan şey sonucun tarifidir.

İkinci satırdaki asıl değişiklik sayıda değil, **iş biriminde** gerçekleşiyor. Yordamsal ifadede
kodun konuştuğu şey `s` adlı tek bir satırdır ve toplam ile adet o satırın alanlarından büyür;
analizin nesnesi bir kayıttır. Bütün-nesne ifadesinde kodun konuştuğu şey `bolge` ve `tuketim` adlı
iki sütundur; tek bir satırın adı bile geçmez. İstatistiksel programlama dillerinin yaklaşımı
buradan gelir: veri kaydın değil, **alanın** birimiyle ele alınır ve işlem alanın tamamına
uygulandığı için dolaşma kodun görünen kısmından tümüyle çıkar.

#### Döngü Kaybolmadı, Taşındı

Tablonun son iki sütunu birbirinin tersine gidiyor. **Yazılan döngü** 2'den 0'a iner, **koşan
döngü** 2'den 3'e çıkar. Bu, soyutlamanın en sık yanlış okunan yerlerinden biridir: döngüyü yazmayı
bırakmak döngüyü ortadan kaldırmaz, yalnız yerini değiştirir.

Yordamsal ifade iki döngü yazar: biri kümeyi toplam ve adet için tarar, öteki gruplanmış beş
bölgeyi bölme için dolaşır. Bütün-nesne
ifadesi hiç döngü yazmaz, ama çağırdığı yardımcılar üç döngü koşturur — `sutun` her çağrıldığında
kümeyi bir kez tarar ve iki kez çağrılır, `grup_ortalamasi` bir tarama ve bir birleştirme yapar.
İfade kısaldıkça geçiş sayısı **azalmaz**, bu örnekte artar. Kısalık ile geçiş sayısı arasında bir
bağ olduğunu varsaymak, ölçülmemiş bir iddiadır.

Bunun karşılığında elde edilen şey de sayılabilir: yordamsal ifadedeki dört ara ad — `toplam`,
`adet`, `ortalama`, `sirali` — bütün-nesne ifadesinde üçe, bildirimsel ifadede sıfıra iner. Ara ad,
okunması gereken ve yanlış kullanılabilecek bir şeydir; sayısının düşmesi ifadenin okunma yükünü
düşürür. Bedeli, bir sonraki bölümün konusudur.

Ölçünün neyi saymadığı da yazılmalıdır. Tablo yalnız **analizi yazan kişinin yazdığı** deyimleri
sayar; yardımcıların kendi gövdeleri sayıya girmez. Dört yardımcı ile çözücü bir arada dokuz
deyim tutar ve bu deyimler bir yerde birinin yazdığı deyimlerdir. Soyutlamanın kazancı iş
miktarının azalması değil, **aynı deyimlerin bir kez yazılıp çok kez kullanılmasıdır**; iki sütun
farklı üç analizde de aynı `sutun` çağrısıyla alınır. Bu yüzden ölçü bir üstünlük sıralaması vermez,
yalnız yükün nereye taşındığını gösterir.

#### Tek Deyimin Beş Kararı

Bildirimsel ifade dört şey yazıyor: kaynak, grup alanı, ölçü alanı ve kaç satır isteneceği. Analizin
sonucunu belirleyen kararların hepsi bunlar değildir. Aşağıdaki çözücü, aynı isteği eksiksiz bir
varsayılan kümesiyle karşılar ve her varsayılan tek tek çevrilir.

```python
# Ayni bildirimsel istek, bu kez butun varsayilanlari adiyla tasiyan bir cozucude.
VARSAYILAN = {"eksik": "say", "agirlik": "satir", "ozet": "ortalama",
              "donem": "hepsi", "yon": "azalan"}


def ortanca(v):
    s = sorted(v)
    n = len(s)
    return s[n // 2] if n % 2 else (s[n // 2 - 1] + s[n // 2]) / 2


def coz_tam(istek):
    a = dict(VARSAYILAN)
    a.update(istek)
    satirlar = [s for s in istek["kaynak"]
                if (a["donem"] == "hepsi" or s["donem"] == a["donem"])
                and (a["eksik"] == "say" or s["tuketim_m3"] > 0)]
    if a["agirlik"] == "abone":
        birim = {}
        for s in satirlar:
            birim.setdefault(s["abone_no"], (s[a["grup"]], []))[1].append(s[a["olcu"]])
        satirlar = [{a["grup"]: g, a["olcu"]: sum(v) / len(v)} for g, v in birim.values()]
    kova = {}
    for s in satirlar:
        kova.setdefault(s[a["grup"]], []).append(s[a["olcu"]])
    ozet = {g: (sum(v) / len(v) if a["ozet"] == "ortalama" else ortanca(v))
            for g, v in kova.items()}
    sirali = sorted(ozet.items(), key=lambda p: p[1], reverse=a["yon"] == "azalan")
    return sirali[:a.get("ilk", len(sirali))]


TEMEL = {"kaynak": OKUMA, "grup": "bolge", "olcu": "tuketim_m3", "ilk": 3}
DEGISIM = [("okunmamis satir", "eksik", "say", "dus"),
           ("olcunun agirligi", "agirlik", "satir", "abone"),
           ("ozet islevi", "ozet", "ortalama", "ortanca"),
           ("donem kapsami", "donem", "hepsi", "2026-01"),
           ("siralama yonu", "yon", "azalan", "artan")]
taban = dict(coz_tam(TEMEL))["dogu"]
y2 = hiza([20, 12, 12, 14, 12, 24])
print("\n" + y2(["gizlenen karar", "varsayilan", "dogu m3", "degistirilen", "dogu m3",
                 "rapor edilen ilk uc"]))
degerler = [taban]
for ad, anahtar, onceki, sonra in DEGISIM:
    istek = {**TEMEL, anahtar: sonra}
    ucu = coz_tam(istek)
    d = dict(coz_tam({k: v for k, v in istek.items() if k != "ilk"}))["dogu"]
    degerler.append(d)
    print(y2([ad, onceki, f"{taban:.3f}", sonra, f"{d:.3f}",
              " ".join(b for b, _ in ucu)]))
print(f"\ntek deyimlik ifade {len(TEMEL)} karari yaziyor, {len(VARSAYILAN)} karari "
      f"varsayilana birakiyor")
print(f"varsayilanlar yazilirsa istek {len(TEMEL)} -> {len(TEMEL) + len(VARSAYILAN)} "
      f"anahtar olur ve ifade hala 1 deyimdir")
print(f"dogu ortalamasi {min(degerler):.3f} ile {max(degerler):.3f} arasinda "
      f"{max(degerler) - min(degerler):.3f} m3 oynuyor")
```

```
gizlenen karar        varsayilan     dogu m3  degistirilen     dogu m3     rapor edilen ilk uc
okunmamis satir              say      24.674           dus      25.593       dogu merkez kuzey
olcunun agirligi           satir      24.674         abone      24.814       dogu merkez kuzey
ozet islevi             ortalama      24.674       ortanca      24.120       dogu merkez kuzey
donem kapsami              hepsi      24.674       2026-01      25.540       dogu merkez kuzey
siralama yonu             azalan      24.674         artan      24.674        bati guney kuzey

tek deyimlik ifade 4 karari yaziyor, 5 karari varsayilana birakiyor
varsayilanlar yazilirsa istek 4 -> 9 anahtar olur ve ifade hala 1 deyimdir
dogu ortalamasi 24.120 ile 25.593 arasinda 1.473 m3 oynuyor
```

Tek deyim dört karar yazıyor, beş kararı varsayılana bırakıyor. Beş varsayılan tek tek çevrildiğinde
doğu ortalaması 24,674 m³ yerine sırasıyla 25,593 — 24,814 — 24,120 — 25,540 m³ olur; en düşük ile
en yüksek arasında **1,473 m³**, yani taban değerin yaklaşık yüzde altısı kadar bir açıklık vardır.
Beşincisi sayıyı hiç oynatmaz ama rapor edilen üç bölgeyi tamamen değiştirir: azalan sırada doğu,
merkez ve kuzey yazılır, artan sırada batı, güney ve kuzey.

Buradaki asıl gözlem şudur: aynı beş karar **yordamsal ifadede de vardır**. Orada da okunmamış
satırlar sayılmıştır, ağırlık satırdır ve özet aritmetik ortalamadır. Fark, kararların
**yazılabileceği yer sayısıdır**. Yordamsal ifadede aynı beş varsayılan üç deyime dağılır — döngü,
bölme ve sıralama satırlarına — ve her biri elle değiştirilebilir bir yerdedir. Tek deyimlik
ifadede böyle bir satır yoktur; karar ancak isteğe bir anahtar olarak eklenirse görünür hale
gelir. Varsayılanların hepsi açıkça yazıldığında istek dört anahtardan dokuz anahtara çıkar ve
ifade **hâlâ tek deyimdir** — kaydın bedeli deyim değil, anahtardır.

Kursun kuralı burada tam olarak karşılığını buluyor. Bir soyutlama işi kısalttığı kadar kararı da
gizler; varsayılanını bilmediğin bir işlev, senin adına karar veren bir işlevdir. Kısa ifade bu
yüzden kötü değildir — kararın nerede yazıldığını bilmemek kötüdür.

#### Özet

- Aynı analiz üç ifade biçiminde yazıldığında aynı sonucu verir: doğu 24,674 — merkez 22,396 —
  kuzey 20,210 m³.
- İfadenin ölçüsü deyim, ara değişken ve döngü sayısıdır: yordamsal 6/6/2, bütün-nesne 4/3/0,
  bildirimsel 1/0/0.
- Yazılan döngü 2'den 0'a inerken koşan döngü 2'den 3'e çıkar; soyutlama döngüyü yok etmez, taşır
  ve bu örnekte bir tane de ekler.
- Tek deyimlik ifade 4 kararı yazar, 5 kararı varsayılana bırakır; beş varsayılan çevrildiğinde doğu
  ortalaması 1,473 m³ genişliğinde bir aralıkta oynar ve sıralama yönü rapor edilen üç bölgeyi
  tamamen değiştirir.
- Aynı kararlar yordamsal ifadede de vardır; değişen şey kararın yazılabileceği yer sayısıdır.

#### Sonraki Adım

Bildirimsel ifade tek deyime indi, ama bu inişin bir bedeli var: dört anahtarlık bir sözlük, bir
analizin ne yaptığını sırasıyla anlatmaz. Sonraki ders aradaki biçimi ölçer — dönüştürmeyi tek tek
**fiillerden** kurulu bir zincir olarak yazan yaklaşım. Ölçü aynı ailedendir: aynı yedi adımlık
dönüştürme ara değişkenlerle yazıldığında kaç ad bağlanıyor, iç içe çağrıyla yazıldığında okuma
sırası yürütme sırasıyla kaç adımda örtüşüyor, zincir olarak yazıldığında hangi ara sonucun satır
sayısı görünmez oluyor.

### Veri Çerçevesi Dilbilgisi

Source: https://academia.sh/tr/kurslar/python-ile-analiz/veri-cercevesi-dilbilgisi

Aynı yedi adımlık dönüştürme üç yazımla ifade edilir ve üçü de aynı sonucu verir: ara değişkenli yazım 7 deyim ve 6 ara değişken tutar, iç içe çağrı ve fiil zinciri tek deyim ve 0 ara değişken. Ayrım okuma sırasında ortaya çıkar — okuma sırası yürütme sırasıyla ara değişkenli yazımda 7/7, fiil zincirinde 7/7, iç içe çağrıda 1/7 adımda örtüşür. Zincirin bedeli ara sonuçların adsız kalmasıdır: 6 ara sonucun 0'ının satır sayısı görünür ve iz alan bir fiil eklendiğinde çağrı sayısı 7'den 15'e çıkarak 3.199 → 3.080 → 3.080 → 750 → 750 → 5 → 5 → 3 dizisi okunur hale gelir. Aynı altı fiilden birinin yeri değiştiğinde rapor edilen bölge sayısı 5'ten 1'e, doğu ortalaması 7,301 m³'ten 0,593 m³'e iner.

Bir önceki ders bildirimsel ifadeyi tek deyime indirdi ve bedelini saydı: dört anahtarlık bir sözlük
dört kararı yazıyor, beş kararı varsayılana bırakıyordu. O sözlüğün ikinci bir eksiği daha vardı.
Bir analiz yalnız bir tanım değildir, bir **sıradır**: önce şu satırlar atılır, sonra şu sütun
türetilir, sonra gruplanır. Anahtar-değer sözlüğü bu sırayı hiç yazmaz.

Bu ders aradaki biçimi ölçer. **Veri çerçevesi dilbilgisi (grammar of data)**, dönüştürmeyi tek tek
**fiillerden** kurulu bir zincir olarak yazar: süz, ekle, grupla, özetle, sırala, ilk. Ölçü iki
sayıdır: aynı dönüştürme kaç deyimde ve kaç ara değişkenle yazılıyor ve zincir kısaldığında hangi
ara sonuç görünmez oluyor.

- **AO5.** Küme K03'ten devralınır ve **kurgudur**: 3.199 satır, 1.329 abone, 10/25/40 m³ tarife
  basamakları. Tohum koda yazılıdır.
- **AO6.** Fiil zinciri **standart kitaplıkla modellenir**. `Cerceve` küçük bir modeldir: her fiil
  yeni bir çerçeve döndürür, hiçbir fiil kendi girdisini değiştirmez. Hiçbir araç taklit edilmez.
- **AO7.** Ölçülen dönüştürme yedi fiilden oluşur: okunmuş satırları süz, 25 m³ üzerindeki aşımı
  ekle, aşımı pozitif olanları süz, bölgeye göre grupla, ortalama aşımı özetle, azalan sırala,
  ilk üçü al.
- **AO8.** İki sayı yan yana durur. **Kazanç** ara değişken ve deyim sayısıdır. **Gizlenen karar**
  iki başlıktır: zincirde ara sonuçların satır sayısı görünmez ve fiil sırası koda yazılı olsa da
  sırayı seçen kararın gerekçesi hiçbir yerde yazılı değildir.

#### Yedi Fiil, Üç Yazım

```python
# dilbilgisi.py — MODELDIR. Once K03'un 3.199 satirlik kurgu kumesi ayni tohumla
# yeniden uretilir, sonra fiil temelli zincir standart kitaplikla modellenir.
import ast
import inspect
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
DONEM, TARIFE = ["2026-01", "2026-02", "2026-03"], [10, 25, 40]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]


def uretec(t: int):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki() -> float:
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


def ayrik(u: float, w: list[float]) -> int:
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


_kalan, _elenen = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    kayit = {"abone_no": 10001 + i, "bolge": b[0], "taban": b[2],
             "hane_kisi": ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05]),
             "memnuniyet": ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17]) + 1}
    (_elenen if r() < 0.046 else _kalan).append(kayit)
_gecerli = {k["abone_no"] for k in _kalan}
OKUMA = []
for k in _kalan + _elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(
            k["taban"] * math.exp((r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100
            + 0.5) / 100
        if k["abone_no"] not in _gecerli:
            r()
            continue
        OKUMA.append({"abone_no": k["abone_no"], "bolge": k["bolge"], "donem": DONEM[d],
                      "tuketim_m3": t, "memnuniyet": k["memnuniyet"],
                      "basamak": 1 if t < TARIFE[0] else 2 if t < TARIFE[1]
                      else 3 if t < TARIFE[2] else 4})
        r()


class Cerceve:
    """Veri cercevesi dilbilgisinin MODELI: her fiil yeni bir cerceve dondurur."""

    def __init__(self, satirlar, anahtar=None, iz=None):
        self.satirlar, self.anahtar = satirlar, anahtar
        self.iz = [] if iz is None else iz

    def suz(self, kosul):
        return Cerceve([s for s in self.satirlar if kosul(s)], self.anahtar, self.iz)

    def ekle(self, ad, f):
        return Cerceve([{**s, ad: f(s)} for s in self.satirlar], self.anahtar, self.iz)

    def grupla(self, ad):
        return Cerceve(self.satirlar, ad, self.iz)

    def ozetle(self, ad, f):
        kova = {}
        for s in self.satirlar:
            kova.setdefault(s[self.anahtar], []).append(s)
        return Cerceve([{self.anahtar: g, ad: f(v)} for g, v in kova.items()], None, self.iz)

    def sirala(self, ad, azalan=True):
        return Cerceve(sorted(self.satirlar, key=lambda s: s[ad], reverse=azalan),
                       self.anahtar, self.iz)

    def ilk(self, n):
        return Cerceve(self.satirlar[:n], self.anahtar, self.iz)

    def say(self, etiket):
        self.iz.append((etiket, len(self.satirlar)))
        return Cerceve(self.satirlar, self.anahtar, self.iz)


FIIL = ("suz", "ekle", "grupla", "ozetle", "sirala", "ilk")
# Ic ice yazim icin ayni fiillerin islev bicimi.
def suz(c, k): return c.suz(k)
def ekle(c, a, f): return c.ekle(a, f)
def grupla(c, a): return c.grupla(a)
def ozetle(c, a, f): return c.ozetle(a, f)
def sirala(c, a): return c.sirala(a)
def ilk(c, n): return c.ilk(n)
# Donusturmenin kosullari ve olcusu.
def okundu(s): return s["tuketim_m3"] > 0
def asim(s): return s["tuketim_m3"] - 25
def pozitif(s): return s["asim_m3"] > 0
def ortalama_asim(v): return sum(s["asim_m3"] for s in v) / len(v)


KAYNAK = Cerceve(OKUMA)


def ara_degiskenli(c):
    okunan = c.suz(okundu)
    asimli = okunan.ekle("asim_m3", asim)
    asan = asimli.suz(pozitif)
    gruplu = asan.grupla("bolge")
    ozet = gruplu.ozetle("ortalama_asim", ortalama_asim)
    dizili = ozet.sirala("ortalama_asim")
    return dizili.ilk(3)


def ic_ice(c):
    return ilk(sirala(ozetle(grupla(suz(ekle(
        suz(c, okundu), "asim_m3", asim), pozitif), "bolge"),
        "ortalama_asim", ortalama_asim), "ortalama_asim"), 3)


def zincirli(c):
    return (c
            .suz(okundu)
            .ekle("asim_m3", asim)
            .suz(pozitif)
            .grupla("bolge")
            .ozetle("ortalama_asim", ortalama_asim)
            .sirala("ortalama_asim")
            .ilk(3))


def agac(f):
    return ast.parse(inspect.getsource(f)).body[0]


def fiil_adi(n):
    if not isinstance(n, ast.Call):
        return None
    ad = n.func.attr if isinstance(n.func, ast.Attribute) else getattr(n.func, "id", None)
    return ad if ad in FIIL else None


# Her fiil cagrisi icin: hangi deyimde, kac fiil cagrisini iceriyor, kodda nerede.
def cagrilar(f):
    g, out = agac(f), []
    for i, deyim in enumerate(g.body):
        for n in ast.walk(deyim):
            if fiil_adi(n):
                ic = sum(1 for m in ast.walk(n) if m is not n and fiil_adi(m))
                out.append({"deyim": i, "ic": ic,
                            "yer": (n.func.end_lineno, n.func.end_col_offset)})
    return out


def ad_sayisi(f):
    return len({n.id for n in ast.walk(agac(f))
                if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Store)})


# Okuma sirasi kodun konumundan, yurutme sirasi ic ice gecmislikten okunur.
def ortusme(f):
    c = cagrilar(f)
    oku = sorted(range(len(c)), key=lambda j: c[j]["yer"])
    yur = sorted(range(len(c)), key=lambda j: (c[j]["deyim"], c[j]["ic"]))
    return sum(1 for a, b in zip(oku, yur) if a == b), len(c)


def hiza(w):
    return lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                             for j, v in enumerate(h))


YAZIM = [("ara degiskenli", ara_degiskenli), ("ic ice cagri", ic_ice),
         ("fiil zinciri", zincirli)]
y = hiza([18, 8, 8, 15, 18, 16])
print(y(["yazim", "deyim", "fiil", "ara degisken", "okuma=yurutme", "adli ara sonuc"]))
for ad, f in YAZIM:
    n, t = ortusme(f)
    print(y([ad, len(agac(f).body), t, ad_sayisi(f), f"{n}/{t}", f"{ad_sayisi(f)}/{t - 1}"]))
sonuc = [[(s["bolge"], round(s["ortalama_asim"], 3)) for s in f(KAYNAK).satirlar]
         for _, f in YAZIM]
print(f"\nkurgu kume {len(OKUMA)} satir; uc yazim ayni sonucu veriyor: "
      f"{'evet' if all(s == sonuc[0] for s in sonuc) else 'hayir'}")
print("  " + ", ".join(f"{b} {v}" for b, v in sonuc[0]))
```

```
yazim                deyim    fiil   ara degisken     okuma=yurutme  adli ara sonuc
ara degiskenli           7       7              6               7/7             6/6
ic ice cagri             1       7              0               1/7             0/6
fiil zinciri             1       7              0               7/7             0/6

kurgu kume 3199 satir; uc yazim ayni sonucu veriyor: evet
  dogu 7.301, merkez 6.532, kuzey 4.986
```

Üç yazım aynı yedi fiili çağırır ve aynı üç satırı döndürür. Ayrıldıkları yer ilk üç sütunda
görünür. Ara değişkenli yazım her adımı bir ada bağlar: yedi deyim, altı ara değişken. İç içe çağrı
ile fiil zinciri ikisi de tek deyimdir ve hiçbir ad bağlamaz.

Üç yazımın da aynı sonucu vermesi modelin bir özelliğinden gelir: hiçbir fiil kendi girdisini
değiştirmez, her biri yeni bir çerçeve döndürür. Bu, zincirin kurulabilmesinin ön koşuludur. Girdiyi
yerinde değiştiren bir fiil zincire takıldığında, aynı çerçeveye bakan ikinci bir ifade sessizce
başka bir sonuç okur; ara değişkenli yazımda bu en azından bir adın üzerine yazılması olarak
görünür, zincirde hiç görünmez.

Dördüncü sütun bu ikisini birbirinden ayırır. **Okuma sırası** kodun konumundan okunur — hangi fiil
adı sayfada daha önce yazılmıştır. **Yürütme sırası** iç içe geçmişlikten çıkar — en içteki çağrı
önce koşar. Ara değişkenli yazımda ikisi yedi adımda da örtüşür, çünkü her deyim bir öncekinin
sonucunu alır. Fiil zincirinde de yedi adımda örtüşür: zincir soldan sağa ve yukarıdan aşağıya
yürüdüğü için okunan sıra koşan sıradır. İç içe çağrıda örtüşme **yedide bire** iner. Sayfada ilk
okunan `ilk` en son koşar, en sonda okunan `suz` en önce koşar; yalnız ortadaki `grupla` iki sırada
da dördüncüdür. Zincirin kazancı budur: aynı tek deyimlik yazım, tersine okunmayı ortadan kaldırır.

#### İz Bırakan Zincir

Son sütun bedeli veriyor. Ara değişkenli yazımda altı ara sonucun altısı bir ada bağlıdır; herhangi
birinin satır sayısı istendiği anda okunabilir. Zincirde bu sayı **sıfırdır**. Zincir bir bütün
olarak koşar ve arasında hiçbir şey durmaz; süzgecin 3.000 satır mı yoksa 3 satır mı bıraktığı,
sonuca bakılmadan bilinemez. Bu, dilbilgisinin gizlediği karardır: **hangi adımda kaç satırın
düştüğü**. Modelde `say` fiili bunu geri kazandırır.

```python
izli = (Cerceve(OKUMA, None, []).say("giris")
        .suz(okundu).say("suz okunmus")
        .ekle("asim_m3", asim).say("ekle asim_m3")
        .suz(pozitif).say("suz asim>0")
        .grupla("bolge").say("grupla bolge")
        .ozetle("ortalama_asim", ortalama_asim).say("ozetle ortalama")
        .sirala("ortalama_asim").say("sirala azalan")
        .ilk(3).say("ilk 3"))
y2 = hiza([20, 10, 10])
print(y2(["asama", "satir", "degisim"]))
onceki = None
for etiket, n in izli.iz:
    print(y2([etiket, n, "-" if onceki is None else n - onceki]))
    onceki = n
print(f"\niz {len(izli.iz)} say cagrisiyla alindi: fiil 7 -> cagri {7 + len(izli.iz)}, "
      f"gorunur ara sonuc 0/6 -> 6/6")
```

```
asama                    satir   degisim
giris                     3199         -
suz okunmus               3080      -119
ekle asim_m3              3080         0
suz asim>0                 750     -2330
grupla bolge               750         0
ozetle ortalama              5      -745
sirala azalan                5         0
ilk 3                        3        -2

iz 8 say cagrisiyla alindi: fiil 7 -> cagri 15, gorunur ara sonuc 0/6 -> 6/6
```

Dizi zincirin ne yaptığını satır satır anlatıyor. İlk süzgeç 119 satır düşürür — okunmamış
kayıtların sayısı, önceki dersle aynı. `ekle` hiçbir satıra dokunmaz, yalnız sütun ekler. İkinci
süzgeç **2.330 satır** düşürür ve asıl daralma buradadır: 3.080 okumanın yalnız 750'si 25 m³'ün
üzerindedir. Özetleme 745 satırı eritip beş bölgeye indirir, son fiil ikisini keser.

Bedel de görünüyor: 7 fiil 15 çağrıya çıkar. Ara sonuç görünürlüğü 0/6'dan 6/6'ya gider ve karşılığı
sekiz fazladan çağrıdır. Zincirin okunma kolaylığı ile ara sonucun görünürlüğü aynı anda elde
edilmez; ikisinden biri sekiz çağrı ile satın alınır. Sayı olarak söylenirse: **görünürlüğün fiil
başına bedeli bir çağrıdan biraz fazladır** ve iz alınmayan bir zincirde 2.330 satırlık düşüş hiçbir
uyarı üretmeden geçer. İzin bir yan kazancı daha var: `grupla` ve `sirala` aşamalarındaki sıfır
değişimler, o iki fiilin satır sayısına hiç dokunmadığını sayıyla gösterir. Bir zincirin hangi
fiillerinin satır ürettiğini ya da düşürdüğünü bilmek, hata arandığında bakılacak yeri yediden ikiye
indirir.

#### Fiilin Sırası Bir Karardır

Zincirin fiilleri koda yazılıdır ve sırası görünür. Görünmeyen şey, o sıranın **neden** o sıra
olduğudur. Aşağıda aynı altı fiil iki farklı sırayla yazılır: süzgeç bir kez özetlemeden önce, bir
kez özetlemeden sonra.

```python
def once_suz(c):
    return (c.suz(okundu).ekle("asim_m3", asim).suz(pozitif)
            .grupla("bolge").ozetle("ortalama_asim", ortalama_asim)
            .sirala("ortalama_asim"))


def sonra_suz(c):
    return (c.suz(okundu).ekle("asim_m3", asim)
            .grupla("bolge").ozetle("ortalama_asim", ortalama_asim)
            .suz(lambda s: s["ortalama_asim"] > 0).sirala("ortalama_asim"))


SIRA = [("suz ozetlemeden once", once_suz), ("suz ozetlemeden sonra", sonra_suz)]
y3 = hiza([26, 8, 14, 12, 12])
print(y3(["fiil sirasi", "fiil", "cikan satir", "dogu m3", "toplam m3"]))
okunan = []
for ad, f in SIRA:
    sonuc_c = f(Cerceve(OKUMA, None, []))
    d = {s["bolge"]: s["ortalama_asim"] for s in sonuc_c.satirlar}
    okunan.append((len(sonuc_c.satirlar), d.get("dogu", 0.0)))
    print(y3([ad, len(cagrilar(f)), len(sonuc_c.satirlar), f"{d.get('dogu', 0):.3f}",
              f"{sum(d.values()):.3f}"]))
print(f"\nayni {len(cagrilar(once_suz))} fiil, tek bir fiilin yeri degisti: "
      f"rapor edilen bolge sayisi {okunan[0][0]} -> {okunan[1][0]}, "
      f"dogu ortalamasi {okunan[0][1]:.3f} -> {okunan[1][1]:.3f} m3")
```

```
fiil sirasi                   fiil   cikan satir     dogu m3   toplam m3
suz ozetlemeden once             6             5       7.301      24.539
suz ozetlemeden sonra            6             1       0.593       0.593

ayni 6 fiil, tek bir fiilin yeri degisti: rapor edilen bolge sayisi 5 -> 1, dogu ortalamasi 7.301 -> 0.593 m3
```

Altı fiil aynı, biri yer değiştirdi ve rapor bambaşka bir şey söylüyor. İlk sırada süzgeç satırlara
uygulanır: aşımı olan 750 okumanın bölge ortalamaları hesaplanır ve doğu için 7,301 m³ çıkar. İkinci
sırada süzgeç **özete** uygulanır: bütün okumaların bölge ortalaması alınır — negatif aşımlar da
sayılır — ve sonra ortalaması pozitif kalan bölgeler tutulur. Beş bölgeden yalnız doğu ayakta kalır
ve sayısı 0,593 m³'e iner.

İki sayı da yanlış değildir; farklı iki soruya cevaptır. İlki "aşım yapan okumaların ortalama aşımı
ne kadar" sorusunun cevabı, ikincisi "hangi bölgenin ortalama abonesi basamağı aşıyor" sorusunun.
Dilbilgisinin verdiği şey her ikisini de iki satır kodla yazabilmektir; vermediği şey hangisinin
sorulduğunun kayda geçmesidir. Zincir sırayı yazar, **sıranın gerekçesini yazmaz**; kursun kuralı
burada da geçerlidir — sırası yazılmayan bir fiil değil, gerekçesi yazılmayan bir sıra ölçülmemiş
sayılır.

#### Özet

- Aynı yedi fiil üç yazımda aynı sonucu verir: doğu 7,301 — merkez 6,532 — kuzey 4,986 m³ ortalama
  aşım.
- Ara değişkenli yazım 7 deyim ve 6 ara değişken, iç içe çağrı ve fiil zinciri 1 deyim ve 0 ara
  değişken tutar.
- Okuma sırası yürütme sırasıyla ara değişkenli yazımda 7/7, zincirde 7/7, iç içe çağrıda 1/7
  örtüşür; zincirin kazancı tersine okunmayı ortadan kaldırmasıdır.
- Zincirde 6 ara sonucun 0'ının satır sayısı görünür; iz alan fiil eklendiğinde çağrı 7'den 15'e
  çıkar ve 3.199 → 3.080 → 750 → 5 → 3 dizisi okunur olur. İkinci süzgeç tek başına 2.330 satır
  düşürür.
- Aynı altı fiilden birinin yeri değişince rapor edilen bölge sayısı 5'ten 1'e, doğu ortalaması
  7,301 m³'ten 0,593 m³'e iner.

#### Sonraki Adım

Dönüştürme fiillerden kuruldu ve her fiil bir öncekinin çıktısını aldı. Aynı düşünce görselleştirmede
de karşımıza çıkar: bir grafik tek bir çağrı değil, üst üste bindirilmiş **katmanlardan** oluşur —
veri, eşleme, geometri, istatistik, ölçek. Sonraki ders bunu sayar: aynı grafik hazır bir çağrıyla
kurulduğunda kaç bağımsız değişken alıyor, katmanlarla kurulduğunda kaç katman tutuyor ve üzerine
bir seri daha eklendiğinde kaç katman yeniden kullanılıyor. Yanında gizlenen karar durur: bir
katmanın sessizce uyguladığı istatistik.

### Grafik Dilbilgisi

Source: https://academia.sh/tr/kurslar/python-ile-analiz/grafik-dilbilgisi

Aynı sütun grafiği iki yolla kurulur: hazır bir çağrı 9 bağımsız değişken alır, katmanlı kuruluş 5 katman tutar ve ikisi de aynı değerleri verir. Üzerine bir referans çizgisi eklendiğinde hazır çağrının imzası 9'dan 11'e çıkar ve önceki kurulumdan hiçbir kalem yeniden kullanılmaz; katmanlı kuruluş 5'ten 6 katmana çıkar ve 5 katman aynen kalır. Gizlenen karar istatistik katmanındadır: aynı beş katmanın dördü sabit tutulup yalnız istatistik değiştirildiğinde en uzun çubuk kuzey, doğu ve yine kuzey olur — adet 921, ortalama 24,674 m³, toplam 18.613,81 m³ üç ayrı sıralama üretir. Konum ayarı da aynı biçimde davranır: 6 katman değişmeden dururken doğu/2 için okunan sayı 281'den 0,504'e iner.

Bir önceki ders dönüştürmeyi fiillerden kurdu ve her fiilin bir öncekinin çıktısını aldığını
gösterdi. Görselleştirmede de benzer bir kuruluş vardır, ama parçalar birbirini izlemez —
**üst üste biner**. **Grafik dilbilgisi (grammar of graphics)** bir grafiği tek bir çağrı olarak
değil, birbirinden bağımsız katmanların toplamı olarak ele alır: veri, eşleme, istatistik, geometri,
ölçek, konum.

Bu dersin sayısı katman sayısıdır: aynı grafik hazır bir çağrıyla kurulduğunda kaç kalem yazılıyor,
katmanlarla kurulduğunda kaç katman tutuyor ve grafik büyüdüğünde kaç kalem yeniden kullanılıyor.
Yanında kursun ikinci sayısı durur: bir katman yazılmadığında onun yerine hangi varsayılan geçiyor
ve grafiğin okunan sayısı ne oluyor.

- **AO9.** Küme K03'ten devralınır ve **kurgudur**: 3.199 satır, 1.329 abone, 10/25/40 m³ tarife
  basamakları. Tohum koda yazılıdır.
- **AO10.** Katmanlı grafik **standart kitaplıkla modellenir**. `Katman` bir tür ve birkaç ayardır,
  `Grafik` bir katman listesidir; çizim metin çubuklarıyla yapılır, çünkü ölçülen şey biçim değil
  **katman sayısıdır**. Hiçbir araç ya da kitaplık taklit edilmez.
- **AO11.** Grafik türünün eksen alanının ölçeğine uyup uymadığı bu derste **ölçülmez**; o soru
  Elektronik Tablolarla Analiz kursunun Temel Grafikler dersinde ölçüldü. Burada tür yalnız bir
  **geometri katmanıdır** ve sayılan şey katmanların sayısı ile birbirinden bağımsız olup olmadığıdır.
- **AO12.** İki sayı yan yana durur. **Kazanç** grafiği genişletirken yeniden kullanılan kalem
  sayısıdır. **Gizlenen karar** yazılmayan katmandır: istatistik ve konum katmanı yazılmadığında
  grafik yine çizilir, ama çubukların boyu başka bir şeyi gösterir.

#### Bir Grafik Kaç Katman

```python
# grafik.py — MODELDIR. Once K03'un 3.199 satirlik kurgu kumesi ayni tohumla
# uretilir, sonra katmanli gorsellestirme standart kitaplikla modellenir.
import inspect
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
DONEM, TARIFE = ["2026-01", "2026-02", "2026-03"], [10, 25, 40]
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]


def uretec(t: int):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki() -> float:
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


def ayrik(u: float, w: list[float]) -> int:
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


_kalan, _elenen = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    kayit = {"abone_no": 10001 + i, "bolge": b[0], "taban": b[2],
             "hane_kisi": ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05]),
             "memnuniyet": ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17]) + 1}
    (_elenen if r() < 0.046 else _kalan).append(kayit)
_gecerli = {k["abone_no"] for k in _kalan}
OKUMA = []
for k in _kalan + _elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(
            k["taban"] * math.exp((r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100
            + 0.5) / 100
        if k["abone_no"] not in _gecerli:
            r()
            continue
        OKUMA.append({"abone_no": k["abone_no"], "bolge": k["bolge"], "donem": DONEM[d],
                      "tuketim_m3": t, "memnuniyet": k["memnuniyet"],
                      "basamak": 1 if t < TARIFE[0] else 2 if t < TARIFE[1]
                      else 3 if t < TARIFE[2] else 4})
        r()


class Katman:
    """Katmanli gorsellestirmenin MODELI: katman bir tur ve birkac ayardir."""

    def __init__(self, tur, **ayar):
        self.tur, self.ayar = tur, ayar

    def imza(self):
        return (self.tur, tuple(sorted(
            (k, f"{len(v)} satir" if isinstance(v, list) else str(v))
            for k, v in self.ayar.items())))


OZET = {"adet": lambda v, a: float(len(v)),
        "ortalama": lambda v, a: sum(s[a] for s in v) / len(v),
        "toplam": lambda v, a: sum(s[a] for s in v)}


class Grafik:
    def __init__(self, katmanlar=()):
        self.katmanlar = list(katmanlar)

    def ekle(self, *k):
        return Grafik(self.katmanlar + list(k))

    def degistir(self, k):
        return Grafik([k if e.tur == k.tur else e for e in self.katmanlar])

    def ayar(self, tur, **yoksa):
        for k in self.katmanlar:
            if k.tur == tur:
                return {**yoksa, **k.ayar}
        return yoksa

    # Katmanlar sirayla islenir: veri kovalanir, istatistik uygulanir, konum ayarlanir.
    def deger(self):
        veri = self.ayar("veri", satirlar=[])["satirlar"]
        e = self.ayar("eslesme")
        ozet = self.ayar("istatistik", ozet="adet")["ozet"]
        kova = {}
        for s in veri:
            kova.setdefault((s[e["x"]], s[e["renk"]] if "renk" in e else None), []).append(s)
        d = {a: OZET[ozet](v, e["y"]) for a, v in kova.items()}
        if self.ayar("konum", ayar="yigilmis")["ayar"] == "oranli":
            butun = {}
            for (x, _), v in d.items():
                butun[x] = butun.get(x, 0.0) + v
            d = {a: v / butun[a[0]] for a, v in d.items()}
        return d

    def ciz(self, baslik):
        d = self.deger()
        alt = self.ayar("olcek", alt=0.0)["alt"]
        ust = max(d.values())
        print(f"{baslik} — {len(self.katmanlar)} katman")
        for (x, r), v in sorted(d.items(), key=lambda p: -p[1]):
            n = round(24 * (v - alt) / (ust - alt)) if ust > alt else 0
            print(f"  {x if r is None else f'{x}/{r}':<14}{v:9.3f}  " + "#" * max(0, n))
        for k in self.katmanlar:
            if k.tur == "geometri" and k.ayar.get("bicim") == "cizgi":
                v = k.ayar["deger"]
                print(f"  {k.ayar['etiket']:<14}{v:9.3f}  "
                      + "-" * max(0, round(24 * (v - alt) / (ust - alt))))


TEMEL = Grafik().ekle(
    Katman("veri", satirlar=OKUMA),
    Katman("eslesme", x="bolge", y="tuketim_m3"),
    Katman("istatistik", ozet="ortalama"),
    Katman("geometri", bicim="cubuk"),
    Katman("olcek", eksen="y", alt=0.0))
TEMEL.ciz("bolge basina ortalama tuketim (m3)")
```

```
bolge basina ortalama tuketim (m3) — 5 katman
  dogu             24.674  ########################
  merkez           22.396  ######################
  kuzey            20.210  ####################
  guney            16.602  ################
  bati             13.778  #############
```

Grafik beş katmandan kuruldu ve her katman tek bir soruya cevap veriyor. **Veri** katmanı hangi
satırların çizileceğini söyler. **Eşleme** hangi alanın hangi eksene gideceğini söyler. **İstatistik**
katmanı ham satırlardan çizilecek sayının nasıl türetileceğini söyler — burada her bölgenin
ortalaması. **Geometri** katmanı o sayının hangi biçimde çizileceğini, **ölçek** katmanı ekseni
söyler.

Bu ayrımın önemi katmanların **birbirinden bağımsız** olmasıdır. Geometriyi çubuktan noktaya çevirmek
öbür dört katmana dokunmaz; istatistiği ortalamadan adete çevirmek geometriyi değiştirmez. Bir
grafik, seçeneklerin sabit bir listesi değil, birleştirilebilir parçalardan kurulmuş bir cümledir.

Bağımsızlık, katmanların sırasız olduğu anlamına gelmez. `deger` yöntemi işi belli bir sırayla
yapar: önce veri eşlemeye göre kovalara ayrılır, sonra her kovaya istatistik uygulanır, en sonda
konum ayarı uygulanır. Katmanlar sayfaya hangi sırayla yazılırsa yazılsın bu işlem sırası değişmez;
değişen tek şey hangi katmanın listede bulunduğudur. Fiil zincirinde sıra yazımın kendisiydi, burada
sıra dilbilgisinin kuralıdır ve yazımdan okunmaz.

#### Katman Eklemek, Çağrıyı Genişletmek

Aynı grafik hazır bir çağrıyla da kurulabilir: bütün kararlar tek bir imzada toplanır ve çağıran
kişi yalnız bağımsız değişkenleri doldurur. İkisinin farkı grafik büyüdüğünde ortaya çıkar.

```python
def hazir_cubuk(satirlar, x, y, ozet, renk=None, konum="yigilmis", alt=0.0, ust=None,
                baslik=""):
    """Hazir cagri MODELI: butun kararlar tek bir imzada toplanir."""
    return Grafik().ekle(Katman("veri", satirlar=satirlar),
                         Katman("eslesme", x=x, y=y),
                         Katman("istatistik", ozet=ozet),
                         Katman("geometri", bicim="cubuk"),
                         Katman("olcek", eksen="y", alt=alt))


# Referans cizgisi eklemek icin imzaya iki bagimsiz degisken daha girmesi gerekir.
def hazir_cubuk_referansli(satirlar, x, y, ozet, renk=None, konum="yigilmis", alt=0.0,
                           ust=None, baslik="", referans=None, referans_etiket=""):
    g = hazir_cubuk(satirlar, x, y, ozet, renk, konum, alt, ust, baslik)
    return g if referans is None else g.ekle(
        Katman("geometri", bicim="cizgi", etiket=referans_etiket, deger=referans))


GENEL = sum(s["tuketim_m3"] for s in OKUMA) / len(OKUMA)
REFERANSLI = TEMEL.ekle(Katman("geometri", bicim="cizgi", etiket="genel ortalama",
                               deger=round(GENEL, 3)))
eski = [k.imza() for k in TEMEL.katmanlar]
kalan = sum(1 for k in REFERANSLI.katmanlar if k.imza() in eski)


def arg(f):
    return len(inspect.signature(f).parameters)


def hiza(w):
    return lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                             for j, v in enumerate(h))


ayni_mi = hazir_cubuk(OKUMA, "bolge", "tuketim_m3", "ortalama").deger() == TEMEL.deger()
print(f"hazir cagri ile katmanli kurulus ayni degerleri veriyor: "
      f"{'evet' if ayni_mi else 'hayir'}")
y = hiza([22, 16, 22, 22])
print("\n" + y(["kurulus", "temel grafik", "referans eklenince", "yeniden kullanilan"]))
print(y(["hazir cagri (argum.)", arg(hazir_cubuk), arg(hazir_cubuk_referansli), 0]))
print(y(["katman listesi", len(TEMEL.katmanlar), len(REFERANSLI.katmanlar), kalan]))
print()
REFERANSLI.ciz("ayni grafik, referans cizgisi eklenmis")
```

```
hazir cagri ile katmanli kurulus ayni degerleri veriyor: evet

kurulus                   temel grafik    referans eklenince    yeniden kullanilan
hazir cagri (argum.)                 9                    11                     0
katman listesi                       5                     6                     5

ayni grafik, referans cizgisi eklenmis — 6 katman
  dogu             24.674  ########################
  merkez           22.396  ######################
  kuzey            20.210  ####################
  guney            16.602  ################
  bati             13.778  #############
  genel ortalama   19.713  -------------------
```

İlk satır çıpadır: iki kuruluş aynı değerleri verir, yani ölçülen şey sonuç değil **yapıdır**. Hazır
çağrı temel grafiği 9 bağımsız değişkenle kurar, katman listesi 5 katmanla. Sayı olarak hazır çağrı
daha az kalem tutuyor gibi görünmez, ama asıl ayrım ikinci sütunda.

Grafiğe genel ortalamayı gösteren bir çizgi eklendiğinde hazır çağrının **imzası** 9'dan 11'e çıkar.
Bu, o çağrıyı kullanan her yerin gördüğü yeni bir imzadır ve önceki kurulumdan yeniden kullanılan
kalem sayısı **sıfırdır**: çağrı bir bütündür, parçası taşınamaz. Katmanlı kuruluşta liste 5'ten 6'ya
çıkar ve **5 katman aynen kalır**; eklenen tek şey bir geometri katmanıdır. Genişleme burada
listeye, orada imzaya yazılır.

Bunun bir de ters yönü var ve yazılmalıdır. Hazır çağrı, sık kullanılan bir grafiği tek satırda
verir ve beş katmanı elle dizmeyi gerektirmez; katmanlı kuruluşta aynı grafiği kuran herkes aynı beş
katmanı yeniden yazar. Ölçü bir üstünlük sıralaması değildir: hazır çağrı **tekrarı**, katmanlı
kuruluş **genişlemeyi** ucuzlatır.

#### İstatistik Katmanı Yazılmazsa

Katmanların bağımsızlığının bedeli, bir katman yazılmadığında grafiğin hata vermeden çizilmesidir.
Model yazılmayan katmanın yerine bir varsayılan koyar ve çubuklar yine görünür.

```python
y2 = hiza([14, 12, 12, 12, 12, 12, 16])
BOLGELER = ("kuzey", "dogu", "merkez", "guney", "bati")
print(y2(["istatistik"] + list(BOLGELER) + ["en uzun cubuk"]))
uzun = []
for ozet in ("adet", "ortalama", "toplam"):
    g = TEMEL.degistir(Katman("istatistik", ozet=ozet))
    d = {a[0]: v for a, v in g.deger().items()}
    uzun.append(max(d.items(), key=lambda p: p[1])[0])
    print(y2([ozet] + [f"{d[b]:.3f}" for b in BOLGELER] + [uzun[-1]]))
print(f"\nayni {len(TEMEL.katmanlar)} katmanin dordu ayni; yalniz istatistik katmani "
      f"degisti ve en uzun cubuk {' -> '.join(uzun)} oldu")

RENKLI = Grafik([Katman("veri", satirlar=OKUMA),
                 Katman("eslesme", x="bolge", y="tuketim_m3", renk="basamak"),
                 Katman("istatistik", ozet="adet"),
                 Katman("geometri", bicim="cubuk"),
                 Katman("olcek", eksen="y", alt=0.0)])
y3 = hiza([14, 12, 12, 12, 12])
print("\n" + y3(["konum ayari", "dogu/1", "dogu/2", "dogu/3", "dogu/4"]))
ikinci = []
for ayar in ("yigilmis", "oranli"):
    d = RENKLI.ekle(Katman("konum", ayar=ayar)).deger()
    ikinci.append(d[("dogu", 2)])
    print(y3([ayar] + [f"{d[('dogu', b)]:.3f}" for b in (1, 2, 3, 4)]))
print(f"\nkatman sayisi {len(RENKLI.katmanlar) + 1} her iki ayarda da ayni; tek bir "
      f"katmanin tek ayari degisti ve dogu/2 icin okunan sayi {ikinci[0]:.0f} yerine "
      f"{ikinci[1]:.3f} oldu")
```

```
istatistik           kuzey        dogu      merkez       guney        bati   en uzun cubuk
adet               921.000     557.000     600.000     645.000     476.000           kuzey
ortalama            20.210      24.674      22.396      16.602      13.778            dogu
toplam           18613.810   13743.340   13437.310   10708.020    6558.150           kuzey

ayni 5 katmanin dordu ayni; yalniz istatistik katmani degisti ve en uzun cubuk kuzey -> dogu -> kuzey oldu

konum ayari         dogu/1      dogu/2      dogu/3      dogu/4
yigilmis            21.000     281.000     225.000      30.000
oranli               0.038       0.504       0.404       0.054

katman sayisi 6 her iki ayarda da ayni; tek bir katmanin tek ayari degisti ve dogu/2 icin okunan sayi 281 yerine 0.504 oldu
```

Üç satır aynı veriyi, aynı eşlemeyi, aynı geometriyi ve aynı ölçeği kullanıyor. Değişen tek şey
istatistik katmanıdır ve grafiğin söylediği şey üç kez değişiyor. Adet seçildiğinde en uzun çubuk
kuzeydedir, çünkü kuzeyde 921 okuma vardır. Ortalama seçildiğinde en uzun çubuk doğuya geçer,
çünkü doğu 557 okumayla az kalabalık, ortalama tüketimi ise en yüksek bölgedir. Toplam
seçildiğinde çubuk kuzeye geri döner. Üçü de doğrudur ve **üçü de aynı sütun grafiğidir**;
hangisinin çizildiğini söyleyen tek şey yazılmış ya da yazılmamış olan istatistik katmanıdır.

Konum ayarı aynı davranışı bir kez daha gösterir. Altı katman iki koşumda da birebir aynıdır; tek
fark konum katmanının ayarıdır. Yığılmış ayarda doğu bölgesinin ikinci tarife basamağında **281
okuma** vardır ve çubuğun boyu bir sayıdır. Oranlı ayarda aynı yer **0,504** olur ve çubuğun boyu
bir paydır. Bir okuyucu ikinci grafiğe bakıp "doğuda ikinci basamak yarıya yakın" der ve haklıdır;
ilkine bakıp "doğuda ikinci basamakta 281 okuma var" der ve o da haklıdır. Kaç okuma olduğu ikinci
grafikten, payın ne olduğu ilkinden okunamaz.

Kursun kuralı grafikte de aynen geçerlidir: bir soyutlama işi kısalttığı kadar kararı da gizler.
Katmanlı kuruluşun sağladığı şey kararların **adlandırılmış** olmasıdır — istatistik bir katmandır,
konum bir katmandır ve grafiğin listesine bakan biri hangisinin yazıldığını görebilir. Yazılmayan
katman ise varsayılana düşer ve grafik yine çizilir; sayfaya bakan kişinin elinde çubuğun neyi
ölçtüğünü söyleyen bir kayıt kalmaz.

#### Özet

- Aynı sütun grafiği hazır çağrıyla 9 bağımsız değişkende, katmanlı kuruluşla 5 katmanda kurulur ve
  ikisi de aynı değerleri verir.
- Referans çizgisi eklendiğinde hazır çağrının imzası 9'dan 11'e çıkar ve yeniden kullanılan kalem
  sayısı 0'dır; katman listesi 5'ten 6'ya çıkar ve 5 katman aynen kalır.
- Hazır çağrı tekrarı, katmanlı kuruluş genişlemeyi ucuzlatır; ölçü bir üstünlük sıralaması değildir.
- İstatistik katmanı değiştiğinde grafiğin dört katmanı sabit kalır ama en uzun çubuk kuzey, doğu ve
  yine kuzey olur: adet 921, ortalama 24,674 m³, toplam 18.613,81 m³.
- Konum ayarı katman sayısını değiştirmeden okunan sayıyı değiştirir: doğu bölgesinin ikinci
  basamağı yığılmış ayarda 281, oranlı ayarda 0,504 olarak okunur.

#### Sonraki Adım

Üç ders üç yaklaşımı ölçtü: aynı analizin kaç deyimde ifade edildiği, dönüştürmenin kaç fiil ve kaç
ara değişkenle yazıldığı, grafiğin kaç katmanla kurulduğu. Hepsinde ölçü ifadenin biçimiydi ve
hiçbirinde bir ortamın öbüründen iyi olduğu söylenmedi. Geriye seçimin kendisi kalıyor: bir ekip
hangi ortamda çalışacağına neye bakarak karar verir. Sonraki ders bunu ölçüte bağlar — ekibin eldeki
becerisi, çevresindeki hazır parçaların kapsamı ve işin üretime taşınabilirliği — ve üç ortamı üç
ölçütte sayıyla karşılaştırıp seçimin hangi ölçütte değiştiğini yazar. Kurs orada kapanır ve yirmi
iki dersin kazancıyla gizlenen kararını tek bir tabloda sayar.

### Ortam Seçimi

Source: https://academia.sh/tr/kurslar/python-ile-analiz/ortam-secimi

Üç ortam üç ölçütte sayıyla karşılaştırılır ve her ölçüt başka bir ortamı öne çıkarır: dokuz kişilik ekipte yazabilen sayısı 7 / 2 / 4, dokuz adımın hazır parçayla karşılananı 7 / 9 / 5, dokuz üretim kaleminin ortamca karşılananı 3 / 1 / 9 çıkar. Eşit ağırlıkta ham toplam 17 / 12 / 18 olur ve seçim bir puanla belirlenir. Ağırlık taraması seçimin üç ölçütün üçünde de değiştiğini gösterir: ekip ağırlığı 2, ekosistem ağırlığı 2, üretim ağırlığı 0 olduğunda seçim başka ortama geçer; üçüncü ortam ancak ekosistem ağırlığı 4 olunca 39 puanla seçilir. Aynı dokuz sayı aralığa yayılarak normalleştirildiğinde puan 175 / 100 / 140 olur ve hiçbir ham sayı değişmeden seçim döner.

Üç ders üç yaklaşımı ölçtü: aynı analizin kaç deyimde ifade edildiği, dönüştürmenin kaç fiille
yazıldığı, grafiğin kaç katmanla kurulduğu. Üçünde de ölçülen şey ifadenin biçimiydi ve hiçbirinde
bir ortamın öbüründen iyi olduğu söylenmedi. Geriye seçimin kendisi kalıyor.

Bu dersin sayısı **ölçüt başına sayıdır**. Üç ortam üç ölçütte sayılır ve seçimin hangi ölçütte
değiştiği aranır. Yanında kursun ikinci sayısı durur: üç ölçütü tek puana indiren işlem de bir
soyutlamadır ve kendi varsayılanlarını taşır.

- **AO13.** Küme K03'ten devralınır ve **kurgudur**: 3.199 satır, 1.329 abone, 10/25/40 m³ tarife
  basamakları. Tohum koda yazılıdır.
- **AO14.** Üç ortam **yaklaşımlarıyla** anılır ve Python'da **modellenir**: genel amaçlı bir dilin
  dizi ve veri çerçevesi arayüzü, analiz odaklı bir dilin bütün-nesne ortamı, hesabın veri deposunda
  yapıldığı bildirimsel sorgu ortamı. Hiçbir kütüphane ya da ürün adı geçmez.
- **AO15.** Ölçütler ve birimleri şunlardır. **Ekip:** dokuz kişilik ekipte o ortamda analizi
  yazabilen kişi sayısı. **Ekosistem:** analizin dokuz adımından hazır parçayla karşılananların
  sayısı. **Üretime taşıma:** dokuz üretim kaleminden ortamın karşıladıklarının sayısı.
- **AO16.** Envanter **kurgudur** ve bu ekibin kendi dokuz adımı içindir; ortamlar hakkında genel
  bir sıralama değildir. **Karşılaştırmalı üstünlük iddiası yazılmaz.**

#### Üç Ortam, Üç Ölçüt

Sorulan analiz üç ortamda da aynıdır ve dersin çıpasıdır: bölge başına ortalama tüketim, azalan
sırada ilk üç bölge. Değişen şey cevap değil, cevaba giden yolun envanteridir.

```python
# ortam_secimi.py — MODELDIR. Once K03'un 3.199 satirlik kurgu kumesi ayni tohumla
# uretilir, sonra uc ortamin envanteri standart kitaplikla modellenir.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26),
         ("bati", 0.14, 14), ("merkez", 0.18, 23)]


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


# Hane ve memnuniyet cekilir ama bu derste kullanilmaz; uretec sirasi korunur.
_kalan, _elenen = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    (_elenen if r() < 0.046 else _kalan).append(
        {"abone_no": 10001 + i, "bolge": b[0], "taban": b[2]})
_gecerli = {k["abone_no"] for k in _kalan}
OKUMA = []
for k in _kalan + _elenen:
    r = uretec(TOHUM + 7000 + k["abone_no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(
            k["taban"] * math.exp((r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100
            + 0.5) / 100
        if k["abone_no"] not in _gecerli:
            r()
            continue
        OKUMA.append({"bolge": k["bolge"], "tuketim_m3": t})
        r()

ORTAM = ["genel amacli", "analiz odakli", "bildirimsel"]
G, A, B = ORTAM
ADIM = ["okuma", "tip", "suzme", "birlestirme", "gruplama", "ornekleme", "metin",
        "grafik", "yayim"]
KALEM = ["surum", "kilit", "zamanlama", "izleme", "hata", "erisim", "gunluk",
         "geri-alma", "olcek"]

# Envanter KURGUDUR ve bu ekibin kendi dokuz adimi icindir; ortamlar hakkinda
# genel bir siralama degildir. Ikinci sutun yazabilen, ucuncu okuyabilen ortamlar.
EKIP = [("cozumleyici-1", {G, A, B}, {G, A, B}), ("cozumleyici-2", {G, A}, {G, A, B}),
        ("cozumleyici-3", {G, B}, {G, A, B}), ("cozumleyici-4", {G, B}, {G, B}),
        ("cozumleyici-5", {G}, {G, B}), ("cozumleyici-6", {G}, {G, B}),
        ("cozumleyici-7", {G}, {G}), ("cozumleyici-8", {B}, {G, B}),
        ("cozumleyici-9", set(), {G})]
HAZIR = {G: set(ADIM) - {"grafik", "yayim"}, A: set(ADIM), B: set(ADIM[:5])}
KARSILANAN = {G: {"surum", "kilit", "gunluk"}, A: {"surum"}, B: set(KALEM)}

OLCUT = {"ekip": {o: sum(1 for _, y, _ in EKIP if o in y) for o in ORTAM},
         "ekosistem": {o: len(HAZIR[o]) for o in ORTAM},
         "uretim": {o: len(KARSILANAN[o]) for o in ORTAM}}


def hiza(w):
    return lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j])
                             for j, v in enumerate(h))


def ilk_uc(satirlar):
    t, n = {}, {}
    for s in satirlar:
        t[s["bolge"]] = t.get(s["bolge"], 0.0) + s["tuketim_m3"]
        n[s["bolge"]] = n.get(s["bolge"], 0) + 1
    return sorted(((b, t[b] / n[b]) for b in t), key=lambda p: -p[1])[:3]


print(f"kurgu kume (tohum {TOHUM}): {len(OKUMA)} satir, {len(_gecerli)} abone")
print("sorulan analiz uc ortamda da ayni: "
      + ", ".join(f"{b} {v:.3f}" for b, v in ilk_uc(OKUMA)))
y = hiza([27, 15, 16, 14])
print("\n" + y(["olcut (9 uzerinden)"] + ORTAM))
for k, d in OLCUT.items():
    print(y([k] + [d[o] for o in ORTAM]))
print(y(["ham toplam (27 uzerinden)"]
        + [sum(OLCUT[k][o] for k in OLCUT) for o in ORTAM]))
```

```
kurgu kume (tohum 20260218): 3199 satir, 1329 abone
sorulan analiz uc ortamda da ayni: dogu 24.674, merkez 22.396, kuzey 20.210

olcut (9 uzerinden)           genel amacli   analiz odakli   bildirimsel
ekip                                     7               2             4
ekosistem                                7               9             5
uretim                                   3               1             9
ham toplam (27 uzerinden)               17              12            18
```

Tablo tek bir gözlemle başlar: **üç ölçütün üçü de başka bir ortamı öne çıkarıyor**. Ekipte genel
amaçlı ortam 7 kişiyle, ekosistemde analiz odaklı ortam 9 adımla, üretime taşımada bildirimsel ortam
9 kalemle önde durur. Bildirimsel ortamda elle kurulacak dört adım vardır: yeniden örnekleme, metin
ayrıştırma, katmanlı grafik ve yayım.

Böyle bir tabloda "hangisi iyi" sorusunun cevabı yoktur, çünkü ölçütler farklı şeyleri sayıyor.
Cevabı olan tek soru şudur: bu üç sayı nasıl birleştirilecek. Ham toplam 17, 12 ve 18 verir ve seçim
**tek bir puanla** belirlenir.

#### Seçim Hangi Ölçütte Değişiyor

Ham toplam üç ölçüte eşit ağırlık verir; bu eşitlik ölçülmüş değil, varsayılmıştır. Ağırlığı ölçüt
ölçüt değiştirip seçimin nerede döndüğüne bakmak, varsayımı sınamanın sayılabilir yoludur.

```python
def puan(agirlik):
    return {o: sum(agirlik[k] * OLCUT[k][o] for k in OLCUT) for o in ORTAM}


def secim(p):
    return max(ORTAM, key=lambda o: p[o])


def yaz(p):
    return " / ".join(str(p[o]) for o in ORTAM)


BIR = {k: 1 for k in OLCUT}
TEMEL = puan(BIR)
y2 = hiza([18, 15, 15, 15, 15, 15])
print("\n" + y2(["olcut agirligi"] + list(range(5))))
for k in OLCUT:
    print(y2([k] + [secim(puan({**BIR, k: w})) for w in range(5)]))
print(f"\nesit agirlikta secim: {secim(TEMEL)} ({yaz(TEMEL)})")
for k in OLCUT:
    aday = [(abs(w - 1), w) for w in range(13)
            if w != 1 and secim(puan({**BIR, k: w})) != secim(TEMEL)]
    w = min(aday)[1]
    p = puan({**BIR, k: w})
    print(f"  {k} agirligi 1 -> {w}: secim {secim(p)} ({yaz(p)})")
kacinci = [w for w in range(13) if secim(puan({**BIR, "ekosistem": w})) == A]
p = puan({**BIR, "ekosistem": kacinci[0]})
print(f"  analiz odakli ortam yalniz ekosistem agirligi {kacinci[0]} olunca "
      f"seciliyor ({yaz(p)})")
```

```

olcut agirligi                  0              1              2              3              4
ekip                  bildirimsel    bildirimsel   genel amacli   genel amacli   genel amacli
ekosistem             bildirimsel    bildirimsel   genel amacli   genel amacli  analiz odakli
uretim               genel amacli    bildirimsel    bildirimsel    bildirimsel    bildirimsel

esit agirlikta secim: bildirimsel (17 / 12 / 18)
  ekip agirligi 1 -> 2: secim genel amacli (24 / 14 / 22)
  ekosistem agirligi 1 -> 2: secim genel amacli (24 / 21 / 23)
  uretim agirligi 1 -> 0: secim genel amacli (14 / 11 / 9)
  analiz odakli ortam yalniz ekosistem agirligi 4 olunca seciliyor (38 / 39 / 33)
```

Cevap tabloda duruyor: **seçim üç ölçütün üçünde de değişiyor**, ama eşikler ayrıdır. Ekip ağırlığı
1'den 2'ye çıktığında seçim genel amaçlı ortama geçer ve puanlar 24 ile 22 olur; ekosistem ağırlığı
2 olduğunda aynı yere geçer; üretime taşıma hiç sayılmazsa yine oraya. Üçünün de seçimi
çevirebilmesinin nedeni bir puanlık başlangıç farkıdır.

Üçüncü ortamın davranışı ayrıca yazılmalıdır. Analiz odaklı ortam eşit ağırlıkta 12 puanla en düşük
toplamı alır, ekosistem ağırlığı 4 olduğunda 39 puanla seçilen ortam olur. Hiçbir sayı
değişmemiştir; değişen tek şey dokuz adımın hazır karşılanmasının kaç kat sayıldığıdır. Ağırlık bir
ölçüm değil bir karardır.

#### Ölçütün Kendi Varsayılanı

Ağırlık tek varsayılan değildir. Sayılar toplanmadan önce karşılaştırılabilir kılınır ve bu da bir
kuraldır. Üç kural yan yana konuyor: ham sayıları toplamak, her ölçütte en yükseği 100 sayıp
ötekileri oranlamak, en yükseği 100 en düşüğü 0 sayıp aradakini yaymak.

```python
def normal(kural, olcut):
    toplam = {o: 0.0 for o in ORTAM}
    for d in olcut.values():
        v = [d[o] for o in ORTAM]
        for o in ORTAM:
            if kural == "ham sayi":
                toplam[o] += d[o]
            elif kural == "en iyiye oranlama":
                toplam[o] += 100.0 * d[o] / max(v)
            else:
                toplam[o] += 100.0 * (d[o] - min(v)) / (max(v) - min(v))
    return toplam


y3 = hiza([22, 15, 16, 14, 16])
print("\n" + y3(["normallestirme"] + ORTAM + ["secim"]))
for kural in ("ham sayi", "en iyiye oranlama", "araliga yayma"):
    t = normal(kural, OLCUT)
    print(y3([kural] + [f"{t[o]:.1f}" for o in ORTAM] + [secim(t)]))

OKUR = dict(OLCUT, ekip={o: sum(1 for _, _, k in EKIP if o in k) for o in ORTAM})
print("\n" + y3(["ekip olcutu"] + ORTAM + ["acik fark"]))
for ad, olcut in (("yazabilen", OLCUT), ("okuyabilen", OKUR)):
    t = normal("ham sayi", olcut)
    s = sorted((t[o] for o in ORTAM), reverse=True)
    print(y3([ad] + [f"{t[o]:.0f}" for o in ORTAM] + [f"{s[0] - s[1]:.0f}"]))
```

```

normallestirme           genel amacli   analiz odakli   bildirimsel           secim
ham sayi                         17.0            12.0          18.0     bildirimsel
en iyiye oranlama               211.1           139.7         212.7     bildirimsel
araliga yayma                   175.0           100.0         140.0    genel amacli

ekip olcutu              genel amacli   analiz odakli   bildirimsel       acik fark
yazabilen                          17              12            18               1
okuyabilen                         19              13            21               2
```

Üç kural aynı dokuz sayıdan üç ayrı puan üretiyor ve üçüncüsü seçimi çeviriyor. Ham toplamdaki bir
puanlık fark, oranlamada 211,1 ile 212,7 arasındaki 1,6 puana karşılık gelir ve seçim aynı kalır.
Aralığa yayma kuralı her ölçütte en düşük değeri sıfırlar ve ortadaki değeri aralığın genişliğine
göre bastırır: üretim ölçütünde aralık 8 birimdir, bu yüzden 3 puanlık ortanca değer 25 puana iner
ve toplamlar 175 ile 140 olur. Hiçbir ham sayı değişmeden sıralama değişmiştir.

İkinci tablo bir kademe içeridekini gösteriyor. Ekip ölçütü yazabilen kişi sayısı diye tanımlandı;
okuyabilen kişi sayısı da savunulabilir bir tanımdır. Tanım değişince bu kümede seçim değişmez, ama
toplamlar 19 / 13 / 21 olur ve açık fark 1 puandan 2 puana çıkar; aynı seçim iki katı güvenle
verilmiş görünür.

Üç ölçütü tek puana indiren işlev bir soyutlamadır; ağırlık, normalleştirme kuralı ve ölçüt tanımı
onun varsayılanlarıdır. Varsayılanı yazılmayan bir puan bir ölçüm değil, bir seçimdir.

#### Özet

- Üç ortam üç ölçütte sayılınca her ölçüt başka bir ortamı öne çıkarır: ekip 7 / 2 / 4, ekosistem
  7 / 9 / 5, üretime taşıma 3 / 1 / 9.
- Eşit ağırlıkta ham toplam 17 / 12 / 18 olur ve seçim bir puanlık farkla belirlenir.
- Seçim üç ölçütün üçünde de değişir, eşikleri ayrıdır: ekip 2, ekosistem 2, üretim 0. Üçüncü ortam
  ancak ekosistem ağırlığı 4 olunca 39 puanla seçilir.
- Üç normalleştirme kuralı 17 / 12 / 18, 211,1 / 139,7 / 212,7 ve 175 / 100 / 140 puan verir;
  üçüncüsü hiçbir ham sayı değişmeden seçimi çevirir.
- Ekip ölçütü okuyabilen diye tanımlanınca seçim aynı kalır, açık fark 1 puandan 2 puana çıkar.

#### Kurs Kapanışı

Yirmi iki dersin hesabı tek bir tabloda duruyor ve her sayı kendi dersinden okundu.

| Ders | Elle yapınca | Soyutlamayla | Gizlenen karar |
|---|---|---|---|
| Sayısal 01 çalışma ortamı | 2/17 adım aynı | 11/17 kilit dosyasıyla | 5 karar, 1.116 → 1.264 |
| Sayısal 02 diziler | 22 satır, 188.864 bayt | 2 satır, 30.320 bayt | 2 karar, 31,896 ↔ 26,839 |
| Sayısal 03 vektörleştirme | 10 satır, 1 geçiş | 2 satır, 3 geçiş | 3 karar, 102.034 → 1.240 |
| Sayısal 04 yayılım | 30.320 bayt ek | 0 bayt ek | 2 karar, 1.260 → 1.587.600 |
| Sayısal 05 doğrusal cebir | 823.059.744 işlem | 704 işlem | 2 karar, yüzde 50,09 sapma |
| Çerçeve 01 seri ve çerçeve | 781.710 karşılaştırma | 2.361 geçiş | 2 karar, 3.316,42 m³ fark |
| Çerçeve 02 okuma ve yazma | 7 dönüşüm kuralı | 1 okuma çağrısı | 3 karar, 1.123 hücre eksiğe |
| Çerçeve 03 indeksleme | 2.658 karşılaştırma | 21 geçiş | 3 karar, 95 / 94 / 617 satır |
| Çerçeve 04 filtreleme | 5.034 geçiş, 16.184 bayt | 6.398 geçiş, 3.399 bayt | 2 karar, 1.835 / 2.026 / 1.915 |
| Çerçeve 05 gruplama | 15.995 anahtar araması | 3.199 geçiş | 2 karar, 160 satır düşer |
| Çerçeve 06 birleştirme | 2.616.782 karşılaştırma | 4.017 geçiş | 2 karar, 12.250,65 m³ fazla |
| Çerçeve 07 zaman serisi | 41.587 geçiş | 3.212 geçiş | 3 karar, 4.850,82 ↔ 7.882,58 |
| Çerçeve 08 metin işlemleri | 9 satır, 0 satır çıktı | 3 çağrı, 3.199 satır | 3 karar, 256 satır sessizce |
| Başarım 01 bellek kullanımı | 173.837 bayt | 3.628 bayt, 47,9 kat | 3 karar, 108 satır basamak değişir |
| Başarım 02 parça parça işleme | 1.152.229 bayt tepe | 36.132 bayt, 32 kat | 3 karar, 1.035 → 0 abone |
| Başarım 03 sütunlu motorlar | 19.194 değer | 2.835 değer, 6,8 kat | 3 karar, 2.835 → 4.883 değer |
| Başarım 04 paralel ve dağıtık | 3.199 birim | 8.921 birim, eşik 10.833 | 3 karar, 10.833 → 20.119 eşik |
| Başarım 05 sorguyu veriye götürmek | 3.199 satır taşınır | 5 satır, 1.919 kat | 2 karar, 1.919 → 11 kat |
| Alternatif 01 ifade biçimleri | 6 deyim, 6 ara değişken | 1 deyim, 0 ara değişken | 5 karar, 1,473 m³ açıklık |
| Alternatif 02 fiil zinciri | 7 deyim, 6 ara değişken | 1 deyim, 0 ara değişken | 2 karar, 7,301 → 0,593 m³ |
| Alternatif 03 grafik katmanları | 9 bağımsız değişken | 5 katman, 5 yeniden kullanılan | 2 karar, 921 ↔ 24,674 |
| Alternatif 04 ortam seçimi | 3 ölçüt, 3 önde gelen | 1 puan, 1 seçim | 3 karar, 1 puanlık fark |

İkinci ve üçüncü sütunun yönü sabit değildir: bazen soyutlama işi kısaltır (2.616.782 karşılaştırma
4.017 geçişe iner), bazen kısaltmaz (11.340 çarpma iki yazımda da aynıdır), bazen bedeli artırır
(30.320 bayt 90.960 bayta çıkar). Kazancın yönü soyutlamadan değil, ölçülen işten gelir.

Dördüncü sütun kursun kuralıdır: **bir soyutlama işi kısalttığı kadar kararı da gizler;
varsayılanını bilmediğin bir işlev, senin adına karar veren bir işlevdir. Varsayılanı yazılmayan
çağrı ölçülmemiş sayılır.** Yirmi iki dersin adıyla yazdığı **gizlenen karar sayısı 60'tır**. Bu
sayı bir kusur değil, kursun çıktısıdır: sayılmayan varsayılan sıfır görünür ve sıfır görünen
varsayılan her çağrıda sessizce uygulanır.

Yirmi iki derste araç takımı kuruldu ve her varsayılanı yazıldı. Ama araç hâlâ yalnız **ne olduğunu**
söylüyor: doğu bölgesinin ortalaması 24,674 m³, kuzeyin 20,210 m³. Bu sayıların bir örneklemden mi
kütleden mi geldiği sorulmadı; aradaki 4,464 m³'lük farkın gerçek mi gürültü mü olduğu da sorulmadı.
Sonraki kurs, İstatistiksel Analiz ve Keşifsel Veri Analizi, bu iki soruyu ele alır.

