INFO-H510 Week 2 Data Dive

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>

Numeric Summary of Columns in the data

Categorical Columns

# Summary of Geography column
table(data$Geography)
## 
##  France Germany   Spain 
##    5014    2510    2477
# Summary of Exited column
table(data$Exited)
## 
##    0    1 
## 7964 2038

Continuous Columns

# 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

Novel Questions

  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.

  2. 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

    • Customers are a vital resource for any bank, with knowledge on contributing factors that are most likely to lead a customer to churn, bank’s could develop specialized schemes and optimize their existing ones.
    • Risk Management and Financial Planning are important for any bank to consider as they would help them mitigate potential financial risks and allow them to prepare for proactive measures.

These are a couple of ways in which the analysis can be instrumental in providing valuable insights and support for informed decision-making.

  1. What proportion on the customers tend to churn?
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.

  1. Among the customers that haven’t churned, how are they distributed among geographies?
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.

Visual Summaries

Plot 1 - Distribution of customer’s age

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.

Plot 2 - Box plot contrasting the correlation among the class of geography and age.

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.

Plot 3 - Scatter Plot of Balance Vs Age

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”.