Krok 1: Import bibliotek¶

In [1]:
# Importujemy pandas - do pracy z danymi (Excel w Pythonie)
import pandas as pd

# Importujemy PyCaret - do wczytania modelu i predykcji
from pycaret.classification import *

# Importujemy json - do wczytania ustawień modelu (threshold)
import json

print("✅ Biblioteki zaimportowane!")
✅ Biblioteki zaimportowane!

Krok 2: Przygotowanie "nowych" klientów¶

Skąd bierzemy nowych klientów?¶

W prawdziwej produkcji dostałbyś plik CSV z nowymi klientami od systemu CRM.

Tutaj symulujemy to wybierając 20 losowych klientów z oryginalnego datasetu.

Co robimy?¶

  1. Wczytujemy oryginalny dataset
  2. Losujemy 20 klientów (więcej = bardziej zróżnicowana próbka)
  3. Usuwamy kolumnę Churn (w prawdziwej produkcji jej nie ma!)
  4. Zapisujemy jako data/new_customers.csv
In [10]:
# Wczytujemy oryginalny dataset
df_original = pd.read_csv('data/WA_Fn-UseC_-Telco-Customer-Churn.csv')

print(f"📊 Oryginalny dataset: {len(df_original)} klientów")

# Losujemy 20 klientów (więcej = bardziej zróżnicowana próbka)
# sample(20) - wylosuj 20 wierszy
# random_state=123 - "ziarnko losowości" dla powtarzalnych wyników
# UWAGA: Zmieniliśmy z random_state=42 na 123, bo akurat 42 wylosowało samych klientów wysokiego ryzyka!
new_customers = df_original.sample(20, random_state=123)

# WAŻNE: Usuwamy kolumnę Churn
# W prawdziwej produkcji nowi klienci NIE MAJĄ tej kolumny
# (nie wiemy jeszcze czy odejdą - to właśnie chcemy przewidzieć!)
new_customers = new_customers.drop('Churn', axis=1)

# Zapisujemy do pliku CSV
new_customers.to_csv('data/new_customers.csv', index=False)

print(f"\n✅ Przygotowano {len(new_customers)} nowych klientów")
print("💾 Zapisano jako: data/new_customers.csv")
print(f"📋 Kolumny: {len(new_customers.columns)} (bez Churn!)")

# Wyświetlamy pierwszych 3 klientów
print("\n🔍 Pierwsi 3 klienci:")
new_customers.head(3)
📊 Oryginalny dataset: 7043 klientów

✅ Przygotowano 20 nowych klientów
💾 Zapisano jako: data/new_customers.csv
📋 Kolumny: 20 (bez Churn!)

🔍 Pierwsi 3 klienci:
Out[10]:
customerID gender SeniorCitizen Partner Dependents tenure PhoneService MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod MonthlyCharges TotalCharges
941 0811-GSDTP Female 0 No Yes 13 No No phone service DSL No Yes No No No No Month-to-month No Electronic check 30.15 382.2
1404 1970-KKFWL Female 0 No No 35 Yes Yes No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Bank transfer (automatic) 23.30 797.1
5515 2892-GESUL Female 0 Yes Yes 18 Yes No No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Mailed check 19.35 309.25

Krok 3: Wczytanie zapisanego modelu¶

In [11]:
# Wczytujemy model który zapisaliśmy w train.ipynb
# load_model odczytuje plik .pkl i przywraca model do pamięci
# To jak odtworzenie gry z zapisu - model jest gotowy do użycia!

print("📂 Wczytywanie modelu z pliku...")

# Wczytujemy model (PyCaret automatycznie doda rozszerzenie .pkl)
model = load_model('models/churn_model')

print("✅ Model wczytany!")
print(f"📊 Typ modelu: {type(model).__name__}")
print("🔧 Model zawiera: wytrenowany algorytm + preprocessing pipeline")
📂 Wczytywanie modelu z pliku...
Transformation Pipeline and Model Successfully Loaded
✅ Model wczytany!
📊 Typ modelu: Pipeline
🔧 Model zawiera: wytrenowany algorytm + preprocessing pipeline

Krok 4: Wczytanie metadanych (ustawień modelu)¶

In [34]:
# Wczytujemy metadata.json - ustawienia modelu
# Zawiera: threshold (próg decyzyjny), optymalizację, informacje biznesowe

print("📂 Wczytywanie metadanych...")

# Otwieramy plik JSON i wczytujemy do słownika Python
with open('models/metadata.json', 'r', encoding='utf-8') as f:
    metadata = json.load(f)

print("\n✅ Metadata wczytane!")
print("\n📋 Ustawienia modelu:")
print(json.dumps(metadata, indent=2, ensure_ascii=False))

# Wyciągamy threshold - będziemy go używać do podjęcia decyzji
threshold = metadata['threshold']
print(f"\n🎯 Threshold: {threshold}")
print(f"💡 Znaczenie: Jeśli prawdopodobieństwo >= {threshold}, to przewidujemy 'Churn = Yes'")
📂 Wczytywanie metadanych...

✅ Metadata wczytane!

📋 Ustawienia modelu:
{
  "threshold": 0.5,
  "optimized_for": "recall",
  "business_reason": "false negatives are costly",
  "model_type": "LogisticRegression",
  "train_date": "2026-01-07 20:26:39"
}

🎯 Threshold: 0.5
💡 Znaczenie: Jeśli prawdopodobieństwo >= 0.5, to przewidujemy 'Churn = Yes'

Krok 5: Wczytanie nowych klientów¶

In [35]:
# Wczytujemy plik z nowymi klientami
# W prawdziwej produkcji dostałbyś taki plik z systemu CRM, bazy danych, API, itp.

print("📂 Wczytywanie nowych klientów...")

customers = pd.read_csv('data/new_customers.csv')

print(f"\n✅ Wczytano {len(customers)} klientów")
print(f"📋 Kolumny: {len(customers.columns)}")
print(f"🔍 Brak kolumny 'Churn' - to właśnie będziemy przewidywać!")

# Wyświetlamy dane
print("\n👥 Klienci do oceny:")
customers
📂 Wczytywanie nowych klientów...

✅ Wczytano 20 klientów
📋 Kolumny: 20
🔍 Brak kolumny 'Churn' - to właśnie będziemy przewidywać!

👥 Klienci do oceny:
Out[35]:
customerID gender SeniorCitizen Partner Dependents tenure PhoneService MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod MonthlyCharges TotalCharges
0 0811-GSDTP Female 0 No Yes 13 No No phone service DSL No Yes No No No No Month-to-month No Electronic check 30.15 382.20
1 1970-KKFWL Female 0 No No 35 Yes Yes No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Bank transfer (automatic) 23.30 797.10
2 2892-GESUL Female 0 Yes Yes 18 Yes No No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Mailed check 19.35 309.25
3 2842-BCQGE Male 0 No No 43 Yes Yes Fiber optic No No No No No No Month-to-month Yes Credit card (automatic) 75.35 3161.40
4 4807-IZYOZ Female 0 No No 51 Yes No No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Bank transfer (automatic) 20.65 1020.75
5 9451-WLYRI Female 0 Yes No 53 Yes No No No internet service No internet service No internet service No internet service No internet service No internet service One year No Credit card (automatic) 19.05 990.45
6 1767-TGTKO Female 0 Yes Yes 8 Yes No DSL No No No No No No Month-to-month Yes Electronic check 45.45 411.75
7 8033-VCZGH Male 0 Yes No 50 Yes Yes Fiber optic Yes No Yes No Yes Yes One year Yes Electronic check 103.95 5231.30
8 6689-VRRTK Female 1 No No 44 Yes Yes Fiber optic Yes Yes Yes No Yes Yes One year Yes Credit card (automatic) 109.80 4860.35
9 6997-UVGOX Male 0 Yes Yes 71 Yes No DSL Yes Yes Yes Yes Yes Yes Two year No Bank transfer (automatic) 85.45 6029.90
10 1602-IJQQE Female 0 No No 4 Yes Yes Fiber optic No No No No No No Month-to-month No Electronic check 75.35 338.10
11 6976-BWGLQ Female 0 Yes Yes 72 Yes Yes No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Bank transfer (automatic) 25.20 1787.35
12 8066-POXGX Female 0 No No 13 No No phone service DSL No No No No No Yes Month-to-month Yes Electronic check 35.10 446.10
13 5862-BRIXZ Male 0 No No 46 No No phone service DSL Yes No Yes Yes Yes Yes Two year Yes Bank transfer (automatic) 60.75 2893.40
14 5317-FLPJF Female 0 No No 66 No No phone service DSL Yes No Yes Yes Yes Yes Two year Yes Bank transfer (automatic) 61.35 4193.40
15 4193-IBKSW Male 0 Yes Yes 72 Yes Yes No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Bank transfer (automatic) 24.75 1769.60
16 8680-CGLTP Male 0 No No 29 Yes No DSL Yes Yes No Yes No No One year Yes Electronic check 58.75 1696.20
17 0616-ATFGB Male 1 Yes No 1 No No phone service DSL No No No No No No Month-to-month No Electronic check 25.05 25.05
18 0426-TIRNE Female 0 No No 1 Yes No No No internet service No internet service No internet service No internet service No internet service No internet service Month-to-month No Mailed check 20.90 20.90
19 5549-ZGHFB Male 0 Yes Yes 50 Yes Yes No No internet service No internet service No internet service No internet service No internet service No internet service Two year No Mailed check 24.95 1261.45

Krok 6: Wykonanie predykcji¶

Co się dzieje podczas predykcji?¶

  1. Preprocessing - PyCaret automatycznie:

    • Konwertuje TotalCharges na liczby
    • Normalizuje cechy numeryczne
    • Koduje cechy kategoryczne (Yes/No → 1/0)
  2. Model oblicza prawdopodobieństwa:

    • Patrzy na cechy klienta (tenure, MonthlyCharges, Contract, itp.)
    • Oblicza prawdopodobieństwo dla każdej klasy (Yes i No)
    • Używa threshold z metadata.json do podjęcia decyzji
  3. Zwraca wyniki:

    • prediction_label - ostateczna decyzja (Yes/No) z zastosowaniem threshold
    • prediction_score - prawdopodobieństwo dla predicted class (nie zawsze dla "Yes"!)
In [36]:
# Wykonujemy predykcje dla nowych klientów
# predict_model bierze model i dane, zwraca te same dane + kolumny z przewidywaniami

print("🔮 Wykonywanie predykcji...\n")

# WAŻNE: Przekazujemy threshold z metadata.json!
# Bez tego PyCaret użyłby domyślnego threshold = 0.5
# probability_threshold mówi: "jeśli prawdopodobieństwo >= threshold, to Yes"
print(f"🎯 Używam threshold z metadata.json: {threshold}")

# Predykcje - PyCaret dodaje kolumny: prediction_label i prediction_score
predictions = predict_model(model, data=customers, probability_threshold=threshold)

print("\n✅ Predykcje zakończone!")
print("\n📊 Dodane kolumny:")
print("   - prediction_label: Decyzja modelu z threshold={threshold} (Yes/No)")
print("   - prediction_score: Prawdopodobieństwo dla predicted class (0.0-1.0)")
🔮 Wykonywanie predykcji...

🎯 Używam threshold z metadata.json: 0.5

✅ Predykcje zakończone!

📊 Dodane kolumny:
   - prediction_label: Decyzja modelu z threshold={threshold} (Yes/No)
   - prediction_score: Prawdopodobieństwo dla predicted class (0.0-1.0)

Krok 7: Wyświetlenie wyników predykcji¶

In [37]:
# Wybieramy najważniejsze kolumny do wyświetlenia
# customerID - identyfikator klienta
# tenure - ile miesięcy jest klientem
# MonthlyCharges - miesięczny rachunek
# Contract - typ umowy
# prediction_score - prawdopodobieństwo odejścia
# prediction_label - decyzja (Yes/No)

result_columns = ['customerID', 'tenure', 'MonthlyCharges', 'Contract', 
                  'prediction_score', 'prediction_label']

results = predictions[result_columns].copy()

# Zaokrąglamy prawdopodobieństwo do 4 miejsc po przecinku
results['prediction_score'] = results['prediction_score'].round(4)

print("\n" + "="*80)
print("📊 WYNIKI PREDYKCJI")
print("="*80)

results
================================================================================
📊 WYNIKI PREDYKCJI
================================================================================
Out[37]:
customerID tenure MonthlyCharges Contract prediction_score prediction_label
0 0811-GSDTP 13 30.150000 Month-to-month 0.6416 No
1 1970-KKFWL 35 23.299999 Two year 0.9845 No
2 2892-GESUL 18 19.350000 Two year 0.9797 No
3 2842-BCQGE 43 75.349998 Month-to-month 0.6829 No
4 4807-IZYOZ 51 20.650000 Two year 0.9949 No
5 9451-WLYRI 53 19.049999 One year 0.9921 No
6 1767-TGTKO 8 45.450001 Month-to-month 0.6182 No
7 8033-VCZGH 50 103.949997 One year 0.7288 No
8 6689-VRRTK 44 109.800003 One year 0.7213 No
9 6997-UVGOX 71 85.449997 Two year 0.9931 No
10 1602-IJQQE 4 75.349998 Month-to-month 0.6575 Yes
11 6976-BWGLQ 72 25.200001 Two year 0.9981 No
12 8066-POXGX 13 35.099998 Month-to-month 0.5896 Yes
13 5862-BRIXZ 46 60.750000 Two year 0.9629 No
14 5317-FLPJF 66 61.349998 Two year 0.9823 No
15 4193-IBKSW 72 24.750000 Two year 0.9981 No
16 8680-CGLTP 29 58.750000 One year 0.9396 No
17 0616-ATFGB 1 25.049999 Month-to-month 0.6292 Yes
18 0426-TIRNE 1 20.900000 Month-to-month 0.7937 No
19 5549-ZGHFB 50 24.950001 Two year 0.9948 No

Krok 8: Analiza wyników predykcji¶

🔍 Jak działa prediction_score?¶

WAŻNE: prediction_score to prawdopodobieństwo dla predicted class, NIE zawsze dla "Yes"!

Przykłady:

  • Klient A: prediction_label = Yes, prediction_score = 0.85 → 85% szans że odejdzie
  • Klient B: prediction_label = No, prediction_score = 0.73 → 73% szans że zostanie

Model już zastosował threshold (z metadata.json) i zwrócił decyzję w prediction_label.

Nie musimy ręcznie stosować threshold - PyCaret to już zrobił!

Dlaczego threshold = 0.5?¶

  • Standardowy próg (50% szans)
  • Balans między wykrywaniem odchodzących a fałszywymi alarmami
  • W projekcie 06 przeanalizowaliśmy różne thresholdy (0.3, 0.5, 0.7)
In [38]:
# Używamy prediction_label - PyCaret już zastosował threshold
# prediction_label zawiera ostateczną decyzję modelu (Yes/No)

print(f"🎯 Model użył threshold: {threshold}")
print("\n📊 Wyjaśnienie kolumn:")
print("\nKolumny:")
print("  - prediction_label: Ostateczna decyzja modelu (Yes/No)")
print("  - prediction_score: Prawdopodobieństwo dla predicted class (0.0-1.0)")
print("\n💡 UWAGA: prediction_score to prawdopodobieństwo dla tej klasy którą model wybrał!")
print("   Jeśli prediction_label = No, to score = prawdopodobieństwo że ZOSTANIE")
print("   Jeśli prediction_label = Yes, to score = prawdopodobieństwo że ODEJDZIE")

# Dodajemy kolumnę z wyjaśnieniem
results['explanation'] = results.apply(
    lambda row: f"Model przewiduje: {row['prediction_label']} (pewność: {row['prediction_score']:.2%})",
    axis=1
)

print("\n" + "="*80)
print("🔍 SZCZEGÓŁOWE WYNIKI Z WYJAŚNIENIAMI")
print("="*80)

results[['customerID', 'prediction_label', 'prediction_score', 'explanation']]
🎯 Model użył threshold: 0.5

📊 Wyjaśnienie kolumn:

Kolumny:
  - prediction_label: Ostateczna decyzja modelu (Yes/No)
  - prediction_score: Prawdopodobieństwo dla predicted class (0.0-1.0)

💡 UWAGA: prediction_score to prawdopodobieństwo dla tej klasy którą model wybrał!
   Jeśli prediction_label = No, to score = prawdopodobieństwo że ZOSTANIE
   Jeśli prediction_label = Yes, to score = prawdopodobieństwo że ODEJDZIE

================================================================================
🔍 SZCZEGÓŁOWE WYNIKI Z WYJAŚNIENIAMI
================================================================================
Out[38]:
customerID prediction_label prediction_score explanation
0 0811-GSDTP No 0.6416 Model przewiduje: No (pewność: 64.16%)
1 1970-KKFWL No 0.9845 Model przewiduje: No (pewność: 98.45%)
2 2892-GESUL No 0.9797 Model przewiduje: No (pewność: 97.97%)
3 2842-BCQGE No 0.6829 Model przewiduje: No (pewność: 68.29%)
4 4807-IZYOZ No 0.9949 Model przewiduje: No (pewność: 99.49%)
5 9451-WLYRI No 0.9921 Model przewiduje: No (pewność: 99.21%)
6 1767-TGTKO No 0.6182 Model przewiduje: No (pewność: 61.82%)
7 8033-VCZGH No 0.7288 Model przewiduje: No (pewność: 72.88%)
8 6689-VRRTK No 0.7213 Model przewiduje: No (pewność: 72.13%)
9 6997-UVGOX No 0.9931 Model przewiduje: No (pewność: 99.31%)
10 1602-IJQQE Yes 0.6575 Model przewiduje: Yes (pewność: 65.75%)
11 6976-BWGLQ No 0.9981 Model przewiduje: No (pewność: 99.81%)
12 8066-POXGX Yes 0.5896 Model przewiduje: Yes (pewność: 58.96%)
13 5862-BRIXZ No 0.9629 Model przewiduje: No (pewność: 96.29%)
14 5317-FLPJF No 0.9823 Model przewiduje: No (pewność: 98.23%)
15 4193-IBKSW No 0.9981 Model przewiduje: No (pewność: 99.81%)
16 8680-CGLTP No 0.9396 Model przewiduje: No (pewność: 93.96%)
17 0616-ATFGB Yes 0.6292 Model przewiduje: Yes (pewność: 62.92%)
18 0426-TIRNE No 0.7937 Model przewiduje: No (pewność: 79.37%)
19 5549-ZGHFB No 0.9948 Model przewiduje: No (pewność: 99.48%)

Krok 9: Podsumowanie statystyk¶

In [39]:
# Liczymy ile klientów model sklasyfikował jako "odejdzie" vs "zostanie"

churn_yes = (results['prediction_label'] == 'Yes').sum()
churn_no = (results['prediction_label'] == 'No').sum()

print("\n" + "="*80)
print("📊 PODSUMOWANIE")
print("="*80)

print(f"\n👥 Liczba klientów: {len(results)}")
print(f"\n🔴 Przewidywane ODEJŚCIA (Churn = Yes): {churn_yes} ({churn_yes/len(results)*100:.1f}%)")
print(f"🟢 Przewidywane POZOSTANIE (Churn = No): {churn_no} ({churn_no/len(results)*100:.1f}%)")

# Statystyki prawdopodobieństw - tylko dla klientów z ryzykiem odejścia
at_risk = results[results['prediction_label'] == 'Yes']
if len(at_risk) > 0:
    print(f"\n📈 Statystyki pewności dla klientów z ryzykiem odejścia:")
    print(f"   Średnia pewność: {at_risk['prediction_score'].mean():.4f}")
    print(f"   Minimum: {at_risk['prediction_score'].min():.4f}")
    print(f"   Maximum: {at_risk['prediction_score'].max():.4f}")

print("\n" + "="*80)
================================================================================
📊 PODSUMOWANIE
================================================================================

👥 Liczba klientów: 20

🔴 Przewidywane ODEJŚCIA (Churn = Yes): 3 (15.0%)
🟢 Przewidywane POZOSTANIE (Churn = No): 17 (85.0%)

📈 Statystyki pewności dla klientów z ryzykiem odejścia:
   Średnia pewność: 0.6254
   Minimum: 0.5896
   Maximum: 0.6575

================================================================================

Krok 10: Rekomendacje akcji (co zrobić z wynikami)¶

In [26]:
# Dla klientów z wysokim ryzykiem odejścia - generujemy rekomendacje
# 
# WAŻNE: Poziomy ryzyka stosujemy TYLKO dla klientów z prediction_label = Yes
# Dla nich prediction_score = prawdopodobieństwo ODEJŚCIA, więc możemy ocenić ryzyko:
#   HIGH RISK = prawdopodobieństwo >= 0.7 (70%) - bardzo pewne że odejdzie
#   MEDIUM RISK = prawdopodobieństwo 0.5-0.7 (50-70%) - średnie ryzyko
#   LOW RISK = klienci z prediction_label = No (zostają)

def get_risk_level(prob):
    """Określa poziom ryzyka na podstawie prawdopodobieństwa ODEJŚCIA."""
    if prob >= 0.7:
        return "🔴 HIGH RISK"
    elif prob >= 0.5:
        return "🟡 MEDIUM RISK"
    else:
        return "🟢 LOW RISK"

def get_action(prob):
    """Rekomenduje akcję na podstawie ryzyka."""
    if prob >= 0.7:
        return "PILNE: Natychmiastowy kontakt z działem retencji + oferta specjalna"
    elif prob >= 0.5:
        return "Kontakt telefoniczny + analiza przyczyn niezadowolenia"
    else:
        return "Monitoring - brak pilnych działań"

# Stosujemy poziomy ryzyka TYLKO dla prediction_label = Yes
# W tym przypadku prediction_score = prawdopodobieństwo że ODEJDZIE (możemy ocenić jak bardzo)
# Dla prediction_label = No → automatycznie LOW RISK (klient zostaje)
results['risk_level'] = results.apply(
    lambda row: get_risk_level(row['prediction_score']) if row['prediction_label'] == 'Yes' else '🟢 LOW RISK',
    axis=1
)
results['recommended_action'] = results.apply(
    lambda row: get_action(row['prediction_score']) if row['prediction_label'] == 'Yes' else 'Monitoring - brak pilnych działań',
    axis=1
)

print("\n" + "="*80)
print("🎯 REKOMENDACJE AKCJI")
print("="*80)

# Wyświetlamy tylko klientów z ryzykiem (prediction_label = Yes)
at_risk = results[results['prediction_label'] == 'Yes'].copy()

if len(at_risk) > 0:
    print(f"\n⚠️ KLIENCI WYMAGAJĄCY UWAGI: {len(at_risk)}\n")
    
    for idx, row in at_risk.iterrows():
        print(f"👤 Klient: {row['customerID']}")
        print(f"   Prawdopodobieństwo: {row['prediction_score']:.2%}")
        print(f"   Poziom ryzyka: {row['risk_level']}")
        print(f"   Akcja: {row['recommended_action']}")
        print()
else:
    print("\n✅ Brak klientów z wysokim ryzykiem odejścia!")
================================================================================
🎯 REKOMENDACJE AKCJI
================================================================================

⚠️ KLIENCI WYMAGAJĄCY UWAGI: 3

👤 Klient: 1602-IJQQE
   Prawdopodobieństwo: 65.75%
   Poziom ryzyka: 🟡 MEDIUM RISK
   Akcja: Kontakt telefoniczny + analiza przyczyn niezadowolenia

👤 Klient: 8066-POXGX
   Prawdopodobieństwo: 58.96%
   Poziom ryzyka: 🟡 MEDIUM RISK
   Akcja: Kontakt telefoniczny + analiza przyczyn niezadowolenia

👤 Klient: 0616-ATFGB
   Prawdopodobieństwo: 62.92%
   Poziom ryzyka: 🟡 MEDIUM RISK
   Akcja: Kontakt telefoniczny + analiza przyczyn niezadowolenia

Krok 11: Zapisanie wyników do pliku¶

In [27]:
# Zapisujemy kompletne wyniki do pliku CSV
# W prawdziwej produkcji mógłbyś to wysłać:
# - Do bazy danych
# - Do systemu CRM
# - Na email dla zespołu retencji
# - Do dashboardu BI

output_file = 'data/predictions_results.csv'

# Zapisujemy pełne wyniki (wszystkie kolumny)
predictions.to_csv(output_file, index=False)

print(f"💾 Zapisano wyniki do pliku: {output_file}")
print(f"📊 Plik zawiera {len(predictions)} klientów z pełnymi danymi + predykcjami")

# Zapisujemy też podsumowanie (tylko najważniejsze kolumny)
summary_file = 'data/predictions_summary.csv'
results.to_csv(summary_file, index=False)

print(f"💾 Zapisano podsumowanie do: {summary_file}")
print(f"📋 Zawiera: ID, prawdopodobieństwo, decyzja, rekomendacje")

print("\n✅ Wszystkie wyniki zapisane!")
💾 Zapisano wyniki do pliku: data/predictions_results.csv
📊 Plik zawiera 20 klientów z pełnymi danymi + predykcjami
💾 Zapisano podsumowanie do: data/predictions_summary.csv
📋 Zawiera: ID, prawdopodobieństwo, decyzja, rekomendacje

✅ Wszystkie wyniki zapisane!

🎉 Podsumowanie¶

Co zrobiliśmy?¶

  1. ✅ Przygotowaliśmy 20 "nowych" klientów (bez kolumny Churn)
  2. ✅ Wczytaliśmy zapisany model z train.ipynb
  3. ✅ Załadowaliśmy metadata.json (threshold, ustawienia)
  4. ✅ Wykonaliśmy predykcje z threshold z metadata.json (probability_threshold=threshold)
  5. ✅ Zrozumieliśmy że prediction_score = prawdopodobieństwo dla predicted class
  6. ✅ Wygenerowaliśmy rekomendacje akcji dla klientów z ryzykiem odejścia
  7. ✅ Zapisaliśmy wyniki do plików CSV

🔍 Jak działa prediction_score?¶

KLUCZOWE: prediction_score NIE jest zawsze prawdopodobieństwem odejścia!

Jeśli prediction_label = Yes → prediction_score = prawdopodobieństwo ODEJŚCIA
Jeśli prediction_label = No  → prediction_score = prawdopodobieństwo POZOSTANIA

Przykłady:

  • Klient A: prediction_label=Yes, score=0.85 → 85% pewności że ODEJDZIE → HIGH RISK
  • Klient B: prediction_label=No, score=0.73 → 73% pewności że ZOSTANIE → LOW RISK

Co dalej?¶

W prawdziwej produkcji:

  1. Automatyzacja - skrypt uruchamiany codziennie/co tydzień
  2. Integracja - wyniki do CRM, bazy danych, dashboardu
  3. Akcje - automatyczne emaile do zespołu retencji
  4. Monitoring - śledzenie skuteczności (ile klientów zatrzymano)

💡 Kluczowe pliki wyjściowe:¶

  • predictions_results.csv - pełne dane + predykcje
  • predictions_summary.csv - podsumowanie z rekomendacjami

Model jest w produkcyjnym użyciu! 🚀