"""
============================================================================
CASH LOAN APPLICATION MODEL - PRECISION TESHIS VE IYILESTIRME ANALIZI
============================================================================
Yapisi:
BOLUM A - TESHIS : Precision neden dusuk? (baz oran carpan ayristirmasi)
CASE 1 : Recall & Gini SABIT -> makul precision tavani
CASE 2 : Gini sabit, recall'dan AZ feragat -> etkisi
CASE 3 (referans): Precision'i asil ne yukseltir -> Gini
Metodoloji notu: Case 1-2, gozlenen (recall %73.8, FPR %29.7) noktasi CAPA
alinarak muhafazakar turetilir; tek-parametreli parametrik ROC varsayimina
yaslanmaz (o varsayimlar bu veriyle celisiyordu). Rakamlar 'en az bu kadar'
niteligindedir ve ham skor dagilimindan dogrulanmalidir.
============================================================================
"""
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
# --- Girdi (ALL / Test) ---
N_BAD, N_GOOD = 5478, 124627
N = N_BAD + N_GOOD
BR = N_BAD / N # baz oran = %4.21
GINI = 0.6037; AUC = 0.5 + GINI/2
R = 0.7382 # mevcut recall
P_cur = 0.0984 # mevcut precision
TP = R * N_BAD
FP_cur = TP / P_cur - TP
FPR_cur = FP_cur / N_GOOD
def precision(recall, fpr):
"""Precision kimligi: P = (R*BR) / (R*BR + FPR*(1-BR))"""
return (recall*BR) / (recall*BR + fpr*(1-BR))
# ===========================================================================
# BOLUM A - TESHIS
# ===========================================================================
print("="*66)
print("BOLUM A - TESHIS")
print("="*66)
print(f"Mevcut: recall {R:.1%}, FPR {FPR_cur:.1%}, precision {P_cur:.1%}, FP {FP_cur:,.0f}")
print(f"Baz oran (bad rate): {BR:.2%}\n")
print("Precision'in baz orana duyarliligi (R ve FPR sabit):")
for b in [BR, 0.10, 0.20, 0.50]:
p = (R*b)/(R*b+FPR_cur*(1-b))
print(f" bad rate {b:>5.1%} -> precision {p:>5.1%}")
print(" => Ayni model, sadece dusuk bad rate yuzunden dusuk precision veriyor.\n")
# ===========================================================================
# CASE 1 - Recall & Gini SABIT
# ===========================================================================
print("="*66); print("CASE 1 - Recall & Gini SABIT (esik ince-ayari)"); print("="*66)
c1 = {}
for label, cut in [("Muhafazakar (FPR -%20)", 0.20), ("Iyimser (FPR -%30)", 0.30)]:
fpr = FPR_cur*(1-cut); p = precision(R, fpr)
c1[label] = (fpr, p, fpr*N_GOOD)
print(f" {label:<24}: FPR {fpr:.1%}, precision {p:.1%}, FP {fpr*N_GOOD:,.0f}")
print(f" => Gercekci beklenti: precision {P_cur:.1%} -> ~%12-13.5 (mutevazi).\n")
# ===========================================================================
# CASE 2 - Gini sabit, recall'dan AZ feragat
# ===========================================================================
print("="*66); print("CASE 2 - Gini sabit, recall'dan AZ feragat"); print("="*66)
k = AUC/(1-AUC)
def fpr_pl(r): return 1-(1-r)**(1/k)
scale = FPR_cur / fpr_pl(R)
slope = R/FPR_cur
c2 = {}
for r2 in [0.70, 0.68]:
dr = R - r2
fpr_low = FPR_cur - dr/slope; p_low = precision(r2, fpr_low)
fpr_hi = fpr_pl(r2)*scale; p_hi = precision(r2, fpr_hi)
c2[r2] = (p_low, p_hi, fpr_low*N_GOOD, fpr_hi*N_GOOD)
print(f" recall {r2:.0%}: precision ~{p_low:.1%}..{p_hi:.1%}, FP {fpr_hi*N_GOOD:,.0f}..{fpr_low*N_GOOD:,.0f}")
print(" => Ayni Gini'de recall'dan feragat precision'i COK AZ artirir.\n")
# ===========================================================================
# CASE 3 (referans) - Gini
# CAPALI YAKLASIM: mevcut gercek nokta (Gini %60.4 -> precision %9.8) capa.
# Gini artisinin GORECELI etkisini, ayni recall'da FPR'nin bi-normal'e gore
# ne oranda dustugunu kullanarak mevcut FPR'ye uygula. Boylece mutlak
# (guvenilmez) bi-normal precision yerine, mevcut noktadan turetilmis
# gercekci bir uplift elde ederiz.
# ===========================================================================
print("="*66); print("CASE 3 (referans) - Recall %73.8 sabit, Gini artarsa (capali)"); print("="*66)
def fpr_binorm(g):
auc=0.5+g/2; dd=np.sqrt(2)*norm.ppf(auc)
return 1-norm.cdf(dd - norm.ppf(1-R))
fpr_base_bn = fpr_binorm(GINI) # bi-normal mevcut Gini'de FPR
c3 = {}
for g in [0.60, 0.65, 0.70, 0.75]:
# bi-normal FPR oranini mevcut GERCEK FPR'ye uygula (capali)
fpr_anchored = FPR_cur * (fpr_binorm(g)/fpr_base_bn)
p = precision(R, fpr_anchored)
c3[g] = p
print(f" Gini {g:.0%} -> precision ~{p:.1%} (FPR {fpr_anchored:.1%})")
print(" => Precision'i anlamli yukseltmenin TEK yolu ayrim gucu (Gini).")
print(" (Mevcut %9.8 capa; Gini +5 puan precision'i ~%2-3 puan artirir.)")
# ===========================================================================
# GORSELLESTIRME
# ===========================================================================
plt.rcParams['font.family'] = 'DejaVu Sans'
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
GD='#1a6b54'; GR='#2e9e7d'; RED='#c0392b'; AMB='#c98a1a'; GREY='#7f8c8d'; INK='#2c3e50'
fig = plt.figure(figsize=(15, 10))
gs = fig.add_gridspec(2, 2, hspace=0.36, wspace=0.24)
# PANEL A
axA = fig.add_subplot(gs[0,0])
brs = np.linspace(0.02, 0.55, 100)
axA.plot(brs*100, [(R*b)/(R*b+FPR_cur*(1-b))*100 for b in brs], color=GD, lw=2.5)
axA.scatter([BR*100],[P_cur*100], s=170, color=RED, zorder=5, edgecolor='white', lw=2)
axA.annotate(f'BIZIM DURUM\nbad rate %{BR*100:.1f}\nprecision %{P_cur*100:.1f}',
(BR*100, P_cur*100), xytext=(BR*100+8, P_cur*100+9),
fontsize=9, color=RED, fontweight='bold',
arrowprops=dict(arrowstyle='->', color=RED, lw=1.5))
for b in [0.10, 0.20]:
p=(R*b)/(R*b+FPR_cur*(1-b)); axA.scatter([b*100],[p*100], s=70, color=GREY, zorder=4)
axA.text(b*100+1, p*100, f'%{p*100:.0f}', fontsize=8, color=GREY)
axA.set_xlabel('Populasyondaki bad orani (%)'); axA.set_ylabel('Precision (%)')
axA.set_title('A) TESHIS: precision\'i asil bastiran dusuk bad rate\n(ayni model, recall & FPR sabit)',
fontweight='bold', color=INK, fontsize=10)
# PANEL B
axB = fig.add_subplot(gs[0,1])
states=['MEVCUT','CASE 1\n(ayni R & Gini)']
fp_makul = c1["Iyimser (FPR -%30)"][2]
tp_vals=[TP,TP]; fp_vals=[FP_cur, fp_makul]
x=np.arange(2); w=0.5
axB.bar(x,tp_vals,w,color=GR,label='TP (dogru bad)')
axB.bar(x,fp_vals,w,bottom=tp_vals,color=RED,alpha=0.75,label='FP (bosuna red good)')
for i,(tp,fp) in enumerate(zip(tp_vals,fp_vals)):
axB.text(i,tp/2,f'{tp:,.0f}',ha='center',va='center',color='white',fontweight='bold',fontsize=9)
axB.text(i,tp+fp/2,f'{fp:,.0f}',ha='center',va='center',color='white',fontweight='bold',fontsize=9)
prc = P_cur if i==0 else c1["Iyimser (FPR -%30)"][1]
axB.text(i,tp+fp+900,f'precision %{prc*100:.1f}',ha='center',color=INK,fontweight='bold',fontsize=9)
axB.set_xticks(x); axB.set_xticklabels(states)
axB.set_ylabel('Red edilen basvuru adedi')
axB.set_title('B) CASE 1: ayni recall & Gini\'de makul kazanc\n%9.8 -> ~%13 (mutevazi ama gercek)',
fontweight='bold', color=INK, fontsize=10)
axB.legend(loc='upper right', fontsize=8)
# PANEL C
axC = fig.add_subplot(gs[1,0])
rr=[0.68,0.70,R]
p_hi=[c2[0.68][1],c2[0.70][1],P_cur]; p_lo=[c2[0.68][0],c2[0.70][0],P_cur]
axC.fill_between([r*100 for r in rr],[p*100 for p in p_lo],[p*100 for p in p_hi],
color=GR, alpha=0.25, label='Makul aralik')
axC.plot([r*100 for r in rr],[p*100 for p in p_hi],'o-',color=GD,lw=2)
axC.scatter([R*100],[P_cur*100],s=150,color=RED,zorder=5,edgecolor='white',lw=2,label='Mevcut')
axC.set_ylim(8,14)
axC.set_xlabel('Recall (%)'); axC.set_ylabel('Precision (%)')
axC.set_title('C) CASE 2: ayni Gini\'de recall\'dan AZ feragat\nprecision neredeyse degismiyor (%9.8 -> ~%10)',
fontweight='bold', color=INK, fontsize=10)
axC.legend(loc='upper left', fontsize=8)
# PANEL D (capali: mevcut %9.8 noktasindan turetilmis goreceli uplift)
axD = fig.add_subplot(gs[1,1])
gs_grid=np.linspace(0.60,0.78,60); pg=[]
for g in gs_grid:
fpr_anch = FPR_cur*(fpr_binorm(g)/fpr_base_bn)
pg.append(precision(R,fpr_anch)*100)
axD.plot(gs_grid*100,pg,color=GD,lw=2.5)
axD.scatter([GINI*100],[P_cur*100],s=150,color=RED,zorder=5,edgecolor='white',lw=2)
axD.annotate(f'MEVCUT\nGini %{GINI*100:.0f}, prec %{P_cur*100:.1f}',(GINI*100,P_cur*100),
xytext=(GINI*100+1.5,P_cur*100-3.5),fontsize=9,color=RED,fontweight='bold',
arrowprops=dict(arrowstyle='->',color=RED,lw=1.5))
for g in [0.65,0.70]:
axD.scatter([g*100],[c3[g]*100],s=70,color=GR,zorder=4)
axD.text(g*100+0.4,c3[g]*100,f'%{c3[g]*100:.1f}',fontsize=8,color=GD,fontweight='bold')
axD.set_xlabel('Gini (%)'); axD.set_ylabel('Precision (%) @ recall %73.8')
axD.set_title('D) CASE 3 (referans): precision\'i asil ne yukseltir\nGini +5 puan -> precision anlamli artar (mevcut capa)',
fontweight='bold', color=INK, fontsize=10)
fig.suptitle('Cash Loan Application Model | Precision: Teshis ve Gercekci Iyilestirme (ALL/Test)',
fontsize=15, fontweight='bold', color=GD, y=0.985)
fig.text(0.5,0.005,
'Not: Case 1-2 gozlenen (recall %73.8, FPR %29.7) noktasi capa alinarak muhafazakar turetilmistir; '
'parametrik ROC varsayimina yaslanmaz. Kesin degerler ham skor dagilimindan dogrulanmalidir.',
ha='center', fontsize=8, color=GREY, style='italic')
plt.savefig('precision_analysis.png', dpi=150, bbox_inches='tight', facecolor='white')
print("\n[OK] precision_analysis.png yazildi.")
Yorumlar
Yorum Gönder