1. Pricing Elasticity of Demand
Let’s take a look at the pricing elasticity of demand (PED) or \(E_D\). The intuition behind \(E_D\) is that is a measure of how sensitive demand is to price change. Typically, for a product, we can set the price \(p_i\) and measure the amount of quantity \(q_i\) sold, and these values form a pair \((p_i, q_i)\). If a product is sold at multiple prices, then we have multiple price-quantity pairs as follows.
\((p_1, q_1)\)
\((p_2, q_2)\)
\(\ldots\)
\((p_n, q_n)\)
Plotting these pairs of points forms what is known as the demand curve
. Below is a fake dataset of prices and quantities.
[1]:
import pandas as pd
import numpy as np
np.random.seed(37)
df = pd.DataFrame({
'p': np.arange(1, 30, 1) + 1.99,
'q': np.exp(-0.1 * np.arange(1, 30, 1)) * 100.0
})
df
[1]:
p | q | |
---|---|---|
0 | 2.99 | 90.483742 |
1 | 3.99 | 81.873075 |
2 | 4.99 | 74.081822 |
3 | 5.99 | 67.032005 |
4 | 6.99 | 60.653066 |
5 | 7.99 | 54.881164 |
6 | 8.99 | 49.658530 |
7 | 9.99 | 44.932896 |
8 | 10.99 | 40.656966 |
9 | 11.99 | 36.787944 |
10 | 12.99 | 33.287108 |
11 | 13.99 | 30.119421 |
12 | 14.99 | 27.253179 |
13 | 15.99 | 24.659696 |
14 | 16.99 | 22.313016 |
15 | 17.99 | 20.189652 |
16 | 18.99 | 18.268352 |
17 | 19.99 | 16.529889 |
18 | 20.99 | 14.956862 |
19 | 21.99 | 13.533528 |
20 | 22.99 | 12.245643 |
21 | 23.99 | 11.080316 |
22 | 24.99 | 10.025884 |
23 | 25.99 | 9.071795 |
24 | 26.99 | 8.208500 |
25 | 27.99 | 7.427358 |
26 | 28.99 | 6.720551 |
27 | 29.99 | 6.081006 |
28 | 30.99 | 5.502322 |
We can plot this data as demand curves. The demand curve on the left plots the raw values while the demand curve on the right plots the log of the values.
[2]:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(15, 3.5))
df \
.set_index(['p'])['q'] \
.plot(kind='line', ylabel='q', title='Price vs Quantity', ax=ax[0])
np.log(df) \
.set_index(['p'])['q'] \
.plot(kind='line', ylabel='q', title='Price vs Quantity (Log-Log)', ax=ax[1])
fig.tight_layout()
1.1. Elasticity
There are several ways to compute PED. One common way is defined as follows.
\(E_{\langle P \rangle} = \dfrac{\Delta Q}{\Delta P}\), where
\(\Delta Q\) is the percent change in quantity (eg the quantity of demand), and
\(\Delta P\) is the percent change in price.
Notice that \(\Delta Q\) and \(\Delta P\) are percent changes
, not absolute changes
; elasticity is not the slope of the demand curve! It’s more clear if we write out \(E_{\langle P \rangle}\) as follows.
\(E_{\langle P \rangle} = \dfrac{\dfrac{\Delta Q}{Q}}{\dfrac{\Delta P}{P}} = \dfrac{\Delta Q}{Q} \dfrac{P}{\Delta P} = \dfrac{\Delta Q}{\Delta P} \dfrac{P}{Q}\)
Let’s focus on \(\Delta Q\), which implies a difference between two quantites, \(\Delta Q = q_i - q_j\). Also, \(\Delta P\) implies the difference between two prices, \(\Delta P = p_i - p_j\). It should be obvious that \(q_i\) is the quantity/demand associated with the price \(p_i\), and likewise, \(q_j\) is the quantity/demand associated with the price \(p_j\).
Ok, so \(\Delta Q = q_i - q_j\), so what is \(Q\) then–is it \(q_i\) or \(q_j\)? Really, \(Q = q_i\), and likewise, \(P = p_i\) since we are moving from the point \((p_i, q_i)\) to the other point \((p_j, q_j)\). By now, you should know that \(E_{\langle P \rangle}\) is determined by two points, not a single point.
Ok, so if we are moving from a point \((p_i, q_i)\) to another point \((p_j, q_j)\) (meaning, we are changing the price from \(p_i\) to \(p_j\)), then we can write \(E_{\langle P \rangle}\) to be even more clear as follows.
\(E_{\langle P \rangle} = \dfrac{\dfrac{q_i - q_j}{q_i}}{\dfrac{p_i - p_j}{p_i}} = \dfrac{q_i - q_j}{q_i} \dfrac{p_i}{p_i - p_j} = \dfrac{q_i - q_j}{p_i - p_j} \dfrac{p_i}{q_i}\)
If instead we are going from \((p_j, q_j)\) to \((p_i, q_i)\), then \(E_D\) will be written out as follows.
\(E_{\langle P \rangle} = \dfrac{\dfrac{q_j - q_i}{q_j}}{\dfrac{p_j - p_i}{p_j}} = \dfrac{q_j - q_i}{q_j} \dfrac{p_j}{p_j - p_i} = \dfrac{q_j - q_i}{p_j - p_i} \dfrac{p_j}{q_j}\)
In general, we can go from any point to another (both points have to be on the demand curve, obviously) and compute \(E_{\langle P \rangle}\) with respect to the starting point. Note that \(E_{\langle P \rangle}^{ij}\) (going from point \(i\) to point \(j\)) is different from \(E_{\langle P \rangle}^{ji}\) (going from point \(j\) to point \(i\)). Additionally, even if the demand curve is linear, \(E_{\langle P \rangle}^{ij} \neq E_{\langle P \rangle}^{ji}\).
Generally speaking, no matter how we compute or define PED, its value is typically negative, \(E_D \leq 0\), but the interpretation of \(E_D\) is based on its absolute value.
\(E_D = 0\) means perfectly inelastic,
\(0 < E_D < 1\) means relatively inelastic,
\(E_D = 1\) means unit elastic,
\(E_D > 1\) means relatively elastic, and
\(E_D = \infty\) means perfectly elastic.
Let’s compute \(E_{\langle P \rangle}\) for all pairs of points on the demand curve above.
[3]:
import itertools
def compute_elasticity(p_i, p_j, q_i, q_j):
a = (q_i - q_j) / (p_i - p_j)
b = p_i / q_j
return a * b
p2q = {np.log(r['p']): np.log(r['q']) for _, r in df.iterrows()}
price_pairs = np.log(df['p'].values)
price_pairs = itertools.product(price_pairs, price_pairs)
price_pairs = filter(lambda tup: tup[0] != tup[1], price_pairs)
price_pairs = list(price_pairs)
ped_df = pd.DataFrame([(p_i, p_j, p2q[p_i], p2q[p_j], compute_elasticity(p_i, p_j, p2q[p_i], p2q[p_j]))
for p_i, p_j in price_pairs], columns=['p_i', 'p_j', 'q_i', 'q_j', 'ped'])
Let’s visualize \(E_D\) for all the pairs of points on the demand curve. Note that the size of the circle correlates with \(|E_D^{ij}|\) and the color of the cirlce is red if \(|E_D^{ij}| < 1\) and blue if \(|E_D^{ij}| > 1\).
[4]:
_ = ped_df.assign(s=lambda d: np.abs(d['ped']) * 20) \
.plot(
kind='scatter',
x='p_i', y='p_j', s='s',
color=np.abs(ped_df['ped']).apply(lambda v: 'r' if v < 1 else 'b'),
title=r'$E_{\langle P \rangle}$ for all pairs of point on demand curve, (log)'
)
/opt/anaconda3/lib/python3.9/site-packages/pandas/plotting/_matplotlib/core.py:1114: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored
scatter = ax.scatter(
To emphasize the point that \(E_{\langle P \rangle}^{ij} \neq E_{\langle P \rangle}^{ji}\), look at the following where we go from
(1.095273, 4.50517) to (1.383791, 4.40517), \(E_{\langle P \rangle}^{ij}=-0.086176\), and then in reverse from
(1.383791, 4.40517) to (1.095273, 4.50517), \(E_{\langle P \rangle}^{ji}=-0.106460\).
This asymmetry is an example of the index number problem.
[5]:
ped_df.iloc[[0, df.shape[0] - 1]]
[5]:
p_i | p_j | q_i | q_j | ped | |
---|---|---|---|---|---|
0 | 1.095273 | 1.383791 | 4.50517 | 4.40517 | -0.086176 |
28 | 1.383791 | 1.095273 | 4.40517 | 4.50517 | -0.106460 |
1.2. Arc elasticity
There is another way to compute \(E_D\) known as arc elasticity
and this approach is invariant to the ordering of the points on the demand curve. Arc elasticity is defined as follows.
\(E_d = \dfrac{p_i + p_j}{q_i + q_j} \times \dfrac{\Delta Q}{\Delta P}\), where
\(\Delta Q = q_i - q_j\), and
\(\Delta P = p_i - p_j\), as before.
[6]:
def compute_arc_elasticity(p_i, p_j, q_i, q_j):
a = (p_i + p_j) / (q_i + q_j)
b = (q_i - q_j) / (p_i - p_j)
return a * b
arc_df = pd.DataFrame([(p_i, p_j, p2q[p_i], p2q[p_j], compute_arc_elasticity(p_i, p_j, p2q[p_i], p2q[p_j]))
for p_i, p_j in price_pairs], columns=['p_i', 'p_j', 'q_i', 'q_j', 'ped'])
[7]:
_ = arc_df.assign(s=lambda d: np.abs(d['ped']) * 20) \
.plot(
kind='scatter',
x='p_i', y='p_j', s='s',
color=np.abs(ped_df['ped']).apply(lambda v: 'r' if v < 1 else 'b'),
title=r'$E_d$ for all pairs of point on demand curve, (log)'
)
/opt/anaconda3/lib/python3.9/site-packages/pandas/plotting/_matplotlib/core.py:1114: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored
scatter = ax.scatter(
You can see that arc elasticity is invariant to the order of the points. Additionally, the \(E_d\) is the average of the corresponding \(E_{\langle P \rangle}^{ij}\) and \(E_{\langle P \rangle}^{ji}\).
[8]:
ped_df.iloc[[0, df.shape[0] - 1]]['ped'].mean()
[8]:
-0.09631811146031723
[9]:
arc_df.iloc[[0, df.shape[0] - 1]]
[9]:
p_i | p_j | q_i | q_j | ped | |
---|---|---|---|---|---|
0 | 1.095273 | 1.383791 | 4.50517 | 4.40517 | -0.096432 |
28 | 1.383791 | 1.095273 | 4.40517 | 4.50517 | -0.096432 |
1.3. Point elasticity
Point elasticity does NOT require two points. It is defined at a single point using the price, quantity and slope of the tangent line to that point. Point elasticity is defined as follows.
\(E_p = \dfrac{\mathrm{d}q}{\mathrm{d}p} \times \dfrac{p_i}{q_i}\)
If the demand curve is linear, then we can use linear regression to estimate \(\dfrac{\mathrm{d}q}{\mathrm{d}p}\). The regression will have the following functional form
\(Q \sim \beta_0 + \beta_1 P + \epsilon\), where
\(\dfrac{\mathrm{d}q}{\mathrm{d}p} = \beta_1\). In the regression below, \(\beta_1 = -0.95402491\).
[10]:
from sklearn.linear_model import LinearRegression
Xy = np.log(df)
X = Xy[['p']]
y = Xy['q']
m = LinearRegression()
m.fit(X, y)
m.intercept_, m.coef_
[10]:
(6.49483055957322, array([-1.27045807]))
Now we can compute \(E_p\) for each point.
[11]:
pnt_df = np.log(df).assign(slope=m.coef_[0], ped=lambda d: d['slope'] * d['p'] / d['q'])
pnt_df
[11]:
p | q | slope | ped | |
---|---|---|---|---|
0 | 1.095273 | 4.50517 | -1.270458 | -0.308867 |
1 | 1.383791 | 4.40517 | -1.270458 | -0.399088 |
2 | 1.607436 | 4.30517 | -1.270458 | -0.474355 |
3 | 1.790091 | 4.20517 | -1.270458 | -0.540819 |
4 | 1.944481 | 4.10517 | -1.270458 | -0.601773 |
5 | 2.078191 | 4.00517 | -1.270458 | -0.659211 |
6 | 2.196113 | 3.90517 | -1.270458 | -0.714455 |
7 | 2.301585 | 3.80517 | -1.270458 | -0.768446 |
8 | 2.396986 | 3.70517 | -1.270458 | -0.821897 |
9 | 2.484073 | 3.60517 | -1.270458 | -0.875385 |
10 | 2.564180 | 3.50517 | -1.270458 | -0.929394 |
11 | 2.638343 | 3.40517 | -1.270458 | -0.984357 |
12 | 2.707383 | 3.30517 | -1.270458 | -1.040678 |
13 | 2.771964 | 3.20517 | -1.270458 | -1.098745 |
14 | 2.832625 | 3.10517 | -1.270458 | -1.158948 |
15 | 2.889816 | 3.00517 | -1.270458 | -1.221691 |
16 | 2.943913 | 2.90517 | -1.270458 | -1.287400 |
17 | 2.995232 | 2.80517 | -1.270458 | -1.356537 |
18 | 3.044046 | 2.70517 | -1.270458 | -1.429608 |
19 | 3.090588 | 2.60517 | -1.270458 | -1.507181 |
20 | 3.135059 | 2.50517 | -1.270458 | -1.589897 |
21 | 3.177637 | 2.40517 | -1.270458 | -1.678490 |
22 | 3.218476 | 2.30517 | -1.270458 | -1.773812 |
23 | 3.257712 | 2.20517 | -1.270458 | -1.876856 |
24 | 3.295466 | 2.10517 | -1.270458 | -1.988795 |
25 | 3.331847 | 2.00517 | -1.270458 | -2.111029 |
26 | 3.366951 | 1.90517 | -1.270458 | -2.245243 |
27 | 3.400864 | 1.80517 | -1.270458 | -2.393489 |
28 | 3.433665 | 1.70517 | -1.270458 | -2.558294 |
The optimal price is the one associated with the point elasticity equal to 1, \(E_d = 1\) (the inflection point at which elasticity goes from inelastic to elastic). The following shows the optimal price.
[12]:
s = pnt_df \
.assign(diff=lambda d: np.abs(d['ped'] + 1)) \
.sort_values(['diff']) \
.drop(columns=['diff']) \
.iloc[0]
p_opt = np.exp(s.p)
q_opt = np.exp(s.q)
e_opt = s.ped
pd.Series([
p_opt,
q_opt,
e_opt
], index=['price', 'quantity', 'ped'])
[12]:
price 13.990000
quantity 30.119421
ped -0.984357
dtype: float64
Here, we plot \(E_p\) versus \(p\).
[13]:
from matplotlib.patches import Ellipse
fig, ax = plt.subplots(figsize=(7, 3.5))
pnt_df \
.assign(p=lambda d: np.exp(d['p'])) \
.set_index(['p'])['ped'] \
.plot(kind='line', ylabel=r'$E_p$', title=rf'$E_p$ vs p', ax=ax)
ax.add_artist(Ellipse(
(p_opt, e_opt),
width=0.5,
height=0.1,
color='red',
fill=False,
clip_on=False
))
fig.tight_layout()
1.4. Marginal revenue
Marginal revenue is related to point elasticity as follows.
\(R' = P \left( 1 + \dfrac{1}{E_d} \right)\)
There are 2 things to note in the plot below.
Demand is elastic where marginal revenue is positive.
Demand is inelastic where marginal revenue is negative.
[37]:
pnt_df \
.assign(
p=lambda d: np.exp(d['p']),
q=lambda d: np.exp(d['q']),
ped=lambda d: d['slope'] * (d['p'] / d['q']),
mr=lambda d: d['p'] * (1 + (1 / d['ped']))
) \
.set_index(['p'])[['q', "mr"]].plot(kind='line')
[37]:
<Axes: xlabel='p'>