This dataset holds crucial information regarding bank customer churn, offering insights into the dynamics of customer retention and attrition within the banking industry. The project’s objective is to analyze this data, unveiling patterns and trends using statistical tools. This week, the goal is to perform basic data analysis on the data and answer a few fundamental questions.
The categorical and continuous columns in the dataset are listed
below -
Categorical Columns - Geography,
Gender, HasCrCard,
IsActiveMember, Exited
Continuous Columns - Age, Balance,
Estimated Salary, Credit Score,
Tenure, No. Of Products
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
#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>
# Summary of Geography column
table(data$Geography)
##
## France Germany Spain
## 5014 2510 2477
# Summary of Exited column
table(data$Exited)
##
## 0 1
## 7964 2038
# Summary of Balance column
print(summary(data$Balance))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0 0 97199 76491 127648 250898
# Summary of Age column
print(summary(data$Age))
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 18.00 32.00 37.00 38.92 44.00 92.00 1
Are there any other features that could help us understand the
data better?
Information about their transaction history and their
current employment status would provide a much better
understanding of the data. None of the features in the data provide us
with a real-time information about the customer, but the previously
mentioned features can contribute to a clearer understanding of customer
churn, helping us pinpoint the specific time period when a customer has
churned. Apart from providing information about when a customer has
churned, it would help us understand the contributing factors that led
to churn in rela-time.
As stated earlier the goal of the project is to analyze, unveil
patterns and trends in this dataset. So, how can the insights gained
from this project be valuable?
The analysis on the churn data could provide valuable information and it
would benefit both the customer and bank
These are a couple of ways in which the analysis can be instrumental
in providing valuable insights and support for informed
decision-making.
churned_counts <- table(data$Exited)
n_not_churned <- churned_counts[1]
n_churned <- churned_counts[2]
total <- n_not_churned + n_churned
n_not_churned_normalized <- n_not_churned / total
n_churned_normalized <- n_churned / total
print(paste("Percentage of customers that have churned -", round(n_churned_normalized * 100, 2), "%"))
## [1] "Percentage of customers that have churned - 20.38 %"
print(paste("Percentage of customers that have not churned -", round(n_not_churned_normalized * 100, 2), "%"))
## [1] "Percentage of customers that have not churned - 79.62 %"
Majority of the customers have not churned, regardless a significant
proportion of them have.
n_not_churned <- nrow(filter(data, Exited == 0))
not_churned_by_geography <- data |>
filter(Exited == 0) |>
group_by(Geography) |>
summarize(percentage = n() * 100 / n_not_churned)
print(not_churned_by_geography)
## # A tibble: 4 × 2
## Geography percentage
## <chr> <dbl>
## 1 France 52.8
## 2 Germany 21.3
## 3 Spain 25.9
## 4 <NA> 0.0126
More than half of the customers from France have not churned, while
nearly a quarter of them haven’t in Germany and Spain.
hist(data$Age, main = "Distribution of Customer Age", xlab = "Customer's Age", col = "lightgreen")
Vast majority of the tend to fall in the range of 30-40 years, so it
is important to realize create policies that benefit not just the
majority, but everyone as well.
ggplot(data, aes(x=Geography, y=Age, fill=Geography)) +
geom_boxplot() +
labs(title = "Box Plot of Age by Geography", x = "Geography", y = "Age") +
scale_fill_manual(values = c("lightblue", "lightgreen", "lightcoral"))
## Warning: Removed 1 rows containing non-finite values (`stat_boxplot()`).
The age distribution among geographies is remarkably similar, so
schemes can be developed together rather than an individual plan for
each. Since the distribution is very much with regard to age, it would
be possible to develop generalized policies.
ggplot(data, aes(x=Balance, y=Age)) +
geom_point(alpha=0.6) +
labs(title = "Scatter Plot of Balance vs Age", x = "Balance", y = "Age")
## Warning: Removed 1 rows containing missing values (`geom_point()`).
Surprisingly there is little to no correlation among Balance and Age, apart from that a lot of the accounts have a balance of “0”.