Ders 06 / 22
Seri ve Veri Çerçevesi
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.
İçindekiler
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
pythonile 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
# 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
# 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
# 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.