Spaces:
Runtime error
Runtime error
mathiasleys
commited on
Commit
·
e144a1c
1
Parent(s):
e819afa
Fix text typos
Browse files
app.py
CHANGED
@@ -31,31 +31,30 @@ st.subheader("Setting optimal prices with Bayesian stats 📈")
|
|
31 |
st.header("Let's start with the basics 🏁")
|
32 |
|
33 |
st.markdown("The beginning is usually a good place to start so we'll kick things off there.")
|
34 |
-
st.markdown("""The one crucial piece information we need to find the optimal price is
|
35 |
-
**how demand behaves over different price points**. \nIf we can
|
36 |
-
can expect demand to be for a wide range of prices, we can figure out which price optimizes our
|
37 |
-
(i.e., revenue, profit, ...).""")
|
38 |
st.markdown("""For the keen economists amongst you, this is beginning to sound a lot like a
|
39 |
**demand curve**.""")
|
40 |
|
41 |
st.markdown("""Estimating a demand curve, sounds easy enough right? \nLet's assume we have
|
42 |
demand with constant price elasticity; so a certain percent change in price will cause a
|
43 |
-
constant percent change in demand, independent of the price level.
|
44 |
-
|
45 |
-
st.markdown("So our data
|
46 |
st.image("assets/images/ideal_case_demand.png")
|
47 |
st.markdown("""Alright now we can get out our trusted regression toolbox and fit a nice curve
|
48 |
-
through the data because we know that our constant-elasticity demand function
|
49 |
-
like this:""")
|
50 |
st.latex(sympy.latex(sympy.Eq(sympy.Function(D)(p), a*p**(-eta), evaluate=False)))
|
51 |
st.write("with shape parameter a and price elasticity η")
|
52 |
st.image("assets/images/ideal_case_demand_fitted.png")
|
53 |
-
st.markdown("""Now that we have a reasonable estimate of our demand, we can derive our
|
54 |
-
profit at different price points because we know the following holds:""")
|
55 |
st.latex(f"{profit} = {p}*{sympy.Function(D)(p)} - [{var_cost}*{sympy.Function(D)(p)} + {fixed_cost}]")
|
56 |
st.image("assets/images/ideal_case_profit_curve.png")
|
57 |
-
st.markdown("""Finally we can dust off our good old
|
58 |
-
price which we expect will optimize profit which
|
59 |
st.image("assets/images/ideal_case_optimal_profit.png")
|
60 |
st.markdown("""Voilà there you have it: we should price this product at 4.24 and we can expect
|
61 |
a bottom-line profit of 7.34""")
|
@@ -63,9 +62,9 @@ st.markdown("So can we kick back & relax now? \nWell, there are a few issues wi
|
|
63 |
|
64 |
# (2) Dynamic demand curves
|
65 |
st.header("The demands they are a-changin' 🎸")
|
66 |
-
st.markdown("""We
|
67 |
-
curve once and be done with it. \nWhy? Because demand is influenced by many factors (e.g.,
|
68 |
-
trends, competitor actions, human behavior, etc.) that tend to change a lot over time.""")
|
69 |
st.write("Below you can see an (exaggerated) example of what we're talking about:")
|
70 |
|
71 |
with open("assets/images/dynamic_demand.gif", "rb") as file_:
|
@@ -86,24 +85,23 @@ st.markdown("""So far, we have assumed that we get (and keep getting) data on de
|
|
86 |
different price points. \n
|
87 |
Not only is this assumption **unrealistic**, it is also very **undesirable**""")
|
88 |
st.markdown("""Why? Because getting demand data on a wide spectrum of price points implies that
|
89 |
-
we are spending a significant amount of time setting prices
|
90 |
-
too low! \n
|
91 |
Which is ironically exactly the opposite of what we set out to achieve.""")
|
92 |
-
st.markdown("In practice, our demand will rather look something like this:")
|
93 |
st.image("assets/images/realistic_demand.png")
|
94 |
-
st.markdown("""As we can see, we have tried three price points in the past (€7.5, €10 and €11)
|
95 |
-
|
96 |
st.markdown("""On a side note: keep in mind that we still assume the same latent demand curve and
|
97 |
optimal price point of €4.24 \n
|
98 |
-
So (for the sake of the example) we have been massively overpricing our product in the past""")
|
99 |
st.image("assets/images/realistic_demand_latent_curve.png")
|
100 |
-
st.markdown("""This
|
101 |
though. \n
|
102 |
Intuitively, it makes sense that we can make a reasonable estimate of expected demand at €8 or €9,
|
103 |
given the observed demand at €7.5 and €10. \nBut can we extrapolate further to €2 or €20 with the
|
104 |
-
same reasonable confidence?""")
|
105 |
st.markdown("""This is a nice example of a very well-known problem in statistics called the
|
106 |
-
**\"exploration-exploitation
|
107 |
👉 **Exploration**: We want to explore the demand for a diverse enough range of price points
|
108 |
so that we can accurately estimate our demand curve. \n
|
109 |
👉 **Exploitation**: We want to exploit all the knowledge we have gained through exploring and
|
@@ -161,17 +159,17 @@ st.markdown("""And eventually we arrive at a new price: €5.25! Which is indeed
|
|
161 |
to the actual optimal price of €4.24""")
|
162 |
st.markdown("""Now that we have our first updated price point, why stop there? Let's simulate 10
|
163 |
demand data points at this price point from out latent demand curve and check whether Thompson
|
164 |
-
sampling will edge us even closer to that optimal €4.24 point""")
|
165 |
st.image("assets/images/updated_prices_demand.png")
|
166 |
-
st.markdown("""We know the drill by
|
167 |
Let's recalculate our posteriors with this extra information.""")
|
168 |
st.image(["assets/images/posterior_demand_2.png", "assets/images/posterior_profit_2.png"])
|
169 |
st.markdown("""We immediately notice that the demand (and profit) posteriors are much less spread
|
170 |
-
apart this time around which implies that we are more confident in our predictions""")
|
171 |
-
st.markdown("Now, we can sample just one curve from the distribution")
|
172 |
st.image(["assets/images/posterior_demand_sample_2.png", "assets/images/posterior_profit_sample_2.png"])
|
173 |
st.markdown("""And finally we arrive at a price point of €4.44 which is eerily close to
|
174 |
-
the actual optimum of €4.24
|
175 |
|
176 |
# (5) Thompson sampling demo
|
177 |
st.header("Demo time 🎮")
|
@@ -187,7 +185,7 @@ elasticity = st.slider(
|
|
187 |
key="latent_elasticity",
|
188 |
min_value=0.05,
|
189 |
max_value=0.95,
|
190 |
-
value=0.
|
191 |
step=0.05,
|
192 |
)
|
193 |
while demo_button:
|
@@ -221,9 +219,11 @@ st.markdown("""If you do want to learn more, we recommend these links:
|
|
221 |
[2](https://thegradient.pub/gaussian-process-not-quite-for-dummies/),
|
222 |
[3](https://sidravi1.github.io/blog/2018/05/15/latent-gp-and-binomial-likelihood)""")
|
223 |
|
224 |
-
st.subheader("""👉 Price optimization is much more complex than just
|
225 |
-
st.markdown("""It sure is
|
226 |
-
|
|
|
|
|
227 |
This means that you can make the price optimization pillar arbitrarily custom/complex. As long as
|
228 |
it takes in a demand function and spits out a price.""")
|
229 |
st.image("assets/images/flywheel_2.png")
|
|
|
31 |
st.header("Let's start with the basics 🏁")
|
32 |
|
33 |
st.markdown("The beginning is usually a good place to start so we'll kick things off there.")
|
34 |
+
st.markdown("""The one crucial piece information we need in order to find the optimal price is
|
35 |
+
**how demand behaves over different price points**. \nIf we can make a decent guess of what we
|
36 |
+
can expect demand to be for a wide range of prices, we can figure out which price optimizes our
|
37 |
+
target (i.e., revenue, profit, ...).""")
|
38 |
st.markdown("""For the keen economists amongst you, this is beginning to sound a lot like a
|
39 |
**demand curve**.""")
|
40 |
|
41 |
st.markdown("""Estimating a demand curve, sounds easy enough right? \nLet's assume we have
|
42 |
demand with constant price elasticity; so a certain percent change in price will cause a
|
43 |
+
constant percent change in demand, independent of the price level. In economics, this is often used
|
44 |
+
as a proxy for demand curves in the wild.""")
|
45 |
+
st.markdown("So our demand data looks something like this:")
|
46 |
st.image("assets/images/ideal_case_demand.png")
|
47 |
st.markdown("""Alright now we can get out our trusted regression toolbox and fit a nice curve
|
48 |
+
through the data because we know that our constant-elasticity demand function has this form:""")
|
|
|
49 |
st.latex(sympy.latex(sympy.Eq(sympy.Function(D)(p), a*p**(-eta), evaluate=False)))
|
50 |
st.write("with shape parameter a and price elasticity η")
|
51 |
st.image("assets/images/ideal_case_demand_fitted.png")
|
52 |
+
st.markdown("""Now that we have a reasonable estimate of our demand function, we can derive our
|
53 |
+
expected profit at different price points because we know the following holds:""")
|
54 |
st.latex(f"{profit} = {p}*{sympy.Function(D)(p)} - [{var_cost}*{sympy.Function(D)(p)} + {fixed_cost}]")
|
55 |
st.image("assets/images/ideal_case_profit_curve.png")
|
56 |
+
st.markdown("""Finally we can dust off our good old high-school math book and find the
|
57 |
+
price which we expect will optimize profit which was ultimately the goal of all this.""")
|
58 |
st.image("assets/images/ideal_case_optimal_profit.png")
|
59 |
st.markdown("""Voilà there you have it: we should price this product at 4.24 and we can expect
|
60 |
a bottom-line profit of 7.34""")
|
|
|
62 |
|
63 |
# (2) Dynamic demand curves
|
64 |
st.header("The demands they are a-changin' 🎸")
|
65 |
+
st.markdown("""We arrive at our first bit of bad news: unfortunately, you can't just estimate a
|
66 |
+
demand curve once and be done with it. \nWhy? Because demand is influenced by many factors (e.g.,
|
67 |
+
market trends, competitor actions, human behavior, etc.) that tend to change a lot over time.""")
|
68 |
st.write("Below you can see an (exaggerated) example of what we're talking about:")
|
69 |
|
70 |
with open("assets/images/dynamic_demand.gif", "rb") as file_:
|
|
|
85 |
different price points. \n
|
86 |
Not only is this assumption **unrealistic**, it is also very **undesirable**""")
|
87 |
st.markdown("""Why? Because getting demand data on a wide spectrum of price points implies that
|
88 |
+
we are spending a significant amount of time setting prices that are either too high or too low! \n
|
|
|
89 |
Which is ironically exactly the opposite of what we set out to achieve.""")
|
90 |
+
st.markdown("In practice, our demand observations will rather look something like this:")
|
91 |
st.image("assets/images/realistic_demand.png")
|
92 |
+
st.markdown("""As we can see, we have tried three price points in the past (€7.5, €10 and €11) and
|
93 |
+
collected demand data.""")
|
94 |
st.markdown("""On a side note: keep in mind that we still assume the same latent demand curve and
|
95 |
optimal price point of €4.24 \n
|
96 |
+
So (for the sake of the example) we have been massively overpricing our product in the past.""")
|
97 |
st.image("assets/images/realistic_demand_latent_curve.png")
|
98 |
+
st.markdown("""This limited data brings along a major challenge in estimating the demand curve
|
99 |
though. \n
|
100 |
Intuitively, it makes sense that we can make a reasonable estimate of expected demand at €8 or €9,
|
101 |
given the observed demand at €7.5 and €10. \nBut can we extrapolate further to €2 or €20 with the
|
102 |
+
same reasonable confidence? Probably not.""")
|
103 |
st.markdown("""This is a nice example of a very well-known problem in statistics called the
|
104 |
+
**\"exploration-exploitation trade-off\"** \n
|
105 |
👉 **Exploration**: We want to explore the demand for a diverse enough range of price points
|
106 |
so that we can accurately estimate our demand curve. \n
|
107 |
👉 **Exploitation**: We want to exploit all the knowledge we have gained through exploring and
|
|
|
159 |
to the actual optimal price of €4.24""")
|
160 |
st.markdown("""Now that we have our first updated price point, why stop there? Let's simulate 10
|
161 |
demand data points at this price point from out latent demand curve and check whether Thompson
|
162 |
+
sampling will edge us even closer to that optimal €4.24 point.""")
|
163 |
st.image("assets/images/updated_prices_demand.png")
|
164 |
+
st.markdown("""We know the drill by now. \n
|
165 |
Let's recalculate our posteriors with this extra information.""")
|
166 |
st.image(["assets/images/posterior_demand_2.png", "assets/images/posterior_profit_2.png"])
|
167 |
st.markdown("""We immediately notice that the demand (and profit) posteriors are much less spread
|
168 |
+
apart this time around which implies that we are more confident in our predictions.""")
|
169 |
+
st.markdown("Now, we can sample just one curve from the distribution.")
|
170 |
st.image(["assets/images/posterior_demand_sample_2.png", "assets/images/posterior_profit_sample_2.png"])
|
171 |
st.markdown("""And finally we arrive at a price point of €4.44 which is eerily close to
|
172 |
+
the actual optimum of €4.24""")
|
173 |
|
174 |
# (5) Thompson sampling demo
|
175 |
st.header("Demo time 🎮")
|
|
|
185 |
key="latent_elasticity",
|
186 |
min_value=0.05,
|
187 |
max_value=0.95,
|
188 |
+
value=0.25,
|
189 |
step=0.05,
|
190 |
)
|
191 |
while demo_button:
|
|
|
219 |
[2](https://thegradient.pub/gaussian-process-not-quite-for-dummies/),
|
220 |
[3](https://sidravi1.github.io/blog/2018/05/15/latent-gp-and-binomial-likelihood)""")
|
221 |
|
222 |
+
st.subheader("""👉 Price optimization is much more complex than just optimizing a simple profit function?""")
|
223 |
+
st.markdown("""It sure is. In reality, there are many added complexities that come into play, such
|
224 |
+
as inventory/capacity constraints, complex cost structures, ...""")
|
225 |
+
st.markdown("""The nice thing about our setup is that it consists of three components that you can
|
226 |
+
change pretty much independently from each other. \n
|
227 |
This means that you can make the price optimization pillar arbitrarily custom/complex. As long as
|
228 |
it takes in a demand function and spits out a price.""")
|
229 |
st.image("assets/images/flywheel_2.png")
|