Created
January 14, 2019 16:44
-
-
Save ESeufert/b8e2872380b0f0fdac699ccda80c2bc3 to your computer and use it in GitHub Desktop.
Showcasing how LTV curves are dependent on Retention Curves
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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