İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat