İçerikler
🗺️ Haritam
☕ Destek OlGiriş YapKayıt Ol
Ana sayfavaka çalışması

E-ticaret sepet terki: neden ayrılıyorlar, nasıl geri döndürürsün?

Dönüşüm hunisi · 22 dakika okuma · Python + XGBoost + interaktif simülasyon

Türkiye'de e-ticaret pazarının büyüklüğü 2024'te 700 milyar TL'yi aştı. Bu pazarda ortalama sepet terk oranı %70'in üzerinde — yani her 10 kişiden 7'si ödeme adımına geçmeden ayrılıyor.

Sepet terki soyut bir kayıp değil, somut bir optimizasyon problemi. Hangi kullanıcı neden ayrılıyor, kim ne zaman geri dönebilir? Bu soruların cevabı hem teknik hem de iş stratejisi gerektiriyor.

Dönüşüm Hunisi
10.000 oturum örneği · Üzerine gel
Oturum açtı10.000
2.760(%28 terk)
Ürün inceledi7.240
4.060(%56 terk)
Sepete ekledi3.180
1.560(%49 terk)
Ödeme sayfası1.620
748(%46 terk)
Satın aldı872
Sepet → Satın alma
%27.4
Genel dönüşüm
%8.7
En büyük kayıp
Sepet → Ödeme

Sepet terkinin anatomisi

Yukarıdaki huniye bakıldığında en büyük kayıp sepetten ödeme sayfasına geçişte yaşanıyor: 3.180 kişi sepete ürün ekliyor ama sadece 1.620'si ödemeye geçiyor. %49 terk oranı. Neden?

  • Zorunlu üyelik: "Hesap oluştur" adımı ile karşılaşmak conversion'ı %25 düşürüyor
  • Beklenmedik kargo ücreti: Kargo maliyeti ödemede ortaya çıkınca %35 terk
  • Güven eksikliği: Tanınmayan markada ödeme bilgisi girme direnci
  • Fiyat karşılaştırma: "Şimdi değil, biraz düşüneyim" — özellikle mobilde

Veri seti ve EDA

Olist Brazilian E-Commerce Dataset: 100K+ sipariş, müşteri davranışı ve ödeme verileri. Kaggle'da açık olarak erişilebilir.

import pandas as pd
import numpy as np

# Temel metrikler
df = pd.read_csv("olist_orders_dataset.csv")
print(df["order_status"].value_counts())
# delivered       96478
# shipped          1107
# canceled          625
# ...

# Sepet terk oranı (ödeme başlatılmış ama tamamlanmamış)
terk_orani = df[df["order_status"] == "canceled"].shape[0] / len(df)
print(f"İptal oranı: {terk_orani:.2%}")  # ~0.63%

# Olist'te asıl sepet terki: session log'larından türetme
# Gerçek e-ticarette: payment_attempt başlayıp order oluşmayan kayıtlar
# Cihaz tipine göre dönüşüm (simüle veri)
cihaz_df = pd.DataFrame({
    "cihaz": ["Masaüstü", "Tablet", "Mobil"],
    "sepet_olusturma": [0.32, 0.28, 0.24],
    "tamamlama":       [0.38, 0.28, 0.19],
})
cihaz_df["genel_donusum"] = (
    cihaz_df["sepet_olusturma"] * cihaz_df["tamamlama"]
)
print(cihaz_df.round(3))

# cihaz  sepet_olusturma  tamamlama  genel_donusum
# Masaüstü          0.320      0.380          0.122
# Tablet            0.280      0.280          0.078
# Mobil             0.240      0.190          0.046  ← %60 fark!

Masaüstü kullanıcısının genel dönüşüm oranı mobil kullanıcının 2.6 katı. Mobil öncelikli pazarda bu fark doğrudan kayıp anlamına geliyor. UX iyileştirmesinin ROI'si en yüksek kanallardan biri mobil checkout akışı.

Feature engineering

# Oturum bazlı özellikler
features = [
    "device_type",           # masaustu / tablet / mobil
    "session_duration_min",  # oturumda geçirilen süre
    "pages_viewed",          # incelenen sayfa sayısı
    "cart_value",            # sepet tutarı
    "n_items",               # ürün sayısı
    "traffic_source",        # organik / ücretli / email / sosyal
    "hour_of_day",           # günün saati
    "is_weekend",            # hafta sonu mu?
    "is_returning_user",     # daha önce alışveriş yapmış mı?
    "discount_applied",      # indirim kodu uygulandı mı?
    "free_shipping",         # ücretsiz kargo var mı?
]

# Yeni türetilen özellikler
df["val_per_item"]    = df["cart_value"] / df["n_items"]
df["engagement_rate"] = df["pages_viewed"] / df["session_duration_min"]
df["late_night"]      = ((df["hour_of_day"] >= 22) |
                          (df["hour_of_day"] <= 6)).astype(int)

X = df[features + ["val_per_item","engagement_rate","late_night"]]
y = df["completed_purchase"]  # 1 = tamamladı, 0 = terk etti

XGBoost modeli

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OrdinalEncoder
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
import xgboost as xgb
from sklearn.metrics import roc_auc_score, average_precision_score

cat_cols = ["device_type", "traffic_source"]
num_cols = [c for c in X.columns if c not in cat_cols]

preprocessor = ColumnTransformer([
    ("cat", OrdinalEncoder(handle_unknown="use_encoded_value",
                           unknown_value=-1), cat_cols),
    ("num", "passthrough", num_cols),
])

model = Pipeline([
    ("prep", preprocessor),
    ("clf", xgb.XGBClassifier(
        n_estimators=400,
        learning_rate=0.04,
        max_depth=5,
        scale_pos_weight=3,   # ~%70 terk = dengesizlik
        subsample=0.8,
        colsample_bytree=0.8,
        random_state=42,
        verbosity=0,
    ))
])

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42)

model.fit(X_train, y_train)
y_prob = model.predict_proba(X_test)[:, 1]

print(f"ROC-AUC  : {roc_auc_score(y_test, y_prob):.4f}")   # ~0.871
print(f"PR-AUC   : {average_precision_score(y_test, y_prob):.4f}")  # ~0.761

Interaktif: Kullanıcı profili simülatörü

Model özellikleri değiştirdikçe tahmin nasıl değişiyor? Aşağıda bir kullanıcının profilini oluştur ve satın alma olasılığını gör.

Kullanıcı Profil Simülatörü
Özellikleri değiştir → satın alma olasılığını gör
Sepet tutarı₺250
Ürün sayısı2 ürün
Oturum süresi8 dakika
Cihaz
Trafik kaynağı
Gün
%76Yüksek
Satın alma
olasılığı
Etkili faktörler
Kaynak🟡 Orta
Cihaz🟢 İyi
Tutar🟢 Uygun

Özellik önemi

import shap

explainer   = shap.TreeExplainer(model["clf"])
X_test_prep = model["prep"].transform(X_test)
shap_values = explainer.shap_values(X_test_prep)

shap.summary_plot(shap_values, X_test_prep,
                  feature_names=cat_cols + num_cols,
                  max_display=10, show=False)

# En etkili özellikler (ortalama |SHAP|):
# 1. traffic_source      — email trafiği en yüksek dönüşüm
# 2. is_returning_user   — geri dönen kullanıcılar 3x daha iyi tamamlıyor
# 3. session_duration    — uzun oturum = ciddi alıcı
# 4. device_type         — masaüstü dominansı
# 5. free_shipping       — ücretsiz kargo toggleı ciddi etkili
# 6. cart_value          — yüksek tutar = tereddüt
# 7. discount_applied    — indirim kodu tamamlamayı artırıyor

Segment bazlı aksiyon planı

# Kullanıcıları risk segmentlerine ayır
y_prob_train = model.predict_proba(X_train)[:, 1]
df_train = X_train.copy()
df_train["tamamlama_olasiligi"] = y_prob_train
df_train["segment"] = pd.cut(
    df_train["tamamlama_olasiligi"],
    bins=[0, 0.30, 0.55, 1.0],
    labels=["Yüksek Risk", "Orta Risk", "Düşük Risk"]
)

# Her segment için farklı müdahale
MUDAHALELER = {
    "Yüksek Risk": [
        "Exit-intent popup: %10 anlık indirim",
        "Güven sinyalleri göster: SSL, iade garantisi, yorumlar",
        "Ücretsiz kargo eşiği: sepet tutarını görünür yap",
    ],
    "Orta Risk": [
        "Terk sonrası email: 1 saat içinde sepet hatırlatma",
        "Sosyal kanıt: 'X kişi şu an inceliyor'",
        "Stok uyarısı: 'Son 3 ürün kaldı'",
    ],
    "Düşük Risk": [
        "Upsell/cross-sell önerisi ekle",
        "Sadakat puanı hatırlatması",
        "Express kargo seçeneği sun",
    ],
}

for segment, mudahaleler in MUDAHALELER.items():
    n = (df_train["segment"] == segment).sum()
    print(f"
{segment}: {n} kullanıcı")
    for m in mudahaleler:
        print(f"  • {m}")

Interaktif: Gelir kurtarma hesaplayıcı

Dönüşüm oranını az da olsa artırmak büyük gelir farkı yaratır. Kendi metriklerinle hesapla:

Gelir Kurtarma Hesaplayıcı
Dönüşümü %X iyileştirirsen ne kazanırsın?
Aylık ziyaretçi50.000
Sepet oluşturma oranı28%
Mevcut tamamlama oranı22%
Ortalama sipariş değeri380
Hedef iyileştirme (göreli)15%
Mevcut aylık gelir
₺1.170.400
3.080 sipariş/ay
Kurtarılabilir ek gelir
₺175.560
+462 ek sipariş/ay
Yıllık potansiyel
₺2.106.720
Yalnızca bu kanaldan

A/B testi tasarımı

from scipy import stats
import numpy as np

# Kontrol: mevcut tamamlama oranı
p_kontrol  = 0.22
# Deney: exit-intent popup ile hedef
p_deney    = 0.25   # +3 puan iyileştirme hedefi

# Gerekli örneklem büyüklüğü
alpha = 0.05
guc   = 0.80

effect = p_deney - p_kontrol
pooled = (p_kontrol + p_deney) / 2
n = (
    2 * pooled * (1 - pooled) *
    (stats.norm.ppf(1 - alpha/2) + stats.norm.ppf(guc)) ** 2
) / effect ** 2

print(f"Her grup için gereken örneklem: {int(n):,}")
# ~3.200 kullanıcı / grup → ~6-7 gün trafik ile test edilebilir

# Kural: testin çalışma süresi
haftalik_trafik = 5000
print(f"Tahmini test süresi: {2*n/haftalik_trafik:.1f} hafta")

Sonuç

Sepet terk optimizasyonu tek bir çözümle bitmez. En etkili yaklaşım:

  • Ölç: Hangi adımda en çok kayıp var? Segment bazında fark nedir?
  • Tahmin et: Kim yüksek risk taşıyor? Müdahaleyi önceliklendir.
  • Dene: Her müdahale için A/B testi — sezgi yeterli değil.
  • Kişiselleştir: Herkese aynı popup yerine profile özel müdahale.

Gelir kurtarma hesaplayıcıdan görüldüğü gibi, dönüşümde küçük bir yüzdelik iyileştirme bile yıllık büyük gelir farkı yaratıyor. Bu yüzden sepet terki, veri ekibinin en yüksek ROI'li çalışma alanlarından biri.

Kaynaklar: Olist E-Commerce Dataset (Kaggle) · XGBoost dokümantasyonu · SHAP dokümantasyonu

Bu içerik: