Projecting Customer Lifetime Value using the BG/NBD and the Gamma-Gamma models.

Yassir Afif
17 min readJan 28, 2023

--

A case study of predicting Customer Lifetime Value using the famous probabilistic models BG/NBD and Gamma-Gamma.

Marketing analytics is a large sub-field of marketing that uses advanced quantitative techniques to track, measure and analyze the marketing performance of an organization or an entity, It gives companies the ability to identify trends and patterns in consumer behavior and make data-driven decisions to optimize marketing campaigns and identify the best opportunities for growth.

One of the major areas in this field is the area of Customer Lifetime Value (CLV) modeling. The concept of CLV was first introduced in 1988 by Robert Shaw and Merlin Stone in their book titled Database Marketing: Strategy and Implementation. It can be defined as the present value of the future cash flows attributed to the customer during his/her entire relationship with the company(Faris et al).

Understanding and correctly calculating the CLV can help companies identify the most profitable customers, which in turn can facilitate the task of resource allocation when trying to retain customers. In this case high valuable customers deserve more attention and more resources than less valuable ones. CLV prediction can also help companies to budget and forecast future sales and revenue.

One of the most powerful tools for predicting customer lifetime value in non-contractual settings is the BG/NBD model (combined with the Gamma-Gamma model), also known as the Beta-Geometric/Negative Binomial Distribution model. It was created and introduced by Peter Fader , Bruce Hardie and Ka Lok Lee in their famous article published in 2005 in the prestigious journal « Marketing Science » , and titled : “Counting Your Customers” the Easy Way: An Alternative to the Pareto/NBD Model.

The BG/NBD model is an integrated probabilistic model that describes two aspects of consumer behavior, the buying behavior and the churn behavior. This is done by using a combination of the following probability distributions:

  • The Poisson distribution to model transactions and the exponential distribution to model the time between transactions.
  • Because different consumers have different buying behaviors, The BG/NBD model uses the Gamma distribution to model the variation in the buying behavior across the population. Note that the combination of the Poisson/Gamma distributions is known as the negative binomial distribution (NBD) and this is where the name of the model comes from.
  • After each purchase, a customer makes a decision whether to remain a customer or to churn. According to the creators of the model, this behavior can be modeled using the shifted geometric distribution.
  • The Beta distribution is used to describe the variation in the churn probability across the population.

To calculate the CLV, we need to predict the expected value of purchases using the Gamma-Gamma model.

According to the creators of the model, the value of a given customer’s transaction follows a gamma distribution and the heterogeneity between customers follows similarly a gamma distribution. The use of the gamma distribution in both cases is the reason why the model is called Gamma-Gamma.

The combination of the BG/NBD model and the Gamma-Gamma model can, not only, predict the CLV of each customer, but also has the ability to provide us with accurate answers to the following questions :

  • Which customers are still customers and which ones of them will order again in the next period.
  • The number of orders each customer will place and the average order value of each customer’s order.

In this article we will demonstrate how to implement these models using the Lifetimes library and a dataset provided by a renowned UK based online retailer. After predicting the CLV for each customer we will segment the customers using the KMeans algorithm before using the resulting segments to analyze the frequency and the monetary metrics.

Importing the packages and preparing the data.

We will use Seaborn and matplotlib for visualizing the data, Pandas for loading and cleaning the data and the Lifetimes library for the models and functions that we will use.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.metrics import mean_squared_error
from lifetimes import BetaGeoFitter
from lifetimes import GammaGammaFitter
from lifetimes.utils import calibration_and_holdout_data
from lifetimes.utils import summary_data_from_transaction_data
from lifetimes.plotting import plot_calibration_purchases_vs_holdout_purchases
from decimal import Decimal
import datetime as dt

df = pd.read_csv('OnlineRetail.csv')
df.drop('Unnamed: 0', inplace=True, axis=1)
df
The raw data

Let’s verify the existence of NaN values.

df.isna().sum()

We need to drop these NaN values.

df.dropna(inplace=True)

We have some negative values in the quantity column that we need to eliminate.

# Dropping rows with negative quantity.
df = df[~df['Quantity'] < 0]
df.info()

Let’s modify the datatypes of the different columns, we set the types of “InvoiceNo”’, “CustomerID” and “StockCode”’ to string type because they may contain characters other than integers. We set the type of the “UnitPrice” column to decimal because it is the most appropriate data type for monetary values in Python.

# Setting data types 
df['InvoiceNo'] = df['InvoiceNo'].astype('str')
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
df['CustomerID'] = df['CustomerID'].astype('str')
df['Description'] = df['Description'].astype('str')
df['StockCode'] = df['StockCode'].astype('str')
df['Country'] = df['Country'].astype('str')
df['UnitPrice'] = df['UnitPrice'].apply(Decimal)

Computing the RFM metrics :

The RFM framework of thinking, which stands for Recency, Frequency, and Monetary Value, is a widely used tool in marketing to analyze customer behavior and identify high-value customers. The model is based on the idea that customers who have recently made a purchase, made an important number of purchases during their relationship with the company, and spent a significant amount of money are more valuable to the company than those who have not.

In order to create the summary data (Recency, frequency and monetary) for each customer we need to compute the monetary value of the transactions.

# Creating the monetary value of the transactions (quantity * price)
df['Monetary'] = df['Quantity'] * df['UnitPrice']
df['Monetary'] = df['Monetary'].apply(Decimal)

Now, we can compute the summary data using the Lifetimes method, “summary_data_from_transaction_data”.

# Computing the summary data (Recency, Frequency, monetary and tenure)
df_rfmt = summary_data_from_transaction_data(transactions = df,
customer_id_col = 'CustomerID',
datetime_col = 'InvoiceDate',
monetary_value_col = 'Monetary')
df_rfmt.head()
Summary data
  • The recency variable is a measure of the time elapsed since the customer’s last purchase.
  • The frequency corresponds to the number of orders placed by a customer.
  • The monetary metric shows the total revenue generated by each customer.
  • Tenure, or T, represents how long a given customer has been with the company.

Fitting and testing the BG/NBD model.

The first step is to get a calibration period and an observation (holdout) period for each customer. The first period will be used to fit the model and the second period will be used to test the model.

# size of the data 
diff_time = df['InvoiceDate'].max() - df['InvoiceDate'].min()
diff_time

We have 373 days of data, we will use 200 days to fit the model and 173 days to test the model.

# Getting the ending date of the calibration period. 
end_date_cal = df['InvoiceDate'].min() + dt.timedelta(days=200)
end_date_obs = end_date_cal + (diff_time - dt.timedelta(days=200))

Now, we can split our dataset using the Lifetimes method “calibration_and_holdout_data”.

df_rfmt_cal = calibration_and_holdout_data(transactions=df, 
customer_id_col="CustomerID",
datetime_col = "InvoiceDate",
calibration_period_end=end_date_cal,
observation_period_end= end_date_obs)
df_rfmt_cal

The BG/NBD model uses the L2 regularization. To find the best L2 coefficient we will use “Grid Search”.

l2_coefs = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
l2_list = []
rmse_list = []
for coef in l2_coefs :
# Fitting the model using the calibration dataset.
model = BetaGeoFitter(penalizer_coef=coef)
model.fit(df_rfmt_cal['frequency_cal'],
df_rfmt_cal['recency_cal'],
df_rfmt_cal['T_cal'])
# Predicting the frequency for the holdout period for all customers.
pred_freq = pd.DataFrame(model.predict(df_rfmt_cal['duration_holdout'],
df_rfmt_cal['frequency_cal'], df_rfmt_cal['recency_cal'], df_rfmt_cal['T_cal']), columns=['pred_frequency']).reset_index()
# Merging the two dataframes and dropping NaN values.
new_df = df_rfmt_cal.reset_index().merge(pred_freq, on='CustomerID').dropna()

# Computing the rmse score
rmse_score = np.sqrt(mean_squared_error(new_df['frequency_holdout'],new_df['pred_frequency']))
l2_list.append(coef)
rmse_list.append(rmse_score)

# Getting the results
resl = pd.DataFrame(np.array(rmse_list), columns=['rmse_score'])\
.merge(pd.DataFrame(np.array(l2_list), columns=['L2 coefs']), right_index=True, left_index=True)
resl

We fit the model with the best L2 coefficient which is 0.80.

# Fitting the model using the calibration dataset.
model = BetaGeoFitter(penalizer_coef=0.80)
model.fit(df_rfmt_cal['frequency_cal'],
df_rfmt_cal['recency_cal'],
df_rfmt_cal['T_cal'])

We can inspect the performance of the model visually using the method “plot_calibration_purchases_vs_holdout_purchases” which compares the predicted purchases(the frequency values predicted by the model) to the holdout purchases (the frequency values not seen by the model).

From the plot we can see that the model is doing a good job predicting the frequencies in the holdout dataset.

Predicting the next six months CLV for each customer.

After fitting the model, the first thing to do is to predict the number of purchases that each customer will make in the next six months . To do this we will use the method; “conditional_expected_number_of_purchases_up_to_time” .

# Predicting the number of purchases in the next 180 days for all customers.
df_rfmt['predicted_purchases'] = model.conditional_expected_number_of_purchases_up_to_time(180,
df_rfmt['frequency'],
df_rfmt['recency'],
df_rfmt['T'])
df_rfmt.dropna(inplace=True)
# Getting rid of negative values.
df_rfmt = df_rfmt[df_rfmt['monetary_value']>0]
df_rfmt

The next thing to do is to fit the Gamma-Gamma model which we will use to predict the spending of each customer in the next six months.

# Fitting the GammaGamma model 

gg_model = GammaGammaFitter()
gg_model.fit(df_rfmt['frequency'], df_rfmt['monetary_value'])

Now, we can predict the spending using the Gamma-Gamma model’s method ; “conditional_expected_average_profit”.

df_rfmt['pred_monetary'] = gg_model.conditional_expected_average_profit(
df_rfmt['frequency'],
df_rfmt['monetary_value'])
df_rfmt

Finally, let’s compute the CLV for the next six months for each customer using the method ; “ customer_lifetime_value”.

# Predicting the CLV.
df_rfmt['CLV'] = gg_model.customer_lifetime_value(
model,
df_rfmt['frequency'],
df_rfmt['recency'],
df_rfmt['T'],
df_rfmt['monetary_value'],
time = 6,# In months
)
df_rfmt

Customer segmentation using the KMeans algorithm.

The next step in our case study is to cluster the customers using the generated data and the clustering algorithm KMeans. the first thing to do is to find the optimal number of clusters for our dataset. To achieve this we will use the “Elbow” method which will be implemented using the “KElbowVisualizer” from the Yellowbrick library.

from yellowbrick.cluster import KElbowVisualizer


# Instantiate the clustering model and visualizer
km_model = KMeans()
visualizer = KElbowVisualizer(km_model, k=(2,10))

visualizer.fit(df_rfmt) # Fit the data to the visualizer
visualizer.show() # Finalize and render the figure

We can see that the optimal number of clusters or the elbow value is 4.

Note that the distortion score used to find the optimal number of clusters is the sum of squared errors, given that the error for a given point is the distance between this point and the centroid to which it was assigned.

We can now fit the KMeans model with the optimal number of clusters.

km_model = KMeans(n_clusters=4)
km_model.fit(df_rfmt)
# Creating a new column called cluster whose values are the corresponding cluster for each point.
df_rfmt['cluster'] = km_model.labels_

Let’s group by clusters to verify if there is a difference between the clusters in terms of CLV.

# Grouping by clusters
df_clusters = df_rfmt.groupby(['cluster'])['CLV']\
.agg(['mean', "count"])\
.reset_index()

df_clusters.columns = ["cluster", "avg_CLV", "n_customers"]

df_clusters['perct_customers'] = (df_clusters['n_customers']/df_clusters['n_customers']\
.sum())*100
df_clusters

We can notice from the table that cluster 1 which represents 0.149 % of the customer base, has the highest average CLV which is equal to 37242.6£, followed by cluster 3 with an average CLV of 16019.64£, then we have cluster 2 in the third place with an average CLV of 4537.56£ followed by cluster 0 (97.86% of the customer base) with 395.08£ as an average CLV.

Let’s rename this clusters “Diamond”, “Gold”, “Sivler” and “Bronze” respectively.

df_rfmt['customer_category'] = df_rfmt['cluster']\
.replace({3:"Gold", 1:"Diamond", 2:"Silver", 0:"Bronze"})

Let’s visualize the differences in terms of CLV between the four groups.

# Grouping by customer category
df_cat = pd.DataFrame(df_rfmt.groupby(['customer_category'])['CLV']\
.agg('mean')).reset_index()


# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="CLV", data=df_cat)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("CLV", size=14)

# Setting the title for the graph
plt.title("CLV per category")

# Finally showing the plot
plt.show()

let’s compute the contribution of each category to the total CLV of the next six months and visualize the results

df_cat["contribution_to_CLV"] = df_cat['CLV']/df_cat['CLV'].sum()*100

# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="contribution_to_CLV", data=df_cat)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=12)

# Setting the label for y-axis
plt.ylabel("contribution to CLV in %", size=12)

# Setting the title for the graph
plt.title("The contribution of each category to the total CLV of the next six months")

# Finally showing the plot
plt.show()

We can see from the bar plot that “Diamond” and “Gold” customers contribute to more than 90% of the total CLV of the next six months even though they represent less than 1% of the customer base.

and the contribution of “Bronze” customers is less than 1% of the total CLV, even though they represent more than 97% of the customer base.

Analyzing the frequency metric.

Let’s start by investigating the distribution of the metric.

sns.displot(df_rfmt['frequency'])
plt.show()

We’ll now investigate the frequency for each customer category.

df_freq = pd.DataFrame(df_rfmt.groupby(['customer_category'])['frequency']\
.mean()\
.reset_index())


# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="frequency", data=df_freq)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("Frequency", size=14)

# Setting the title for the graph
plt.title("Frequency per category")

# Finally showing the plot
plt.show()

We can see that “Gold” customers have the highest mean frequency.

By analyzing the data we found that 90% of the customers made less than 10 purchases. With the following visualizations we will try to find which customer categories are omnipresent in this group of customers and which ones are not.

# Getting the number of customers per category for those with a frequency less than 10.

df_freq_2 = pd.DataFrame(df_rfmt[df_rfmt['frequency'] < 10]\
.groupby(['customer_category'])['customer_category']\
.agg('count'))

df_freq_2.columns=['n_customers']

df_freq_2 = df_freq_2.reset_index()


# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="n_customers", data=df_freq_2)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("number of customers", size=14)

# Setting the title for the graph
plt.title("Nombre of customers per category for those with frequency less than 10")

# Finally showing the plot
plt.show()
# Getting the number of customers per category for those with a frequency greater than 10.

df_freq_1 = pd.DataFrame(df_rfmt[df_rfmt['frequency'] > 10]\
.groupby(['customer_category'])['customer_category']\
.agg('count'))

df_freq_1.columns = ['n_customers']

df_freq_1 = df_freq_1.reset_index()


# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="n_customers", data=df_freq_1)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("number of customers", size=14)

# Setting the title for the graph
plt.title("Nombre of customers per category for those with frequency higher than 10")

# Finally showing the plot
plt.show()

As we can see from the plots above, the distribution of the frequency variable is so skewed towards the left, with 90 % of customers having a frequency less than 10.

There are no “Diamond” customers with a frequency less than 10, so this means that all “Diamond” customers made more than 10 purchases during the given period. On average they made more than 25 purchases (see the Frequency per category plot).

Six out of seven “Gold” customers made more than 10 purchases in the given period, on average, they made more than 39 purchases.

Analyzing the monetary value.

Let’s start by examining the distribution of this variable.

sns.displot(df_rfmt['monetary_value'])
plt.show()

We will now explore the monetary value for each customer category.

df_mon = pd.DataFrame(df_rfmt.groupby(['customer_category'])['monetary_value'].mean().reset_index())

# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="monetary_value", data=df_mon)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("average monetary value", size=14)

# Setting the title for the graph
plt.title("average monetary value per category")

# Finally showing the plot
plt.show()
# Getting the number of customers per category for those with a monetary value greater than the 80th percentile.

# Getting the 80th percentile
perct = df_rfmt['monetary_value'].quantile(q=0.8)

df_mon_1 = pd.DataFrame(df_rfmt[df_rfmt['monetary_value'] > perct]\
.groupby(['customer_category'])['customer_category']\
.agg('count'))

df_mon_1.columns = ['n_customers']

df_mon_1 = df_mon_1.reset_index()


# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="n_customers", data=df_mon_1)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("number of customers", size=14)

# Setting the title for the graph
plt.title("Nombre of customers per category for those with a monetary value greater than the 80th percentile.")

# Finally showing the plot
plt.show()
# Getting the number of customers per category for those with a monetary value less than the 80th percentile.

df_mon_2= pd.DataFrame(df_rfmt[df_rfmt['monetary_value'] < perct]\
.groupby(['customer_category'])['customer_category']\
.agg('count'))

df_mon_2.columns = ['n_customers']

df_mon_2 = df_mon_2.reset_index()


# Code source : https://www.geeksforgeeks.org/how-to-annotate-bars-in-barplot-with-matplotlib-in-python/
# Defining the plot size
plt.figure(figsize=(8, 8))

# Defining the values for x-axis, y-axis
# and from which dataframe the values are to be picked
plots = sns.barplot(x="customer_category", y="n_customers", data=df_mon_2)

# Iterating over the bars one-by-one
for bar in plots.patches:

# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
# x-coordinate: bar.get_x() + bar.get_width() / 2
# y-coordinate: bar.get_height()
# free space to be left to make graph pleasing: (0, 8)
# ha and va stand for the horizontal and vertical alignment
plots.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=15, xytext=(0, 8),textcoords='offset points')

plt.xlabel("Customer category", size=14)

# Setting the label for y-axis
plt.ylabel("number of customers", size=14)

# Setting the title for the graph
plt.title("Nombre of customers per category for those with a monetary value less than the 80th percentile.")

# Finally showing the plot
plt.show()

The distribution plot shows a distribution highly skewed towards the left, meaning that a large number of customers spent small amounts of money and few customers spent relatively high amounts of money during the given period.

The 80th percentile is 211£, meaning that 80 % of the customers spent less than 211£ during the given period. 100% of these customers with a spending less than 211£ are “Bronze” and “Silver” customers.

“Diamond” and “Gold” customers spent more than the 80th percentile, with an average spending of 2355.36£ for “Gold” customers and an average spending of 2772£ for “Diamond” customers (see the average spending per category plot).

Conclusion :

In conclusion, the BG/NBD and Gamma-Gamma models are powerful tools for predicting customer lifetime value in a non-contractual setting. By using a real-world dataset from an online retailer, we were able to demonstrate the effectiveness of these models in providing valuable information about customer behavior and buying patterns. This information can be used to develop efficient marketing strategies to retain and attract high-value customers.

References :

  • Fader, P.S., Hardie, B.G. and Lee, K.L., 2005. “Counting your customers” the easy way: An alternative to the Pareto/NBD model. Marketing science, 24(2), pp.275–284.
  • Fader, P.S. and Hardie, B.G., 2013. The Gamma-Gamma model of monetary value.
  • Farris, Paul W.; Neil T. Bendle; Phillip E. Pfeifer; David J. Reibstein (2010). Marketing Metrics: The Definitive Guide to Measuring Marketing Performance. Upper Saddle River, New Jersey: Pearson Education,

--

--