<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/predict-customer-churn-logistic-regression-decision-tree-and-random-forest/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://datascienceplus.com/predict-customer-churn-logistic-regression-decision-tree-and-random-forest/"> R Programming – DataScience+</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Customer churn occurs when customers or subscribers stop doing business with a company or service, also known as customer attrition. It is also referred as loss of clients or customers. One industry in which churn rates are particularly useful is the telecommunications industry, because most customers have multiple options from which to choose within a geographic location.</p>
<p>Similar concept with <a rel="nofollow" href="https://datascienceplus.com/predict-employee-turnover-with-python/" target="_blank">predicting employee turnover</a>, we are going to predict customer churn using <a rel="nofollow" href="https://www.ibm.com/communities/analytics/watson-analytics-blog/guide-to-sample-datasets/" target="_blank">telecom dataset</a>. We will introduce Logistic Regression, Decision Tree, and Random Forest. But this time, we will do all of the above in R. Let’s get started!</p>
<h2>Data Preprocessing</h2>
<p>The data was downloaded from <a rel="nofollow" href="https://www.ibm.com/communities/analytics/watson-analytics-blog/guide-to-sample-datasets/" target="_blank">IBM Sample Data Sets</a>. Each row represents a customer, each column contains that customer’s attributes:</p>
<pre>
library(plyr)
library(corrplot)
library(ggplot2)
library(gridExtra)
library(ggthemes)
library(caret)
library(MASS)
library(randomForest)
library(party)

churn &lt;- read.csv('Telco-Customer-Churn.csv')
str(churn)
<em>'data.frame':	7043 obs. of 21 variables:
$ customerID : Factor w/ 7043 levels &quot;0002-ORFBO&quot;,&quot;0003-MKNFE&quot;,..: 5376 3963 2565 5536 6512 6552 1003 4771 5605 4535 ...
$ gender : Factor w/ 2 levels &quot;Female&quot;,&quot;Male&quot;: 1 2 2 2 1 1 2 1 1 2 ...
$ SeniorCitizen : int 0 0 0 0 0 0 0 0 0 0 ...
$ Partner : Factor w/ 2 levels &quot;No&quot;,&quot;Yes&quot;: 2 1 1 1 1 1 1 1 2 1 ...
$ Dependents : Factor w/ 2 levels &quot;No&quot;,&quot;Yes&quot;: 1 1 1 1 1 1 2 1 1 2 ...
$ tenure : int 1 34 2 45 2 8 22 10 28 62 ...
$ PhoneService : Factor w/ 2 levels &quot;No&quot;,&quot;Yes&quot;: 1 2 2 1 2 2 2 1 2 2 ...
$ MultipleLines : Factor w/ 3 levels &quot;No&quot;,&quot;No phone service&quot;,..: 2 1 1 2 1 3 3 2 3 1 ...
$ InternetService : Factor w/ 3 levels &quot;DSL&quot;,&quot;Fiber optic&quot;,..: 1 1 1 1 2 2 2 1 2 1 ...
$ OnlineSecurity : Factor w/ 3 levels &quot;No&quot;,&quot;No internet service&quot;,..: 1 3 3 3 1 1 1 3 1 3 ...
$ OnlineBackup : Factor w/ 3 levels &quot;No&quot;,&quot;No internet service&quot;,..: 3 1 3 1 1 1 3 1 1 3 ...
$ DeviceProtection: Factor w/ 3 levels &quot;No&quot;,&quot;No internet service&quot;,..: 1 3 1 3 1 3 1 1 3 1 ...
$ TechSupport : Factor w/ 3 levels &quot;No&quot;,&quot;No internet service&quot;,..: 1 1 1 3 1 1 1 1 3 1 ...
$ StreamingTV : Factor w/ 3 levels &quot;No&quot;,&quot;No internet service&quot;,..: 1 1 1 1 1 3 3 1 3 1 ...
$ StreamingMovies : Factor w/ 3 levels &quot;No&quot;,&quot;No internet service&quot;,..: 1 1 1 1 1 3 1 1 3 1 ...
$ Contract : Factor w/ 3 levels &quot;Month-to-month&quot;,..: 1 2 1 2 1 1 1 1 1 2 ...
$ PaperlessBilling: Factor w/ 2 levels &quot;No&quot;,&quot;Yes&quot;: 2 1 2 1 2 2 2 1 2 1 ...
$ PaymentMethod : Factor w/ 4 levels &quot;Bank transfer (automatic)&quot;,..: 3 4 4 1 3 3 2 4 3 1 ...
$ MonthlyCharges : num 29.9 57 53.9 42.3 70.7 ...
$ TotalCharges : num 29.9 1889.5 108.2 1840.8 151.7 ...
$ Churn : Factor w/ 2 levels &quot;No&quot;,&quot;Yes&quot;: 1 1 2 1 2 2 1 1 2 1 ...
</em>
</pre>
<p>The raw data contains 7043 rows (customers) and 21 columns (features). The “Churn” column is our target.</p>
<p>We use sapply to check the number if missing values in each columns. We found that there are 11 missing values in “TotalCharges” columns. So, let’s remove all rows with missing values.</p>
<pre>
sapply(churn, function(x) sum(is.na(x)))
<em>customerID gender SeniorCitizen Partner 0 0 0 0 Dependents tenure PhoneService MultipleLines 0 0 0 0 InternetService OnlineSecurity OnlineBackup DeviceProtection 0 0 0 0 TechSupport StreamingTV StreamingMovies Contract 0 0 0 0 PaperlessBilling PaymentMethod MonthlyCharges TotalCharges 0 0 0 11 Churn 0 </em>
</pre>
<pre>
churn &lt;- churn[complete.cases(churn), ]
</pre>
<h3>Look at the variables, we can see that we have some wranglings to do.</h3>
<p>1. We will change “No internet service” to “No” for six columns, they are: “OnlineSecurity”, “OnlineBackup”, “DeviceProtection”, “TechSupport”, “streamingTV”, “streamingMovies”.</p>
<pre>
cols_recode1 &lt;- c(10:15)
for(i in 1:ncol(churn[,cols_recode1])) {
        churn[,cols_recode1][,i] &lt;- as.factor(mapvalues
                                              (churn[,cols_recode1][,i], from =c(&quot;No internet service&quot;),to=c(&quot;No&quot;)))
}
</pre>
<p>2. We will change “No phone service” to “No” for column “MultipleLines”</p>
<pre>
churn$MultipleLines &lt;- as.factor(mapvalues(churn$MultipleLines, 
                                           from=c(&quot;No phone service&quot;),
                                           to=c(&quot;No&quot;)))
</pre>
<p>3. Since the minimum tenure is 1 month and maximum tenure is 72 months, we can group them into five tenure groups: “0–12 Month”, “12–24 Month”, “24–48 Months”, “48–60 Month”, “> 60 Month”</p>
<pre>
min(churn$tenure); max(churn$tenure)
<em>[1] 1
[1] 72
</em>
</pre>
<pre>
group_tenure = 0 &amp; tenure  12 &amp; tenure  24 &amp; tenure  48 &amp; tenure  60){
        return('> 60 Month')
    }
}
churn$tenure_group &lt;- sapply(churn$tenure,group_tenure)
churn$tenure_group &lt;- as.factor(churn$tenure_group)
</pre>
<p>4. Change the values in column “SeniorCitizen” from 0 or 1 to “No” or “Yes”.</p>
<pre>
churn$SeniorCitizen &lt;- as.factor(mapvalues(churn$SeniorCitizen,
                                      from=c(&quot;0&quot;,&quot;1&quot;),
                                      to=c(&quot;No&quot;, &quot;Yes&quot;)))
</pre>
<p>5. Remove the columns we do not need for the analysis.</p>
<pre>
churn$customerID &lt;- NULL
churn$tenure &lt;- NULL
</pre>
<h2>Exploratory data analysis and feature selection</h2>
<p><b>Correlation between numeric variables</b></p>
<pre>
numeric.var &lt;- sapply(churn, is.numeric)
corr.matrix &lt;- cor(churn[,numeric.var])
corrplot(corr.matrix, main=&quot;\n\nCorrelation Plot for Numerical Variables&quot;, method=&quot;number&quot;)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_1-490x227.png?w=450&ssl=1" alt="" class="wp-image-9498" data-recalc-dims="1"></a><br>
The Monthly Charges and Total Charges are correlated. So one of them will be removed from the model. We remove Total Charges.</p>
<pre>
churn$TotalCharges &lt;- NULL
</pre>
<p><b>Bar plots of categorical variables</b></p>
<pre>
p1 &lt;- ggplot(churn, aes(x=gender)) + ggtitle(&quot;Gender&quot;) + xlab(&quot;Gender&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p2 &lt;- ggplot(churn, aes(x=SeniorCitizen)) + ggtitle(&quot;Senior Citizen&quot;) + xlab(&quot;Senior Citizen&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p3 &lt;- ggplot(churn, aes(x=Partner)) + ggtitle(&quot;Partner&quot;) + xlab(&quot;Partner&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p4 &lt;- ggplot(churn, aes(x=Dependents)) + ggtitle(&quot;Dependents&quot;) + xlab(&quot;Dependents&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
grid.arrange(p1, p2, p3, p4, ncol=2)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_2.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_2-490x227.png?w=450&ssl=1" alt="" class="wp-image-9499" data-recalc-dims="1"></a></p>
<pre>
p5 &lt;- ggplot(churn, aes(x=PhoneService)) + ggtitle(&quot;Phone Service&quot;) + xlab(&quot;Phone Service&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p6 &lt;- ggplot(churn, aes(x=MultipleLines)) + ggtitle(&quot;Multiple Lines&quot;) + xlab(&quot;Multiple Lines&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p7 &lt;- ggplot(churn, aes(x=InternetService)) + ggtitle(&quot;Internet Service&quot;) + xlab(&quot;Internet Service&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p8 &lt;- ggplot(churn, aes(x=OnlineSecurity)) + ggtitle(&quot;Online Security&quot;) + xlab(&quot;Online Security&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
grid.arrange(p5, p6, p7, p8, ncol=2)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_3.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_3-490x227.png?w=450&ssl=1" alt="" class="wp-image-9500" data-recalc-dims="1"></a></p>
<pre>
p9 &lt;- ggplot(churn, aes(x=OnlineBackup)) + ggtitle(&quot;Online Backup&quot;) + xlab(&quot;Online Backup&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p10 &lt;- ggplot(churn, aes(x=DeviceProtection)) + ggtitle(&quot;Device Protection&quot;) + xlab(&quot;Device Protection&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p11 &lt;- ggplot(churn, aes(x=TechSupport)) + ggtitle(&quot;Tech Support&quot;) + xlab(&quot;Tech Support&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p12 &lt;- ggplot(churn, aes(x=StreamingTV)) + ggtitle(&quot;Streaming TV&quot;) + xlab(&quot;Streaming TV&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
grid.arrange(p9, p10, p11, p12, ncol=2)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_4.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_4-490x227.png?w=450&ssl=1" alt="" class="wp-image-9501" data-recalc-dims="1"></a></p>
<pre>
p13 &lt;- ggplot(churn, aes(x=StreamingMovies)) + ggtitle(&quot;Streaming Movies&quot;) + xlab(&quot;Streaming Movies&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p14 &lt;- ggplot(churn, aes(x=Contract)) + ggtitle(&quot;Contract&quot;) + xlab(&quot;Contract&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p15 &lt;- ggplot(churn, aes(x=PaperlessBilling)) + ggtitle(&quot;Paperless Billing&quot;) + xlab(&quot;Paperless Billing&quot;) + 
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p16 &lt;- ggplot(churn, aes(x=PaymentMethod)) + ggtitle(&quot;Payment Method&quot;) + xlab(&quot;Payment Method&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
p17 &lt;- ggplot(churn, aes(x=tenure_group)) + ggtitle(&quot;Tenure Group&quot;) + xlab(&quot;Tenure Group&quot;) +
  geom_bar(aes(y = 100*(..count..)/sum(..count..)), width = 0.5) + ylab(&quot;Percentage&quot;) + coord_flip() + theme_minimal()
grid.arrange(p13, p14, p15, p16, p17, ncol=2)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_5.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_5-490x227.png?w=450&ssl=1" alt="" class="wp-image-9502" data-recalc-dims="1"></a></p>
<p>All of the categorical variables seem to have a reasonably broad distribution, therefore, all of them will be kept for the further analysis.</p>
<h2>Logistic Regression</h2>
<p><b>First, we split the data into training and testing sets</b></p>
<pre>
intrain&lt;- createDataPartition(churn$Churn,p=0.7,list=FALSE)
set.seed(2017)
training&lt;- churn[intrain,]
testing&lt;- churn[-intrain,]
</pre>
<p><b>Confirm the splitting is correct</b></p>
<pre>
dim(training); dim(testing)
<em>[1] 4924 19
[1] 2108 19
</em>
</pre>
<p><b>Fitting the Logistic Regression Model</b></p>
<pre>
LogModel &lt;- glm(Churn ~ .,family=binomial(link=&quot;logit&quot;),data=training)
print(summary(LogModel))
<em>Call:
glm(formula = Churn ~ ., family = binomial(link = &quot;logit&quot;), data = training)
Deviance Residuals: Min 1Q Median 3Q Max -2.0370 -0.6793 -0.2861 0.6590 3.1608 Coefficients:
Estimate Std. Error z value Pr(>|z|) (Intercept) -2.030149 1.008223 -2.014 0.044053 * genderMale -0.039006 0.077686 -0.502 0.615603 SeniorCitizenYes 0.194408 0.101151 1.922 0.054611 . PartnerYes -0.062031 0.092911 -0.668 0.504363 DependentsYes -0.017974 0.107659 -0.167 0.867405 PhoneServiceYes -0.387097 0.788745 -0.491 0.623585 MultipleLinesYes 0.345052 0.214799 1.606 0.108187 InternetServiceFiber optic 1.022836 0.968062 1.057 0.290703 InternetServiceNo -0.829521 0.978909 -0.847 0.396776 OnlineSecurityYes -0.393647 0.215993 -1.823 0.068379 . OnlineBackupYes -0.113220 0.213825 -0.529 0.596460 DeviceProtectionYes -0.025797 0.213317 -0.121 0.903744 TechSupportYes -0.306423 0.220920 -1.387 0.165433 StreamingTVYes 0.399209 0.395912 1.008 0.313297 StreamingMoviesYes 0.338852 0.395890 0.856 0.392040 ContractOne year -0.805584 0.127125 -6.337 2.34e-10 ***
ContractTwo year -1.662793 0.216346 -7.686 1.52e-14 ***
PaperlessBillingYes 0.338724 0.089407 3.789 0.000152 ***
PaymentMethodCredit card (automatic) -0.018574 0.135318 -0.137 0.890826 PaymentMethodElectronic check 0.373214 0.113029 3.302 0.000960 ***
PaymentMethodMailed check 0.001400 0.136446 0.010 0.991815 MonthlyCharges -0.005001 0.038558 -0.130 0.896797 tenure_group0-12 Month 1.858899 0.205956 9.026 &lt; 2e-16 ***
tenure_group12-24 Month 0.968497 0.201452 4.808 1.53e-06 ***
tenure_group24-48 Month 0.574822 0.185500 3.099 0.001943 ** tenure_group48-60 Month 0.311790 0.200096 1.558 0.119185 ---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 5702.8 on 4923 degrees of freedom
Residual deviance: 4094.4 on 4898 degrees of freedom
AIC: 4146.4
Number of Fisher Scoring iterations: 6
</em>
</pre>
<p><b>Feature Analysis</b><br>
The top three most-relevant features include Contract, tenure_group and PaperlessBilling.</p>
<pre>
anova(LogModel, test=&quot;Chisq&quot;)
<em>Analysis of Deviance Table
Model: binomial, link: logit
Response: Churn
Terms added sequentially (first to last)
Df Deviance Resid. Df Resid. Dev Pr(>Chi) NULL 4923 5702.8 gender 1 0.39 4922 5702.4 0.5318602 SeniorCitizen 1 95.08 4921 5607.3 &lt; 2.2e-16 ***
Partner 1 107.29 4920 5500.0 &lt; 2.2e-16 ***
Dependents 1 27.26 4919 5472.7 1.775e-07 ***
PhoneService 1 1.27 4918 5471.5 0.2597501 MultipleLines 1 9.63 4917 5461.8 0.0019177 ** InternetService 2 452.01 4915 5009.8 &lt; 2.2e-16 ***
OnlineSecurity 1 183.83 4914 4826.0 &lt; 2.2e-16 ***
OnlineBackup 1 69.94 4913 4756.1 &lt; 2.2e-16 ***
DeviceProtection 1 47.58 4912 4708.5 5.287e-12 ***
TechSupport 1 82.78 4911 4625.7 &lt; 2.2e-16 ***
StreamingTV 1 4.90 4910 4620.8 0.0269174 * StreamingMovies 1 0.36 4909 4620.4 0.5461056 Contract 2 309.25 4907 4311.2 &lt; 2.2e-16 ***
PaperlessBilling 1 14.21 4906 4297.0 0.0001638 ***
PaymentMethod 3 38.85 4903 4258.1 1.867e-08 ***
MonthlyCharges 1 0.10 4902 4258.0 0.7491553 tenure_group 4 163.67 4898 4094.4 &lt; 2.2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
</em>
</pre>
<p>Analyzing the deviance table we can see the drop in deviance when adding each variable one at a time. Adding InternetService, Contract and tenure_group significantly reduces the residual deviance. The other variables such as PaymentMethod and Dependents seem to improve the model less even though they all have low p-values.</p>
<p><b>Assessing the predictive ability of the Logistic Regression model</b></p>
<pre>
testing$Churn &lt;- as.character(testing$Churn)
testing$Churn[testing$Churn==&quot;No&quot;] &lt;- &quot;0&quot;
testing$Churn[testing$Churn==&quot;Yes&quot;] &lt;- &quot;1&quot;
fitted.results &lt;- predict(LogModel,newdata=testing,type='response')
fitted.results  0.5,1,0)
misClasificError &lt;- mean(fitted.results != testing$Churn)
print(paste('Logistic Regression Accuracy',1-misClasificError))
<em>[1] &quot;Logistic Regression Accuracy 0.796489563567362&quot;
</em>
</pre>
<p><b>Logistic Regression Confusion Matrix</b></p>
<pre>
print(&quot;Confusion Matrix for Logistic Regression&quot;); table(testing$Churn, fitted.results > 0.5)
<em>[1] &quot;Confusion Matrix for Logistic Regression&quot;
FALSE TRUE
0 1392 156
1 273 287
</em>
</pre>
<p><b>Odds Ratio</b><br>
One of the interesting performance measurements in logistic regression is Odds Ratio.Basically, Odds ratio is what the odds of an event is happening.</p>
<pre>
exp(cbind(OR=coef(LogModel), confint(LogModel)))
<em>Waiting for profiling to be done...
OR 2.5 % 97.5 %
(Intercept) 0.1313160 0.01815817 0.9461855
genderMale 0.9617454 0.82587632 1.1199399
SeniorCitizenYes 1.2145919 0.99591418 1.4807053
PartnerYes 0.9398537 0.78338071 1.1276774
DependentsYes 0.9821863 0.79488224 1.2124072
PhoneServiceYes 0.6790251 0.14466019 3.1878587
MultipleLinesYes 1.4120635 0.92707245 2.1522692
InternetServiceFiber optic 2.7810695 0.41762316 18.5910286
InternetServiceNo 0.4362582 0.06397364 2.9715699
OnlineSecurityYes 0.6745919 0.44144273 1.0296515
OnlineBackupYes 0.8929545 0.58709919 1.3577947
DeviceProtectionYes 0.9745328 0.64144877 1.4805460
TechSupportYes 0.7360754 0.47707096 1.1344691
StreamingTVYes 1.4906458 0.68637788 3.2416264
StreamingMoviesYes 1.4033353 0.64624171 3.0518161
ContractOne year 0.4468271 0.34725066 0.5717469
ContractTwo year 0.1896086 0.12230199 0.2861341
PaperlessBillingYes 1.4031556 1.17798691 1.6725920
PaymentMethodCredit card (automatic) 0.9815977 0.75273387 1.2797506
PaymentMethodElectronic check 1.4523952 1.16480721 1.8145076
PaymentMethodMailed check 1.0014007 0.76673087 1.3092444
MonthlyCharges 0.9950112 0.92252949 1.0731016
tenure_group0-12 Month 6.4166692 4.30371945 9.6544837
tenure_group12-24 Month 2.6339823 1.78095906 3.9256133
tenure_group24-48 Month 1.7768147 1.23988035 2.5676783
tenure_group48-60 Month 1.3658675 0.92383315 2.0261505
</em>
</pre>
<h2>Decision Tree</h2>
<p><b>Decision Tree visualization</b><br>
For illustration purpose, we are going to use only three variables for plotting Decision Trees, they are “Contract”, “tenure_group” and “PaperlessBilling”.</p>
<pre>
tree &lt;- ctree(Churn~Contract+tenure_group+PaperlessBilling, training)
plot(tree, type='simple')
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_6-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_6-1-490x227.png?w=450&ssl=1" alt="" class="wp-image-9512" data-recalc-dims="1"></a><br>
1. Out of three variables we use, Contract is the most important variable to predict customer churn or not churn.<br>
2. If a customer in a one-year or two-year contract, no matter he (she) has PapelessBilling or not, he (she) is less likely to churn.<br>
3. On the other hand, if a customer is in a month-to-month contract, and in the tenure group of 0–12 month, and using PaperlessBilling, then this customer is more likely to churn.</p>
<p><b>Decision Tree Confusion Matrix</b><br>
We are using all the variables to product confusion matrix table and make predictions.</p>
<pre>
pred_tree &lt;- predict(tree, testing)
print(&quot;Confusion Matrix for Decision Tree&quot;); table(Predicted = pred_tree, Actual = testing$Churn)
<em>[1] &quot;Confusion Matrix for Decision Tree&quot;
Actual
Predicted No Yes
No 1395 346
Yes 153 214
</em>
</pre>
<p><b>Decision Tree Accuracy</b></p>
<pre>
p1 &lt;- predict(tree, training)
tab1 &lt;- table(Predicted = p1, Actual = training$Churn)
tab2 &lt;- table(Predicted = pred_tree, Actual = testing$Churn)
print(paste('Decision Tree Accuracy',sum(diag(tab2))/sum(tab2)))
<em>[1] &quot;Decision Tree Accuracy 0.763282732447818&quot;
</em>
</pre>
<p>The accuracy for Decision Tree has hardly improved. Let’s see if we can do better using Random Forest.</p>
<h2>Random Forest</h2>
<p><b>Random Forest Initial Model</b></p>
<pre>
rfModel &lt;- randomForest(Churn ~., data = training)
print(rfModel)
<em>Call:
randomForest(formula = Churn ~ ., data = training) Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 4
OOB estimate of error rate: 20.65%
Confusion matrix:
No Yes class.error
No 3245 370 0.1023513
Yes 647 662 0.4942704
</em>
</pre>
<p>The error rate is relatively low when predicting “No”, and the error rate is much higher when predicting “Yes”.</p>
<p><b>Random Forest Prediction and Confusion Matrix</b></p>
<pre>
pred_rf &lt;- predict(rfModel, testing)
caret::confusionMatrix(pred_rf, testing$Churn)
<em>Confusion Matrix and Statistics
Reference
Prediction No Yes
No 1381 281
Yes 167 279
Accuracy : 0.7875 95% CI : (0.7694, 0.8048)
No Information Rate : 0.7343 P-Value [Acc > NIR] : 9.284e-09 Kappa : 0.4175 Mcnemar's Test P-Value : 9.359e-08 Sensitivity : 0.8921 Specificity : 0.4982 Pos Pred Value : 0.8309 Neg Pred Value : 0.6256 Prevalence : 0.7343 Detection Rate : 0.6551 Detection Prevalence : 0.7884 Balanced Accuracy : 0.6952 'Positive' Class : No
</em>
</pre>
<p><b>Random Forest Error Rate</b></p>
<pre>
plot(rfModel)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_7-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_7-1-490x227.png?w=450&ssl=1" alt="" class="wp-image-9513" data-recalc-dims="1"></a><br>
We use this plot to help us determine the number of trees. As the number of trees increases, the OOB error rate decreases, and then becomes almost constant. We are not able to decrease the OOB error rate after about 100 to 200 trees.</p>
<p><b>Tune Random Forest Model</b></p>
<pre>
t &lt;- tuneRF(training[, -18], training[, 18], stepFactor = 0.5, plot = TRUE, ntreeTry = 200, trace = TRUE, improve = 0.05)
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_8-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_8-1-490x227.png?w=450&ssl=1" alt="" class="wp-image-9514" data-recalc-dims="1"></a><br>
We use this plot to give us some ideas on the number of mtry to choose. OOB error rate is at the lowest when mtry is 2. Therefore, we choose mtry=2.</p>
<p><b>Fit the Random Forest Model After Tuning</b></p>
<pre>
rfModel_new &lt;- randomForest(Churn ~., data = training, ntree = 200, mtry = 2, importance = TRUE, proximity = TRUE)
print(rfModel_new)
<em>Call:
randomForest(formula = Churn ~ ., data = training, ntree = 200, mtry = 2, importance = TRUE, proximity = TRUE) Type of random forest: classification
Number of trees: 200
No. of variables tried at each split: 2
OOB estimate of error rate: 19.7%
Confusion matrix:
No Yes class.error
No 3301 314 0.0868603
Yes 656 653 0.5011459
</em>
</pre>
<p>OOB error rate decreased to 19.7% from 20.65% earlier.</p>
<p><b>Random Forest Predictions and Confusion Matrix After Tuning</b></p>
<pre>
pred_rf_new &lt;- predict(rfModel_new, testing)
caret::confusionMatrix(pred_rf_new, testing$Churn)
<em>Confusion Matrix and Statistics
Reference
Prediction No Yes
No 1404 305
Yes 144 255
Accuracy : 0.787 95% CI : (0.7689, 0.8043)
No Information Rate : 0.7343 P-Value [Acc > NIR] : 1.252e-08 Kappa : 0.3989 Mcnemar's Test P-Value : 4.324e-14 Sensitivity : 0.9070 Specificity : 0.4554 Pos Pred Value : 0.8215 Neg Pred Value : 0.6391 Prevalence : 0.7343 Detection Rate : 0.6660 Detection Prevalence : 0.8107 Balanced Accuracy : 0.6812 'Positive' Class : No
</em>
</pre>
<p>The accuracy did not increase but the sensitivity improved, compare with the initial Random Forest model.</p>
<p><b>Random Forest Feature Importance</b></p>
<pre>
varImpPlot(rfModel_new, sort=T, n.var = 10, main = 'Top 10 Feature Importance')
</pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_10.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2017/11/churn_10-490x227.png?w=450&ssl=1" alt="" class="wp-image-9515" data-recalc-dims="1"></a></p>
<h2>Summary</h2>
<p>From the above example, we can see that Logistic Regression and Random Forest performed better than Decision Tree for customer churn analysis for this particular dataset.</p>
<p>Throughout the analysis, I have learned several important things:<br>
1. Features such as tenure_group, Contract, PaperlessBilling, MonthlyCharges and InternetService appear to play a role in customer churn.<br>
2. There does not seem to be a relationship between gender and churn.<br>
3. Customers in a month-to-month contract, with PaperlessBilling and are within 12 months tenure, are more likely to churn; On the other hand, customers with one or two year contract, with longer than 12 months tenure, that are not using PaperlessBilling, are less likely to churn.</p>
<p>Source code that created this post can be found <a rel="nofollow" href="https://github.com/susanli2016/Data-Analysis-with-R/blob/master/customer_churn.Rmd" target="_blank">here</a>. I would be pleased to receive feedback or questions on any of the above.</p>
<ul></ul>
<p><strong>
<p>Related Post</p>
<p></p></strong>
<ol>
<li><a rel="nofollow" href="https://datascienceplus.com/find-your-best-customers-with-customer-segmentation-in-python/" title="Permanent Link toFind Your Best Customers with Customer Segmentation in Python" target="_blank">Find Your Best Customers with Customer Segmentation in Python</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/principal-component-analysis-unsupervised-learning/" title="Permanent Link toPrincipal Component Analysis – Unsupervised Learning" target="_blank">Principal Component Analysis – Unsupervised Learning</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/arima-models-and-intervention-analysis/" title="Permanent Link toARIMA models and Intervention Analysis" target="_blank">ARIMA models and Intervention Analysis</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/a-gentle-introduction-on-market-basket-analysis%E2%80%8A-%E2%80%8Aassociation-rules/" title="Permanent Link toA Gentle Introduction on Market Basket Analysis — Association Rules" target="_blank">A Gentle Introduction on Market Basket Analysis — Association Rules</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/building-a-book-recommender-system-the-basics-knn-and-matrix-factorization/" title="Permanent Link toBuilding A Book Recommender System – The Basics, kNN and Matrix Factorization" target="_blank">Building A Book Recommender System – The Basics, kNN and Matrix Factorization</a></li>
</ol>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://datascienceplus.com/predict-customer-churn-logistic-regression-decision-tree-and-random-forest/"> R Programming – DataScience+</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/mtZuVPHMCaw" height="1"></a></div>