Ders 10 / 22
Gruplama ve Toplulaştırma
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.
İçindekiler
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
nsü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
# 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
# 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ı
# 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
# 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.