Ders 11 / 16
Geriye Dönük Doldurma
Değişen bir kuralın geçmiş dönemlere uygulanmasının sayılması: kural on iki dönemin tamamına uygulandığında 12 dönem ve 2200 satır yeniden hesaplanıyor, yalnız iki döneme uygulandığında 2 dönem ve 400 satır yeterli oluyor ama rapor tam uygulamadan 16,98 ile 29,57 arasında sapıyor. Karışık rapor altı beklentinin hiçbirini bozmuyor, kendi içinde tutarlı çıkıyor ve kuralın hücrede yarattığı yüzde 2,75'lik sıçrama doğal dönem oynaklığının ortancasının altında kaldığı için hiçbir denetime takılmıyor. Sapmanın kaynağı veri değil hangi dönemlerin yeniden hesaplandığıdır; satıra kural sürümü damgalandığında karışıklık tek bir karşılaştırmayla görünür oluyor.
İçindekiler
Bir önceki dersin son bloğu işyeri tüketimini 1,05 katsayıyla raporlayan bir kural yazdı ve altı beklentinin hiçbiri bozulmadı. Beklentiler haklıydı, çünkü ortada bir bozulma yoktu: kural kasıtlı olarak değişmişti. Orada kapatılmayan soru şuydu — kural bugünden itibaren geçerliyse, eski kuralla hesaplanmış on bir dönem ne olacak.
Bu ders o soruyu ölçer. Bütün dönemleri yeniden hesaplamak koşum bedeli demektir; yalnız birkaç dönemi yeniden hesaplamak ise raporun bir kısmını yeni, kalanını eski kuralla bırakır. İkinci seçenek ucuzdur, hızlıdır ve ortaya çıkardığı rapor kendi içinde tutarlı görünür. Bu kursun ikinci iddiasının en net örneği budur.
- DN49. Kaynak ve beş aşamalı hat kurgudur; gerçek bir depo, dönüştürme çerçevesi ya da orkestratör çağrılmaz. Tohum 20260218, rapor koşumdan bağımsızdır.
- DN50. Geri doldurma (backfill), değişen bir kuralın ya da düzeltilen bir verinin geçmiş dönemlere uygulanıp o dönemlerin yeniden hesaplanmasıdır. Ölçülen şey hangi dönemlerin yeniden hesaplandığıdır.
- DN51. Kural değişikliği bir bozulma değil karardır: işyeri tüketimi 1,00 yerine 1,05 katsayıyla raporlanacak. Yeni kural doğru kabul edilir; tartışılan şey kapsamıdır.
- DN52. Taban çizgisi geri doldurmasız koşumdur: hiçbir geçmiş dönem yeniden hesaplanmaz, rapor bütünüyle eski kuralla durur.
- DN53. Yeniden işleme ile yinelemeye dayanıklılık Veri Mühendisliğine Giriş kursunda satır düzeyinde ölçüldü ve tekrarlanmaz; burada ölçülen dönem düzeyidir.
- DN54. Bedel dönem ve satır olarak yazılır, ham süre yazılmaz.
- DN55. Tüketim dönem farkından hesaplandığı için bir dönemi yeniden hesaplamak bir önceki dönemin kaynağını da okumayı gerektirir; pencere kaynakta bir dönem geniştir.
- DN56. Çözünürlük: tüketim satırı 2200, özet 55 hücre, sunum 5 değer. Sunum üzerinde oran basılmaz, değerin kendisi yazılır.
Geri Doldurmanın Kapsam Sorusu
Bir kural değiştiğinde üç seçenek vardır ve üçü de savunulabilir. Hiç geri doldurmamak raporu tutarlı bırakır ama bugünden öncesini yanlış kuralla anlatır. Tümünü geri doldurmak raporun tamamını yeni kurala taşır ve on iki dönemin hepsini yeniden hesaplatır. Kısmen geri doldurmak en ucuzudur ve genellikle bir gerekçesi de vardır: yalnız denetlenen dönemler, yalnız açık kalan çeyrek, yalnız veri hâlâ elde olan pencere.
Kısmi seçeneğin sorunu bedelinde değil görünmezliğindedir. Ortaya çıkan rapor iki kuralın birleşimidir, ama satırların hiçbiri hangi kuralla hesaplandığını taşımaz. Aşağıdaki blok kaynağı, beş aşamayı ve kuralı seçilen dönemlere uygulayan geri doldurma işlevini kurar.
# KURGUDUR. Olcum aginin sayac okumalari ve bes asamali hat ders icinde # tanimlanir; gercek bir depo, cerceve ya da orkestrator cagrilmaz. TOHUM, M32 = 20260218, 0xFFFFFFFF def uretec(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def sonraki(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return sonraki BOLGE = ["dogu", "bati", "kuzey", "guney", "merkez"] TIP = ["mesken", "isyeri"] DONEM = [f"2026-{a:02d}" for a in range(1, 13)] ABONE = {} for i in range(200): r = uretec(TOHUM + 37 * i) ABONE[f"A{i + 1:03d}"] = {"bolge": BOLGE[int(r() * 5)], "tip": TIP[int(r() * 2)], "baslangic": 10000 + int(r() * 60000)} def kaynak_uret(): kayit = [] for i, (ab, a) in enumerate(ABONE.items()): e = a["baslangic"] for d in DONEM: r = uretec(TOHUM + 101 * i + 7 * DONEM.index(d)) e += 4 + int(r() * 46) kayit.append({"abone": ab, "donem": d, "bolge": a["bolge"], "tip": a["tip"], "endeks": e, "birim": "m3"}) return kayit def a1_ayikla(kayit): """Zorunlu alanlari tasimayan satir dusurulur.""" ZOR = ("abone", "donem", "bolge", "endeks", "birim") tut = [k for k in kayit if all(a in k for a in ZOR)] return tut, len(kayit) - len(tut) def a2_tekille(kayit): gor, tut = set(), [] for k in kayit: ad = (k["abone"], k["donem"]) if ad in gor: continue gor.add(ad) tut.append(k) return tut, len(kayit) - len(tut) def a3_tuketim(kayit): """Donem farkindan tuketim. Onceki donem yoksa satir hesaplanamaz.""" tablo = {(k["abone"], k["donem"]): k for k in kayit} cikti, dusen = [], 0 for k in kayit: i = DONEM.index(k["donem"]) if i == 0: continue onceki = tablo.get((k["abone"], DONEM[i - 1])) if onceki is None: dusen += 1 continue try: f = int(k["endeks"]) - int(onceki["endeks"]) except (TypeError, ValueError): dusen += 1 continue cikti.append(dict(k, m3=f)) return cikti, dusen def a4_ozet(kayit): top = {} for k in kayit: ad = (k["bolge"], k["donem"]) top[ad] = top.get(ad, 0) + k["m3"] return top, 0 def a5_sun(ozet): """Sunum: bolge basina donem ortalamasi.""" bolge = {} for (b, d), v in ozet.items(): bolge.setdefault(b, []).append(v) return {b: round(sum(v) / len(v), 2) for b, v in bolge.items()}, 0 def geri_doldur(kayitlar, donemler, kural_yeni=True): """Kural degisti: isyeri tuketimi 1,00 yerine 1,05 katsayiyla raporlanacak. Yalnizca secilen donemler yeniden hesaplanir.""" v, _ = a1_ayikla(kayitlar) v, _ = a2_tekille(v) v, _ = a3_tuketim(v) yeni = [] for k in v: kat = 1.05 if (kural_yeni and k["donem"] in donemler and k["tip"] == "isyeri") else 1.0 yeni.append(dict(k, m3=k["m3"] * kat)) oz, _ = a4_ozet(yeni) sun, _ = a5_sun(oz) return sun
Üç Rapor ve İki Bedel
İkinci blok aynı kaynaktan üç rapor üretir: kural hiç uygulanmamış, on iki dönemin tamamına
uygulanmış ve yalnız 2026-07 ile 2026-08 dönemlerine uygulanmış. Ardından iki geri doldurma
penceresinin bedeli sayılır.
PENCERE = ["2026-07", "2026-08"] KAYNAK = kaynak_uret() def fark(a, b): return {k: round(a[k] - b[k], 2) for k in a} ESKI = geri_doldur(KAYNAK, [], kural_yeni=False) TAM = geri_doldur(KAYNAK, DONEM) KARISIK = geri_doldur(KAYNAK, PENCERE) print("eski kural :", ESKI) print("tam uygulama :", TAM) print("iki donem :", KARISIK) print("karisik - tam :", fark(KARISIK, TAM)) print("karisik - eski :", fark(KARISIK, ESKI)) def bedel(donemler): """Yeniden hesaplanan donem , yazilan tuketim satiri , okunan kaynak donemi ve satiri.""" i = min(DONEM.index(d) for d in donemler) okunan = sorted(set(donemler) | {DONEM[max(i - 1, 0)]}) yazilan = 200 * sum(1 for d in donemler if d != DONEM[0]) return len(donemler), yazilan, len(okunan), 200 * len(okunan) print() print(f"{'pencere':<13}{'donem':>7}{'yazilan':>9}{'okunan donem':>14}{'okunan satir':>14}") for ad, dl in (("tum donemler", DONEM), ("iki donem", PENCERE)): print(f"{ad:<13}" + "".join(f"{x:>{w}}" for x, w in zip(bedel(dl), (7, 9, 14, 14))))
eski kural : {'merkez': 1321.45, 'kuzey': 1212.55, 'dogu': 881.36, 'bati': 833.09, 'guney': 1065.55}
tam uygulama : {'merkez': 1357.32, 'kuzey': 1235.57, 'dogu': 902.13, 'bati': 859.22, 'guney': 1095.81}
iki donem : {'merkez': 1327.75, 'kuzey': 1216.36, 'dogu': 885.15, 'bati': 837.77, 'guney': 1070.65}
karisik - tam : {'merkez': -29.57, 'kuzey': -19.21, 'dogu': -16.98, 'bati': -21.45, 'guney': -25.16}
karisik - eski : {'merkez': 6.3, 'kuzey': 3.81, 'dogu': 3.79, 'bati': 4.68, 'guney': 5.1}
pencere donem yazilan okunan donem okunan satir
tum donemler 12 2200 12 2400
iki donem 2 400 3 600
Bedel tablosu kısmi seçeneğin neden seçildiğini gösterir: iki dönemlik pencere 2 dönem ve
400 satır yeniden hesaplarken tam uygulama 12 dönem ve 2200 satır hesaplıyor. Okunan
kaynak sütunu dönemler arası bağımlılığın bedelidir: iki dönemi yeniden hesaplamak için üç
dönemin kaynağı okunuyor, çünkü 2026-07 tüketimi 2026-06 endeksine dayanır. Pencere kaynakta
her zaman bir dönem geniştir ve bu genişlik pencerenin başında ödenir.
Sapma satırı dersin ana sayısıdır. Karışık rapor tam uygulamadan −16,98 ile −29,57 arasında sapıyor; beş bölgenin hiçbiri tam uygulamanın verdiği değerde değil. Karşılığında karışık rapor eski kuraldan da farklı — beş değerin hepsi 3,79 ile 6,30 arasında yukarıda. Yani ortaya çıkan rapor ne eski kuralın raporudur ne yeni kuralın; üçüncü bir şeydir ve o üçüncü şeyin karşılığında gerçek dünyada hiçbir kural yoktur.
Sapmanın kaynağı veri değildir. Üç raporun tamamı aynı 2400 kaynak satırından, aynı beş aşamayla üretildi; hiçbir satır kaybolmadı, hiçbir satır bozulmadı. Farkı yaratan tek şey hangi dönemlerin yeniden hesaplandığıdır.
Karışık Rapor Hangi Denetimden Geçiyor
Üçüncü blok karışık raporu bir önceki dersin altı beklentisiyle sınar, raporun kendi içinde tutarlı olup olmadığına bakar ve kuralın hücrede yarattığı sıçramayı bölgenin doğal dönem oynaklığıyla karşılaştırır. Bu son karşılaştırma en iyimser durumu temsil eder: bir gözlemci raporu dönem dönem inceleyip sıçrama arasa ne görürdü.
import statistics def yeniden_hesapla(kayitlar, donemler): """Ayni kural , iki fark: satirlar donuyor ve her satira kural surumu damgalaniyor.""" v, _ = a1_ayikla(kayitlar) v, _ = a2_tekille(v) v, _ = a3_tuketim(v) return [dict(k, kural="v2" if k["donem"] in donemler else "v1", m3=k["m3"] * (1.05 if (k["donem"] in donemler and k["tip"] == "isyeri") else 1.0)) for k in v] def beklentiler(v): """Bir onceki dersin alti beklentisi , donusumun kendi ciktisi uzerinde.""" oz = a4_ozet(v)[0] ad = [(k["abone"], k["donem"]) for k in v] d = {"anahtar_tekil": len(ad) != len(set(ad)), "negatif_yok": any(k["m3"] < 0 for k in v), "tavan": any(k["m3"] > 200 for k in v), "satir_orani": abs(len(v) - 2200) / 2200 > 0.02, "hucre_tamligi": len(oz) != 55, "oynaklik": False} for b in BOLGE: h = [x for (bb, _), x in oz.items() if bb == b] if any(abs(x - statistics.median(h)) / statistics.median(h) > 0.30 for x in h): d["oynaklik"] = True return [a for a, s in d.items() if s], oz BOZULAN, OZ_KAR = beklentiler(yeniden_hesapla(KAYNAK, PENCERE)) OZ_ESKI = a4_ozet(yeniden_hesapla(KAYNAK, []))[0] print("karisik raporda bozulan beklenti:", BOZULAN or "yok") print("rapor kendi icinde tutarli mi:", all(abs(KARISIK[b] - round(sum(x for (bb, _), x in OZ_KAR.items() if bb == b) / 11, 2)) < 0.01 for b in BOLGE)) for b in ("merkez", "dogu"): dogal = [abs(OZ_ESKI[(b, DONEM[i])] - OZ_ESKI[(b, DONEM[i - 1])]) / OZ_ESKI[(b, DONEM[i - 1])] for i in range(2, 12)] kural = [(OZ_KAR[(b, d)] - OZ_ESKI[(b, d)]) / OZ_ESKI[(b, d)] for d in PENCERE] print(f"{b:<7} dogal degisim ortanca={statistics.median(dogal):.4f} " f"en buyuk={max(dogal):.4f} kuralin sicramasi={max(kural):.4f}")
karisik raporda bozulan beklenti: yok rapor kendi icinde tutarli mi: True merkez dogal degisim ortanca=0.0883 en buyuk=0.1874 kuralin sicramasi=0.0275 dogu dogal degisim ortanca=0.0909 en buyuk=0.1541 kuralin sicramasi=0.0234
Üç satır aynı şeyi söyler: hiçbir denetim bunu bildirmiyor. Altı beklentinin hiçbiri bozulmuyor, çünkü bozulacak bir şey yok — satır sayısı 2200, hücre sayısı 55, anahtarlar tekil, hiçbir değer eksi ya da tavanın üstünde. Rapor kendi içinde tutarlı: her bölgenin sunum değeri kendi on bir özet hücresinin ortalamasına eşit. Kaynağa bakan denetimler de sessiz kalır, çünkü kaynak hiç değişmemiştir.
Son iki satır en iyimser gözlemciyi de eler. Kuralın hücrede yarattığı en büyük sıçrama merkezde yüzde 2,75, doğuda yüzde 2,34; oysa aynı bölgelerin doğal dönem değişiminin ortancası yüzde 8,83 ve yüzde 9,09, en büyüğü yüzde 18,74 ve yüzde 15,41. Kuralın izi doğal oynaklığın ortancasının bile üçte biri kadar. Dönem dönem bakan bir gözlemci sıçramayı arasa da bulamaz, çünkü sıçrama gürültünün içindedir.
İki koşumun da kendi içinde tutarlı görünmesi bu dersin çekirdeğidir. Ne tam uygulama ne kısmi uygulama bir hata üretir; ikisi de sonuna kadar koşar, ikisi de temiz bir rapor basar. Aralarındaki tek fark bir liste — hangi dönemlerin yeniden hesaplandığı — ve o liste raporun hiçbir yerinde yazmaz.
Kural Sürümü Damgası
Dördüncü blok eksik olanı ekler. yeniden_hesapla her satıra hangi kuralla hesaplandığını yazıyor;
raporda kaç sürüm bulunduğuna bakmak yeterli olur. Aynı blok kursun kuralını da sınar: geri doldurma
iki kez koşturulduğunda ne oluyor.
V_TAM = yeniden_hesapla(KAYNAK, DONEM) V_KAR = yeniden_hesapla(KAYNAK, PENCERE) for ad, v in (("tam", V_TAM), ("karisik", V_KAR)): s = sorted(set(k["kural"] for k in v)) print(f"{ad:<8} kural surumu={str(s):<14} raporda tek surum mu:", len(s) == 1) print("kaynaktan iki kez kosuldu:", geri_doldur(KAYNAK, PENCERE) == geri_doldur(KAYNAK, PENCERE)) yamali = [dict(k, m3=k["m3"] * (1.05 if k["donem"] in PENCERE and k["tip"] == "isyeri" else 1.0)) for k in V_KAR] print("cikti uzerine ikinci kez:", fark(a5_sun(a4_ozet(yamali)[0])[0], KARISIK))
tam kural surumu=['v2'] raporda tek surum mu: True
karisik kural surumu=['v1', 'v2'] raporda tek surum mu: False
kaynaktan iki kez kosuldu: True
cikti uzerine ikinci kez: {'merkez': 6.62, 'kuzey': 4.0, 'dogu': 3.98, 'bati': 4.92, 'guney': 5.35}
Damga tek bir alandır ve karışıklığı tek bir karşılaştırmaya indirir: raporda iki sürüm varsa rapor karışıktır. Bedeli de tek bir alandır, 2200 satırın her birine yazılan bir dize. Damganın yazılma anı önemlidir; hesap yapıldıktan sonra hangi satırın hangi kuralla üretildiği geriye dönük çıkarılamaz, çünkü satırda kalan tek şey sonucun kendisidir.
Son iki satır kursun kuralını iki yönden gösterir. Geri doldurma kaynaktan hesaplandığında iki kez koşturmak sonucu değiştirmez; işlev yinelemeye dayanıklıdır ve rapor koşum sayısından bağımsızdır. Aynı düzeltme çıktının üzerine yama olarak uygulandığında ikinci koşum katsayıyı bir kez daha çarpar ve rapor 3,98 ile 6,62 arasında bir kez daha kayar. İki yol da aynı sonucu hedefler, biri yeniden koşturulabilir, öbürü yalnızca bir kez doğrudur — ve hangisinin koşulduğunu rapora bakarak ayırt etmenin yolu yoktur.
Özet
- Geri doldurma kapsam kararıdır: tam uygulama 12 dönem ve 2200 satır yeniden hesaplatırken iki dönemlik pencere 2 dönem ve 400 satırla yetiniyor, ama kaynakta üç dönem okunuyor çünkü tüketim bir önceki dönemin endeksine dayanır.
- Kısmi geri doldurmanın raporu tam uygulamadan −16,98 ile −29,57 arasında sapıyor ve eski kuraldan da 3,79 ile 6,30 arasında ayrılıyor; ortaya çıkan rapor iki kuralın hiçbirine ait değil.
- Sapmanın kaynağı veri değil hangi dönemlerin yeniden hesaplandığıdır; üç rapor da aynı 2400 kaynak satırından, tek satır kaybetmeden üretiliyor.
- Karışık rapor altı beklentinin hiçbirini bozmuyor, kendi içinde tutarlı çıkıyor ve kuralın hücrede yarattığı en büyük sıçrama (yüzde 2,75) doğal dönem oynaklığının ortancasının (yüzde 8,83) çok altında kaldığı için dönem dönem bakan gözlemciye de görünmüyor.
- Satıra kural sürümü damgalandığında karışıklık tek bir karşılaştırmayla görünür oluyor; geri doldurma kaynaktan hesaplandığında yinelemeye dayanıklıdır, çıktı üzerine yama olarak uygulandığında ikinci koşum raporu 3,98 ile 6,62 arasında bir kez daha kaydırır.
Sonraki Adım
On bir derste hat kuruldu: veri altı ayrı yoldan alındı, kaybının ve yinelenmesinin sunuma ne yaptığı sayıldı, dönüşümün nerede yapılacağı, hangi sırayla koşacağı, hangi katmanda duracağı, nasıl sınanacağı ve geçmişe nasıl uygulanacağı yazıldı. Bu on bir dersin tamamında hattı çalıştıran tek şey vardı: bir insanın elle verdiği başlatma kararı. Hangi görevin ne zaman koşacağı, bir görev düştüğünde ardındakilerin ne yapacağı, aynı işin iki kez tetiklenip tetiklenmediği ve bu dersin kural sürümü damgasını kimin yazacağı hiç sorulmadı. Sonraki konu hattı bir görev çizgesine çevirir ve çizgenin şeklinin bir başarısızlığı nereye kadar taşıdığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.