Ders 07 / 22
Veri Okuma ve Yazma
3.199 satır ve 7 sütunluk 139.238 baytlık bir dosya tek çağrıyla okunuyor ve tip çıkarımı 7 sütunun 3'ünü yanlış tiple veriyor: abone numarası tam sayı olduğu için ortalaması 10.690,5 diye hesaplanabiliyor, tarife basamağının ortalaması 27,4 m³ çıkıyor, endeks sütunundaki 3.008 sayısal değer 80 'yok' yüzünden metne dönüyor ve sütunun en büyük değeri 'yok' oluyor. Çıkarım bütün satırlar yerine ilk 100 satırdan yapılınca geçiş sayısı 22.393'ten 700'e iner, ama sayaç numarası tam sayıya kayar: 2.077. satırda başlayan harfli kodların 1.123 hücresi hiçbir uyarı üretmeden eksiğe döner ve gidiş dönüşte 3.199 hücre değişir. Biçim kaybı her iki çıkarımda da 422 hücrededir.
İçindekiler
Bir önceki derste küme kod içinde üretildi ve her sütunun tipi yazıldığı yerde belliydi. Gerçek bir
küme ise dosyadan gelir ve ayırıcıyla bölünmüş bir dosyada tip diye bir şey yoktur: her hücre bir
metin parçasıdır. 10001 ile "10001" arasındaki farkı dosya taşımaz, okuyan taraf üretir.
Bu üretim işine tip çıkarımı denir ve bir veri çerçevesi arayüzünün en sessiz kararıdır. Tek bir okuma çağrısı yazılır, karşılığında sütunları tiplenmiş bir çerçeve gelir ve hangi sütunun neden o tipe düştüğü hiçbir yerde görünmez. Veri Toplama ve Hazırlama kursunun yapısal ayrıştırma dersi alanın yerinin kaymasını ölçmüştü; orada seçici yanlış alandan okuyordu. Burada alan yerindedir ve kayan şey tiptir. Bu dersin ölçüsü iki sayıdır: çıkarım kaç sütunu yanlış tiple verdi ve aynı dosya okunup geri yazıldığında kaç hücre değişti.
- VC6. Dosya biçimi tip taşımaz. Ayırıcıyla bölünmüş bir dosyada her hücre metindir; tip,
okuma anında kararla üretilir. Küme kurgudur ve
pythonile, yalnız standart kitaplıkla modellenir. - VC7. Tip çıkarımı, bir sütunun bütün değerleri aynı tipe ayrışıyorsa o tipi seçmektir. Tek bir değer ayrışmazsa sütunun tamamı metne düşer; kural sütun genelindedir, hücre başına değildir.
- VC8. Yanlış tip burada işlemsel tanımlanır: tip hatası vermeyen ama anlamsız bir sonuç üreten ya da meşru bir işlemi sessizce boş döndüren tip yanlıştır.
- VC9. Gidiş dönüş denetimi, aynı dosyanın okunup geri yazılmasıdır. Değişen hücre sayısı, okuma kararının kaybettiği bilgiyi doğrudan sayar.
Dosyada Tip Yoktur
# 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()] import csv, os SUTUN = ["abone_no", "sayac_no", "donem", "endeks", "tuketim_m3", "tarife_kodu", "bolge"] bolge_of = {k["abone_no"]: k["bolge"] for k in abone} def sayac_kodu(no: int) -> str: """Numaralandirma semasi 10900'uncu aboneden sonra harfli koda gecti.""" return f"A{no - 9000:05d}" if no >= 10900 else f"{no - 9000:06d}" def endeks_hucresi(no: int, d: int, kumul: float) -> str: """Saha formundan gelen endeks: bos birakilmis ya da 'yok' yazilmis olabilir.""" u = uretec(TOHUM + 9100 + no * 4 + d)() return "" if u < 0.033 else "yok" if u < 0.055 else str(100000 + (no - 10000) * 37 + int(kumul)) satirlar, kumul = [], {} for o in UZUN: no, t = o["abone_no"], o["tuketim_m3"] kumul[no] = kumul.get(no, 0.0) + t satirlar.append([str(no), sayac_kodu(no), o["donem"], endeks_hucresi(no, DONEM.index(o["donem"]), kumul[no]), f"{t:.2f}", "10" if t <= 10 else "25" if t <= 25 else "40", bolge_of[no]]) with open("okuma.csv", "w", newline="") as f: yazici = csv.writer(f) yazici.writerow(SUTUN) yazici.writerows(satirlar) print(f"okuma.csv: {len(satirlar)} satir x {len(SUTUN)} sutun = " f"{len(satirlar) * len(SUTUN)} hucre, {os.path.getsize('okuma.csv')} bayt") print(f"endeks sutunu: {sum(1 for s in satirlar if s[3] == '')} bos, " f"{sum(1 for s in satirlar if s[3] == 'yok')} 'yok' yazili") ilk = next(i for i, s in enumerate(satirlar) if s[1].startswith("A")) print(f"sayac_no sutununda ilk harfli kod {ilk + 1}. satirda: {satirlar[ilk][1]}")
okuma.csv: 3199 satir x 7 sutun = 22393 hucre, 139238 bayt endeks sutunu: 111 bos, 80 'yok' yazili sayac_no sutununda ilk harfli kod 2077. satirda: A01900
Dosya yedi sütun taşıyor ve üçü çıkarımı zorlayacak biçimde kurulmuştur. Sayaç numarası sıfırla doldurulmuş altı haneli bir koddur, ama numaralandırma şeması bir noktada değişmiş ve 10.900’üncü aboneden sonrası harfle başlayan bir kod almıştır; dosya abone numarasına göre sıralı olduğu için ilk harfli kod ancak 2.077. satırda görünür. Endeks sütunu saha formundan gelir ve iki ayrı eksiklik biçimi taşır: 111 hücre boştur, 80 hücreye elle “yok” yazılmıştır. Tarife basamağı bir kategoridir ama rakamla kodlanmıştır.
Bunların hiçbiri bozuk veri değildir. Üçü de sahada geçerli, kaynağında anlamlı ve okunabilir kayıtlardır. Sorun, hepsinin tek bir çıkarım kuralının önünden geçecek olmasıdır.
Yazma tarafı da aynı sessizlikte karar verir. Bir çerçeve dosyaya yazılırken en az dört şey seçilir: alan ayırıcısı, ondalık ayırıcısı, ayırıcı içeren değerin nasıl tırnaklanacağı ve eksik değerin hangi metinle temsil edileceği. Bu derste eksik değer boş hücre olarak yazılır; bu, eksikliğin kaynaktaki bütün biçimlerini tek bir biçime çökerten bir karardır. Okuma bir tip üretir, yazma o tipi geri metne çevirir ve iki dönüşüm birbirinin tersi olmak zorunda değildir. Aradaki farkı ölçmenin ucuz ve eksiksiz yolu, sonucu kaynakla karşılaştırmaktır.
Çıkarımın Verdiği Karar
# cikarim.py — tip cikarimi: bir sutunun butun degerleri ayni tipe ayrisiyorsa o # tip secilir, ayrismiyorsa sutun metin kalir. Yalniz bos hucre eksik sayilir. def ayristir(h: str): if h == "": return None, "eksik" try: return int(h), "tam" except ValueError: pass try: return float(h), "ondalik" except ValueError: return h, "metin" def cikarim(satirlar, ornek=None): """Sutun tipini ornekten cikarir; ornek None ise butun satirlar taranir.""" kesit = satirlar if ornek is None else satirlar[:ornek] tip, gecis = [], 0 for j in range(len(SUTUN)): gorulen = set() for s in kesit: gecis += 1 gorulen.add(ayristir(s[j])[1]) gorulen.discard("eksik") tip.append("tam" if gorulen == {"tam"} else "ondalik" if gorulen and gorulen <= {"tam", "ondalik"} else "metin") return tip, gecis def oku(satirlar, tip): """Cikarilan tiplerle okur; sutun tipine ayrismayan hucre sessizce eksige doner.""" tablo, dusen = [], 0 for s in satirlar: kayit = [] for j, h in enumerate(s): d, t = ayristir(h) if d is None or tip[j] == "metin": kayit.append(None if d is None else h) elif t == "metin" or (tip[j] == "tam" and t == "ondalik"): kayit.append(None) dusen += 1 else: kayit.append(int(h) if tip[j] == "tam" else float(h)) tablo.append(kayit) return tablo, dusen def geri_yaz(tablo): return [["" if v is None else repr(v) if isinstance(v, float) else str(v) for v in k] for k in tablo] tam_tip, tam_gecis = cikarim(satirlar) ornek_tip, ornek_gecis = cikarim(satirlar, 100) tablo, dusen = oku(satirlar, tam_tip) o_tablo, o_dusen = oku(satirlar, ornek_tip) donen, o_donen = geri_yaz(tablo), geri_yaz(o_tablo) EN = [14, 12, 12, 12, 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(["sutun", "tum satir", "ilk 100", "donusen", "donusen/100"])) for j, ad in enumerate(SUTUN): d1 = sum(1 for i in range(len(satirlar)) if donen[i][j] != satirlar[i][j]) d2 = sum(1 for i in range(len(satirlar)) if o_donen[i][j] != satirlar[i][j]) print(y([ad, tam_tip[j], ornek_tip[j], d1, d2])) print(f"\ntam tarama {tam_gecis} hucre gecisi, ilk 100 satirdan cikarim {ornek_gecis} gecis") print(f"tam tiple okumada {dusen} hucre sessizce eksige dondu, " f"ornek tiple okumada {o_dusen} hucre")
sutun tum satir ilk 100 donusen donusen/100 abone_no tam tam 0 0 sayac_no metin tam 0 3199 donem metin metin 0 0 endeks metin metin 0 0 tuketim_m3 ondalik ondalik 422 422 tarife_kodu tam tam 0 0 bolge metin metin 0 0 tam tarama 22393 hucre gecisi, ilk 100 satirdan cikarim 700 gecis tam tiple okumada 0 hucre sessizce eksige dondu, ornek tiple okumada 1123 hucre
Tabloda iki ayrı olay var. Birincisi son sütunlarda: sayaç numarası bütün satırlar tarandığında metin, ilk 100 satır tarandığında tam sayı çıkıyor. Çıkarım yanlış yapmıyor; ilk 100 satırda o sütunun bütün değerleri gerçekten tam sayıya ayrışıyor. Örneklemin gördüğü şey doğrudur, kümenin tamamı için yanlıştır ve aradaki farkı yalnız 2.077. satır bilir.
Bu kayışın bedeli iki sayıda görünüyor. Sayaç numarası tam sayı okunduğunda gidiş dönüşte 3.199 hücrenin tamamı değişir: 2.076 hücre baştaki sıfırlarını kaybeder, harfli 1.123 hücre hiçbir tipe ayrışmadığı için sessizce eksiğe döner. Bir hata satırı yoktur; sütun okunmuş, çerçeve kurulmuş, satır sayısı 3.199 kalmıştır. Kaybın tek izi, o abonelerin sayaç numarasının artık boş olmasıdır.
İkinci olay bütün çıkarımlarda ortaktır: tüketim sütununda 422 hücre gidiş dönüşte değişiyor.
Kaynakta değerler iki ondalıklı sabit biçimle yazılmıştır; okunduğunda sayıya döner ve geri
yazıldığında sayının kendi kısa gösterimini alır. 0.00 hücresi 0.0 olur, 20.10 hücresi
20.1 olur. Değer aynıdır, metin değildir. Bir gidiş dönüş denetimi bunu yakalar ve tipin
kendisinden değil biçiminden kaynaklandığını söyler; ikisini ayırmak, hangi kaybın önemli
olduğuna karar vermenin ilk adımıdır.
Kazanç ve Gizlenen Karar
# belirti.py — yanlis tipin belirtisi: hata vermeyen ama anlamsiz ya da sessizce # bos donen bir islem. Bayt olcusu gidis donusteki dosya boyutundan alinir. import io sut = lambda j: [k[j] for k in tablo] print(f"abone_no tam sayi okundu : ortalama abone numarasi " f"{round(sum(sut(0)) / len(satirlar), 1)}") print(f"tarife_kodu tam sayi okundu: ortalama tarife basamagi " f"{round(sum(sut(5)) / len(satirlar), 1)} m3") endeks = [v for v in sut(3) if v is not None] print(f"endeks metin okundu : {sum(1 for v in endeks if v != 'yok')} sayisal deger " f"metne dondu, sutunun en buyugu {max(endeks)!r}") def bayt(kayitlar) -> int: b = io.StringIO() yazici = csv.writer(b) yazici.writerow(SUTUN) yazici.writerows(kayitlar) return len(b.getvalue().encode()) print(f"\nbayt : kaynak {bayt(satirlar)}, tam cikarimla geri yazim {bayt(donen)}, " f"ornek cikarimla {bayt(o_donen)}") print(f"gecis : tam tarama {tam_gecis}, ilk 100 satir {ornek_gecis}") print(f"kod : sutun basina elle donusum 7 kural, cikarimli okuma 1 cagri") yanlis = sum(1 for j in (0, 3, 5) if tam_tip[j] in ("tam", "metin")) print(f"sonuc : {len(SUTUN)} sutunun {yanlis} tanesi yanlis tiple okundu; ornek " f"100 satirdan alininca {yanlis + 1} oldu ve {o_dusen} hucre sessizce eksige dondu")
abone_no tam sayi okundu : ortalama abone numarasi 10690.5 tarife_kodu tam sayi okundu: ortalama tarife basamagi 27.4 m3 endeks metin okundu : 3008 sayisal deger metne dondu, sutunun en buyugu 'yok' bayt : kaynak 139238, tam cikarimla geri yazim 138816, ornek cikarimla 127926 gecis : tam tarama 22393, ilk 100 satir 700 kod : sutun basina elle donusum 7 kural, cikarimli okuma 1 cagri sonuc : 7 sutunun 3 tanesi yanlis tiple okundu; ornek 100 satirdan alininca 4 oldu ve 1123 hucre sessizce eksige dondu
İlk üç satır yanlış tipin belirtisini gösteriyor ve üçünde de bir hata mesajı yoktur. Abone
numarasının ortalaması 10.690,5 çıkar; bu bir sayıdır, hesaplanabilir, raporlanabilir ve hiçbir
şey ifade etmez. Tarife basamağının ortalaması 27,4 m³ çıkar; basamaklar 10, 25 ve 40’tır,
27,4 diye bir basamak yoktur. Endeks sütununun 3.008 sayısal değeri metne dönmüştür ve
sütunun en büyük değeri 'yok' olur, çünkü metin karşılaştırması harfi rakamdan büyük sayar.
Üçünün ortak yanı, yanlışlığın çerçevede değil kullanımda ortaya çıkmasıdır. Çerçeve kurulduğunda hiçbir şey bozuk görünmez: satır sayısı doğrudur, sütun adları yerindedir, hücreler doludur. Yanlış tip ancak o sütuna bir işlem uygulandığında konuşur ve konuştuğunda da hata değil sayı üretir. Bu yüzden tipin denetimi, çerçeve kurulduktan sonra değil, okuma kararının yanında yapılır.
Kazanç tarafı açık: yedi sütun için ayrı ayrı dönüşüm kuralı yazmak yerine tek bir okuma çağrısı yazılır ve çerçeve tiplenmiş gelir. Tam tarama 22.393 hücre geçişi tutar; çıkarım ilk 100 satırla sınırlanırsa geçiş 700’e, yani otuz ikide bire iner. Bayt tarafında da kazanç görünür: tam çıkarımla geri yazılan dosya 138.816 bayttır, örnek çıkarımla 127.926 bayta düşer.
Gizlenen karar tam da o bayt düşüşünün içindedir. 10.890 baytlık tasarruf bir sıkıştırma değil, bir kayıptır: 1.123 sayaç numarası yazılmamıştır. Örneklem büyüklüğü hiçbir yerde yazılmadan seçilmiş, çıkarım o seçime göre karar vermiş, okuma hiçbir uyarı üretmemiştir. Varsayılanı yazılmayan bir okuma çağrısı, kaç satıra baktığını da söylemeyen bir çağrıdır ve bu kursun kuralı gereği ölçülmemiş sayılır.
Özet
- Ayırıcıyla bölünmüş bir dosya tip taşımaz; tip okuma anında çıkarımla üretilir ve kural sütun genelindedir: tek bir ayrışmayan değer sütunun tamamını metne düşürür.
- Bütün satırlar tarandığında 7 sütunun 3’ü yanlış tiple gelir: abone numarasının ortalaması
10.690,5, tarife basamağının ortalaması 27,4 m³ diye hesaplanabilir, endeksin 3.008 sayısal
değeri metne döner ve sütunun en büyüğü
'yok'olur. - Çıkarım ilk 100 satırdan yapılınca geçiş 22.393’ten 700’e iner, ama sayaç numarası tam sayıya kayar: 2.077. satırda başlayan harfli kodların 1.123 hücresi uyarısız eksiğe döner.
- Gidiş dönüş denetimi kaybı doğrudan sayar: örnek çıkarımla 3.199 hücre değişir, dosya 139.238 bayttan 127.926 bayta iner ve düşüşün tamamı yazılmamış sayaç numarasıdır.
- Biçim kaybı tipten ayrı bir kalemdir: iki ondalıklı sabit yazımın kısa gösterime dönmesi her iki çıkarımda da 422 hücreyi değiştirir ve hiçbir değeri bozmaz.
Sonraki Adım
Küme artık bir çerçevededir ve sütunlarının tipi bilinerek okunmuştur. Sıradaki soru satıra nasıl erişileceğidir. Bir çerçeveden “10.100 ile 10.200 arasındaki aboneler” istendiğinde iki ayrı yol vardır: etiket dizinine göre adla erişmek ve sıraya göre konumla erişmek. İki yol aynı aralığı tarif ediyormuş gibi yazılır, aynı adı taşır, yan yana durur. Sonraki ders aynı dilimi iki erişim biçiminde çalıştırır ve tek bir sayıyı sorar: her biri kaç satır döndürüyor ve aralığın son ucu sonuca dahil mi.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.