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?¶
- Wczytujemy oryginalny dataset
- Losujemy 20 klientów (więcej = bardziej zróżnicowana próbka)
- Usuwamy kolumnę Churn (w prawdziwej produkcji jej nie ma!)
- 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?¶
Preprocessing - PyCaret automatycznie:
- Konwertuje
TotalChargesna liczby - Normalizuje cechy numeryczne
- Koduje cechy kategoryczne (Yes/No → 1/0)
- Konwertuje
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
Zwraca wyniki:
prediction_label- ostateczna decyzja (Yes/No) z zastosowaniem thresholdprediction_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?¶
- ✅ Przygotowaliśmy 20 "nowych" klientów (bez kolumny Churn)
- ✅ Wczytaliśmy zapisany model z
train.ipynb - ✅ Załadowaliśmy metadata.json (threshold, ustawienia)
- ✅ Wykonaliśmy predykcje z threshold z metadata.json (
probability_threshold=threshold) - ✅ Zrozumieliśmy że
prediction_score= prawdopodobieństwo dla predicted class - ✅ Wygenerowaliśmy rekomendacje akcji dla klientów z ryzykiem odejścia
- ✅ 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:
- Automatyzacja - skrypt uruchamiany codziennie/co tydzień
- Integracja - wyniki do CRM, bazy danych, dashboardu
- Akcje - automatyczne emaile do zespołu retencji
- Monitoring - śledzenie skuteczności (ile klientów zatrzymano)
💡 Kluczowe pliki wyjściowe:¶
predictions_results.csv- pełne dane + predykcjepredictions_summary.csv- podsumowanie z rekomendacjami
Model jest w produkcyjnym użyciu! 🚀