Skip to content

Instantly share code, notes, and snippets.

@ESeufert
Created January 14, 2019 16:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ESeufert/b8e2872380b0f0fdac699ccda80c2bc3 to your computer and use it in GitHub Desktop.
Save ESeufert/b8e2872380b0f0fdac699ccda80c2bc3 to your computer and use it in GitHub Desktop.
Showcasing how LTV curves are dependent on Retention Curves
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import pandas as pd
import numpy as np
import random
def build_userbase( n, payer_percentage ):
users = pd.DataFrame( columns=[ "user", "payer", "payment_probability", "payment" ] )
for x in range( 1, n + 1 ):
payer = True if random.randint( 1, 100 ) <= ( payer_percentage * 100 ) else 0
payment_probability = 0
payment = 0
churn_probability = float( random.randint( 1, 10 ) ) / 100
if payer:
payment_probability = float( random.randint( 1, 25 ) ) / 100
payment = float( random.randint( 1, 100 ) )
users = users.append(
{ "user": x, "payer": payer,
"payment_probability": payment_probability, "payment": payment,
"churn_probability": churn_probability, "churned": 0 }, ignore_index=True )
return users
def build_cumulative_revenue( users, days ):
payers = users[ users[ 'payer' ] == 1 ]
daily_revenue = [ 0 ] * ( days + 1 )
daily_cumulative_revenue = [ 0 ] * ( days + 1 )
for x in range( 1, days + 1 ):
daily_revenue[ x ] = 0
daily_cumulative_revenue[ x ] = 0
this_daily_revenue = 0
for index, p in payers.iterrows():
this_payment_probability = float( random.randint( 1, 100 ) ) / 100
this_payment = p[ "payment" ] if this_payment_probability <= p[ "payment_probability" ] else 0
this_daily_revenue += this_payment
daily_revenue[ x ] = this_daily_revenue
daily_cumulative_revenue[ x ] = ( daily_cumulative_revenue[ x - 1 ] + daily_revenue[ x ] ) if x > 1 else daily_revenue[ x ]
return daily_revenue, daily_cumulative_revenue
def build_cumulative_revenue_with_churn( users, days ):
payers = users[ users[ 'payer' ] == 1 ]
daily_revenue = [ 0 ] * ( days + 1 )
daily_cumulative_revenue = [ 0 ] * ( days + 1 )
for x in range( 1, days + 1 ):
daily_revenue[ x ] = 0
daily_cumulative_revenue[ x ] = 0
this_daily_revenue = 0
for index, p in payers.iterrows():
if( not p[ "churned" ] ):
#if they didn't churn out
this_churn_probability = float( random.randint( 1, 100 ) ) / 100
if this_churn_probability > p[ "churn_probability" ]:
#if this isn't their day to churn
this_payment_probability = float( random.randint( 1, 100 ) ) / 100
this_payment = p[ "payment" ] if this_payment_probability <= p[ "payment_probability" ] else 0
this_daily_revenue += this_payment
else:
#they are churning
payers.loc[ index, "churned" ] = True
daily_revenue[ x ] = this_daily_revenue
daily_cumulative_revenue[ x ] = ( daily_cumulative_revenue[ x - 1 ] + daily_revenue[ x ] ) if x > 1 else daily_revenue[ x ]
users.loc[ users[ "payer" ] == True ] = payers
return daily_revenue, daily_cumulative_revenue
def build_DAU_with_churn( users, days ):
DAU = [ 0 ] * ( days + 1 )
churn = [ 0 ] * ( days + 1 )
for x in range( 1, days + 1 ):
for index, u in users.iterrows():
if( not u[ "churned" ] ):
#if the user has not yet churned
this_churn_probability = float( random.randint( 1, 100 ) ) / 100
if this_churn_probability > u[ "churn_probability" ]:
#if this user is not churning on this day
#increment the DAU
DAU[ x ] += 1
else:
churn[ x ] += 1
users.loc[ index, "churned" ] = True
return DAU, churn
#
# Build initial userbase
#
users = build_userbase( 1000, payer_percentage=0.05 )
users[ "churned" ] = users[ "churned" ].astype('bool')
#
# Get daily revenue values
#
dr_users = users
daily_revenue, daily_cumulative_revenue = build_cumulative_revenue( dr_users, 365 )
#
# Get daily revenue values with churn
#
drc_users = users
daily_revenue_with_churn, daily_cumulative_revenue_with_churn = build_cumulative_revenue_with_churn( drc_users, 365 )
#
# Get DAU and Churn
#
dau_users = users
DAU, churn = build_DAU_with_churn( dau_users, 365 )
#
# Print Revenue Graph
#
fig, ax = plt.subplots()
plt.rcParams['figure.figsize'] = [10, 5]
plt.plot( daily_cumulative_revenue, '-g', label='Cumulative Revenue', linewidth=3 )
plt.plot( daily_revenue, '-r', label='Daily Revenue', linewidth=3 )
plt.legend(loc='upper left')
plt.ylabel( 'Revenue' )
fmt = '${x:,.0f}'
tick = ticker.StrMethodFormatter( fmt )
ax.yaxis.set_major_formatter( tick )
plt.xticks( rotation=25 )
fig.suptitle( 'Cumulative and Daily Revenue, No Churn', fontsize=14 )
plt.show()
#
# Print Revenue with Churn Graph
#
fig, ax = plt.subplots()
plt.rcParams['figure.figsize'] = [10, 5]
plt.plot( daily_cumulative_revenue_with_churn, '-g', label='Cumulative Revenue (with Churn)', linewidth=3 )
plt.plot( daily_revenue_with_churn, '-r', label='Daily Revenue (with Churn)', linewidth=3 )
plt.legend(loc='center right')
plt.ylabel( 'Revenue' )
fmt = '${x:,.0f}'
tick = ticker.StrMethodFormatter( fmt )
ax.yaxis.set_major_formatter( tick )
plt.xticks( rotation=25 )
fig.suptitle( 'Cumulative and Daily Revenue with Churn', fontsize=14 )
plt.show()
#
# Print DAU and Churn Graph
#
fig, ax1 = plt.subplots()
plt.rcParams['figure.figsize'] = [10, 5]
ax1.plot( DAU, '-r', label='Cohort DAU', linewidth=3 )
ax1.set_ylabel( 'DAU' )
ax1.plot( churn , '-y', label='Daily Cohort Churn', linewidth=3 )
ax1.legend( loc='upper right' )
fig.suptitle( 'DAU and Daily Churn Values', fontsize=14 )
plt.show()
x = np.arange( 1, 365, 1 )
#
# Print a random LTV graph
#
ltv = np.log( x )
fig, ax1 = plt.subplots()
plt.rcParams['figure.figsize'] = [10, 5]
ax1.set_ylabel( 'LTV' )
ax1.plot( ltv, '-g', label='LTV Curve (Arbitrary)', linewidth=3 )
ax1.legend( loc='center right' )
fig.suptitle( 'An Arbitrary LTV Curve', fontsize=14 )
fmt = '${x:,.0f}'
tick = ticker.StrMethodFormatter( fmt )
ax1.yaxis.set_major_formatter( tick )
plt.show()
#
# Print a random retention graph
#
retention = np.exp( -.05 * x )
fig, ax1 = plt.subplots()
plt.rcParams[ 'figure.figsize' ] = [ 10, 5 ]
ax1.set_ylabel( 'Retention' )
ax1.plot( retention, '-r', label='Retention Curve (Arbitrary)', linewidth=3 )
ax1.legend( loc='center right' )
fig.suptitle( 'An Arbitrary Retention Curve', fontsize=14 )
vals = ax1.get_yticks()
ax1.set_yticklabels(['%1.2f%%' %i for i in vals])
plt.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment