AllLife Bank wants to focus on its credit card customer base in the next financial year. They have been advised by their marketing research team, that the penetration in the market can be improved. Based on this input, the Marketing team proposes to run personalized campaigns to target new customers as well as upsell to existing customers. Another insight from the market research was that the customers perceive the support services of the back poorly. Based on this, the Operations team wants to upgrade the service delivery model, to ensure that customer queries are resolved faster. Head of Marketing and Head of Delivery both decide to reach out to the Data Science team for help
To identify different segments in the existing customer, based on their spending patterns as well as past interaction with the bank, using clustering algorithms, and provide recommendations to the bank on how to better market to and service these customers.
The data provided is of various customers of a bank and their financial attributes like credit limit, the total number of credit cards the customer has, and different channels through which customers have contacted the bank for any queries (including visiting the bank, online and through a call center).
Data Dictionary
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# to scale the data using z-score
from sklearn.preprocessing import StandardScaler
# to perform k-means clustering and compute silhouette scores
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# to compute distances
from scipy.spatial.distance import cdist
# to perform hierarchical clustering, compute cophenetic correlation, and create dendrograms
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage, cophenet
# to compute distances
from scipy.spatial.distance import pdist
# to visualize the elbow curve and silhouette scores
from yellowbrick.cluster import KElbowVisualizer, SilhouetteVisualizer
data = pd.read_excel('Credit+Card+Customer+Data.xlsx')
df = data.copy()
df.head()
| Sl_No | Customer Key | Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 87073 | 100000 | 2 | 1 | 1 | 0 |
| 1 | 2 | 38414 | 50000 | 3 | 0 | 10 | 9 |
| 2 | 3 | 17341 | 50000 | 7 | 1 | 3 | 4 |
| 3 | 4 | 40496 | 30000 | 5 | 1 | 1 | 4 |
| 4 | 5 | 47437 | 100000 | 6 | 0 | 12 | 3 |
Sl_No and Customer Key columns are not required
# Dropping Sl_No and Customer Key columns
df.drop("Sl_No", axis=1, inplace=True)
df.drop("Customer Key", axis=1, inplace=True)
# Number of Columns and rows
df.shape
(660, 5)
# Summary of the data
df.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Avg_Credit_Limit | 660.0 | 34574.242424 | 37625.487804 | 3000.0 | 10000.0 | 18000.0 | 48000.0 | 200000.0 |
| Total_Credit_Cards | 660.0 | 4.706061 | 2.167835 | 1.0 | 3.0 | 5.0 | 6.0 | 10.0 |
| Total_visits_bank | 660.0 | 2.403030 | 1.631813 | 0.0 | 1.0 | 2.0 | 4.0 | 5.0 |
| Total_visits_online | 660.0 | 2.606061 | 2.935724 | 0.0 | 1.0 | 2.0 | 4.0 | 15.0 |
| Total_calls_made | 660.0 | 3.583333 | 2.865317 | 0.0 | 1.0 | 3.0 | 5.0 | 10.0 |
# Structure of the data
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 660 entries, 0 to 659 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Avg_Credit_Limit 660 non-null int64 1 Total_Credit_Cards 660 non-null int64 2 Total_visits_bank 660 non-null int64 3 Total_visits_online 660 non-null int64 4 Total_calls_made 660 non-null int64 dtypes: int64(5) memory usage: 25.9 KB
# Unique values in each column
data.nunique()
Sl_No 660 Customer Key 655 Avg_Credit_Limit 110 Total_Credit_Cards 10 Total_visits_bank 6 Total_visits_online 16 Total_calls_made 11 dtype: int64
# function to plot a boxplot and a histogram along the same scale.
def histogram_boxplot(data, feature, figsize=(12, 7), kde=False, bins=None):
"""
Boxplot and histogram combined
data: dataframe
feature: dataframe column
figsize: size of figure (default (12,7))
kde: whether to the show density curve (default False)
bins: number of bins for histogram (default None)
"""
f2, (ax_box2, ax_hist2) = plt.subplots(
nrows=2, # Number of rows of the subplot grid= 2
sharex=True, # x-axis will be shared among all subplots
gridspec_kw={"height_ratios": (0.25, 0.75)},
figsize=figsize,
) # creating the 2 subplots
sns.boxplot(
data=data, x=feature, ax=ax_box2, showmeans=True, color="violet"
) # boxplot will be created and a triangle will indicate the mean value of the column
sns.histplot(
data=data, x=feature, kde=kde, ax=ax_hist2, bins=bins, palette="winter"
) if bins else sns.histplot(
data=data, x=feature, kde=kde, ax=ax_hist2
) # For histogram
ax_hist2.axvline(
data[feature].mean(), color="green", linestyle="--"
) # Add mean to the histogram
ax_hist2.axvline(
data[feature].median(), color="black", linestyle="-"
) # Add median to the histogram
# function to create labeled barplots
def labeled_barplot(data, feature, perc=False, n=None):
"""
Barplot with percentage at the top
data: dataframe
feature: dataframe column
perc: whether to display percentages instead of count (default is False)
n: displays the top n category levels (default is None, i.e., display all levels)
"""
total = len(data[feature]) # length of the column
count = data[feature].nunique()
if n is None:
plt.figure(figsize=(count + 1, 5))
else:
plt.figure(figsize=(n + 1, 5))
plt.xticks(rotation=90, fontsize=15)
ax = sns.countplot(
data=data,
x=feature,
palette="Paired",
order=data[feature].value_counts().index[:n].sort_values(),
)
for p in ax.patches:
if perc == True:
label = "{:.1f}%".format(
100 * p.get_height() / total
) # percentage of each class of the category
else:
label = p.get_height() # count of each level of the category
x = p.get_x() + p.get_width() / 2 # width of the plot
y = p.get_height() # height of the plot
ax.annotate(
label,
(x, y),
ha="center",
va="center",
size=12,
xytext=(0, 5),
textcoords="offset points",
) # annotate the percentage
plt.show() # show the plot
# histogram_box plot
histogram_boxplot(df, "Avg_Credit_Limit")
df["Avg_Credit_Limit"].mean()
31981.51001540832
The data distribution is right skewed with a mean value of 31981.51
labeled_barplot(df, "Total_calls_made", perc=True)
Percentage of customer = 14.7%
labeled_barplot(df, "Total_visits_online", perc=True)
Total = 21.8%
labeled_barplot(df, "Total_visits_bank", perc=True)
Total_Visits_0=100
plt.figure(figsize=(15, 7))
sns.heatmap(df.corr(), annot=True, vmin=-
1, vmax=1, fmt=".2f", cmap="Spectral")
plt.show()
The highest positive correlations are:
The highest negative correlations are:
# histogram_box plot for all the numerical values
for column in df.columns:
histogram_boxplot(df, column)
sns.pairplot(data=df, diag_kind="kde")
<seaborn.axisgrid.PairGrid at 0x7f4b89b850d0>
sns.pairplot(df, hue="Total_Credit_Cards",)
<seaborn.axisgrid.PairGrid at 0x7f4b89e16d50>
# Checking for duplicate observations
df.duplicated().sum()
11
There are 11 duplicate observations. We will remove them from the data.
#Removing duplicated data
df = df[(~df.duplicated())].copy()
df.shape
(649, 5)
# Checking for duplicate observations
df.duplicated().sum()
0
Now there is no duplicated values
# Checking missing values
df.isnull().sum()
Avg_Credit_Limit 0 Total_Credit_Cards 0 Total_visits_bank 0 Total_visits_online 0 Total_calls_made 0 dtype: int64
# Checking missing values
df.isna().sum()
Avg_Credit_Limit 0 Total_Credit_Cards 0 Total_visits_bank 0 Total_visits_online 0 Total_calls_made 0 dtype: int64
There is no missing values
threshold = 3
outlier = {}
for col in df.columns:
i = df[col]
mean = np.mean(df[col])
std = np.std(df[col])
list1 = []
for v in i:
z = (v - mean) / std
if z > threshold:
list1.append(v)
list1.sort()
outlier[i.name] = list1
print("The following are the outliers in the data:")
for key, value in outlier.items():
print("\n", key, ":", value)
The following are the outliers in the data: Avg_Credit_Limit : [153000, 155000, 156000, 156000, 157000, 158000, 163000, 163000, 166000, 166000, 167000, 171000, 172000, 172000, 173000, 176000, 178000, 183000, 184000, 186000, 187000, 195000, 195000, 200000] Total_Credit_Cards : [] Total_visits_bank : [] Total_visits_online : [12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15] Total_calls_made : []
# functions to treat outliers by flooring and capping
def treat_outliers(df, col):
"""
Treats outliers in a variable
df: dataframe
col: dataframe column
"""
Q1 = df[col].quantile(0.25) # 25th quantile
Q3 = df[col].quantile(0.75) # 75th quantile
IQR = Q3 - Q1
Lower_Whisker = Q1 - 1.5 * IQR
Upper_Whisker = Q3 + 1.5 * IQR
# all the values smaller than Lower_Whisker will be assigned the value of Lower_Whisker
# all the values greater than Upper_Whisker will be assigned the value of Upper_Whisker
df[col] = np.clip(df[col], Lower_Whisker, Upper_Whisker)
return df
def treat_outliers_all(df, col_list):
"""
Treat outliers in a list of variables
df: dataframe
col_list: list of dataframe columns
"""
for c in col_list:
df = treat_outliers(df, c)
return df
new_df = treat_outliers_all(df, df.columns)
# scaling the dataset before clustering
scaler = StandardScaler()
subset = new_df.copy()
subset_scaled = scaler.fit_transform(subset)
# creating a dataframe of the scaled columns
subset_scaled_df = pd.DataFrame(subset_scaled, columns=subset.columns)
subset_scaled_df.head()
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | |
|---|---|---|---|---|---|
| 0 | 2.366936 | -1.247087 | -0.860606 | -0.623244 | -1.248443 |
| 1 | 0.627015 | -0.786701 | -1.476410 | 2.686783 | 1.881237 |
| 2 | 0.627015 | 1.054846 | -0.860606 | 0.259430 | 0.142526 |
| 3 | -0.068953 | 0.134072 | -0.860606 | -0.623244 | 0.142526 |
| 4 | 2.366936 | 0.594459 | -1.476410 | 2.686783 | -0.205216 |
# histogram_box plot for all the numerical values
for column in subset_scaled_df.columns:
histogram_boxplot(subset_scaled_df, column)
sns.pairplot(data=subset_scaled_df, diag_kind="kde")
<seaborn.axisgrid.PairGrid at 0x7f4b84a6d210>
plt.figure(figsize=(15, 7))
sns.heatmap(subset_scaled_df.corr(), annot=True, vmin=-
1, vmax=1, fmt=".2f", cmap="Spectral")
plt.show()
# Finding optimal no. of clusters
k_means_df = subset_scaled_df.copy()
clusters = range(1, 10)
meanDistortions = []
for k in clusters:
model = KMeans(n_clusters=k)
model.fit(k_means_df)
prediction = model.predict(k_means_df)
distortion = (
sum(
np.min(cdist(k_means_df,
model.cluster_centers_, "euclidean"), axis=1)
)
/ k_means_df.shape[0]
)
meanDistortions.append(distortion)
print("Number of Clusters:", k, "\tAverage Distortion:", distortion)
plt.plot(clusters, meanDistortions, "bx-")
plt.xlabel("k")
plt.ylabel("Average distortion")
plt.title("Selecting k with the Elbow Method")
plt.show()
Number of Clusters: 1 Average Distortion: 2.073136602035325 Number of Clusters: 2 Average Distortion: 1.4978375339862937 Number of Clusters: 3 Average Distortion: 1.1772224293618558 Number of Clusters: 4 Average Distortion: 1.0689466335012168 Number of Clusters: 5 Average Distortion: 1.0016555965826015 Number of Clusters: 6 Average Distortion: 0.9493370148679439 Number of Clusters: 7 Average Distortion: 0.9120600596999384 Number of Clusters: 8 Average Distortion: 0.8885545708522814 Number of Clusters: 9 Average Distortion: 0.8656344324222063
Appropriate value for k seems to be 3 or 4.
sil_score = []
cluster_list = list(range(2, 10))
for n_clusters in cluster_list:
clusterer = KMeans(n_clusters=n_clusters)
preds = clusterer.fit_predict((k_means_df))
score = silhouette_score(k_means_df, preds)
sil_score.append(score)
print("For n_clusters = {}, the silhouette score is {})".format(
n_clusters, score))
plt.plot(cluster_list, sil_score)
plt.show()
For n_clusters = 2, the silhouette score is 0.41999597804777794) For n_clusters = 3, the silhouette score is 0.5180797381243987) For n_clusters = 4, the silhouette score is 0.37986496617829596) For n_clusters = 5, the silhouette score is 0.3547325270386784) For n_clusters = 6, the silhouette score is 0.27229030080435984) For n_clusters = 7, the silhouette score is 0.26726998767685833) For n_clusters = 8, the silhouette score is 0.2574106133109355) For n_clusters = 9, the silhouette score is 0.24344136490539667)
Silhouette score for 3 is higher than that for 2. So, we will choose 3 as value of k.
# finding optimal no. of clusters with silhouette coefficients
visualizer = SilhouetteVisualizer(KMeans(2, random_state=1))
visualizer.fit(k_means_df)
visualizer.show()
<Axes: title={'center': 'Silhouette Plot of KMeans Clustering for 649 Samples in 2 Centers'}, xlabel='silhouette coefficient values', ylabel='cluster label'>
# finding optimal no. of clusters with silhouette coefficients
visualizer = SilhouetteVisualizer(KMeans(3, random_state=1))
visualizer.fit(k_means_df)
visualizer.show()
<Axes: title={'center': 'Silhouette Plot of KMeans Clustering for 649 Samples in 3 Centers'}, xlabel='silhouette coefficient values', ylabel='cluster label'>
# finding optimal no. of clusters with silhouette coefficients
visualizer = SilhouetteVisualizer(KMeans(4, random_state=1))
visualizer.fit(k_means_df)
visualizer.show()
<Axes: title={'center': 'Silhouette Plot of KMeans Clustering for 649 Samples in 4 Centers'}, xlabel='silhouette coefficient values', ylabel='cluster label'>
take 3 as number of clusters
kmeans = KMeans(n_clusters=3, random_state=0)
kmeans.fit(k_means_df)
# creating a copy of the original data
df1 = df.copy()
# adding kmeans cluster labels to the original and scaled dataframes
k_means_df["K_means_segments"] = kmeans.labels_
df1["K_means_segments"] = kmeans.labels_
k_means_df.head()
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | K_means_segments | |
|---|---|---|---|---|---|---|
| 0 | 2.366936 | -1.247087 | -0.860606 | -0.623244 | -1.248443 | 2 |
| 1 | 0.627015 | -0.786701 | -1.476410 | 2.686783 | 1.881237 | 1 |
| 2 | 0.627015 | 1.054846 | -0.860606 | 0.259430 | 0.142526 | 2 |
| 3 | -0.068953 | 0.134072 | -0.860606 | -0.623244 | 0.142526 | 2 |
| 4 | 2.366936 | 0.594459 | -1.476410 | 2.686783 | -0.205216 | 0 |
df1.head()
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | K_means_segments | |
|---|---|---|---|---|---|---|
| 0 | 100000 | 2 | 1 | 1.0 | 0 | 2 |
| 1 | 50000 | 3 | 0 | 8.5 | 9 | 1 |
| 2 | 50000 | 7 | 1 | 3.0 | 4 | 2 |
| 3 | 30000 | 5 | 1 | 1.0 | 4 | 2 |
| 4 | 100000 | 6 | 0 | 8.5 | 3 | 0 |
hc_df = subset_scaled_df.copy()
# list of distance metrics
distance_metrics = ["euclidean", "chebyshev", "mahalanobis", "cityblock"]
# list of linkage methods
linkage_methods = ["single", "complete", "average", "weighted"]
high_cophenet_corr = 0
high_dm_lm = [0, 0]
for dm in distance_metrics:
for lm in linkage_methods:
Z = linkage(hc_df, metric=dm, method=lm)
c, coph_dists = cophenet(Z, pdist(hc_df))
print(
"Cophenetic correlation for {} distance and {} linkage is {}.".format(
dm.capitalize(), lm, c
)
)
if high_cophenet_corr < c:
high_cophenet_corr = c
high_dm_lm[0] = dm
high_dm_lm[1] = lm
Cophenetic correlation for Euclidean distance and single linkage is 0.6687980551717484. Cophenetic correlation for Euclidean distance and complete linkage is 0.8455463448314544. Cophenetic correlation for Euclidean distance and average linkage is 0.8922750152191901. Cophenetic correlation for Euclidean distance and weighted linkage is 0.8874605817813397. Cophenetic correlation for Chebyshev distance and single linkage is 0.6904171632443976. Cophenetic correlation for Chebyshev distance and complete linkage is 0.8256908105714492. Cophenetic correlation for Chebyshev distance and average linkage is 0.8890063310233792. Cophenetic correlation for Chebyshev distance and weighted linkage is 0.8820500156009561. Cophenetic correlation for Mahalanobis distance and single linkage is 0.659249865882007. Cophenetic correlation for Mahalanobis distance and complete linkage is 0.4243540966152363. Cophenetic correlation for Mahalanobis distance and average linkage is 0.7988622717266841. Cophenetic correlation for Mahalanobis distance and weighted linkage is 0.6423539449312025. Cophenetic correlation for Cityblock distance and single linkage is 0.6718341146221024. Cophenetic correlation for Cityblock distance and complete linkage is 0.8328113244932631. Cophenetic correlation for Cityblock distance and average linkage is 0.8914496698322413. Cophenetic correlation for Cityblock distance and weighted linkage is 0.8559905391810056.
# printing the combination of distance metric and linkage method with the highest cophenetic correlation
print(
"Highest cophenetic correlation is {}, which is obtained with {} distance and {} linkage.".format(
high_cophenet_corr, high_dm_lm[0].capitalize(), high_dm_lm[1]
)
)
Highest cophenetic correlation is 0.8922750152191901, which is obtained with Euclidean distance and average linkage.
Exploring different linkage methods with Euclidean distance only
# list of linkage methods
linkage_methods = ["single", "complete",
"average", "centroid", "ward", "weighted"]
high_cophenet_corr = 0
high_dm_lm = [0, 0]
for lm in linkage_methods:
Z = linkage(hc_df, metric="euclidean", method=lm)
c, coph_dists = cophenet(Z, pdist(hc_df))
print("Cophenetic correlation for {} linkage is {}.".format(lm, c))
if high_cophenet_corr < c:
high_cophenet_corr = c
high_dm_lm[0] = "euclidean"
high_dm_lm[1] = lm
Cophenetic correlation for single linkage is 0.6687980551717484. Cophenetic correlation for complete linkage is 0.8455463448314544. Cophenetic correlation for average linkage is 0.8922750152191901. Cophenetic correlation for centroid linkage is 0.8882199967774133. Cophenetic correlation for ward linkage is 0.7714411007322578. Cophenetic correlation for weighted linkage is 0.8874605817813397.
# printing the combination of distance metric and linkage method with the highest cophenetic correlation
print(
"Highest cophenetic correlation is {}, which is obtained with {} linkage.".format(
high_cophenet_corr, high_dm_lm[1]
)
)
Highest cophenetic correlation is 0.8922750152191901, which is obtained with average linkage.
# list of linkage methods
linkage_methods = [
"average", "centroid", "ward", "weighted"]
# lists to save results of cophenetic correlation calculation
compare_cols = ["Linkage", "Cophenetic Coefficient"]
compare = []
# to create a subplot image
fig, axs = plt.subplots(len(linkage_methods), 1, figsize=(15, 30))
# We will enumerate through the list of linkage methods above
# For each linkage method, we will plot the dendrogram and calculate the cophenetic correlation
for i, method in enumerate(linkage_methods):
Z = linkage(hc_df, metric="euclidean", method=method)
dendrogram(Z, ax=axs[i])
axs[i].set_title(f"Dendrogram ({method.capitalize()} Linkage)")
coph_corr, coph_dist = cophenet(Z, pdist(hc_df))
axs[i].annotate(
f"Cophenetic\nCorrelation\n{coph_corr:0.3f}",
(0.80, 0.80),
xycoords="axes fraction",
)
compare.append([method, coph_corr])
# create and print a dataframe to compare cophenetic correlations for different linkage methods
df_cc = pd.DataFrame(compare, columns=compare_cols)
df_cc = df_cc.sort_values(by="Cophenetic Coefficient")
df_cc
| Linkage | Cophenetic Coefficient | |
|---|---|---|
| 2 | ward | 0.771441 |
| 3 | weighted | 0.887461 |
| 1 | centroid | 0.888220 |
| 0 | average | 0.892275 |
# list of linkage methods
linkage_methods = ["average"]
# lists to save results of cophenetic correlation calculation
compare_cols = ["Linkage", "Cophenetic Coefficient"]
# to create a subplot image
fig, axs = plt.subplots(len(linkage_methods), 1, figsize=(16, 16))
# We will enumerate through the list of linkage methods above
# For each linkage method, we will plot the dendrogram and calculate the cophenetic correlation
for i, method in enumerate(linkage_methods):
Z = linkage(hc_df, metric="euclidean", method=method)
dendrogram(Z, ax=axs)
axs.set_title(f"Dendrogram ({method.capitalize()} Linkage)")
coph_corr, coph_dist = cophenet(Z, pdist(hc_df))
axs.annotate(
f"Cophenetic\nCorrelation\n{coph_corr:0.3f}",
(0.80, 0.80),
xycoords="axes fraction",
)
dendrogram(Z, color_threshold=7.3)
plt.axhline(y=2.6, c="red", lw=1, linestyle="dashdot")
<matplotlib.lines.Line2D at 0x7f4b6e01f490>
# Complete the code to define the hierarchical clustering model
HCmodel = AgglomerativeClustering(
n_clusters=5, affinity='euclidean', linkage='average')
HCmodel.fit(hc_df)
AgglomerativeClustering(affinity='euclidean', linkage='average', n_clusters=5)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
AgglomerativeClustering(affinity='euclidean', linkage='average', n_clusters=5)
# creating a copy of the original data
df2 = df.copy()
# adding hierarchical cluster labels to the original and scaled dataframes
hc_df["HC_segments"] = HCmodel.labels_
df2["HC_segments"] = HCmodel.labels_
hc_df.head()
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | HC_segments | |
|---|---|---|---|---|---|---|
| 0 | 2.366936 | -1.247087 | -0.860606 | -0.623244 | -1.248443 | 2 |
| 1 | 0.627015 | -0.786701 | -1.476410 | 2.686783 | 1.881237 | 4 |
| 2 | 0.627015 | 1.054846 | -0.860606 | 0.259430 | 0.142526 | 1 |
| 3 | -0.068953 | 0.134072 | -0.860606 | -0.623244 | 0.142526 | 1 |
| 4 | 2.366936 | 0.594459 | -1.476410 | 2.686783 | -0.205216 | 3 |
df2.head()
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | HC_segments | |
|---|---|---|---|---|---|---|
| 0 | 100000 | 2 | 1 | 1.0 | 0 | 2 |
| 1 | 50000 | 3 | 0 | 8.5 | 9 | 4 |
| 2 | 50000 | 7 | 1 | 3.0 | 4 | 1 |
| 3 | 30000 | 5 | 1 | 1.0 | 4 | 1 |
| 4 | 100000 | 6 | 0 | 8.5 | 3 | 3 |
k_cluster_profile = df1.groupby("K_means_segments").mean()
k_cluster_profile["count_in_each_segments"] = (
df1.groupby("K_means_segments")["Avg_Credit_Limit"].count().values
)
k_cluster_profile.groupby("K_means_segments").mean().plot.bar(figsize=(15, 6))
<Axes: xlabel='K_means_segments'>
fig, axes = plt.subplots(1, 5, figsize=(16, 6))
fig.suptitle(
"Boxplot of numerical variables for each cluster obtained using K-means Clustering",
fontsize=20,
)
counter = 0
for ii in range(5):
sns.boxplot(
ax=axes[ii], y=df1[df1.columns[counter]
], x=k_means_df["K_means_segments"]
)
counter = counter + 1
fig.tight_layout(pad=2.0)
hc_cluster_profile = df2.groupby("HC_segments").mean()
hc_cluster_profile["count_in_each_segment"] = (
df2.groupby("HC_segments")["Avg_Credit_Limit"].count().values
)
hc_cluster_profile.groupby("HC_segments").mean().plot.bar(figsize=(15, 6))
<Axes: xlabel='HC_segments'>
fig, axes = plt.subplots(1, 5, figsize=(16, 6))
fig.suptitle(
"Boxplot of numerical variables for each cluster obtained using Hierarchical Clustering",
fontsize=20,
)
counter = 0
for ii in range(5):
sns.boxplot(
ax=axes[ii], y=df2[df2.columns[counter]
], x=hc_df["HC_segments"]
)
counter = counter + 1
fig.tight_layout(pad=2.0)
You compare several things, like:
You can also mention any differences or similarities you obtained in the cluster profiles from both the clustering techniques.
k_cluster_profile.style.highlight_max(color="lightgreen", axis=0)
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | count_in_each_segments | |
|---|---|---|---|---|---|---|
| K_means_segments | ||||||
| 0 | 103440.000000 | 8.740000 | 0.600000 | 8.180000 | 1.080000 | 50 |
| 1 | 12239.819005 | 2.411765 | 0.945701 | 3.554299 | 6.891403 | 221 |
| 2 | 34071.428571 | 5.518519 | 3.484127 | 0.981481 | 1.992063 | 378 |
Cluster 0:
Cluster 1:
Cluster 2:
Cluster 0 represents customers with relatively high credit limits, a small number of credit cards, minimal visits to the bank, a significant number of online visits, and few calls made. These customers seem to prefer online interactions and have a low frequency of calls.
Cluster 1 represents customers with the lowest average credit limit, a small number of credit cards, regular visits to the bank, moderate online visits, and the highest number of calls made. These customers are likely less affluent, rely on bank visits, and have a higher need for phone support.
Cluster 2 represents customers with medium to high credit limits, a moderate number of credit cards, frequent visits to the bank, low online visits, and low calls made. These customers prefer visiting the bank and have low online interaction and call frequency.
hc_cluster_profile.style.highlight_max(color="lightgreen", axis=0)
| Avg_Credit_Limit | Total_Credit_Cards | Total_visits_bank | Total_visits_online | Total_calls_made | count_in_each_segment | |
|---|---|---|---|---|---|---|
| HC_segments | ||||||
| 0 | 12091.324201 | 2.401826 | 0.945205 | 3.538813 | 6.894977 | 219 |
| 1 | 33825.396825 | 5.523810 | 3.486772 | 0.984127 | 2.002646 | 378 |
| 2 | 100000.000000 | 2.000000 | 1.000000 | 1.000000 | 0.000000 | 1 |
| 3 | 103440.000000 | 8.740000 | 0.600000 | 8.180000 | 1.080000 | 50 |
| 4 | 50000.000000 | 3.000000 | 0.000000 | 8.500000 | 9.000000 | 1 |
Cluster 0:
Cluster 1:
Cluster 2:
Cluster 3:
Cluster 4:
Cluster 0 represents customers with relatively low credit limits, a small number of credit cards, moderate online visits, and a relatively high number of calls made. These customers have minimal in-person visits to the bank and seem to prefer phone interactions.
Cluster 1 represents customers with medium to high credit limits, a moderate number of credit cards, regular visits to the bank, and moderate online visits. These customers have a balanced interaction pattern and are moderately engaged across multiple channels.
Cluster 2 represents a unique customer with a high credit limit, a small number of credit cards, and limited interactions both online and via phone. This Cluster may represent a distinct group of customers with specific needs.
Cluster 3 represents customers with high credit limits, a significant number of credit cards, frequent online visits, and moderate calls made. These customers prefer online interactions but may occasionally contact the bank through phone calls.
Cluster 4 represents customers with a $50,000 credit limit, three credit cards, and no bank visits. They have a strong preference for online interactions and make the highest number of calls among all Clusters.
These results provide valuable insights into the different customer segments based on their behavior and credit-related characteristics. Businesses can now tailor their marketing strategies, product offerings, and customer service approaches to better serve the distinct needs of each segment.
K-means clustering as selected technique:
Cluster 0: The bank could target these customers with marketing campaigns that promote online banking and phone calls. The bank could also offer them special incentives, such as rewards points or discounts, for using these channels.
Cluster 1: The bank could target these customers with marketing campaigns that promote in-person banking. The bank could also offer them help and support with online banking and phone calls.
Cluster 2: The bank could target these customers with general marketing campaigns that promote all of your banking channels.