library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.4.4 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.0
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)
library(ggplot2)
#Loading data
data <- read_delim("../Churn_Modelling.csv", delim = ",")
## Rows: 10002 Columns: 14
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (3): Surname, Geography, Gender
## dbl (11): RowNumber, CustomerId, CreditScore, Age, Tenure, Balance, NumOfPro...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
str(data)
## spc_tbl_ [10,002 × 14] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ RowNumber : num [1:10002] 1 2 3 4 5 6 7 8 9 10 ...
## $ CustomerId : num [1:10002] 15634602 15647311 15619304 15701354 15737888 ...
## $ Surname : chr [1:10002] "Hargrave" "Hill" "Onio" "Boni" ...
## $ CreditScore : num [1:10002] 619 608 502 699 850 645 822 376 501 684 ...
## $ Geography : chr [1:10002] "France" "Spain" "France" "France" ...
## $ Gender : chr [1:10002] "Female" "Female" "Female" "Female" ...
## $ Age : num [1:10002] 42 41 42 39 43 44 50 29 44 NA ...
## $ Tenure : num [1:10002] 2 1 8 1 2 8 7 4 4 2 ...
## $ Balance : num [1:10002] 0 83808 159661 0 125511 ...
## $ NumOfProducts : num [1:10002] 1 1 3 2 1 2 2 4 2 1 ...
## $ HasCrCard : num [1:10002] 1 0 1 0 NA 1 1 1 0 1 ...
## $ IsActiveMember : num [1:10002] 1 1 0 0 1 0 1 0 NA 1 ...
## $ EstimatedSalary: num [1:10002] 101349 112543 113932 93827 79084 ...
## $ Exited : num [1:10002] 1 0 1 0 0 1 0 1 0 0 ...
## - attr(*, "spec")=
## .. cols(
## .. RowNumber = col_double(),
## .. CustomerId = col_double(),
## .. Surname = col_character(),
## .. CreditScore = col_double(),
## .. Geography = col_character(),
## .. Gender = col_character(),
## .. Age = col_double(),
## .. Tenure = col_double(),
## .. Balance = col_double(),
## .. NumOfProducts = col_double(),
## .. HasCrCard = col_double(),
## .. IsActiveMember = col_double(),
## .. EstimatedSalary = col_double(),
## .. Exited = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
There is multiple variables that could appeal as valuable,
due to numerous variables encapsulating crucial information, such as
Balance, CreditScore,
EstimatedSalary etc. But often the later 2 variables have
association with Balance, thus among all the attributes of
the data is can be safe to assume that Balance would the
most valuable column. There are attributes such as
Tenure, HasCrCard and
NumOfProducts do also have association with the column
Balance, therefore the column Balance could be
considered the most valuable.
Further, other column priority do change upon the domain of the
analyst, but the column Balance tends to be of greater
interest for most of people.
Let Geography be the categorical column which serves as
the influencing variable and Exited be the response
variable.
Null Hypothesis (H0): There is no significant difference in the mean churn rates among customers from different countries (France, Spain, Germany).
Alternative Hypothesis (H1): There is a significant difference in the mean churn rates among customers from different countries.
anova_result <- aov(Exited ~ Geography, data = data)
summary(anova_result)
## Df Sum Sq Mean Sq F value Pr(>F)
## Geography 2 49 24.522 155.8 <2e-16 ***
## Residuals 9998 1574 0.157
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 1 observation deleted due to missingness
From the results the Pr(>F) value is
<2e-16, which is a value much smaller than the usual
significance level (0.05). Further, the
F value is 155.8, which essentially is the
evidence against the null hypothesis. From the result of both these
values it can be concluded that there is a significant difference in
churn rates among customers from different countries.
ggplot(data, aes(x = Geography, y = Exited)) +
geom_boxplot() +
stat_summary(fun = mean, geom = "point", shape = 20, size = 3, color = "red") +
labs(x = "Geography", y = "Exited") +
ggtitle("Boxplot of Churn Rates by Geography")
Hence, there is enough evidence to conclude that there is a significant difference in means churn rates among countries. Therefore, it would be safe to assume that there are significant differences in churn rates among the countries, based on the results of the ANOVA test.
Let, Balance be the influencing variable and
EstimatedSalary be the response variable, it is safe to
assume that age could potentially have a linear relationship with the
likelihood of a customer churning, as younger or older customers might
be more or less likely to leave the bank.
model <- lm(Balance ~ EstimatedSalary, data = data)
summary(model)
##
## Call:
## lm(formula = Balance ~ EstimatedSalary, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -77868 -75955 20770 51017 174669
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 7.511e+04 1.252e+03 59.982 <2e-16 ***
## EstimatedSalary 1.378e-02 1.085e-02 1.271 0.204
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 62390 on 10000 degrees of freedom
## Multiple R-squared: 0.0001614, Adjusted R-squared: 6.141e-05
## F-statistic: 1.614 on 1 and 10000 DF, p-value: 0.2039
We can visualize the result of the model -
predictions <- predict(model)
plot(data$Balance, data$EstimatedSalary, col = "blue", main = "Observed vs. Predicted Values", xlab = "Balance", ylab = "Estimated Salary")
abline(model, col = "red")
legend("topright", legend = "Regression Line", col = "red", lty = 1)
Contrary to the assumption Balance and
EstimatedSalary do not have a linear relationship, due to
the highly scatter values of these 2 column leads to a linear regression
model with slope close to 0. From the visualization it can be stated
that, the column Balance does not scale linearly with
EstimatedSalary, which is counter intuitive to general
assumption.
Similar to earlier, let Balance be the response variable
and CreditScore be the explanatory variable.
model <- lm(Balance ~ CreditScore, data = data)
predictions <- predict(model)
plot(data$CreditScore, data$Balance, col = "blue", main = "Observed vs. Predicted Values", xlab = "Credit Score", ylab = "Balance")
abline(model, col = "red")
legend("topright", legend = "Regression Line", col = "red", lty = 1)
Similar to earlier, both the variables do not have a linear relation as well. This is also a contrastive result since, often the case is that individuals with higher balance tend to have a very high credit scores, and users with low balances tend to have medium to low scores. But is can inferred from the visualization that the popular belief is clearly wrong.
model <- lm(Balance ~ CreditScore, data = data)
# Summarize the model
summary(model)
##
## Call:
## lm(formula = Balance ~ CreditScore, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -77314 -76278 20599 51243 174389
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 73807.820 4245.234 17.386 <2e-16 ***
## CreditScore 4.125 6.455 0.639 0.523
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 62400 on 10000 degrees of freedom
## Multiple R-squared: 4.083e-05, Adjusted R-squared: -5.916e-05
## F-statistic: 0.4083 on 1 and 10000 DF, p-value: 0.5228
# Plot observed vs. predicted values
predictions <- predict(model)
plot(data$CreditScore, data$Balance, col = "blue", main = "Observed vs. Predicted Values", xlab = "Credit Score", ylab = "Balance")
abline(model, col = "red")
legend("topright", legend = "Regression Line", col = "red", lty = 1)