how math can resolve so many issues in the actual world. Once I was in grade college, I actually didn’t see it that approach. I by no means hated math, by the way in which, and neither did I’ve hassle studying many of the fundamental ideas.
Nevertheless, I confess that for many of the courses past the basic arithmetic, I normally thought, “I’ll by no means use that for something in my life”.
These have been different instances, although. There was no Web, no knowledge science, and computer systems have been barely a factor. However time passes. Life occurs, and we get to see the day once we will resolve essential enterprise issues with good previous math!
On this submit, we’ll use the well-known linear regression for a special drawback: predicting buyer churn.
Linear Regression vs Churn
Buyer churn not often occurs in a single day. In lots of circumstances, clients will progressively scale back their buying frequency earlier than stopping fully. Some name that silent churn [1].
Predicting churn might be achieved with the normal churn fashions, which (1) require labeled churn knowledge; (2) typically are complicated to elucidate; (3) detect churn after it already occurred.
However, this venture reveals a special resolution, answering an easier query:
Is that this buyer
slowing down the buying?
This query is answered with the next logic.
We use month-to-month buy developments and linear regression to measure buyer momentum over time. If the shopper continues to extend their bills, the summed quantity will develop over time, resulting in a development upward (or a optimistic slope in a linear regression, if you’ll). The other can also be true. Decrease transaction quantities will add as much as a downtrend.
Let’s break down the logic in small steps, and perceive what we’ll do with the info:
- Mixture buyer transactions by month
- Create a steady time index (e.g. 1, 2, 3…n)
- Fill lacking months with zero purchases
- Match a linear regression line
- Use the slope (transformed to levels) to quantify shopping for conduct
- Evaluation: A damaging slope signifies declining engagement. A optimistic slope signifies growing engagement.
Nicely, let’s transfer on to the implementation subsequent.
Code
The very first thing is importing some modules right into a Python session.
# Imports
import scipy.stats as stats
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
Then, we’ll generate some knowledge that simulates some clients transactions. You possibly can take a look at the whole code on this GitHub repository. The dataset generated brings the columns customer_id, transaction_date, and total_amt, and can appear to be the subsequent image.
Now we’ll create a brand new column that extracts the month of the date, so it turns into simpler for us to group the info later.
# Create new column month
df['mth'] = df['transaction_date'].dt.month
# Group clients by month
df_group = (
df
.groupby(['mth','customer_id'])
['total_amt']
.sum()
.reset_index()
)
Right here is the outcome.

If we rapidly examine if there are clients who haven’t made a transaction each month, we’ll discover a number of circumstances.
That leads us to the subsequent level. We now have to make it possible for, if the shopper doesn’t have at the very least one buy per 30 days, then we’ve so as to add that month with a $0 expense.
Let’s construct a perform that may try this and in addition calculate the slope of the shopper’s buying development.
This perform seems monumental, however we’ll go over it in smaller chunks. Let’s do that.
- Filter the info for a given buyer utilizing Pandas
question()technique. - Make a fast group and examine if the shopper has at the very least one buy for each month.
- If not, we’ll add the lacking month with a $0 expense. I applied this by merging a short lived dataframe with the 12 months and $0 with the unique knowledge. After the merge on months, these durations lacking will likely be rows with
NaNfor the unique knowledge column, which might be stuffed with $0. - Then, we normalize the axes. Do not forget that the X-axis is an index from 1 to 12, however the Y-axis is the expense quantity, in 1000’s of {dollars}. So, to keep away from distortion in our slope, we normalize every part to the identical scale, between 0 and 1. For that, we use the customized perform
min_max_standardize. - Subsequent, we are able to plot the regression utilizing one other customized perform.
- Then we’ll calculate the slope, which is the primary outcome returned from the perform
scipy.linregress(). - Lastly, to calculate the angle of the slope in levels, we’ll attraction to pure arithmetic, utilizing the idea of arc tangent to calculate the angle between the X-axis and the linear regression slope line. In Python, simply use the features
np.arctan()andnp.levels()from numpy.

# Standardize the info
def min_max_standardize(vals):
return (vals - np.min(vals)) / (np.max(vals) - np.min(vals))
#------------
# Fast Operate to plot the regression
def plot_regression(x,y, cust):
plt.scatter(x,y, colour = 'grey')
plt.plot(x,
stats.linregress(x,y).slope*np.array(x) + stats.linregress(x,y).intercept,
colour = 'purple',
linestyle='--')
plt.suptitle("Slope of the Linear Regression [Expenses x Time]")
plt.title(f"Buyer {cust} | Slope: {np.levels(np.arctan(stats.linregress(x,y).slope)):.0f} levels. Constructive = Shopping for extra | Destructive = Shopping for much less", dimension=9, colour='grey')
plt.present()
#-----
def get_trend_degrees(buyer, plot=False):
# Filter the info
one_customer = df.question('customer_id == @buyer')
one_customer = one_customer.groupby('mth').total_amt.sum().reset_index().rename(columns={'mth':'period_idx'})
# Verify if all months are within the knowledge
cnt = one_customer.groupby('period_idx').period_idx.nunique().sum()
# If not, add 0 to the months with out transactions
if cnt < 12:
# Create a DataFrame with all 12 months
all_months = pd.DataFrame({'period_idx': vary(1, 13), 'total_amt': 0})
# Merge with the prevailing one_customer knowledge.
# Use 'proper' merge to maintain all 12 months from 'all_months' and fill lacking total_amt.
one_customer = pd.merge(all_months, one_customer, on='period_idx', how='left', suffixes=('_all', ''))
# Mix the total_amt columns, preferring the precise knowledge over the 0 from all_months
one_customer['total_amt'] = one_customer['total_amt'].fillna(one_customer['total_amt_all'])
# Drop the short-term _all column if it exists
one_customer = one_customer.drop(columns=['total_amt_all'])
# Type by period_idx to make sure right order
one_customer = one_customer.sort_values(by='period_idx').reset_index(drop=True)
# Min Max Standardization
X = min_max_standardize(one_customer['period_idx'])
y = min_max_standardize(one_customer['total_amt'])
# Plot
if plot:
plot_regression(X,y, buyer)
# Calculate slope
slope = stats.linregress(X,y)[0]
# Calculate angle levels
angle = np.arctan(slope)
angle = np.levels(angle)
return angle
Nice. It’s time to put this perform to check. Let’s get two clients:
- C_014.
- That is an uptrend buyer who’s shopping for extra over time.
# Instance of robust buyer
get_trend_degrees('C_014', plot=True)
The plot it yields reveals the development. We discover that, despite the fact that there are some weaker months in between, general, the quantities have a tendency to extend as time passes.

The development is 32 levels, thus pointing nicely up, indicating a robust relationship with this buyer.
- C_003.
- This can be a downtrend buyer who’s shopping for much less over time.
# Instance of buyer cease shopping for
get_trend_degrees('C_003', plot=True)

Right here, the bills over the months are clearly lowering, making the slope of this curve level down. The road is 29 levels damaging, indicating that this buyer goes away from the model, thus requires to be stimulated to come back again.
Earlier than You Go
Nicely, that may be a wrap. This venture demonstrates a easy, interpretable method to detecting declining buyer buy conduct utilizing linear regression.
As an alternative of counting on complicated churn fashions, we analyze buy developments over time to determine when clients are slowly disengaging.
This straightforward mannequin may give us a fantastic notion of the place the shopper is shifting in the direction of, whether or not it’s a higher relationship with the model or shifting away from it.
Actually, with different knowledge from the enterprise, it’s doable to enhance this logic and apply a tuned threshold and rapidly determine potential churners each month, primarily based on previous knowledge.
Earlier than wrapping up, I wish to give correct credit score to the unique submit that impressed me to be taught extra about this implementation. It’s a submit from Matheus da Rocha that you will discover here, in this link.
Lastly, discover extra about me on my web site.
GitHub Repository
Right here you discover the total code and documentation.
https://github.com/gurezende/Linear-Regression-Churn/tree/main
References
[2. Numpy Arctan] https://numpy.org/doc/2.1/reference/generated/numpy.arctan.html
[3. Arctan Explanation] https://www.cuemath.com/trigonometry/arctan/
[4. Numpy Degrees] https://numpy.org/doc/2.1/reference/generated/numpy.degrees.html
[5. Scipy Lineregress] https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.linregress.html

