Spaces:
Sleeping
Sleeping
Add customer LTV calculator code
Browse files- .gitignore +1 -0
- app.py +133 -0
- requirements.txt +2 -0
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
.vscode/launch.json
|
app.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
|
3 |
+
import pandas as pd
|
4 |
+
import streamlit as st
|
5 |
+
|
6 |
+
|
7 |
+
def months_between_dates(start_date, end_date):
|
8 |
+
return (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month)
|
9 |
+
|
10 |
+
def calculate_lifespan(row):
|
11 |
+
if pd.notna(row["Churned"]):
|
12 |
+
return (row["Churned"] - row["Date"]).days
|
13 |
+
else:
|
14 |
+
return (datetime.now() - row["Date"]).days
|
15 |
+
|
16 |
+
def date_filtered_df(df, start_date, end_date):
|
17 |
+
return df[(df['Date'] >= start_date) & (df['Date'] <= end_date)]
|
18 |
+
|
19 |
+
def average_customer_lifespan_calculation(
|
20 |
+
df,
|
21 |
+
start_date,
|
22 |
+
end_date,
|
23 |
+
) -> float:
|
24 |
+
df.sort_values(by=['Customer', 'Date'], inplace=True)
|
25 |
+
mask = (df['Date'] >= start_date) & (df['Date'] <= end_date)
|
26 |
+
df = df.loc[mask]
|
27 |
+
df["Lifespan"] = df.apply(calculate_lifespan, axis=1)
|
28 |
+
df = df.dropna(subset=["Value"])
|
29 |
+
# Calculate average customer lifespan
|
30 |
+
return round(df["Lifespan"].mean(), 0)
|
31 |
+
|
32 |
+
|
33 |
+
def icon_select(value):
|
34 |
+
if value >= 7:
|
35 |
+
return 'π'
|
36 |
+
elif value >= 5:
|
37 |
+
return 'π₯'
|
38 |
+
elif value > 3.5:
|
39 |
+
return 'π€'
|
40 |
+
else:
|
41 |
+
return 'π'
|
42 |
+
|
43 |
+
@st.cache_data(ttl="5m")
|
44 |
+
def get_data(file_link):
|
45 |
+
|
46 |
+
if 'dl=0' in file_link:
|
47 |
+
file_link = file_link.replace('dl=0', 'dl=1')
|
48 |
+
all_data_df = pd.read_excel(file_link)
|
49 |
+
return all_data_df
|
50 |
+
|
51 |
+
|
52 |
+
st.title('Customer LTV Calculator')
|
53 |
+
|
54 |
+
file_link = st.text_input(
|
55 |
+
'Link to data file',
|
56 |
+
)
|
57 |
+
|
58 |
+
if not file_link:
|
59 |
+
st.stop()
|
60 |
+
|
61 |
+
all_data_df = get_data(file_link)
|
62 |
+
|
63 |
+
col1, col2, col3 = st.columns(3)
|
64 |
+
with col1:
|
65 |
+
start_date = st.date_input(
|
66 |
+
'Start Date:',
|
67 |
+
value=pd.to_datetime('2022-09-01'),
|
68 |
+
max_value=pd.to_datetime(datetime.now().date()),
|
69 |
+
format='DD-MM-YYYY',
|
70 |
+
)
|
71 |
+
with col2:
|
72 |
+
end_date = st.date_input(
|
73 |
+
'End Date:',
|
74 |
+
value=pd.to_datetime(datetime.now().date()),
|
75 |
+
max_value=pd.to_datetime(datetime.now().date()),
|
76 |
+
format='DD-MM-YYYY',
|
77 |
+
)
|
78 |
+
with col3:
|
79 |
+
start_datetime = pd.to_datetime(start_date)
|
80 |
+
end_datetime = pd.to_datetime(end_date)
|
81 |
+
number_of_months = months_between_dates(start_datetime, end_datetime)
|
82 |
+
st.write(str(number_of_months), 'months')
|
83 |
+
|
84 |
+
calculated_acl = average_customer_lifespan_calculation(
|
85 |
+
all_data_df,
|
86 |
+
start_datetime,
|
87 |
+
end_datetime,
|
88 |
+
)
|
89 |
+
|
90 |
+
if start_date < end_date:
|
91 |
+
# Filter the dataframe based on the selected date range
|
92 |
+
mask = (all_data_df['Date'] >= start_datetime) & (all_data_df['Date'] <= end_datetime)
|
93 |
+
all_data_df = all_data_df.loc[mask]
|
94 |
+
else:
|
95 |
+
st.error('Error: End date must be after the start date.')
|
96 |
+
|
97 |
+
all_data_date_filtered = date_filtered_df(all_data_df, start_datetime, end_datetime)
|
98 |
+
average_order_size = all_data_date_filtered['Value'].mean()
|
99 |
+
formatted_num = "Β£{:,.2f}".format(average_order_size)
|
100 |
+
st.write('Average order size (AOS):', str(formatted_num))
|
101 |
+
|
102 |
+
purchase_frequency = all_data_date_filtered.groupby('Customer')['Date'].nunique()
|
103 |
+
average_purchase_frequency_rate = purchase_frequency.mean()/number_of_months
|
104 |
+
st.write('Average purchase frequency rate (APFR) per customer per month:', str(round(average_purchase_frequency_rate, 2)))
|
105 |
+
customer_value = average_order_size * average_purchase_frequency_rate
|
106 |
+
customer_value_formatted = "Β£{:,.2f}".format(customer_value)
|
107 |
+
st.write('Customer Value (AOS x APFR):', customer_value_formatted)
|
108 |
+
|
109 |
+
average_customer_lifespan = 12
|
110 |
+
average_customer_lifespan = st.slider(
|
111 |
+
f'Average Customer Lifespan (months) - calculated value {calculated_acl} days',
|
112 |
+
min_value=1,
|
113 |
+
max_value=50,
|
114 |
+
step=1,
|
115 |
+
value=12,
|
116 |
+
)
|
117 |
+
customer_lifetime_vale = average_customer_lifespan * customer_value
|
118 |
+
customer_lifetime_vale_formatted = "Β£{:,.2f}".format(customer_lifetime_vale)
|
119 |
+
st.write('Customer Lifetime Value (CLV):', customer_lifetime_vale_formatted)
|
120 |
+
|
121 |
+
acquisition_cost = 50
|
122 |
+
acquisition_cost = st.slider('Cost of acquisition', min_value=0, max_value=1000, step=10, value=50)
|
123 |
+
clv_cac_ratio = customer_lifetime_vale/acquisition_cost
|
124 |
+
|
125 |
+
all_data_df['year_month'] = all_data_df['Date'].dt.to_period('M')
|
126 |
+
all_data_df = all_data_df.sort_values(by='Date')
|
127 |
+
|
128 |
+
st.write(
|
129 |
+
'CLV to CAC ratio:',
|
130 |
+
"{:,.2f}".format(clv_cac_ratio),
|
131 |
+
': 1',
|
132 |
+
icon_select(clv_cac_ratio),
|
133 |
+
)
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
pandas
|
2 |
+
openpyxl
|