Loading...

Telco Churn

This example uses the Telco Churn dataset to predict which Telco user is likely to churn; that is, customers that will likely stop using Telco.

  • Churn: customers that left within the last month.

  • Services: services of each customer (phone, multiple lines, internet, online security, online backup, device protection, tech support, and streaming TV and movies).

  • Customer account information: how long they’ve been a customer, contract, payment method, paperless billing, monthly charges, and total charges.

  • Customer demographics: gender, age range, and if they have partners and dependents.

We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.

Initialization

This example uses the following version of vastorbit:

import vastorbit as vo

vo.__version__

Connect to VAST. This example uses an existing connection called VASTDSN . For details on how to create a connection, see the Connection tutorial. You can skip the below cell if you already have an established connection.

vo.connect("VASTDSN")

Let’s create a VastFrame of the dataset. The dataset is available here.

churn = vo.read_csv("customers.csv")

Let’s take a look at the first few entries in the dataset.

churn.head(10)
Abc
customerid
Varchar(50)
100%
Abc
gender
Varchar(50)
100%
123
seniorcitizen
Integer
100%
Abc
partner
Varchar(50)
100%
Abc
dependents
Varchar(50)
100%
123
tenure
Integer
100%
Abc
phoneservice
Varchar(50)
100%
Abc
multiplelines
Varchar(50)
100%
Abc
internetservice
Varchar(50)
100%
Abc
onlinesecurity
Varchar(50)
100%
Abc
onlinebackup
Varchar(50)
100%
Abc
deviceprotection
Varchar(50)
100%
Abc
techsupport
Varchar(50)
100%
Abc
streamingtv
Varchar(50)
100%
Abc
streamingmovies
Varchar(50)
100%
Abc
contract
Varchar(50)
100%
Abc
paperlessbilling
Varchar(50)
100%
Abc
paymentmethod
Varchar(50)
100%
123
monthlycharges
Double
100%
123
totalcharges
Double
99%
Abc
churn
Varchar(50)
100%
10895-DQHEWMale0YesNo54YesYesFiber opticNoYesYesNoYesYesMonth-to-monthYesElectronic check104.35278.15Yes
24717-GHADLFemale0NoNo50YesYesDSLNoYesNoYesYesYesTwo yearYesMailed check79.64024.2No
35501-TVMGMMale0NoNo1YesNoDSLNoNoNoNoNoYesMonth-to-monthYesElectronic check55.2555.25No
45879-HMFFHFemale0YesNo72YesYesDSLYesYesYesYesYesYesTwo yearNoBank transfer (automatic)88.056520.8No
56772-WFQRDMale0NoYes40YesNoNoNo internet serviceNo internet serviceNo internet serviceNo internet serviceNo internet serviceNo internet serviceOne yearYesBank transfer (automatic)20.4854.9No
63810-DVDQQFemale0YesYes72YesYesFiber opticYesYesYesYesYesYesTwo yearYesBank transfer (automatic)117.68308.9No
76972-SNKKWFemale0NoNo6YesNoNoNo internet serviceNo internet serviceNo internet serviceNo internet serviceNo internet serviceNo internet serviceMonth-to-monthNoMailed check20.0109.2No
83694-GLTJMFemale0NoNo5YesNoNoNo internet serviceNo internet serviceNo internet serviceNo internet serviceNo internet serviceNo internet serviceMonth-to-monthYesCredit card (automatic)19.6592.05No
98550-XSXUQMale0YesNo48YesYesDSLNoYesYesNoNoYesOne yearYesCredit card (automatic)70.553420.5No
108149-RSOUNFemale0NoNo1YesYesFiber opticNoNoNoNoYesYesMonth-to-monthYesElectronic check93.8593.85Yes

Data Exploration and Preparation

Let’s examine our data.

churn.describe(method = "categorical", unique = True)
dtypecounttoptop_percentunique
"customerid"varchar(50)70430440-QEXBZ0.0147043.0
"gender"varchar(50)7043Male50.4762.0
"seniorcitizen"integer7043083.7852.0
"partner"varchar(50)7043No51.6972.0
"dependents"varchar(50)7043No70.0412.0
"tenure"integer704318.70473.0
"phoneservice"varchar(50)7043Yes90.3172.0
"multiplelines"varchar(50)7043No48.1333.0
"internetservice"varchar(50)7043Fiber optic43.9593.0
"onlinesecurity"varchar(50)7043No49.6663.0
"onlinebackup"varchar(50)7043No43.8453.0
"deviceprotection"varchar(50)7043No43.9443.0
"techsupport"varchar(50)7043No49.3113.0
"streamingtv"varchar(50)7043No39.8983.0
"streamingmovies"varchar(50)7043No39.5433.0
"contract"varchar(50)7043Month-to-month55.0193.0
"paperlessbilling"varchar(50)7043Yes59.2222.0
"paymentmethod"varchar(50)7043Electronic check33.5794.0
"monthlycharges"double704320.050.8661585.0
"totalcharges"double7032[null]0.1566530.0
"churn"varchar(50)7043No73.4632.0

Several variables are categorical, and since they all have low cardinalities, we can compute their dummies. We can also convert all booleans to numeric and fill the missing values.

for column in [
    "DeviceProtection",
    "MultipleLines",
    "PaperlessBilling",
    "Churn",
    "TechSupport",
    "Partner",
    "StreamingTV",
    "OnlineBackup",
    "Dependents",
    "OnlineSecurity",
    "PhoneService",
    "StreamingMovies",
]:
    churn[column].decode("Yes", 1, 0)
churn.one_hot_encode().drop(
    [
        "customerID",
        "gender",
        "Contract",
        "PaymentMethod",
        "InternetService",
    ],
)
churn.fillna()
123
seniorcitizen
Integer
100%
123
partner
Integer
100%
123
dependents
Integer
100%
123
tenure
Integer
100%
123
phoneservice
Integer
100%
123
multiplelines
Integer
100%
123
onlinesecurity
Integer
100%
123
onlinebackup
Integer
100%
123
deviceprotection
Integer
100%
123
techsupport
Integer
100%
123
streamingtv
Integer
100%
123
streamingmovies
Integer
100%
123
paperlessbilling
Integer
100%
123
monthlycharges
Double
100%
123
totalcharges
Real
100%
123
churn
Integer
100%
123
gender_Female
Bool
100%
123
internetservice_DSL
Bool
100%
123
internetservice_Fiber_optic
Bool
100%
123
contract_Month-to-month
Bool
100%
123
contract_One_year
Bool
100%
123
paymentmethod_Bank_transfer_(automatic)
Bool
100%
123
paymentmethod_Credit_card_(automatic)
Bool
100%
123
paymentmethod_Electronic_check
Bool
100%
101054110110111104.35278.15100110001
20005011010111179.64024.2011000000
3000110000001155.2555.25001010001
40107211111111088.056520.8011000100
50014010000000120.4854.9000001100
601172111111111117.68308.9010100100
7000610000000020.0109.2010010000
8000510000000119.6592.05010010010
90104811011001170.553420.5001001010
10000111000011193.8593.85110110001
110116410100110065.84068.0011001000
120001710000000020.05337.9010001010
130104010011000180.03168.75000110001
140114100101000035.41412.4011001010
151005111010000179.63974.7000101100
160104111010000180.253439.0000110001
17011110001000050.4550.45111010000
18000210000000120.4542.45010010000
190006810011000079.65461.45000101010
200112411000000024.7571.75010000000

Let’s compute the correlations between the different variables and the response column.

churn.corr(focus = "Churn")

Many features have a strong correlation with the Churn variable. For example, the customers that have a Month to Month contract are more likely to churn. Having this type of contract gives customers a lot of flexibility and allows them to leave at any time. New customers are also likely to churn.

# No lock-in = Churn
churn.barh(["Contract_Month-to-month", "tenure"], method = "avg", of = "Churn", height = 500)

The following scatter plot shows that providing better tariff plans can prevent churning. Indeed, customers having high total charges are more likely to churn even if they’ve been with the company for a long time.

churn.scatter(["TotalCharges", "tenure"], by = "Churn")

Let’s move on to machine learning.


Machine Learning

LogisticRegression is a very powerful algorithm and we can use it to detect churns. Let’s split our VastFrame into training and testing set to evaluate our model.

train, test = churn.train_test_split(
    test_size = 0.2,
    random_state = 0,
)

Let’s train and evaluate our model.

from vastorbit.machine_learning.vast import LogisticRegression

model = LogisticRegression(
    max_iter = 3000,
)
model.fit(
    train,
    churn.get_columns(exclude_columns = ["churn"]),
    "churn",
    test,
)
model.classification_report()
value
auc0.840838494406836
prc_auc0.46069025204961966
accuracy0.8162422573984859
log_loss0.4404031247622047
precision0.6948640483383686
recall0.5808080808080808
f1_score0.6327372764786795
mcc0.5151626244476125
informedness0.4852546276387337
markedness0.5469139592118089
csi0.46277665995975853

The model is excellent! Let’s run some machine learning on the entire dataset and compute the importance of each feature.

model.drop()
model.fit(
    churn,
    churn.get_columns(exclude_columns = ["churn"]),
    "churn",
)
model.features_importance()

Based on our model, most churning customers are at least one of the following:

  • Paying higher bills

  • New Telco customers

  • Have a monthly contract

Notice that customers have a Fiber Optic option are also likely to churn. Let’s check if this relationship is causal by computing some aggregations.

import vastorbit.sql.functions as fun

# Is Fiber optic a Bad Option? - vastorbit
churn.groupby(
    ["InternetService_Fiber_optic"],
    [
        fun.avg(churn["tenure"])._as("tenure"),
        fun.avg(churn["totalcharges"])._as("totalcharges"),
        fun.avg(churn["contract_month-to-month"])._as("contract_month_to_month"),
        fun.avg(churn["monthlycharges"])._as("monthlycharges"),
    ]
)
123
internetservice_Fiber_optic
Integer
100%
123
tenure
Double
100%
123
totalcharges
Double
100%
123
contract_month_to_month
Double
100%
123
monthlycharges
Double
100%
1132.917958656330753205.3045704134370.687338501291989791.50012919896639
2031.9422346085634671560.08666198359770.4426146440334431443.78824423612872

It seems like the Fiber Optic option in and of itself doesn’t lead to churning, but customers that have this option tend to churn because their contract puts them into one of the three categories we listed before: they’re paying more.

To retain these customers, we’ll need to make some changes to what types of contracts we offer.

We’ll use a lift chart to help us identify which of our customers are likely to churn.

model.lift_chart()

By targeting less than 10% of the entire distribution, our predictions will be more than two times more accurate than the other 90%.

Conclusion

We’ve solved our problem in a pandas-like way, all without ever loading data into memory!