Telco Customer Churn Prediction¶
V tejto ukážkovej úlohe budeme pracovať s dátami z oblasti telekomunikácií. Dataset (súbor Telecom-Churn.csv
) obsahuje historické informácie o zákazníkoch telekomunikačného operátora vrátane informácií o zákazníkoch samotných a informáciach o službách, ktoré u daného operátora majú aktivované. V databáze zákazníkov sa nachádza aj informácia, či daný zákazník napokon od spoločnosti odišiel alebo nie (atribút Churn
). Cieľom úlohy bude vytvoriť analytický model, ktorý bude schopný na základe takýchto údajov o zákazníkovi predpovedať, či od telekomunikačného operátora odídu alebo nie.
Popis dát:
- CustomerID - jedinečný identifikátor zákazníka
- Gender - pohlavie zákazníka
- SeniorCitizen - udáva, či je zákazník senior, alebo nie (1, 0)
- Partner - udáva, či má zákazník partnera alebo nie (Yes, No)
- Dependents - udáva, či má zákazník ľudí na ňom závislých (napr. deti) alebo nie (Yes, No)
- Tenure - ako dlho je už zákazník zákazníkom telekomunikačnej spoločnosti (v mesiacoch)
- PhoneService - udáva, či zákazník má telefonickú službu alebo nie (Yes, No)
- MultipleLines - či zákazník používa viacero liniek (čísel) (Yes, No, No phone service)
- InternetService - typ pripojenia zákazníkna na internet (DSL, Fiber optic, No)
- OnlineSecurity - udáva, či používateľ používa službu zabezpečenia pripojenia (Yes, No, No internet service)
- OnlineBackup - udáva, či používateľ používa službu online zálohovania (Yes, No, No internet service)
- DeviceProtectionWhether - používanie zabezpečenia zariadenia (Yes, No, No internet service)
- TechSupport - služba technickej podpory (Yes, No, No internet service)
- StreamingTV - služba streamovanej televízie (Yes, No, No internet service)
- StreamingMovies - služba streamovania filmov (Yes, No, No internet service)
- Contract - typ kontraktu, ktorý zákazník uzavrel (Month-to-month, One year, Two year)
- PaperlessBilling - či má aktivovanú službu výpisov účtov elektronicky (Yes, No)
- PaymentMethod - spôsob platby (Electronic check, Mailed check, Bank transfer (automatic), Credit card (automatic))
- MonthlyCharges - mesačná výška platby
- TotalCharges - celková výška platby
- Churn - cieľový atribút - či používateľ odišiel od operátora alebo nie (Yes or No)
Načítanie dát¶
Ukážeme si ako dáta v .csv formáte načítame do pythonu. Načítame dáta do dátového rámca a zobrazíme niekoľko prvých záznamov v tabuľke.
Dáta sú v dátovej tabuľke organizované nasledovne - riadky reprezentujú jednotlivé skúmané objekty (teda napr. zákazníkov, ako v tomto prípade) a stĺpce rôzne vlastnosti týchto objektov (atribúty).
#importujeme python knižnice potrebné pre prácu s dátami
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
pd.set_option('display.max_columns', None)
sns.set()
# samotné načítanie dát z .csv súboru pomocou funkcie read_csv()
data = pd.read_csv("Telecom-Churn.csv", index_col=[0])
data.head()
Základné štatistiky atribútov¶
Pomocou rámca pandas
môžeme pohodlne vypočítať základné charakteristiky jednotlivých atribútov. Pomocou rámca seaborn
môžeme zase prehľadne vizualizovať ich rôzne aspekty. V nasledujúcich ukážkach sa pozrieme ako vypočítajte základné štatistiky a vizualizujte distribúciu pravdepodobnosti výskytu hodnôt.
- Pozrieme sa ako spočítame základné charakteristiky číselných atribútov
- Ukážeme si ako vykresliť napr. početnosti hodnôt kategorických atribútov (vykreslíme pre ne histogramy)
- Pozrieme sa, či v dátach sú nejaké chýbajúce hodnoty.
Pre kategorické atribúty môžeme zobraziť ich početnosti funkcie value_counts()
. Tá nám jednoducho zobrazí početnosti všetkých hodnôt, ktoré špecifikovaný atribút nadobúda. V príklade nižšie sa pozrieme, koľko zákazníkov od spoločnosti odchádza a koľko nie - pomocou početnosti atribútu Churn
.
data['Churn'].value_counts()
Početnosť hodnôt tohoto atribútu vieme aj vizualizovať. Môžeme na to použiť funkciu displot()
z rámca Seaborn
, ktorá do grafu vykreslí distribúciu jednotlivých hodnôt tohoto atribútu v datasete.
# funkcii countplot() špecifikujeme dva parametre - vstupný dátový rámec a atribút na osi x, pre ktorý má početnosti vykresliť
g = sns.countplot(data = data, x='Churn')
Pre numerické atribúty môžeme zobraziť základné štatistiky pomocou funkcie describe()
. Tá nám umožní jednoducho spočítať základné charakteristiky - početnosť, priemernú hodnotu, najmenšiu a najvačšiu hodnotu, štandardnú odchýlku a percentily.
Pozrime sa na to, ako dlho zákazníci používajú služby operátora (atribút Tenure
).
data[['Tenure']].describe()
Knižnica seaborn
umožňuje aj tieto štatistiky prehľadne vykresliť vo forme rôznych diagramov. Základným je displot()
, ktorý vykreslí rozdelenie hodnôt numerických atribútov.
# funkcii distplot() špecifikujeme rovnako dva parametre - vstupný dátový rámec a atribút na osi x, pre ktorý má distribúciu vykresliť
g = sns.displot(data = data, x='Tenure')
Závislosti medzi atribútmi¶
Vypočítajte korelačnú maticu pre číselné atribúty a identifikujte, ktoré atribúty sú najviac korelované.
# vytvoríme si pomocný/dočasný dátový rámec, ktorý pozostáva iba z číselných atribútov
data_numeric = data[['Tenure', 'MonthlyCharges', 'TotalCharges']]
# pomocou funkcie corr() spočítame vzájomné korelácie a pomocou funkcie heatmap() ich vykreslíme
g = sns.heatmap(data_numeric.corr(), cmap='RdPu', annot= True)
Vzájomné korelácie môžeme tiež vizualizovať. Môžeme na to použiť tzv. bodový graf (scatterplot). TOmu špecifikujeme, aké hodnoty sa majú zobraziť na jednotlivých osiach (hodnoty ktorých atribútov).
g = sns.scatterplot(data=data, x='TotalCharges', y='Tenure')
Závislosti medzi kategorickými atribútmi¶
Pomocou vizualizácií sa môžeme pozrieť aj na to, aká je závislosť medzi viacerými kategorickými atribútmi. Pomocou krížových tabuliek vieme zistiť početnosť rôznych kombinácií pre rôzne hodnoty atribútov. Rovnako ich vieme dobre vizualizovať pomocou grafov.
Vizualizujte početnosť hodnôt atribútu Contract
. Aký typ kontraktu používa najviac zákazníkov?
# sem vpíšte kód
Teraz sa skúsme pozrieť na to, aké kontrakty majú zákazníci, ktorí odchádzajú a ktorí nie. Typ kontraktu môže byť dôležitým faktorom pri rozhodovaní sa zákazníka, či od poskytovateľa služby odíde alebo nie.
Toto môžme rovnako vizualizovať pomocou funkcie countplot()
a to tak, že špecifikujeme paremeter hue
, ktorý farebne rozlíši časti grafu na základe hodnôt iného atribútu. V tomto prípade teda nastavíme parameter hue
na atribút Churn
.
Akú znalosť viete odvodiť z tejto vizualizácie?
# sem vpíšte kód
Podobne sa vieme pozrieť na takéto vzájomné kombinácie hodnôt jak kategorických, tak aj numerických hodnôt. Skúsme napr. preskúmať, aký je pomer odchádzajúcich/neodchádzajúcich klientov vzhľadom na výšku mesačných platieb za služby (atribút MonthlyCharges
).
Akú znalosť vieme z tejto vizualizácie odvodiť?
g = sns.displot(data = data, x='MonthlyCharges', hue='Churn')
Skúsme teraz zodpovedať tieto otázky:
- Odchádzajú od operátora častejšie starší ľudia alebo nie? (atribút
SeniorCitizen
) - Odchádzajú častejsie muži alebo ženy? (atribút
Gender
) - Aký je vzťah medzi odchodom zákazníka a spôsobom ako platí za služby? (atribút
PaymentMethod
) - Súvisí spolu nejak výška mesačnej platby za služby s typom internetového pripojenia? (atribúty
MonthlyCharges
aInternetService
)
# sem vpíšte kód
# sem vpíšte kód
# sem vpíšte kód
# sem vpíšte kód
Detekcia a nahradenie chýbajúcich hodnôt¶
V dátach sa môžu vyskytovať chýbajúce hodnoty. Môže ísť o chybu (nesprávny zápis, chyba meriacieho zariadenia atď.), no niekedy chýbajúca hodnota nesie význam. V procese prípravy dát je potrebné sa s takýmito hodnotami vysporiadať. Identifikovať ich a vhodne ich nahradiť.
V nasledujúcom kóde sa pozrieme na to, ako nájdeme chýbajúce hodnoty.
data.isna().sum()
Úloha! Porozmýšľajte - ako by sme vedeli odvodiť chýbajúce hodnoty atribútu MonthlyCharges
z hodnôt atribútov TotalCharges
a Tenure
?¶
def replace_missing_MonthlyCharges(row):
TotalCharges = row["TotalCharges"]
Tenure = row["Tenure"]
MonthlyCharges = row["MonthlyCharges"]
if pd.isna(MonthlyCharges):
return TotalCharges / Tenure
else:
return MonthlyCharges
data['MonthlyCharges'] = data.apply(replace_missing_MonthlyCharges, axis = 1)
data['MonthlyCharges'].isna().sum()
Veľmi podobne potom vieme prepočítať chýbajúce hodnoty TotalCharges
- na základe Tenure
a MonthlyCharges
.
def replace_missing_TotalCharges(row):
TotalCharges = row["TotalCharges"]
MonthlyCharges = row["MonthlyCharges"]
Tenure = row["Tenure"]
if pd.isna(TotalCharges):
if Tenure == 0:
return MonthlyCharges
else:
return MonthlyCharges * Tenure
else:
return TotalCharges
data['TotalCharges'] = data.apply(replace_missing_TotalCharges, axis = 1)
data['TotalCharges'].isna().sum()
data.isna().sum()
Selekcia atribútov¶
Ktoré použiť a ktoré nie?
- tie, ktoré majú z hľadiska odchodu členov význam. Ktoré to sú (resp. ktoré práve nie), tak to vyplýva práve z predošlých analýz
- môžeme vynechať jeden z dvojice veľmi korelovaných atrobútov
- čo sme zistili z predošlých krokov?
data.drop(columns=['Gender', 'InternetService', 'MultipleLines', 'TotalCharges'], inplace = True)
Prevedenie dát do podoby vhodnej pre modelovanie¶
Predspracovaný a vyčistený dátový rámec musíme previesť do podoby vhodnej pre modelovanie. Kategorické atribúty musíme transformovať - nahradiť reťazce číslami, keďže prediktívne modely s nimi vedia pracovať.
Väčšina atribútov je binárna - má iba 2 hodnoty (napr. áno/nie) - v takomto prípade je transformácia jednoduchá (nahradíme hodnoty áno/nie pomocou 1/0). Pri ostatných atribútoch nahradíme viacerými číslami.
data['Churn'] = data['Churn'].map({"Yes":1, "No":0})
data['Partner'] = data['Partner'].map({'Yes':1, 'No':0})
data['Dependents'] = data['Dependents'].map({'Yes':1, 'No':0})
data['PaperlessBilling'] = data['PaperlessBilling'].map({'Yes':1, 'No':0})
data['PhoneService'] = data['PhoneService'].map({'Yes':1, 'No':0})
data['OnlineSecurity'] = data['OnlineSecurity'].map({'Yes':1, 'No':0, 'No internet service':0})
data['OnlineBackup'] = data['OnlineBackup'].map({'Yes':1, 'No':0, 'No internet service':0})
data['DeviceProtection'] = data['DeviceProtection'].map({'Yes':1, 'No':0, 'No internet service':0})
data['TechSupport'] = data['TechSupport'].map({'Yes':1, 'No':0, 'No internet service':0})
data['StreamingTV'] = data['StreamingTV'].map({'Yes':1, 'No':0, 'No internet service':0})
data['StreamingMovies'] = data['StreamingMovies'].map({'Yes':1, 'No':0, 'No internet service':0})
data["Contract"] = data["Contract"].map({"Month-to-month": 0, "One year": 1, "Two year" : 2})
data["PaymentMethod"] = data["PaymentMethod"].map({"Mailed check": 0, "Electronic check": 1, "Bank transfer (automatic)" : 2, "Credit card (automatic)" : 3})
data.head()
Rozdelenie dát na trénovanie a testovanie¶
- Rozdeľte dátovú množinu na trénovaciu (použije sa na naučenie analytického modelu) a testovaciu (použije sa na vyhodnotenie).
- Rozdeľte dáta do trénovacej a testovacej množiny v pomere 70/30.
from sklearn.model_selection import train_test_split
X_data = data.drop(columns= 'Churn', axis = 1)
y_data = data['Churn']
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size= 0.3, random_state= 1)
Naučenie klasifikačného modelu¶
Naučíme prediktívny analytický model (na trénovacej množine) - v tomto prípade model rozhodovacieho stromu.
Potom ho vyhodnotíme na testovacej množine - použijeme metriku accuracy
- tá hovorí o presnosti predpovedania - porovná, koľkokrát model na testovacej množine predpovedal správne a koľkokrát nie.
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score
tree = DecisionTreeClassifier(max_depth=4)
model = tree.fit(X_train, y_train)
predictions = model.predict(X_test)
print(f"Presnosť (accuracy) modelu: {accuracy_score(y_test, predictions)}")
Model vyhodnotíme aj pomocou tzv. confusion matrix. Z nej vieme presne odčítať, kde si model počína správne a kde nie.
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, predictions)
print(cm)
Niektoré modely môžeme aj vizualizovať, napr. vo forme diagramu, ako stromový model. V ňom vieme presne povedať, ktoré atribúty boli použité v rozhodovaní a môžeme tak vizualizovať to pravidlo (alebo pravidlá), ktoré sa model na dátach naučil.
from sklearn import tree
plt.style.use('dark_background')
fig, axes = plt.subplots(nrows = 1,ncols = 1,figsize = (21,10), dpi=300)
tree.plot_tree(model, fontsize=8, feature_names=data.columns.values, class_names=["0","1"], filled=True)
plt.show()
Použitie modelu:
- ukážkový zákazník, o ktorom chceme vedieť, či existuje možnosť, že od operátora na základe dát odíde alebo nie.
customer_X = pd.DataFrame([[0, 1, 0, 10, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 70]], columns=list(X_data.columns))
prediction_X = model.predict(customer_X)
Tak čo - odíde alebo nie?
if prediction_X == 1:
print("Zákazník X od operátora asi odíde :( ")
else:
print("Zákazník X bude naďalej využívať služby :) ")