Ders 19 / 22
İstatistiksel 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.
İçindekiler
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 ifadede 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 ifadede 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.
# 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.
# 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 kümeyi iki kez dolaşır: bir kez toplam ve adet için, bir kez bölme için. 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 on beş
deyimden fazla 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.
# 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 altı deyimin beşi bir karara karşılık gelir ve her biri değiştirilebilir bir satırdır. 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.