Metrik, analistin ürünüdür
Yönetim "ciro neden düştü?", pazarlama "kampanya işe yaradı mı?", finans "bir müşteri bize ne kadar kazandırıyor?" diye sorar. Bu soruların hepsi bir metrik (metric) ve onun doğru hesaplanmış, doğru karşılaştırılmış bir değeri ister. pandas ile gruplamayı, istatistik dersinde A/B testini gördün. Bu ders analitik ekiplerin her gün kullandığı kalıpları toplar: KPI ağacı, huni, kohort ve tutundurma, LTV, dönem karşılaştırmaları, köprü analizi, Pareto ve en sık metrik tuzakları.
Veri: Anadolu Market orders, order_items, products ve web_events (Ekim–Kasım 2024 site olayları); ayrıca Kesit Power BI'daki kesit_bi yıldız şemasının Satislar tablosu. Ciro tanımı bu derste de aynı: net = quantity × unit_price × (1 − discount), yalnız delivered ve shipped siparişler.
1. KPI ağacı ve metrik sözleşmesi
Ciro tek başına "neden" söylemez. Onu çarpanlarına ayırınca hangi kolun hareket ettiği görünür:
ciro = aktif müşteri × müşteri başına sipariş (frekans) × ortalama sepet (AOV) AOV = sipariş başına ürün adedi × ortalama birim fiyat web cirosu = ziyaretçi × dönüşüm oranı × AOV
import veri
import pandas as pd
o = veri.oku("anadolu_market", "orders", parse_dates=["order_date"])
i = veri.oku("anadolu_market", "order_items")
i["net"] = i["quantity"] * i["unit_price"] * (1 - i["discount"])
x = o[o["status"].isin(["delivered", "shipped"])].merge(i, on="order_id", validate="one_to_many")
yil = x.groupby(x["order_date"].dt.year).agg(
ciro=("net", "sum"), siparis=("order_id", "nunique"), musteri=("customer_id", "nunique"))
yil["frekans"] = yil["siparis"] / yil["musteri"]
yil["aov"] = yil["ciro"] / yil["siparis"]
print(yil.round(2))
# 2023 → 2024 büyümesinin kaynakları (çarpanların oranları)
b = yil.loc[2024] / yil.loc[2023]
print("ciro x", round(b["ciro"], 3), "= müşteri x", round(b["musteri"], 3),
"· frekans x", round(b["frekans"], 3), "· AOV x", round(b["aov"], 3))
2024'te ciro 1,88 katına çıkmış; bunun büyük kısmı aktif müşteri sayısındaki artıştan (×1,62), daha azı frekans (×1,08) ve AOV'den (×1,07) geliyor. Yönetime "büyüme yeni müşteriden geldi" demek artık bir sayıya dayanıyor.
Her metrik için yazılı bir sözleşme (metric definition) tut: ad, formül, pay ve payda, dahil/hariç durumlar (iptal, iade, test kullanıcıları), zaman damgası (sipariş mi teslim tarihi mi?), sahibi. "Dönüşüm oranı" iki ekipte iki farklı şey demeye başladığı an, toplantılar sayı tartışmasına döner.
2. Huni analizi
Huni (funnel), kullanıcıların adım adım nerede döküldüğünü gösterir. Her adımda o adımı yapan tekil kullanıcı sayılır; olay sayısı değil. Bu veride fark büyüktür: kullanıcılar geri gelir ve adımları tekrar eder, 40 olay da iki kez gönderilmiştir; 2.819 satır 600 kullanıcıdan gelir.
import veri
import pandas as pd
w = veri.oku("anadolu_market", "web_events")
adimlar = ["visit", "product_view", "add_to_cart", "checkout", "purchase"]
huni = w.groupby("event_type")["user_id"].nunique().reindex(adimlar).to_frame("kullanici")
huni["adim_donusum"] = (huni["kullanici"] / huni["kullanici"].shift(1)).round(3)
huni["toplam_donusum"] = (huni["kullanici"] / huni["kullanici"].iloc[0]).round(3)
print(huni)
cihaz = w.pivot_table(index="event_type", columns="device", values="user_id", aggfunc="nunique").reindex(adimlar)
print((cihaz.loc["purchase"] / cihaz.loc["visit"]).round(3))
En büyük kayıp ürün görüntülemeden sepete geçişte (%40); mobilde ziyaretten satın almaya dönüşüm masaüstünden düşük. Huniyi bir kırılımla (cihaz, kaynak, yeni/eski kullanıcı) okumak "nerede" sorusunu "kimde" sorusuna çevirir.
3. Kohort, tutundurma ve churn
Kohort (cohort), aynı dönemde başlayan müşteri grubudur. Tutundurma (retention) tablosu her kohortun sonraki dönemlerde ne kadarının geri geldiğini gösterir. SQL sürümünü Python'dan SQL ile analiz dersinde yazdın; burada pandas ile çeyreklik kohort kuruyoruz.
import veri
import pandas as pd
o = veri.oku("anadolu_market", "orders", parse_dates=["order_date"])
o["donem"] = o["order_date"].dt.to_period("Q")
o["kohort"] = o.groupby("customer_id")["donem"].transform("min")
o["fark"] = (o["donem"] - o["kohort"]).apply(lambda d: d.n)
sayim = o.groupby(["kohort", "fark"])["customer_id"].nunique().unstack() # gelecekteki hücreler NaN kalır
oran = sayim.div(sayim[0], axis=0).round(2)
print(oran.iloc[-6:, :5])
unstack(fill_value=0) yazarsan 2024Q4 kohortunun 1. çeyreği "%0 tutundurma" görünür; oysa o çeyrek henüz yaşanmadı. Ortalama tutundurma eğrisini hesaplarken bu sıfırlar eğriyi aşağı çeker. Gözlemlenmemiş dönemi NaN bırak; ortalamayı yalnız gözlemlenen hücrelerden al.Churn (müşteri kaybı) sözleşme gerektiren bir metriktir. Abonelik işinde iptal tarihi bellidir; e-ticarette "kaybedildi" demek için bir hareketsizlik penceresi seçersin: "son siparişin üzerinden 180 gün geçti". Pencere kısalırsa churn yükselir; pencereyi satın alma döngüsüne göre seç (bu veride iki sipariş arası ortalama süre 86 gün).
4. LTV ve CAC
Müşteri yaşam boyu değeri (customer lifetime value, LTV), bir müşterinin ilişki boyunca bıraktığı katkı payıdır (ciro değil, marj). İki yaygın hesap:
| Yöntem | Formül | Ne zaman |
|---|---|---|
| Basit | AOV × yıllık frekans × marj oranı × beklenen ömür (yıl) | Hızlı tahmin, yönetim sunumu |
| Kohort bazlı | Kohort başına kümülatif marj / kohort büyüklüğü, dönem dönem | Gerçekleşen değeri görmek, kanalları karşılaştırmak |
| Olasılıksal | BG/NBD + Gamma-Gamma (lifetimes kütüphanesi) | Bireysel müşteri tahmini |
Müşteri edinme maliyeti (customer acquisition cost, CAC) = pazarlama harcaması / yeni müşteri. LTV/CAC oranı yaygın bir sağlık göstergesidir; 3 civarı sağlıklı kabul edilir, 1'in altı her müşteride para kaybetmek demektir. Oranı yorumlarken geri ödeme süresine (CAC'nin kaç ayda geri kazanıldığı) de bak.
5. Dönem karşılaştırmaları: MoM, YoY, YTD
MoM (month over month) kısa vadeli ivmeyi, YoY (year over year) mevsimsellikten arındırılmış büyümeyi, YTD (year to date) yılbaşından bugüne birikimi gösterir. Kural: karşılaştırdığın iki dönem aynı uzunlukta ve aynı takvim yapısında olmalı.
import veri
import pandas as pd
o = veri.oku("anadolu_market", "orders", parse_dates=["order_date"])
i = veri.oku("anadolu_market", "order_items")
i["net"] = i["quantity"] * i["unit_price"] * (1 - i["discount"])
x = o[o["status"].isin(["delivered", "shipped"])].merge(i, on="order_id")
def donem(bas, son):
m = (x["order_date"] >= bas) & (x["order_date"] <= son)
return round(x.loc[m, "net"].sum(), 2)
bu = donem("2024-01-01", "2024-06-15")
gecen = donem("2023-01-01", "2023-06-15") # aynı gün: 15 Haziran
print("YTD 2024:", bu, "| aynı dönem 2023:", gecen, "| YoY:", round(bu / gecen - 1, 3))
print("YANLIŞ kıyas (tüm 2023):", donem("2023-01-01", "2023-12-31"))
Takvim etkisi: Bir ayda 5 cumartesi, diğerinde 4 olabilir; bayramlar her yıl başka aya düşer. Günlük ciroda haftanın günü etkisi güçlüyse "aynı gün" karşılaştırması (bu yılın 2. pazartesisi ile geçen yılın 2. pazartesisi) ya da gün başına ortalama kullanılır.
6. Köprü analizi: fiyat, hacim ve karma etkisi
"Ciro 329 bin TL arttı; ne kadarı daha çok satmaktan, ne kadarı fiyattan, ne kadarı ürün karmasından?" Köprü (bridge, price-volume-mix) analizi değişimi parçalara ayırır. Kategori düzeyinde, A adet ve P ortalama birim fiyat olmak üzere:
| Etki | Formül | Anlamı |
|---|---|---|
| Hacim | (ΣA₁ − ΣA₀) × P̄₀ | Karma ve fiyat sabitken toplam adedin artışı |
| Karma | Σ (payᵢ₁ − payᵢ₀) × ΣA₁ × Pᵢ₀ | Satışın pahalı/ucuz kategorilere kayması |
| Fiyat | Σ (Pᵢ₁ − Pᵢ₀) × Aᵢ₁ | Aynı kategoride birim fiyat değişimi |
Üçünün toplamı ciro farkına birebir eşittir; köprü grafiği (waterfall) bu üç çubuğu başlangıç ve bitiş cirosu arasına dizer. Uygulamasını alıştırmalarda yapacaksın.
7. Pareto (ABC) analizi
Ürünleri ciroya göre sırala, kümülatif payı hesapla: ilk %80'i oluşturanlar A, sonraki %15 B, kalan C. Stok, kampanya ve raf kararları bu sınıflara göre verilir. "80/20" bir yasa değil, kontrol edilmesi gereken bir hipotezdir: Anadolu Market'te ürünlerin ilk %20'si cironun yalnız yaklaşık %37'sini yapıyor; ciro geniş bir ürün yelpazesine yayılmış. Müşteri tarafında aynı fikrin çok boyutlu hâli RFM segmentasyonudur (bitirme projesi dersinde).
8. Metrik tuzakları
Oranların ortalaması ≠ ortalama oran
import veri
import pandas as pd
w = veri.oku("anadolu_market", "web_events", parse_dates=["event_time"])
w["gun"] = w["event_time"].dt.date
gunluk = w.pivot_table(index="gun", columns="event_type", values="user_id", aggfunc="nunique", fill_value=0)
gunluk = gunluk[gunluk["visit"] > 0]
print("günlük oranların ortalaması:", round((gunluk["purchase"] / gunluk["visit"]).mean(), 4))
print("toplam / toplam (doğru): ", round(gunluk["purchase"].sum() / gunluk["visit"].sum(), 4))
Günlük oranların ortalaması her güne eşit ağırlık verir: 3 ziyaretçili bir gün ile 30 ziyaretçili bir gün aynı etkiyi yapar. Oran metriklerini daima pay toplamı / payda toplamı olarak hesapla.
Simpson paradoksu ve payda kayması
Bir karşılaştırma her alt grupta bir yönü gösterirken toplamda ters yönü gösterebilir. Aşağıdaki küçük örnekte A kampanyası hem mobilde hem masaüstünde B'den iyidir, ama trafiğinin çoğu düşük dönüşümlü mobilden geldiği için toplamda kötü görünür:
import pandas as pd
df = pd.DataFrame({
"kampanya": ["A", "A", "B", "B"],
"cihaz": ["mobil", "masaüstü", "mobil", "masaüstü"],
"ziyaret": [1000, 100, 100, 1000],
"satis": [100, 30, 8, 250],
})
df["oran"] = df["satis"] / df["ziyaret"]
print(df.pivot(index="cihaz", columns="kampanya", values="oran"))
t = df.groupby("kampanya")[["satis", "ziyaret"]].sum()
print((t["satis"] / t["ziyaret"]).round(3))
Gerçek veride paradoks nadirdir ama karma (mix) etkisi her yerdedir: toplam AOV düştüğünde önce "her kanalda mı düştü, yoksa ucuz kanalın payı mı arttı?" diye sor. Payda kayması da benzer bir tuzaktır: dönüşüm oranı yükseldi, çünkü paydadaki "ziyaret" tanımı bot trafiği filtrelenerek değişti. Metrik sözleşmesi tam da bu yüzden vardır.
import veri
s = veri.oku("kesit_bi", "Satislar")
s = s[s["Durum"] != "İptal"]
s["marj"] = s["Tutar"] - s["Maliyet"]
k = s.groupby("Kanal")[["marj", "Tutar"]].sum()
print("ağırlıklı marj oranı (doğru):", (k["marj"] / k["Tutar"]).round(4).to_dict())
print("satır oranlarının ortalaması:", (s["marj"] / s["Tutar"]).groupby(s["Kanal"]).mean().round(4).to_dict())
Kuzey yıldızı metriği ve koruyucu metrikler
Ürün ekipleri tek bir kuzey yıldızı metriğine (north star metric) odaklanır: ör. "haftalık en az bir sipariş veren aktif müşteri". Onu yükseltirken bozulmaması gerekenler koruyucu metriklerdir (guardrail): iade oranı, ortalama teslim süresi, müşteri şikâyeti. A/B testlerinde de karar yalnız hedef metrikle değil, koruyucularla birlikte verilir.
Göreli ve mutlak değişim
%2'den %3'e çıkan dönüşüm "yüzde 50 arttı" (göreli) ya da "1 puan arttı" (mutlak, yüzde puan) diye anlatılabilir. Sunumda ikisini birlikte ver; küçük tabanlı göreli değişimler abartılı görünür.
Olasılıksal LTV
pip install lifetimes from lifetimes import BetaGeoFitter, GammaGammaFitter from lifetimes.utils import summary_data_from_transaction_data ozet = summary_data_from_transaction_data(df, "customer_id", "order_date", "net", observation_period_end="2024-12-31") bgf = BetaGeoFitter(penalizer_coef=0.01).fit(ozet["frequency"], ozet["recency"], ozet["T"])
Kendi bilgisayarında dene: bu model her müşterinin gelecekteki sipariş sayısını ve harcamasını tahmin eder; tarayıcıda çalışmaz.