Bank customer Churn EDA

Author

Thato Bilankulu

Introduction

This report presents an Exploratory Data Analysis (EDA) conducted on the dataset to uncover key insights prior to building any machine learning models.
EDA is a foundational step in both statistics and machine learning. It allows us to understand the structure, patterns, and relationships within the data while identifying issues such as outliers, skewed distributions, or missing values—any of which can severely affect model performance.


Framing the Business Problem

As emphasized by Aurelien Géron, framing the problem correctly is the first and most crucial step in any data science project.

In this scenario, the core business objective is to reduce customer churn—a common and costly issue for businesses.
From a machine learning perspective, this clearly maps to a binary classification task, where the target is to predict whether a customer is likely to churn (1) or stay (0).


Next Step: Analyzing Variable Distributions

With the problem framed, we now shift focus to understanding the data itself.
We’ll start by exploring the distributions of numerical (quantitative) variables to assess their shapes, detect potential outliers, and evaluate the need for transformations or scaling.

Data Distributions

Account Balance Distribution

The distribution of account balances shows a global maximum at 0, indicating that most customers have no funds in their accounts. Additionally, a local maximum is observed between 110,000 and 150,000, representing a smaller segment of customers with significantly higher balances.

This bimodal pattern may suggest the presence of distinct customer segments and could warrant further segmentation analysis to improve model accuracy and targeting.

Code
# continous plots

df |>  ggplot(mapping = aes(x = CreditScore, fill = churned),
              options(scipen = 999)) + geom_histogram(alpha = 0.5,bins = 50) + scale_fill_paletteer_d("fishualize::Acanthisthius_brasilianus") +
  labs(title = 'Credit Score Distribution: Churned vs Retained Customers', x =
         'Credit Score', y = 'Number of Customers') + theme_minimal()

Code
df |>  ggplot(mapping = aes(x = CreditScore, fill = churned)) + geom_density(alpha =
                                                                               0.5) + scale_fill_paletteer_d("fishualize::Acanthisthius_brasilianus") + labs(title =
                                                                                                                                                               'Credit Score Distribution: Churned vs Retained Customers', x = 'Credit Score', y =
                                                                                                                                                               'Probability Density')

Note: The credit distribution displays a left-skewed (long-tail) pattern, with the majority of customers concentrated in the 600–700 range. A number of significant outliers are present, which could adversely affect the performance of machine learning models if not properly handled (e.g., through scaling or outlier mitigation techniques).

Code
df |>  ggplot(mapping = aes(x = EstimatedSalary, fill = churned)) + geom_histogram(alpha =
                                                                                         0.5,bins = 100) + scale_fill_paletteer_d("fishualize::Acanthisthius_brasilianus") +
  labs(title = 'Estimated Salary Distribution: Churned vs. Retained Customers', x =
         'Estimated Salary (€)', y = 'Number of Customers')

The estimated salary distribution for both churned and retained customers appears approximately uniform, indicating an equal probability of salaries ranging from €20,000 to roughly €200,000 within the bank’s customer base.

Code
# geom_tile plots

df |> count(Geography, churned) |> ggplot(mapping = aes(x = Geography, y = churned, fill =n)) + geom_tile() +
  scale_fill_gradient(high = "#B88244FF", low = "#B8B69EFF", ) + coord_flip() + guides(fill = guide_legend(title = "Churn Count"))+ labs(title='Geographic Distribution of Churned Customers')

Code
geo_table<-df |> group_by(Geography,churned) |> summarize(Amount=n())
`summarise()` has grouped output by 'Geography'. You can override using the
`.groups` argument.
Code
knitr::kable(geo_table)
Geography churned Amount
France No 4204
France Yes 810
Spain No 2064
Spain Yes 413
Germany No 1695
Germany Yes 814
  • The dataset is imbalanced, which can lead to biased or misleading conclusions. The dominance of the majority class may distort the model’s ability to accurately learn and predict outcomes for the minority class.

  • In terms of churn ratio by country, Germany has the highest churn rate.

  • France and Spain exhibit relatively similar churn ratios, both lower than Germany’s.

Code
df |> ggplot() + geom_bar(mapping = aes(x = churned, fill = ActiveMemberChr),
                          position = "dodge") + theme(aspect.ratio = 1)+ 
  labs(title = 'Customer Churn by Active Membership Status',x='Churned',y='Number of Customers')+guides(fill = guide_legend(title = "Active Member")) + scale_fill_manual(values =
                                                                                            c(Yes = "#B88244FF", No = "#B8B69EFF"))

Code
member <- df |> group_by(df$ActiveMemberChr) |> summarize(Amount=n())
knitr::kable(member)
df$ActiveMemberChr Amount
Yes 5151
No 4849
  • The ActiveMember feature is not imbalanced; there is a relatively even distribution between active and inactive customers.
Code
df |> ggplot(mapping = aes(x = churned, y = CreditScore, fill = churned)) + geom_boxplot() +
  coord_flip() + scale_fill_manual(values = c(Yes = "#B88244FF", No = "#527E87FF")) +labs(title ='Credit Score Distribution by Customer Churn',x='Churned',y='Credit Score')

Credit Score Distribution and Outliers

The CreditScore feature contains outliers below 400, which may indicate potential data entry or software errors. Notably, these outliers are observed only among customers who churned, resulting in a long left tail in their distribution.

Overall, the boxplots for credit scores are left-skewed, indicating that most customers have relatively good credit scores. Customers who do not churn exhibit a slightly more pronounced left skew with a shorter tail, while those who churn show a longer left tail due to the outliers and lower scores.

This skewness and presence of outliers may adversely affect model performance and should be carefully addressed during data preprocessing.

Code
df |> ggplot(mapping = aes(x = churned, y = EstimatedSalary, fill =
                             churned)) + geom_boxplot() + coord_flip() + scale_fill_manual(values =
                                                                                                 c(Yes = "#B88244FF", No = "#527E87FF"))+ labs(title = '5 number summary of Estimated Salary to Churn',x='churned',y='Estimated')

As observed in the histogram, the churn distribution across the entire salary range appears to be uniform—meaning customers at all salary levels have an approximately equal likelihood of churning.

Code
df |> ggplot() + geom_bar(mapping = aes(x = churned, fill = ActiveMemberChr),
                          position = "dodge") + theme(aspect.ratio = 1)+ 
  labs(title = 'Customer Churn by Active Membership Status',x='Churned',y='Number of Customers')+guides(fill = guide_legend(title = "Active Member")) + scale_fill_manual(values =
                                                                                            c(Yes = "#B88244FF", No = "#B8B69EFF"))

Membership and Churn Relationship

There is a clear pattern in the data:
- Customers who remain with the bank are more likely to be active members.
- Customers who churn are more frequently non-members.

This suggests that active membership is a strong indicator of customer retention.

Code
df |> ggplot() + geom_bar(mapping = aes(x = Tenure ,fill = churned),
                          position = "dodge") + theme(aspect.ratio = 1) + scale_fill_manual(values = c(Yes = "#B88244FF", No = "#527E87FF"))  + labs(title = 'Customer Churn by Tenure',y='Churn Amount')+guides(fill = guide_legend(title = "Churn"))

Tenure vs Churn Analysis

Most of the bank’s customers fall within the 1 to 9 year tenure range, with approximately 800 customers staying and 200 churning during this period.

Notably, churn is higher at the extremes of tenure:

  • At year 0, about one-third of customers churn.
  • At year 10, approximately one-quarter of customers churn.

This suggests that customer attrition is most common during the initial onboarding stage and again at the end of a long tenure, potentially indicating dissatisfaction or contract completion.

Code
df |> ggplot() + geom_bar(mapping = aes(x = NumOfProducts, fill = churned),
                          position = "dodge") + theme(aspect.ratio = 1)+ labs(title = 'Number of Products to churn',x='Number Of Products Purchased')+guides(fill = guide_legend(title = "Churn")) + scale_fill_manual(values = c(Yes = "#B88244FF", No = "#527E87FF"))  + labs(title = 'Churned vs Retained Customer Distribution',y='Churn Amount')+guides(fill = guide_legend(title = "Churn"))

Relationship Between Number of Products and Churn

df\(NumOfProducts |df\)churned Amount
1 No 3675
1 Yes 1409
2 No 4242
2 Yes 348
3 No 46
3 Yes 220
4 Yes 60

Most customers have either 1 or 2 products. Customers with 1 product exhibit the highest churn rate relative to their group size, while those with 2 products tend to churn less proportionally.

From 3 products onward, churn increases again: - A majority of customers with 3 products churn. - All customers with 4 products churn, although they represent a small portion of the total customer base.

This suggests a U-shaped trend in churn behavior relative to the number of products.

Code
# reload for scatterplot
df <- read_csv("C:/Users/thato/OneDrive/Documents/archive/Churn_Modelling.csv",show_col_types = FALSE)

df |> ggplot(mapping = aes(x = CreditScore, y = Exited, color = Exited)) + geom_jitter(height =
                                                                                         0.3, alpha = .25)+labs(title='Churn By Credit Score Analysis',x='Credit Score')+ 
  scale_colour_gradient(high = "#B88244FF", low = "#B8B69EFF" )

Code
df |> ggplot(mapping = aes(x = Age, y = Exited, color = Exited))+ scale_colour_gradient(high = "#B88244FF", low = "#B8B69EFF" )+
  geom_jitter(height =0.1, alpha = .25)+labs(title = "Relationship Between Customer Age and Churn Rate",y='Churned')

Code
df |> ggplot(mapping = aes(x = Balance, y = Exited, color = Exited))+ 
  geom_jitter(height =0.1, alpha = .25)+
  scale_colour_gradient(high = "#B88244FF", low = "#B8B69EFF" )+labs(title = "Relationship Between Account Balance and Churn Rate",y='Churned')

Logistic Regression and Imbalanced Data

Visual inspection of the scatter plots reveals that the dataset’s imbalance significantly limits the effectiveness of logistic regression. Since the majority of customers do not churn, a logistic model can easily achieve high accuracy by simply predicting the dominant class (“stay”) — without truly learning meaningful patterns.

As a result: - Logistic regression is likely to perform poorly on the minority class (churned customers). - It will struggle to fit the decision boundary for the churners, leading to low recall and poor generalization. - This problem highlights the need for more advanced techniques such as ensemble models, resampling methods (e.g., SMOTE or ROS), or cost-sensitive learning to properly handle the imbalance and improve minority class prediction.

Final Remarks

  • The dataset is imbalanced, which may lead to misleading or biased conclusions.
  • A majority of the bank’s customers have a zero account balance.
  • Most credit scores cluster around the 600–700 range.
  • Churn rates appear relatively uniform across the entire salary spectrum.
  • A linear relationship exists between certain features and churn, suggesting that logistic regression may not be the ideal model.
  • Customers with tenure between 1 and 9 years are the most stable.
  • Among those who churned, most were not active members.
  • Active members are significantly more likely to remain with the bank.
  • Membership appears to be a strong indicator of customer retention.
  • The most reliable customers tend to have 2 products purchased, followed by those with 1 product.