İçeriğe geç
academia.sh

Ders 12 / 22

Zaman Serisi İşlemleri

3.199 okuma yedi günlük dönemlere indirgeniyor ve hiç kayıt taşımayan 5 dönem için iki cevap çıkıyor: sıfır sayılırsa seri 13 satır, atlanırsa 8 satır olur. Toplam ikisinde de 63.060,63 m³'tür ve değişmez, ama toplamdan sonraki her hesap değişir. Haftalık ortalama 4.850,82 m³ ile 7.882,58 m³ arasında oynar; dönemden döneme yüzde değişimin 12 oranından 5'i sıfır paydaya düştüğü için tanımsız kalırken atlanmış seride 7 oranın hiçbiri tanımsız değildir; üç dönemlik hareketli ortalama 2026-02-12 döneminde 708,03 m³ ile 9.387,54 m³ arasında ayrılır. Aynı küme otuz günlük adımla 3 dönem verir ve hiçbiri boş değildir: boş dönemi yaratan şey veri değil adım kararıdır.

İçindekiler

Birleştirme tabloları yan yana getirdi ve satır hesabı yazıldı. Kümede henüz ele alınmamış bir eksen var: zaman. Okuma tablosunda dönem bir metin sütunudur. Sıralanabilir, gruplanabilir, eşitliğe sokulabilir; ama “aradan kaç hafta geçti”, “hangi haftada hiç okuma yok” ya da “son üç dönemin ortalaması nedir” soruları bu haliyle sorulamaz, çünkü metin sütunu iki değer arasındaki uzaklığı bilmez.

Zaman ekseni etiket dizinine taşındığında bu sorular sorulabilir hale gelir ve beraberinde yeni bir karar getirir. Bir zaman ekseni sürekli, veri ise kesiklidir: eksende yer alan her dönemin kümede bir karşılığı olmak zorunda değildir. Hiç kayıt taşımayan bir dönem için iki cevap vardır ve ikisi de savunulabilir — o dönemde tüketim sıfırdı ya da o dönem hiç ölçülmedi. Bu dersin ölçüsü bu iki cevabın ayrıldığı yerdir.

  • VC26. Zaman ekseni, dizinin etiketlerinin tarih olmasıdır. Tarih dizini iki etiket arasındaki uzaklığı bilir ve bu bilgi sıralama, fark alma ve yeniden örneklemeyi mümkün kılar.
  • VC27. Yeniden örnekleme, kayıtları zaman ekseninde daha kaba bir adıma indirgemektir. Çıkan dönem sayısını belirleyen şey veri değil, adım kararıdır.
  • VC28. Boş dönem, eksende yer alan ama hiç kayıt taşımayan dönemdir. Sıfır sayılabilir ya da çıktıda hiç üretilmeyebilir; ikisi arasındaki fark bir satır sayısıdır.
  • VC29. Boş dönem kararı toplamı değiştirmez, çünkü sıfır toplamaya bir şey katmaz. Toplamdan sonra gelen her hesabı değiştirir: ortalamanın paydasını, değişim oranının paydasını ve hareketli pencerenin kapsadığı dönemleri.

Zaman Ekseni

# 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()]

# Okuma tarihi: saha ekibi her ayin okuma penceresinde calisir. Donem metni
# boylece bir tarih eksenine tasinir.
from datetime import date, timedelta

BASLANGIC, SON = date(2026, 1, 1), date(2026, 3, 31)
AY = {"2026-01": 1, "2026-02": 2, "2026-03": 3}

def okuma_tarihi(no: int, donem: str) -> date:
    """Okuma penceresi ayin 18 ile 28'i arasidir."""
    r = uretec(TOHUM + 77000 + no * 4 + AY[donem])
    return date(2026, AY[donem], 18 + ayrik(r(), [0.09] * 10 + [0.10]))

kayit = [{"abone_no": o["abone_no"], "tarih": okuma_tarihi(o["abone_no"], o["donem"]),
          "tuketim_m3": o["tuketim_m3"]} for o in UZUN]
print(f"{len(kayit)} okuma, tarih araligi {min(k['tarih'] for k in kayit)} .. "
      f"{max(k['tarih'] for k in kayit)}")
print(f"eksen {BASLANGIC} ile {SON} arasi {(SON - BASLANGIC).days + 1} gun, "
      f"yedi gunluk {(SON - BASLANGIC).days // 7 + 1} donem")
print(f"toplam {round(sum(k['tuketim_m3'] for k in kayit), 2)} m3")
3199 okuma, tarih araligi 2026-01-18 .. 2026-03-28
eksen 2026-01-01 ile 2026-03-31 arasi 90 gun, yedi gunluk 13 donem
toplam 63060.63 m3

İki sayı arasındaki uyumsuzluk dersin bütün konusunu kuruyor. Eksen 90 günlüktür ve yedi günlük adımla 13 döneme bölünür. Veri ise 3.199 okumadan ibarettir ve okumaların tamamı ayın 18’i ile 28’i arasında toplanmıştır, çünkü saha ekibi her ay belirli bir okuma penceresinde çalışır.

Bu, kümenin bir kusuru değil, işin yapılış biçimidir. Sonucu şudur: eksenin bazı dönemleri hiç okuma taşımaz. Bu dönemler veri toplanmadığı için boştur, tüketim olmadığı için değil. Su akmaya devam etmiş, sayaç dönmüş, ama o hafta kimse okumaya gitmemiştir.

Boş Dönem Nedir

# yeniden_ornekle.py — zaman eksenini yedi gunluk donemlere indirger. Hic kayit
# tasimayan donem icin iki cevap vardir: sifir ya da hic yok.
def yeniden_ornekle(kayitlar, adim: int, bos_sifir=True):
    """bos_sifir dogruysa hic kayit tasimayan donem 0.0 ile uretilir;
    yanlissa o donem ciktida hic gorunmez."""
    kova: dict[int, list[float]] = {}
    for k in kayitlar:
        kova.setdefault((k["tarih"] - BASLANGIC).days // adim, []).append(k["tuketim_m3"])
    cikti = []
    for i in range((SON - BASLANGIC).days // adim + 1):
        v = kova.get(i, [])
        if not v and not bos_sifir:
            continue
        cikti.append({"donem": BASLANGIC + timedelta(days=i * adim),
                      "okuma": len(v), "toplam_m3": round(sum(v), 2)})
    return cikti

dolu = yeniden_ornekle(kayit, 7, bos_sifir=False)
sifirli = yeniden_ornekle(kayit, 7, bos_sifir=True)
EN = [14, 10, 14, 10, 14]
y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0])
                      for k, v in enumerate(h))
print(y(["donem basi", "okuma", "toplam m3", "okuma", "toplam m3"]))
d = {c["donem"]: c for c in dolu}
for c in sifirli:
    e = d.get(c["donem"])
    print(y([str(c["donem"]), c["okuma"], c["toplam_m3"],
             e["okuma"] if e else "-", e["toplam_m3"] if e else "-"]))
ts = round(sum(c["toplam_m3"] for c in sifirli), 2)
td = round(sum(c["toplam_m3"] for c in dolu), 2)
print(f"\nsatir : bos donem sifir sayilinca {len(sifirli)}, atlaninca {len(dolu)}")
print(f"toplam: {ts} m3 ve {td} m3 — toplam degismez")
print(f"ortalama: {round(ts / len(sifirli), 2)} m3 ve {round(td / len(dolu), 2)} m3")
donem basi         okuma     toplam m3     okuma     toplam m3
2026-01-01             0             0         -             -
2026-01-08             0             0         -             -
2026-01-15           460       9260.91       460       9260.91
2026-01-22           800       16777.6       800       16777.6
2026-01-29             0             0         -             -
2026-02-05             0             0         -             -
2026-02-12           106        2124.1       106        2124.1
2026-02-19           690      13715.37       690      13715.37
2026-02-26           305       5829.46       305       5829.46
2026-03-05             0             0         -             -
2026-03-12            87       1665.23        87       1665.23
2026-03-19           521       9477.26       521       9477.26
2026-03-26           230        4210.7       230        4210.7

satir : bos donem sifir sayilinca 13, atlaninca 8
toplam: 63060.63 m3 ve 63060.63 m3 — toplam degismez
ortalama: 4850.82 m3 ve 7882.58 m3

Aynı kümeden iki seri çıkıyor ve ilk bakışta ikisi de aynı şeyi söylüyor: toplam 63.060,63 m³. Bu eşitlik yanıltıcı bir güven verir, çünkü sıfır toplamaya bir şey katmaz ve boş dönem kararı toplamda hiçbir iz bırakmaz.

Ayrım satır sayısında başlıyor: 13 satıra karşı 8 satır. Sıfırla doldurulmuş seride beş dönem 0 tüketim taşır ve bu beş satır bir grafikte tabana inen beş nokta olarak çizilir. Grafiği okuyan biri şunu görür: tüketim yılın ilk iki haftasında sıfırdı, sonra yükseldi, ocak sonunda tekrar sıfıra düştü. Bunların hiçbiri olmamıştır. Sıfır olan şey tüketim değil, o hafta yapılan okuma sayısıdır ve tablonun ikinci sütunu bunu zaten yazmaktadır.

Ortalama farkı bu okumanın sayısal karşılığıdır. Aynı toplam 13’e bölününce 4.850,82 m³, 8’e bölününce 7.882,58 m³ çıkar; ikinci sayı birincinin 1,63 katıdır. “Haftalık ortalama tüketim” diye raporlanacak sayı bu ikisinden biridir ve hangisi olduğu, hiç yazılmayan bir seçenekle belirlenir.

Toplamdan Sonraki Her Hesap

# degisim.py — bos donemin sifir sayilmasi toplami degistirmez, ama toplamdan
# sonra gelen her hesabi degistirir.
def degisim(seri):
    """Donemden doneme yuzde degisim; payda sifirsa oran tanimsizdir."""
    o, tanimsiz = [], 0
    for i in range(1, len(seri)):
        onceki = seri[i - 1]["toplam_m3"]
        if onceki == 0:
            tanimsiz += 1
            o.append(None)
        else:
            o.append(round(100 * (seri[i]["toplam_m3"] - onceki) / onceki, 1))
    return o, tanimsiz

def hareketli(seri, pencere=3):
    """Her donem icin son pencere kadar donemin ortalamasi."""
    o = {}
    for i in range(len(seri)):
        v = [c["toplam_m3"] for c in seri[max(0, i - pencere + 1):i + 1]]
        o[seri[i]["donem"]] = round(sum(v) / len(v), 2)
    return o

d1, t1 = degisim(sifirli)
d2, t2 = degisim(dolu)
print(f"sifirli seri: {len(d1)} degisim orani, {t1} tanesi tanimsiz (payda sifir)")
print(f"atlanmis seri: {len(d2)} degisim orani, {t2} tanesi tanimsiz")
h1, h2 = hareketli(sifirli), hareketli(dolu)
ortak = [g for g in h1 if g in h2]
en_uzak = max(ortak, key=lambda g: abs(h1[g] - h2[g]))
print(f"uc donemlik hareketli ortalama {len(ortak)} ortak donemde karsilastirilir; "
      f"en buyuk fark {en_uzak}: sifirli {h1[en_uzak]} m3, atlanmis {h2[en_uzak]} m3")
aylik = yeniden_ornekle(kayit, 30, bos_sifir=True)
print(f"\nayni kume otuz gunluk adimla {len(aylik)} donem verir ve hicbiri bos degil; "
      f"bos donemi yaratan sey verinin kendisi degil, adim karari")
print(f"\ngecis : donem basina kume taramasi {13 * len(kayit)}, tek kova gecisi "
      f"{len(kayit) + 13}")
print(f"kod   : elle donem dongusu 9 satir, yeniden ornekleme 1 cagri")
sifirli seri: 12 degisim orani, 5 tanesi tanimsiz (payda sifir)
atlanmis seri: 7 degisim orani, 0 tanesi tanimsiz
uc donemlik hareketli ortalama 8 ortak donemde karsilastirilir; en buyuk fark 2026-02-12: sifirli 708.03 m3, atlanmis 9387.54 m3

ayni kume otuz gunluk adimla 3 donem verir ve hicbiri bos degil; bos donemi yaratan sey verinin kendisi degil, adim karari

gecis : donem basina kume taramasi 41587, tek kova gecisi 3212
kod   : elle donem dongusu 9 satir, yeniden ornekleme 1 cagri

Değişim oranı boş dönem kararının en sert karşılığıdır. Sıfırla doldurulmuş seride 12 oranın 5 tanesi tanımsızdır, çünkü paydada sıfır vardır. Bir hesap tablosunda bu hücreler ya boş kalır ya bir hata değeri taşır; ikisi de raporun ortasında açıklanması gereken bir gürültüdür ve verinin kendisinden değil, kaba bir varsayılandan gelir. Atlanmış seride aynı hesabın 7 oranının hiçbiri tanımsız değildir.

Hareketli ortalama ise sessizce sapar. Üç dönemlik pencere 2026-02-12 döneminde sıfırlı seride 708,03 m³, atlanmış seride 9.387,54 m³ verir. Aradaki oran on üç kattır ve sebebi basittir: sıfırlı seride pencere iki boş dönemi kapsar ve ortalamayı üçe böler, atlanmış seride ise pencere üç gerçek okuma dönemini kapsar. İki sayı da doğru hesaplanmıştır, ikisi de aynı pencere adını taşır ve hangisinin raporlanacağı bir cümleyle bile yazılmaz.

Son iki satır kararın nereden geldiğini gösteriyor. Aynı küme otuz günlük adımla üç dönem verir ve hiçbiri boş değildir. Boş dönem verinin bir özelliği değildir; adım kararının ürünüdür. Yedi günlük adım seçildiği anda beş boş dönem yaratılmıştır ve bu, okuma penceresinin haftalık çözünürlükte anlamlı bir sinyal taşımamasından ileri gelir.

Kazanç tarafı da yazılmalıdır. Her dönem için kümeyi baştan tarayan bir yaklaşım 41.587 geçiş tutar; kayıtları bir kez gezip dönem kovasına atan yeniden örnekleme 3.212 geçişle biter ve dokuz satırlık döngü tek çağrıya iner. Kısalan tam da bu çağrının içinde iki karar durur: adım ve boş dönemin ne sayıldığı.

Kaydırma Grup Sınırını Bilmez

# kaydirma.py — fark almak icin seri bir adim kaydirilir. Kaydirma tablo
# sirasini bilir, grup sinirini bilmez.
sirali = sorted(UZUN, key=lambda o: (o["abone_no"], o["donem"]))
ham_fark = [(sirali[i]["abone_no"], sirali[i - 1]["abone_no"],
             round(sirali[i]["tuketim_m3"] - sirali[i - 1]["tuketim_m3"], 2))
            for i in range(1, len(sirali))]
ayni = [f for f in ham_fark if f[0] == f[1]]
sinir = [f for f in ham_fark if f[0] != f[1]]
print(f"{len(sirali)} satirda {len(ham_fark)} fark uretildi")
print(f"ayni abonenin iki donemi: {len(ayni)} fark, ortalama "
      f"{round(sum(f[2] for f in ayni) / len(ayni), 2)} m3")
print(f"iki farkli abone arasi   : {len(sinir)} fark, ortalama "
      f"{round(sum(f[2] for f in sinir) / len(sinir), 2)} m3")
print(f"grup sinirini bilmeyen bir kaydirma {len(ham_fark)} farkin "
      f"{len(sinir)} tanesini iki ayri aboneden hesaplar")
3199 satirda 3198 fark uretildi
ayni abonenin iki donemi: 1939 fark, ortalama -1.12 m3
iki farkli abone arasi   : 1259 fark, ortalama 1.72 m3
grup sinirini bilmeyen bir kaydirma 3198 farkin 1259 tanesini iki ayri aboneden hesaplar

Fark almak, seriyi bir adım kaydırıp kendisinden çıkarmaktır ve uzun biçimli bir tabloda bu işlem tablo sırasını izler. Tablo abone ve dönem sırasındadır, dolayısıyla farkların çoğu doğru çıkar: 1.939 fark aynı abonenin iki ardışık dönemini karşılaştırır. Kalan 1.259 fark ise bir abonenin son dönemi ile bir sonraki abonenin ilk dönemi arasındadır ve hiçbir şeyi ölçmez.

İki ortalama arasındaki fark bu kirlenmenin işaretini veriyor: gerçek dönem farklarının ortalaması -1,12 m³ iken sınır farklarının ortalaması +1,72 m³’tür. İkisi bir arada raporlanırsa tüketimdeki dönemsel düşüş eğilimi zayıflar. Kaydırma işlemi grup sınırını kendiliğinden bilmez; bilmesi için kaydırmanın abone başına yapılması, yani bir gruplama kararının fark almadan önce yazılması gerekir.

Özet

  • Tarih dizini iki etiket arasındaki uzaklığı bilir; metin sütunu bilmez. Yeniden örnekleme, sıralama ve fark alma bu bilgiyle mümkün olur.
  • Aynı küme yedi günlük adımda 13 dönem, otuz günlük adımda 3 dönem verir; boş dönemi yaratan şey veri değil adım kararıdır.
  • Boş dönem sıfır sayılınca seri 13 satır, atlanınca 8 satır olur; toplam ikisinde de 63.060,63 m³’tür ve karar toplamda hiçbir iz bırakmaz.
  • Toplamdan sonraki her hesap değişir: ortalama 4.850,82 ile 7.882,58 m³ arasında oynar, 12 değişim oranının 5’i sıfır payda yüzünden tanımsız kalır, üç dönemlik hareketli ortalama 2026-02-12 döneminde 708,03 ile 9.387,54 m³ arasında ayrılır.
  • Sıfırla doldurulmuş bir seri grafikte tabana inen noktalar üretir ve “tüketim durdu” diye okunur; sıfır olan şey tüketim değil, o dönemde yapılan okuma sayısıdır.

Sonraki Adım

Sayısal sütunlar, tarihler ve anahtarlar ele alındı. Kümede kalan son alan türü metindir: bölge adı, ziyaret sonucu, adres kodu, saha notu. Metin sütunları çerçevede sayısal sütunlarla aynı yerde durur ve aynı biçimde işlenebilir gibi görünür; bir dizgi yöntemi bütün sütuna tek çağrıyla uygulanır ve döngü yazmaya gerek kalmaz. O çağrının eksik hücrede ne yaptığı ise hiçbir yerde görünmez. Sonraki ders vektörleştirilmiş dizgi işlemlerini ele alır, eksik değerde ne döndüklerini sayar ve konuyu kapatı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