CRM Series with Data Science I : Rule-Based Classification
By: Ömer Yıldız
Tags: Classification, Data Analysis, Customer Segmentation, Churn, CRM

CRM Series with Data Science Basic to Expert I : Rule-Based Classification in a Banking Case.

Photo by Martin Adams on Unsplash

Welcome to the first part of this mini-series, where we take a look how can we improve CRM strategies supported with data science methods.

In this series we are pursuing a gradual strategy how is basic to expert.

Rule-Based Classification

Rule-based classification is a simple methodology whereby you would identify an attribute you’d want to segment your customers by and set different thresholds to determine what cluster each customer should be a part of. [1]

Segmenting your customers in this way doesn’t require a lot of skill. It’s intuitive and allows you to apply specific business logic to each segment for an easier way to keep track of specific KPIs. It also allows you to monitor trends over time by identifying how many customers you have in every group. [1]

The downside, however, is that it’s static and hard to add additional attributes to. So, the approach is typically two-dimensional and requires a lot of maintenance to keep up-to-date when customers’ behaviors change, causing the segmentation thresholds to be irrelevant.[1]

Where is Rule-Based Classification Useful ?

Rule-Based Segmentation can use to solve both small and large scale business problems. Especially, comes across Remarketing and Lookalikes operations.

In Remarketing

Remarketing is perhaps where rule based audiences have the most impact.[2] For a bank for instance, a combination of age, sex, credit risk score, deposit, last financal activity information etc.can be used to regaining customers who have not used the product for a long time. Rule based segments can make remarketing relevant and effective by giving estimates to intent.

In Lookalikes

Rule based customer segments are immensely useful for creating Lookalike seeds.[2] Thanks to rule-based calassification, customers can be classify in a similar clusters and their behaviour can predict easily.

/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*/*\*

Business Problem

Photo by Tim Evans on Unsplash

A Banking Case with Rule-Based Classification

Content

Case Data Pre-Processing & Analysis Defining Personas and Creating Segments Based on Personas Prediction

1. Case

A CRM department in a local bank works on classify their lost customers with their past datas. So they want predict with these method that average loss balance and passive duration for future.

Important variables :

AGE: Age of the lost customers

Age of the lost customers JOB: Profession of the lost customers

Profession of the lost customers MARITAL : Marital status of the lost customers

Marital status of the lost customers EDUCATION : Last graduated of the lost customers

Last graduated of the lost customers BALANCE : Losing money ($) per the lost customer

Losing money ($) per the lost customer LAST_TRANSACTION : Time since last transaction (day)

2. Data Pre-Processing & Analysis

In this part, we’re preparing data to ready before detailed analysis, also taking a short look to dataset for general terms.

Let’s import to libraries and read to database we need.

Dataset is downloaded at kaggle. You can find the link in references section.

Also you can reach all detailed scripts from my github page. Link is in references section.

# /*\*/*\*/*\* Importing Libraries *\*/*\*/*\*/

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns



# /*\*/*\*/*\* Reading Datasets *\*/*\*/*\*/

df = pd.read_csv("C:/Users/omery/Desktop/Medium/Rule-Based Classification/BankCustomerData.csv")



pd.set_option('display.max_columns',None)

Now, we can do descriptive analysis and take a short look to dataset.

# /*\*/*\*/*\* Descriptive Analysis *\*/*\*/*\*/



def check_df(dataframe, head=5, tail=5, quan=True):

print("##################### Shape #####################")

print(dataframe.shape)

print("##################### Type #####################")

print(dataframe.dtypes)

print("##################### Head #####################")

print(dataframe.head(head))

print("##################### Tail #####################")

print(dataframe.tail(tail))

print("##################### NA #####################")

print(dataframe.isnull().sum())



if quan:

print("##################### Quantiles #####################")

print(dataframe.quantile([0, 0.05, 0.50, 0.95, 0.99, 1]).T)



check_df(df, head=5, tail=5)

As we can see below; data shape, types, head, NA, quantiles.

Shape: The dimension of dataframe

The dimension of dataframe Type: The data type of each variable

The data type of each variable Head: The first “n” rows of the DataFrame

The first “n” rows of the DataFrame Tail: The tail “n” rows of the DataFrame

The tail “n” rows of the DataFrame NA: Checking if any NULL Value is into DataFrame

Checking if any NULL Value is into DataFrame Quantile: Cut points dividing the range of a probability distribution into continuous intervals with equal probabilities.

Shape & Types

Head & Tail

In this part, we are detecting categorical and numerical variables.

# *\*/ Detection of Categorical and Numerical Variables *\*/



def grab_col_names(dataframe, cat_th=10, car_th=20):



# cat_cols, cat_but_car

cat_cols = [col for col in dataframe.columns if dataframe[col].dtypes == "O"]



num_but_cat = [col for col in dataframe.columns if dataframe[col].nunique() < cat_th and

dataframe[col].dtypes != "O"]



cat_but_car = [col for col in dataframe.columns if dataframe[col].nunique() > car_th and

dataframe[col].dtypes == "O"]



cat_cols = cat_cols + num_but_cat



cat_cols = [col for col in cat_cols if col not in cat_but_car]



# num_cols

num_cols = [col for col in dataframe.columns if dataframe[col].dtypes != "O"]



num_cols = [col for col in num_cols if col not in num_but_cat]



print(f"Variables: {dataframe.shape[1]}")

print(f'cat_cols: {len(cat_cols)}')

print(f'num_cols: {len(num_cols)}')

print(f'cat_but_car: {len(cat_but_car)}')

print(f'num_but_cat: {len(num_but_cat)}')



return cat_cols, num_cols, cat_but_car



cat_cols, num_cols, cat_but_car = grab_col_names(df)

Variable Types

variables : All variables

All variables cat_cols : Categorical variables

Categorical variables num_cols : Numerical variables

Numerical variables cat_but_car : Variables that appears categorical but they have a wide range of cardinality

Variables that appears categorical but they have a wide range of cardinality num_but_cat : Variables that appears categorical but they are numerical

Now, lets take a general view to categorical datas.

# /*\*/*\*/*\* General View to Categorical Datas *\*/*\*/*\*/



def cat_summary(dataframe, plot=False):

# cat_cols = grab_col_names(dataframe)["Categorical_Data"]

for col_name in cat_cols:

print("############## Frequency & Percentage Values of Categorical Datas ########################")

print(pd.DataFrame({col_name: dataframe[col_name].value_counts(),

"Ratio": dataframe[col_name].value_counts() / len(dataframe)}))

Job, Marital Status & Education

Housing, Loan, Contact & Month

Poutcome & Term_Deposit

Of course, numerical datas.

# /*\*/*\*/*\* General View to Numerical Datas *\*/*\*/*\*/



num_cols = [col for col in df.columns if df[col].dtypes !="O" and col not in ["day","campaign","pdays","previous"]]

numerical_col = num_cols



def num_summary(dataframe, numerical_col):



quantiles = [0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 0.95, 0.99]

print("########## Summary Statistics of " + numerical_col + " ############")

print(dataframe[numerical_col].describe(quantiles).T)



for col in num_cols:

num_summary(df, col)

Summary Statistics of Age

Summary Statistics of Balance

Summary Statistics of Last_Transaction

An easy way to data analysis.

# /*\*/*\*/*\* Data Analysis *\*/*\*/*\*/



pd.set_option('display.max_columns',None)

pd.set_option('display.max_rows',None)



def data_analysis(dataframe):

# mean, min, max amount of balance, last transaction and age by job, marital and education.

print(dataframe.groupby(["job","marital","education"]).agg({"balance" : ["mean","min","max"],

"last_transaction" : ["mean","min","max"],

"age" : ["mean","min","max"]}))

# unique values of categorical variables

print("Unique Values of Variables:

", dataframe[cat_cols].nunique())



data_analysis(df)

Balance Amount According to Job-Marital-Education Groups

Unique Values of Each Variables

3. Defining Personas & Creating Segments Based on Personas

# /*\*/*\*/*\* Defining Personas *\*/*\*/*\*/



agg_df = df.groupby(["job","marital","education","age"]).agg({"balance" : "mean",

"last_transaction" : "mean"})



agg_df = agg_df.reset_index()



age_list = ["0_17","18_24","25_34","35_44","45_54","55_69","70_90"]

agg_df["age_cat"] = pd.cut(agg_df["age"], [0,18,25,35,45,55,70,90], labels = age_list)

AVG Last Transaction Time (day) and Balance According to Age Categories

# /*\*/*\*/*\* Creating Rule-Based Personas *\*/*\*/*\*/



agg_df["customer_level_based"] = [row[0].upper()+"_"+ row[1].upper()+"_"+ row[2].upper()+"_"+ str(row[6]).upper() for

row in agg_df.values]





del_list = ["job", "marital", "education", "age", "age_cat"]



agg_df.drop(del_list, axis=1, inplace=True)



agg_df = agg_df.groupby(agg_df["customer_level_based"]).agg({"balance": "mean",

"last_transaction" : "mean" }).sort_values(("balance"),

ascending=False)

agg_df.reset_index(inplace=True)

Defined Personas

# /*\*/*\*/*\* Rule-Based Segmentation *\*/*\*/*\*/

agg_df["segment"]= pd.qcut(agg_df["balance"], 4, labels=["D","C","B","A"])



seg= agg_df.groupby(agg_df["segment"]).agg({"balance" : ["mean", "max", "sum"],

"last_transaction" : ["mean", "max", "min"]})

seg = seg.reset_index()



seg.head().sort_values(("segment"),ascending=False)

Rule-Based Segmentation

4. Prediction

For example, we have a potential lost client who has the following information: “SERVICES_SINGLE_SECONDARY_45_54”.

# /*\*/*\*/*\* Prediction *\*/*\*/*\*/



lost_customer = "SERVICES_SINGLE_SECONDARY_45_54"



agg_df[agg_df["customer_level_based"] == lost_customer]

seg.head()

Segmented the Lost Customers

Balance and Last Transaction Information of the Lost Customers

Result

As a result, with this analyze we can predict average balance loss generated the lost customers, also average of passive duration of lost customers. So, we can prevent losing before the customers completely leaving.

References

Dataset Link : https://www.kaggle.com/kidoen/bank-customers-data/version/2

GitHub : https://github.com/omerta92/Rule-Based-Classification-in-a-Banking-Case..git