WARNING: unbalanced footnote start tag short code found.
If this warning is irrelevant, please disable the syntax validation feature in the dashboard under General settings > Footnote start and end short codes > Check for balanced shortcodes.
Unbalanced start tag short code found before:
“-(-beta/2) - np.sqrt”
We hope you learned something from Part 1 of our post on stylised charting with Python/Matplotlib.
You now know how to do straight line charts using point to point plotting. This post teaches you to do some curves, and some more straight lines, with equations.
As with anything Python, there’s probably a thousand ways to do this, but we’ll illustrate just two. These are:
- creating data in a DataFrame and then plotting; and
- the direct method, where Matplotlib creates lists from the equations, which it then plots.
DataFrame Method
Let’s create our DataFrame and plot the ubiquitous representative firm’s cost curves as shown below. Don’t worry too much about the colours as we’ve selected them to make each line stand out.
The foundation of this chart is the Marginal Cost (MC) curve, represented by the equation:
MC = 47.15385-12.2967*Q+1.230769*Q**2
(the double * represents ‘to the power of’ in Python)
With fixed costs of $100.
The key to this equation is that, per our textbook charts, it initially slopes downwards as the negative linear relationship with quantity dominates the equation, but after a certain point the quantity squared relationship causes the MC curve to increase.
The Average Variable Cost (AVC) and Average Total Cost (ATC) equations are derived directly from the MC equation.
The code for this chart is below.
The ‘normal profits’ and ‘shutdown point’ points are calculated by equating the MC equation with AVC and ATC respectively.
These are directly linked to the annotated arrows attached to the labels, which means the arrows will shift if the equations are changed. You might have to change the text position manually though.
chartType='Textbook_Costs'
if chartType == 'Textbook_Costs':
#derived from marginal cost equation MC = 47.15385-12.2967*Q+1.230769**Q**2
#derived from following data
#50 30 10 5 10 12 25 35 45 55 65 75 85 95 105
#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#parameters for cost curves
theta=100
alpha=47.15385
beta=-12.2967
gamma=1.230769
#functions to create DataFrame of Costs
def MC(x):
return alpha+beta*x+gamma*x**2
def AVC(x):
return (alpha*x+beta*x**2/2+(gamma*x**3)/3)/x
#assumes new constant is zero
def ATC(x):
return theta/x+(alpha*x+beta*x**2/2+(gamma*x**3)/3)/x
#assumes new constant is zero
#solve for quantities of shutdown point and zero pure profits point
root_AVC=((-(-beta/2) - np.sqrt((beta/2)**2 - 4*0))/(2*(gamma/3-gamma)))#
#this is the point where MC=AVC, could also have done derivative of AVC set to zero, i.e. -(beta/2)/(2*gamma/3)
root_ATC=np.roots([-gamma*(2/3), -beta/2,0, theta]).real.max()
#this is the point where MC=ATC, but needed to use np.roots to solve cubic
#Create our DataFrame
df_MC=pd.DataFrame(np.linspace(1, 15, 14*100+1))
df_MC.columns=['Q']
df_MC['MC']=MC(df_MC.Q)
#i.e df_MC['MC']=alpha+beta*df_MC['Q']+gamma*df_MC['Q']**2
df_MC['AVC']=AVC(df_MC.Q)
#i.e. df_MC['AVC']=(alpha*df_MC['Q']+beta*df_MC['Q']**2/2+(gamma*df_MC['Q']**3)/3)/df_MC['Q']
df_MC['ATC']=ATC(df_MC.Q)
#i.e. df_MC['ATC']=theta/df_MC['Q']+(alpha*df_MC['Q']+beta*df_MC['Q']**2/2+(gamma*df_MC['Q']**3)/3)/df_MC['Q']
#let's plot
plt.plot(df_MC['Q'],df_MC['MC'],label='Marginal Cost')
plt.plot(df_MC['Q'],df_MC['AVC'],label='Average Variable Cost')
plt.plot(df_MC['Q'],df_MC['ATC'],label='Average Total Cost')
plt.annotate(s='Normal\nProfits',xy=(root_ATC,MC(root_ATC)),xytext=(6,80),arrowprops=dict(arrowstyle='->'))
plt.annotate(s='Shutdown\nPoint',xy=(root_AVC,MC(root_AVC)),xytext=(4,60),arrowprops=dict(arrowstyle='->'))
plt.legend()
plt.xlim(0,15)
plt.ylim(0,150)
plt.xticks([])
plt.yticks([])
plt.ylabel('P($)', fontsize=14)
plt.xlabel('Q', fontsize=14)
else:
pass
Plotting is fairly straightforward once you have the DataFrame.
We remove the axis ticks and numbers here with the plt.xticks([]) and plt.yticks([]) commands. This truly converts your chart into stylised form.
Finally, you will note that the code for this chart is nested in an if/else loop. This is a little trick we’ve learned to organise our charts.
We often found ourselves with multiple charts in the same Python file, commenting out code we didn’t want depending on which chart we wanted to produce at the time. It was a nightmare if ever you wanted to reproduce a chart later!
A solution is to create a Python (string) variable, called chartType in the above code, and assign it a value (e.g. chartType=’Textbook_Costs’) which calls the chart you want when you run your code.
If you have two or more charts in the same file, you can change the value of chartType to produce whatever chart you want.
Easy, and all of your code stays intact.
Direct Method
Now that we can use DataFrames to indirectly plot our equations, we can now get straight to the point.
More correctly we will use lists to plot our chart rather than DataFrames.
The code is a bit cleaner than creating DataFrames, but the disadvantage is that you can’t view your data as easily.
Our stylised chart here is a simple Linear Programming (LP) problem for two variables – x1 and x2.
The objective function of the LP problem is to maximise x1 + 3*x2 subject to the three constraints shown on the chart.
Let’s look at the code. As a bonus we’ve created a little concrete Pyomo model within the code, which you should be able to solve if you’ve installed Anaconda/Pyomo/GLPK as in our post here.
We won’t go into the Pyomo LP model in any depth here, other than to note that the constraints for the model map to the equations in the chart.
For example, the constraint 3*model.x1 + 4*model.x2 <= 6, maps to the chart equation: x21=(6/4) – (3/4)*x1, where the constraint is rearranged as a function of x2.
The chart line is an equality, but the constraint inequality can be interpreted as above or below the line. At or below the line (<=) contains feasible solutions, above the line is not feasible.
To show this in the chart we create the list x5, which is the minimum x2 value of our constraints for every given x1. We use matplotlib’s fill_between function to illustrate the feasible solution area in blue.
from pyomo.environ import *
import numpy as np
import matplotlib.pyplot as plt
#Do the simple little LP model
model=ConcreteModel()
model.x1=Var(within=NonNegativeReals)
model.x2=Var(within=NonNegativeReals)
model.obj = Objective(expr=model.x1 + 3*model.x2, sense=maximize)
model.con1=Constraint(expr= 3*model.x1 + 4*model.x2 <= 6)
model.con2=Constraint(expr= 2*model.x1 + 7*model.x2 <= 10)
model.con3=Constraint(expr= 1*model.x1 + 6*model.x2 <= 8)
opt = SolverFactory("glpk")
opt.solve(model)
results=opt.solve(model)
print('Optimal value for x1 is '+str(value(model.x1)))
print('Optimal value for x2 is '+str(value(model.x2)))
#Now plot as stylised example
#Set up the data
x1 = np.arange(0,10,0.1)
x21=(6/4)-(3/4)*x1
x22=(10/7)-(2/7)*x1
x23=(8/6)-(1/6)*x1
x4 = np.minimum(x21, x22)
x5=np.minimum(x4,x23)
#Now plot
plt.axis('scaled')
plt.ylim(0, 3)
plt.xlim(0, 6)
plt.aspect=3
plt.plot(x1,x21,x1,x22,x1,x23)
plt.fill_between(x1, x5, color='blue', alpha='0.5')
plt.annotate(s='Feasible\n Area',xy=(0.7,0.3),xytext=(2,2),arrowprops=dict(arrowstyle='->'))
plt.annotate(s='Optimal\n Solution',xy=(value(model.x1),value(model.x2)),xytext=(1,2.5),arrowprops=dict(arrowstyle='->'))
plt.text(1.75,1.2,'2*x1+7*x2<=10',rotation=0, color='g')
plt.text(1.9,0.1,'3*x1+4*x2<=6',rotation=0, color='b')
plt.text(1.5,0.4,'1*x1+6*x2<=8',rotation=0, color='tab:orange')
plt.title("Simple Linear Programming Example", size=16)
plt.ylabel('x2', fontsize=14)
plt.xlabel('x1', fontsize=14)
We do not need to import pandas to run this code. The equations generate lists, for example x21 is the following list (the axis range restriction means that we don’t see all of this list on the chart):
In [2]: x21
Out[2]:
array([ 1.5 , 1.425, 1.35 , 1.275, 1.2 , 1.125, 1.05 , 0.975,
0.9 , 0.825, 0.75 , 0.675, 0.6 , 0.525, 0.45 , 0.375,
0.3 , 0.225, 0.15 , 0.075, 0. , -0.075, -0.15 , -0.225,
-0.3 , -0.375, -0.45 , -0.525, -0.6 , -0.675, -0.75 , -0.825,
-0.9 , -0.975, -1.05 , -1.125, -1.2 , -1.275, -1.35 , -1.425,
-1.5 , -1.575, -1.65 , -1.725, -1.8 , -1.875, -1.95 , -2.025,
-2.1 , -2.175, -2.25 , -2.325, -2.4 , -2.475, -2.55 , -2.625,
-2.7 , -2.775, -2.85 , -2.925, -3. , -3.075, -3.15 , -3.225,
-3.3 , -3.375, -3.45 , -3.525, -3.6 , -3.675, -3.75 , -3.825,
-3.9 , -3.975, -4.05 , -4.125, -4.2 , -4.275, -4.35 , -4.425,
-4.5 , -4.575, -4.65 , -4.725, -4.8 , -4.875, -4.95 , -5.025,
-5.1 , -5.175, -5.25 , -5.325, -5.4 , -5.475, -5.55 , -5.625,
-5.7 , -5.775, -5.85 , -5.925])
In this case we leave the axis ticks in place here so viewers of the chart can link the lines back to the model constraints.
We also put a title into our chart, although our general preference is to put the title outside of the chart (e.g. in word).
We link the arrow pointing to the model solution by using the solution values generated each time by the model.
plt.annotate(s='Optimal\n Solution',xy=(value(model.x1),value(model.x2)),xytext=(1,2.5),arrowprops=dict(arrowstyle='->'))
By doing this, if we change our objective function from maximise x1+3*x2 to maximise x1 + x2, then the solution becomes 2.0 for x1 and 0.0 for x2, and our chart automatically changes as below.
Conclusion
So that’s stylised/stylized charts.
We hope you’ve learned a thing or two. There’s always more packed into charts (e.g. colors etc.) than we can discuss here, but finding information on these is easy once you know what you’re looking for.
You can use many of the charting techniques in our two posts to chart real data.
If you have any questions then please contact us. As we always say, this isn’t our full-time job and we generate Practical Economics content when we can, so we might not reply immediately, but we will get back to you.