In this lecture, we will study Object-Oriented Programming (OOP) and discover that there is a tight link between this programming paradigm and economics. To keep economics front and center, we will explore the usefulness of the OOP by implementing a seminal paper by Gary Becker.
Gary S. Becker (1962). Irrational Behavior and Economic Theory, Journal of Political Economy, 70(1): 1-13.
Roadmap
What are the potential advantages of an OOP implementation?
Understanding OOP allows you to work with two major Python packages:
However, as we know from our implementation of the generalized Roy model the execution speed often suffers.
In [2]:
Image(filename='images/profiling.png')
Out[2]:
Keywords:
You will see all these elements in our implementation of Becker (1962). Let us get started with a basic example. We will develop an object-oriented representation of an econmic agents, who can spend her endowment on either milk or butter. We will keep the decision rule simple and she will just allocate half her endowment to each of the two goods.
In [17]:
class Agent:
def __init__(self, endowment):
""" Initialize agents with endowment as class attributes.
"""
# Endowment
self.endowment = endowment
# Demands
self.butter = None
self.milk = None
def choose(self, price_butter, price_milk):
""" Allocate half of endowment to each
of the two goods.
"""
self.butter = (0.5 * self.endowment)/price_butter
self.milk = (0.5 * self.endowment)/price_milk
def get_demands(self):
""" Return demands.
"""
# Antibugging
assert (self.butter is not None)
# Finish
return self.butter, self.milk
# Special methods start with a double underscore
# and allow a special syntax in the program. Here
# are two examples (in addition to __init__). Of
# course, many more exist.
def __str__(self):
""" String representation of class instance
"""
# Distribute class attributes
endowment = self.endowment
return '\n I am an agent with an endwoment of ' + \
str(endowment) + '.\n\n'
def __eq__(self, other):
""" Check for equality.
"""
# Antibugging
assert isinstance(other, Agent)
# Distribute class attributes
self_end = self.endowment
other_end = other.endowment
# Check equality
return self_end == other_end
def __gt__(self, other):
""" Check for greater than.
"""
# Antibugging
assert isinstance(other, Agent)
# Distribute class attributes
self_end = self.endowment
other_end = other.endowment
# Check equality
return self_end > other_end
Of course, many more special methods exist. A full list is available here.
Let us initialize and instant of the class.
In [18]:
# Initialize and agent with an endowment
ENDOWMENT, PRICE_BUTTER, PRICE_MILK = 10, 2, 3
agent_one = Agent(ENDOWMENT)
agent_one.choose(PRICE_BUTTER, PRICE_MILK)
print ' Let us have a look at the demand for the two goods: '
print '... using the demand() method ', agent_one.get_demands()
print '... accessing the class attributes ', (agent_one.butter, agent_one.milk)
How about exploring the special methods?
In [11]:
# Initialize a second agent with a higher endowment
ENDOWMENT = 10.5
agent_two = Agent(ENDOWMENT)
# Check out the string representation
print agent_two
# Comparisons
print ' Do both agents have the same endowment? ', agent_two == agent_one
print ' Is agent_two richer? ', agent_two > agent_one
The purpose of the paper is
... to show how important theorems of modern economics result from general principle which not only includes rational behavior and survival arguments as special cases, but also much more irrational behavior.
Becker's main conclusion follows:
... economic theory is much more compatible with irrational behavior than had previoulsy been suspected.
In particular, Becker studies the robustness of the fundamental theorem of household demand:
... that the demand curve for any commodity, real income held constant, must be negatively inclined.
Figure 1 from the paper allows us to briefly discuss the traditional theory.
Let us use Figure 2 from the paper to discuss a more general approach.
Overall Becker concludes:
The fundamental theorem of traditional theory - that demand curves are negatively declined - largely results from the change in opportunities alone and is largely independent of the decicion rule.
In an nutshell, even an irrational agent has to live within his means.
In [12]:
# Fundamental Numerical Methods
import numpy as np
# System-specific parameters and functions
import sys
# Plotting
import matplotlib.pyplot as plt
%pylab inline --no-import-all
# Adding the modules subdirectory
sys.path.insert(0, 'modules')
# Project library
from clsAgent import * # only imports derived classes
from clsEconomy import *
from clsAgent import _AgentCls # superclass
Let us set some basic parameters for our illustration:
In [13]:
NUM_AGENTS = 1000 # Number of agents in the population
ENDOWMENT = 10.0 # Endowments of agents
ALPHA = 0.75 # Utility weights
P1 = 1.0 # Price of first good (Numeraire)
NUM_POINTS = 25 # Number of points for grid of price changes
# Construct grid for price changes.
PRICE_GRID = np.linspace(P1, 10, num=NUM_POINTS)
Now we simulate two agent populations of the different types: (1) Random, and (2) Rational.
The agent choose a random bundle on the budget line.
The agent maximizes a Cobb-Douglas utility function subject to the budget constraint.
\begin{align} U(x_1, x_2) = x_1^\alpha x_2^{1 - \alpha} \end{align}Before we dive into the details of the source code here, let us visualize our code base as a Unified Modeling Language (UML) diagram. See here for details on creating UML diagrams in PyCharm.
Agents
<img src="images/clsAgent.png", width=850>
The design of the agent classes also provides an example of a Polymorphism. The agents always choose(), but they choose() differently. The choose() behavior is polymorphic in the sense that it is realized differently depending on the agent's type.
In [14]:
# Initialize base agent
agent_obj = _AgentCls()
agent_obj.set_preference_parameter(ALPHA)
agent_obj.set_endowment(ENDOWMENT)
# Let them try to choose
try:
agent_obj.choose(P1, 1.0)
except NotImplementedError:
print 'The raw agents cannot choose().'
Let us turn to the economy class that aggregates the individual demands to the market demand.
Economy
We now explore alternative agent populations.
In [15]:
# Simulate agent populations of different types
agent_objs = dict()
for type_ in ['random', 'rational']:
agent_objs[type_] = []
for _ in range(NUM_AGENTS):
# Specify agent
if type_ == 'rational':
agent_obj = RationalAgent()
elif type_ == 'random':
agent_obj = RandomAgent()
else:
raise AssertionError
agent_obj.set_preference_parameter(ALPHA)
agent_obj.set_endowment(ENDOWMENT)
# Collect a list fo agents, i.e. the population
agent_objs[type_] += [agent_obj]
Let us get the market demands for different price schedules.
In [16]:
# Get market demands for varying price schedules
market_demands = dict()
for type_ in ['random', 'rational']:
market_demands[type_] = {'demand': [], 'sd': []}
# Initialize economy with agent of particular types
economy_obj = EconomyCls(agent_objs[type_])
# Vary price schedule for the second good.
for p2 in PRICE_GRID:
# Get market demand information
rslt = economy_obj.get_aggregate_demand(P1, p2)
# Construct average demand for second good
demand = rslt['demand'][1]/float(NUM_AGENTS)
# Construct standard deviation for second good
demand_sd = rslt['sd'][1]
# Collect demands and standard deviations
market_demands[type_]['demand'] += [demand]
market_demands[type_]['sd'] += [demand_sd]
Let's compare the demands for an individual agent. Of course, in the rational case all agents have exactly the same demand.
In [11]:
# Draw a random agent from the population
idx = np.random.random_integers(0, NUM_AGENTS - 1)
# Select agent for further study
agent_ration = agent_objs['rational'][idx]
agent_random = agent_objs['random'][idx]
# Obtain individual demands
individual_demands = {}
for type_ in ['rational', 'random']:
# Initialize container for results
individual_demands[type_] = []
# Select the relevant agent type
if type_ == 'rational':
agent_obj = agent_ration
elif type_ == 'random':
agent_obj = agent_random
# Obtain individual demands as we vary the price
# of the second good.
for p2 in PRICE_GRID:
agent_obj.choose(P1, p2)
individual_demands[type_] += [agent_obj.get_individual_demand()[1]]
Let's visiualize the different individual demands as we increase the price of good 2.
In [27]:
# Initialize canvas
ax = plt.figure(figsize=(12,8)).add_subplot(111, axisbg='white')
# Plot individual demands by types
ax.plot(individual_demands['rational'], PRICE_GRID, label='Rational')
ax.plot(individual_demands['random'], PRICE_GRID, label='Random')
# Set axis labels and ranges
ax.set_xlabel(r'$\bar{x}_2$', fontsize=20)
ax.set_ylabel(r'$p_2$', fontsize=20)
ax.set_xlim([0, 10]), ax.set_ylim([1, 10])
# Set up legend
plt.legend(loc='upper center', bbox_to_anchor=(0.50, -0.10),
fancybox=False, frameon=False, shadow=False, ncol=3, fontsize=20)
# Remove first element on y-axis
ax.yaxis.get_major_ticks()[0].set_visible(False)
# Add title
plt.suptitle('Demand for Good 2 by Agent ' + str(idx), fontsize=20)
# Show plot
plt.show()
How about the demands at the aggregate level? Of course, there is heterogeneity around the average demand in the case of random agents.
In [24]:
# Initialize canvas
fig, ax = plt.subplots(1, 2, figsize=(12, 6.5), sharex=True)
# Plot market average demands for the rational type
ax[0].plot(market_demands['rational']['demand'], PRICE_GRID, label='Rational', color='b')
# Plot market average demands and standard deviations for the random type
ax[1].plot(market_demands['random']['demand'], PRICE_GRID, label='Random', color='g')
xerr = np.array(market_demands['random']['sd'])
ax[1].errorbar(market_demands['random']['demand'], PRICE_GRID, xerr=xerr, color='g')
# Set titles
ax[0].set_title('Demand for Good 2 in Population: \n Rational', fontsize=20, y=1.04)
ax[1].set_title('Demand for Good 2 in Population: \n Random', fontsize=20, y=1.04)
# Set axis labels
ax[0].set_xlabel(r'$\bar{x}_2$', fontsize=20)
ax[1].set_xlabel(r'$\bar{x}_2$', fontsize=20)
ax[0].set_ylabel(r'$p_2$', fontsize=20)
ax[1].set_ylabel(r'$p_2$', fontsize=20)
# Set axis ranges
ax[0].set_xlim([0, 8]), ax[0].set_ylim([1, 10])
ax[1].set_xlim([0, 8]), ax[1].set_ylim([1, 10])
# Change background color
ax[0].set_axis_bgcolor('white'), ax[1].set_axis_bgcolor('white')
# Set up legend
ax[0].legend(loc='upper center', bbox_to_anchor=(0.50, -0.15),
fancybox=False, frameon=False, shadow=False, ncol=1, fontsize=20)
ax[1].legend(loc='upper center', bbox_to_anchor=(0.50, -0.15),
fancybox=False, frameon=False, shadow=False, ncol=1, fontsize=20)
# Remove first element on y-axis
ax[0].yaxis.get_major_ticks()[0].set_visible(False)
ax[1].yaxis.get_major_ticks()[0].set_visible(False)
# Adjust spacing between subplots
plt.tight_layout(pad=0.2, w_pad=3)
# Show plots
plt.show()
For completeness, let us return to the object-oriented implementation of the generalized Roy model.
The full source code is availale here.
Prediction in Economics
Milton Friedman (1966). Essays In Positive Economics. Chicago University Press, Chicago, IL.
Friedrich August von Hayek (1975). The Pretense of Knowledge. The Swedish Journal of Economics. 77(4): 433-442.
Object-Oriented Programming
Formatting
In [1]:
import urllib; from IPython.core.display import HTML
HTML(urllib.urlopen('http://bit.ly/1K5apRH').read())
Out[1]: