İçeriğe geç
academia.sh

Ders 01 / 13

Keşifsel Analizin Amacı

Python ile Veri Analizi kursunun bıraktığı iki sayı yeniden ele alınır: doğu ortalaması 24,674 m³, kuzey 20,210 m³, fark 4,464 m³. Küme bir kütle değil örneklemdir — ağdaki 1.400 sayaçtan 1.329'u kayda girmiş, 3.987 olası okumadan 3.199'u gözlenmiştir, kapsam yüzde 80,2. Bölge etiketleri 2.000 kez rastgele yeniden dağıtıldığında hiçbir koşum 4,463 m³'e ulaşmaz; en uç karıştırma 1,754 m³ verir, yani gözlenen farkın payı 2.000'de 1'den küçüktür. Aynı yordam kuzey bölgesinin tek ve çift abone numaralı satırlarına uygulandığında gözlenen 0,232 m³'lük fark 2.000 koşumun 1.314'ünde aşılır. On denetimlik bir tarama 8 soru üretir ve 0 karar verir.

İçindekiler

Python ile Veri Analizi kursu yirmi iki derste bir araç takımı kurdu ve her varsayılanını yazdı. Kapanışta iki sayı kaldı: doğu bölgesinin ortalama tüketimi 24,674 m³, kuzeyin 20,210 m³. Rapor aradaki farkı 4,464 m³ olarak yazdı ve orada durdu. İki soru sorulmadı: bu sayılar ağdaki bütün sayaçları mı özetliyor, yoksa okunabilmiş bir kısmını mı, ve 4,464 m³’lük fark bir bölge farkı mı, yoksa rastlantının aynı büyüklükte üretebildiği bir dalgalanma mı?

Bu ders o iki soruyu sorar ve kursun ölçü birimini kurar. Bundan sonra her sayının yanında ikinci bir sayı durur: oynama payı — hiçbir şey değişmeden o değerin ne kadar oynayabildiği. Kursun kuralı buradan çıkar: yanında oynama payı yazılmayan bir fark, bulgu değil bir gözlemdir. Payı yazılmayan sayı ölçülmemiş sayılır.

  • EA1. Küme kurgudur ve K03–K04’ten devralınır: 3.199 satırlık uzun biçimli okuma tablosu, 1.329 abone, üç dönem, beş bölge, 10/25/40 m³ tarife basamakları. Tohum koda yazılıdır, sayılar koşumdan koşuma aynıdır.
  • EA2. Keşifsel veri analizi (exploratory data analysis) hipotez üretir, doğrulamaz. Çıktısı bir karar değil, cevaplanacak soruların listesidir; doğrulama ayrı bir iştir.
  • EA3. Bu küme bir kütle değil, bir örneklemdir. Gözlenen her özet, kütlenin değil elde bulunan satırların özetidir ve başka bir örneklemde başka çıkardı.
  • EA4. Oynama payı bu kursta yeniden karıştırma (permutation) ile ölçülür: grup etiketleri rastgele yeniden dağıtılır, aynı sayı yeniden hesaplanır ve gözlenen değerin kaç koşumda bir aşıldığı sayılır. Dağılım tablosu ya da kritik değer kullanılmaz.
  • EA5. Payın çözünürlüğü koşum sayısıyla sınırlıdır. 2.000 koşumun hiçbirinde aşılmayan bir fark için söylenebilecek şey “payı sıfırdır” değil, “payı 2.000’de 1’den küçüktür”.

Kütle mi, Örneklem mi

# kesif.py — MODELDIR: K03 ve K04'un kurgu olcum agi ayni tohumla yeniden
# uretilir. Kume kurgudur; hicbir gercek abone, sayac ya da bolge temsil edilmez.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
DONEM = ["2026-01", "2026-02", "2026-03"]
TARIFE = [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):
    """Dogrusal eslemeli uretec: tohum koda yazilidir, cikti kosumdan kosuma ayni."""
    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


_abone, _elenen = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    k = {"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(k)
_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, "hane_kisi": k["hane_kisi"],
                      "memnuniyet": k["memnuniyet"],
                      "basamak": 1 if t < TARIFE[0] else 2 if t < TARIFE[1]
                      else 3 if t < TARIFE[2] else 4})
        r()


def ort(v: list[float]) -> float:
    return sum(v) / len(v)


def bolgenin(ad: str) -> list[float]:
    return [o["tuketim_m3"] for o in OKUMA if o["bolge"] == ad]


dogu, kuzey = bolgenin("dogu"), bolgenin("kuzey")
OLASI = len(_gecerli) * len(DONEM)
print(f"kurgu kume (tohum {TOHUM}): {len(OKUMA)} satir, {len(_gecerli)} abone")
print(f"dogu  {len(dogu):4d} satir, ortalama {ort(dogu):.3f} m3")
print(f"kuzey {len(kuzey):4d} satir, ortalama {ort(kuzey):.3f} m3")
print(f"gozlenen fark {ort(dogu) - ort(kuzey):.3f} m3")
print(f"agdaki sayac {HAM}, kayda giren abone {len(_gecerli)}, "
      f"hic girmeyen {HAM - len(_gecerli)}")
print(f"olasi okuma {OLASI}, gozlenen okuma {len(OKUMA)}, eksik {OLASI - len(OKUMA)}, "
      f"kapsam yuzde {100 * len(OKUMA) / OLASI:.1f}")
kurgu kume (tohum 20260218): 3199 satir, 1329 abone
dogu   557 satir, ortalama 24.674 m3
kuzey  921 satir, ortalama 20.210 m3
gozlenen fark 4.463 m3
agdaki sayac 1400, kayda giren abone 1329, hic girmeyen 71
olasi okuma 3987, gozlenen okuma 3199, eksik 788, kapsam yuzde 80.2

Son iki satır soruyu kapatıyor. Ağda 1.400 sayaç vardır, kayda giren abone sayısı 1.329’dur; 71 sayaç hiçbir satırda görünmez. Kayda giren aboneler üç dönem boyunca okunsaydı 3.987 satır olurdu, gözlenen satır 3.199’dur ve kapsam yüzde 80,2’de kalır. Bu küme ağın tamamı değil, ağdan okunabilmiş kısmıdır — yani bir örneklemdir ve rastgele seçilmiş bir örneklem de değildir, çünkü hangi sayacın okunabildiğini seçen şey bir kura değil sahanın kendisidir.

Bunun sonucu doğrudandır: 24,674 m³ doğu bölgesinin ortalama tüketimi değil, doğu bölgesinde okunabilmiş 557 satırın ortalamasıdır. Aynı ağdan başka bir hafta çekilen 557 satır başka bir sayı verirdi. Sorunun kendisi burada değişiyor: “sayı doğru mu” sorusunun yerini “bu sayı ne kadar oynar” sorusu alıyor.

Önceki kursun kapanışı farkı 4,464 m³ yazmıştı, koşan blok 4,463 m³ yazıyor: kapanış iki yuvarlanmış sayıyı çıkardı, blok tam değerleri. Aradaki 0,001 m³ burada hiçbir şeyi değiştirmez, ama rapor edilen sayının nasıl hesaplandığına bağlı olduğunun en küçük örneğidir.

Farkın Oynama Payı

Fark gerçek mi diye sormanın bir yolu, farkın olmadığı bir dünyayı kurup orada aynı büyüklükte bir farkın ne sıklıkla ortaya çıktığını saymaktır. Bölge etiketi tüketim hakkında hiçbir şey söylemiyorsa, etiketleri rastgele yeniden dağıtmak sonucu değiştirmemelidir.

# pay.py — oynama payi: bolge etiketleri rastgele yeniden dagitilir, ayni fark
# yeniden hesaplanir ve gozlenen degerin kac kosumda bir asildigi sayilir.
KOSUM = 2000
ESIK = 1e-9  # ayni fark iki yoldan hesaplaninca son basamak oynar; esitlik sinanmaz


def karistirma_payi(a: list[float], b: list[float], tohum: int):
    """Iki grubun degerleri tek havuzda toplanir. Her kosumda havuzun len(a)
    tanesi rastgele secilip birinci grup sayilir ve fark yeniden olculur."""
    r = uretec(tohum)
    havuz, na, nb = a + b, len(a), len(b)
    toplam, gozlenen = sum(havuz), ort(a) - ort(b)
    asan, en_uc = 0, 0.0
    for _ in range(KOSUM):
        h, s = havuz[:], 0.0
        for i in range(na):
            j = i + int(r() * (na + nb - i))
            h[i], h[j] = h[j], h[i]
            s += h[i]
        fark = s / na - (toplam - s) / nb
        if abs(fark) >= abs(gozlenen) - ESIK:
            asan += 1
        en_uc = max(en_uc, abs(fark))
    return gozlenen, asan, en_uc


def kuzey_yarisi(tek: bool) -> list[float]:
    return [o["tuketim_m3"] for o in OKUMA
            if o["bolge"] == "kuzey" and (o["abone_no"] % 2 == 1) == tek]


KARSILASTIRMA = [("dogu / kuzey", dogu, kuzey, TOHUM + 501),
                 ("kuzey: tek / cift abone no", kuzey_yarisi(True),
                  kuzey_yarisi(False), TOHUM + 502)]
EN = [28, 7, 7, 12, 12, 8]
y = lambda h: "".join(str(v).rjust(EN[i]) if i else str(v).ljust(EN[0])
                      for i, v in enumerate(h))
print(y(["karsilastirma", "n1", "n2", "gozlenen", "en uc", "asan"]))
asilan = {}
for ad, a, b, tohum in KARSILASTIRMA:
    g, asan, en_uc = karistirma_payi(a, b, tohum)
    asilan[ad] = asan
    print(y([ad, len(a), len(b), f"{g:.3f}", f"{en_uc:.3f}", asan]))
print(f"\n{KOSUM} kosum; asan = gozlenen kadar ya da daha buyuk fark ureten kosum")
for ad, asan in asilan.items():
    print(f"{ad:<28}pay " + (f"1/{KOSUM} degerinden kucuk" if asan == 0
                             else f"{asan / KOSUM:.3f}, 100 kosumun "
                                  f"{round(100 * asan / KOSUM)} tanesinde asildi"))
karsilastirma                    n1     n2    gozlenen       en uc    asan
dogu / kuzey                    557    921       4.463       1.754       0
kuzey: tek / cift abone no      452    469       0.232       1.647    1314

2000 kosum; asan = gozlenen kadar ya da daha buyuk fark ureten kosum
dogu / kuzey                pay 1/2000 degerinden kucuk
kuzey: tek / cift abone no  pay 0.657, 100 kosumun 66 tanesinde asildi

İki satır aynı yordamdan geçti ve zıt cevap verdi. Doğu ile kuzey arasındaki 4,463 m³’lük farka 2.000 karıştırmanın hiçbiri ulaşamadı; en uç fark 1,754 m³’tür, gözlenenin yarısından az. Bölge etiketi tüketim hakkında bir şey söylüyor.

İkinci satır bir denetimdir. Abone numarasının tek ya da çift olması tüketimi etkileyemez; bu kırılım tanım gereği gürültüdür ve araç bunu söylüyor: gözlenen 0,232 m³’lük fark 2.000 koşumun 1.314’ünde, yani yüz koşumun altmış altısında aşılmaktadır. Böyle bir farkı rapora “kuzeyde tek numaralı aboneler daha çok tüketiyor” diye yazmak, karıştırmanın üçte iki sıklıkla ürettiği bir şeyi bulgu saymak olur.

Çözünürlük sınırı ilk satırda görünüyor. Sıfır aşan koşum “pay sıfırdır” demek değildir; 2.000 koşumla ölçülebilen en küçük pay 2.000’de 1’dir ve gözlenen fark bunun altındadır. Koşum sayısı on kat artırılsaydı sınır on kat inerdi. Payın kendisi de bir ölçümdür ve kendi çözünürlüğüyle birlikte yazılır.

Yordamın söylemediği şey de yazılır. Karıştırma yalnız “bu büyüklükte bir fark etiketler karışıkken ne sıklıkla çıkar” sorusuna cevap verir; farkın nedenini söylemez. Doğunun tarife tabanı yüksek olabilir, hane büyüklüğü farklı olabilir, ya da doğuda okunabilen sayaçlar sistematik olarak büyük aboneler olabilir. Küçük pay, açıklama yerine geçmez.

Keşif Karar Vermez, Soru Üretir

Bir farkın payını ölçmek keşfin sonu değil bir adımıdır. Keşfin işi kümeyi dolaşıp hangi soruların sorulmaya değer olduğunu çıkarmaktır; aşağıdaki tarama bunu bir ölçüt listesi olarak yazar ve eşiği aşan her ölçütü bir soruya çevirir.

# soru.py — kesfin ciktisi bir karar degil, bir soru listesidir. Her denetim bir
# olcut hesaplar; esigi asan olcut bir karar degil, cevaplanacak bir soru uretir.
from collections import Counter


def ortanca(v: list[float]) -> float:
    s = sorted(v)
    n = len(s)
    return s[n // 2] if n % 2 else (s[n // 2 - 1] + s[n // 2]) / 2


T, S = [o["tuketim_m3"] for o in OKUMA], sorted(o["tuketim_m3"] for o in OKUMA)
bolge_say = Counter(o["bolge"] for o in OKUMA)
donem_say = Counter(o["donem"] for o in OKUMA)
basamak_say = Counter(o["basamak"] for o in OKUMA)
abone_say = Counter(o["abone_no"] for o in OKUMA)
memnun_say = Counter(o["memnuniyet"] for o in OKUMA)
pay = lambda k: 100 * k / len(OKUMA)

DENETIM = [
    ("tuketim", "sifir yazilmis okuma payi", pay(sum(1 for t in T if t == 0)),
     1.0, "ust", "sifir bir olcum mu, okunamamis sayac mi"),
    ("kapsam", "gozlenen / olasi okuma", 100 * len(OKUMA) / OLASI,
     95.0, "alt", "okunanlar okunamayanlardan farkli mi"),
    ("abone_no", "uc donemi tam olan abone payi",
     100 * sum(1 for v in abone_say.values() if v == 3) / len(abone_say),
     90.0, "alt", "eksik donem rastgele mi dagilmis"),
    ("bolge", "en kucuk / en buyuk bolge",
     100 * min(bolge_say.values()) / max(bolge_say.values()),
     60.0, "alt", "kucuk bolgenin payi ne kadar buyur"),
    ("bolge", "bolge etiketi bos satir payi",
     pay(sum(1 for o in OKUMA if not o["bolge"])), 0.0, "ust",
     "etiketsiz satirlar hangi bolgeye ait"),
    ("donem", "donemler arasi satir acikligi",
     100 * (max(donem_say.values()) - min(donem_say.values())) / max(donem_say.values()),
     10.0, "ust", "donem farki davranis mi, takvim mi"),
    ("tuketim", "ortalama - ortanca acikligi",
     100 * (ort(T) - ortanca(T)) / ortanca(T), 2.0, "ust",
     "dagilim carpik mi, hangi ozet raporlanir"),
    ("tuketim", "en buyuk deger / ucuncu ceyrek", max(S) / S[int(0.75 * len(S))],
     2.5, "ust", "uc degerler ortalamayi ne kadar tasir"),
    ("basamak", "en seyrek basamagin payi", pay(min(basamak_say.values())),
     5.0, "alt", "seyrek basamagin orani ne kadar oynar"),
    ("memnuniyet", "en kalabalik memnuniyet payi", pay(max(memnun_say.values())),
     30.0, "ust", "siralayici olcekte ortalama mesru mu"),
]

E = [12, 32, 8, 7]
z = lambda h: "".join(str(v).ljust(E[i]) if i < 2 else str(v).rjust(E[i])
                      for i, v in enumerate(h[:4])) + "  " + str(h[4])
print(z(["alan", "olcut", "deger", "esik", "uretilen soru"]))
soru = 0
for alan, olcut, deger, esik, yon, metin in DENETIM:
    tetik = deger > esik if yon == "ust" else deger < esik
    soru += tetik
    print(z([alan, olcut, f"{deger:.1f}", f"{esik:.1f}", metin if tetik else "-"]))
print(f"\n{len(DENETIM)} denetim yuruldu, {soru} soru uretildi, 0 karar verildi")
alan        olcut                              deger   esik  uretilen soru
tuketim     sifir yazilmis okuma payi            3.7    1.0  sifir bir olcum mu, okunamamis sayac mi
kapsam      gozlenen / olasi okuma              80.2   95.0  okunanlar okunamayanlardan farkli mi
abone_no    uc donemi tam olan abone payi       66.5   90.0  eksik donem rastgele mi dagilmis
bolge       en kucuk / en buyuk bolge           51.7   60.0  kucuk bolgenin payi ne kadar buyur
bolge       bolge etiketi bos satir payi         0.0    0.0  -
donem       donemler arasi satir acikligi       33.5   10.0  donem farki davranis mi, takvim mi
tuketim     ortalama - ortanca acikligi          3.9    2.0  dagilim carpik mi, hangi ozet raporlanir
tuketim     en buyuk deger / ucuncu ceyrek       2.1    2.5  -
basamak     en seyrek basamagin payi             1.8    5.0  seyrek basamagin orani ne kadar oynar
memnuniyet  en kalabalik memnuniyet payi        33.4   30.0  siralayici olcekte ortalama mesru mu

10 denetim yuruldu, 8 soru uretildi, 0 karar verildi

On denetim yürüdü ve sekiz soru çıktı. Tabloda tek bir karar yoktur: hiçbir satır “şu satırlar elenmeli” ya da “şu bölge daha çok tüketiyor” demez. Keşfin çıktısı budur ve doğrulamadan ayrıldığı yer tam burasıdır. Doğrulama tek bir soruyu alır ve ona bir cevap üretir; keşif kümeyi dolaşır ve cevaplanacak soruların listesini üretir.

Eşiği aşmayan iki satır da bilgi taşıyor. Bölge etiketi boş satır yoktur, çünkü bölge abone kaydından gelir ve Veri Toplama ve Hazırlama kursunda eşleştirilmiştir; en büyük değerin üçüncü çeyreğe oranı 2,1’dir, yani üst uçta aşırı bir kopukluk yoktur. Denetimin geçmesi de bir ölçüm sonucudur.

Eşiklerin kendisi de birer karardır. Yüzde 95’lik kapsam eşiği yerine yüzde 75 yazılsaydı ikinci satır soru üretmezdi; ortalama–ortanca açıklığı için yüzde 2 yerine yüzde 5 seçilseydi yedinci satır susardı. Tablodaki sekiz sayısı kümenin bir özelliği değil, kümeyle eşik listesinin birlikte ürettiği bir sayıdır. Bu yüzden eşikler koda yazılır ve tabloda görünür.

Sekiz sorunun bedeli sonraki konuda ödenir. Keşif sırasında bakılan her kırılım, sınamanın payını büyüten bir borçtur: on ölçüte bakıp en dikkat çekenini sınamak, tek bir ölçüte bakıp onu sınamakla aynı şey değildir. Bu ders yalnız borcun açıldığını yazar.

Özet

  • Küme bir kütle değil örneklemdir: 1.400 sayaçtan 1.329’u kayda girmiş, 3.987 olası okumadan 3.199’u gözlenmiştir ve kapsam yüzde 80,2’dir.
  • Doğu ile kuzey arasındaki gözlenen fark 4,463 m³’tür; bölge etiketleri 2.000 kez rastgele yeniden dağıtıldığında en uç fark 1,754 m³ kalır ve gözlenene hiçbir koşumda ulaşılmaz.
  • Aynı yordam kuzeyin tek ve çift abone numaralı satırlarına uygulandığında gözlenen 0,232 m³’lük fark 2.000 koşumun 1.314’ünde aşılır; araç gürültüyü gürültü olarak bildirir.
  • Payın çözünürlüğü koşum sayısıdır: 2.000 koşumda hiç aşılmayan bir fark için söylenebilecek şey “payı sıfırdır” değil, “payı 2.000’de 1’den küçüktür”.
  • Keşfin çıktısı karar değil sorudur: on denetimlik tarama 8 soru üretmiş, 0 karar vermiştir ve bu sayı kümeyle eşik listesinin ortak ürünüdür.

Sonraki Adım

Bu ders iki bölge ortalamasının farkına baktı ve yanına payını yazdı. Ortalamanın kendisi sorgulanmadı: 557 satırın ortalaması 24,674 m³ diye yazıldığında o satırların nasıl dağıldığı hakkında hiçbir şey söylenmiş olmaz ve aynı ortalamayı veren dağılımlar birbirinden çok farklı olabilir. Sonraki ders tek değişkenli incelemeyi ele alır: aynı ortalamayı veren iki dağılım kurulur, beş sayı özeti ve aralık sayımları basılır, ortalama ile ortanca arasındaki açıklığın çarpıklığı nasıl ele verdiği ve elenmeyen aykırı değerlerin özeti ne kadar oynattığı sayılır.

İ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