Chapter 5 Preference

\[U(x)=u \mbox{ for x} \in X\]

5.1 Tuple

Another types of commonly used vector container in Python is called tuple. Tuple acts like coordinates: \[(x_1, x_2, x_3)\in \mathcal{R}^3, \]

coord1 = (5, 3.3, 4)
print(coord1)

\[(age,\ gender,\ height,\ weight)\]

person1 = (15, "Female", 162, 55.3)
print(person1)

Tuple is immutable.

  • not much built-in methods
tp0 = ("1", 1, 5, "5", "1")
tp0.count("1") # how many "1"
tp0.index(5) # where is 5
  • no way to change its binding values only if you change it to list
list_tp0 = list(tp0)
list_tp0[3] = 4
print(list_tp0)

tp1 = tuple(list_tp0)
print(tp1)

For tuple’s immutability, it is ideal to use it to store data that you don’t want to accidentally mess up, such as:

  • one record from a person;

  • fixed model parameters: \(U(x,y)=x^{\alpha}y^{\beta}\).

preference_params = (4, 5)
def U(x,y):
    (alpha, beta) = preference_params
    return x**alpha*y**beta

U(2,3)
  • (alpha, beta) = preference_params is called unpacking. The left hand side (LHS) can ignore ( ).
alpha, beta = preference_params
print(alpha)
print(beta)
  • Both list and tuple can be unpacked.
tuple_params = (3, 4)
par1, par2 = tuple_params

list_params = [7, 8]
par3, par4 = list_params

print(par1)
print(par2)
print(par3)
print(par4)

5.2 Choice set revisited

\[P_{apple} * x_1 + P_{banana} * x_2 <= I, \] And

\[X=\{ (x_1, x_2)\ | \ P_{apple} * x_1 + P_{banana} * x_2 \leq I\}\] Suppose \(I=10000, P_{apple}=5, P_{banana}=7\). Suppose \(x_1\) and \(x_2\) must be integer.

In this case, it is ideal to model alternative as a tuple of two values.


Choice set as a validation function:

  • alternative must be a tuple.

  • alternative must lie inside the budget constraint.

def validate_c(c):
    assert (
      type(c) is tuple # alternative must be a tuple
      and len(c) == 2
      and all([ type(ci) is int or type(ci) is float for ci in c])
    ), "c must be a tuple of two numbers."
    x1, x2 = c
    
    if 5*x1+7*x2 <= 10000: # alternative must lie inside the budget constraint
        return c
    else:
        return None

# EOF

Since the choice set is finite, we can also choose to list all tuple alternatives as the choice set; all elements pass validation test.

import math # most math functions are in this module

I = 50
P_apple = 5
P_banana = 7

x1_max = math.floor(I / P_apple)

X = []
for x1 in range(x1_max+1):
    x2_max = math.floor((I-5*x1)/P_banana) # 無條件捨去
    list_x1 = [(x1, x2) for x2 in range(x2_max+1)]
    X.extend(list_x1)

# EOF

5.3 Tuple as key

def U_cobbDouglas(x, params=(1,1)):
    assert (
      type(x) is tuple
      and len(x) == 2
      and all([type(xi) is int for xi in range(len(x))])
    ), "Alternative should be a tuple of two integers"
    
    alpha, beta = params
    x1, x2 = x
    u = x1**alpha * x2**beta
    
    return u


# EOF
try: 
    U_cobbDouglas([2,3]) 
except: 
    "Error"

U_cobbDouglas((2,3))
U_cobbDouglas((3,4))

Basically utility function is a mapping from alternative to a real number. We can also use dictionary to define mapping:

alpha, beta = (1,1)
U_cobbDouglas_dict = {
  key: key[0]**alpha * key[1]**beta for key in X
}
  • tuple can act as dictionary key.
U_cobbDouglas_dict.keys()
U_cobbDouglas_dict[(4,2)]
U_cobbDouglas_dict[(3,4)]

5.4 Build a consumer

One consumer:

p_x1=5; p_x2=7; I=100

consumer <- new.env()

consumer$params <- c(1,1)

consumer$preference <- function(x, params=consumer$params){
  assertthat::assert_that(
    is.numeric(x)
    && length(x) == 2,
    msg="alternative must be a vector of 2 numeric values"
  )
  
  u = x[[1]]**params[[1]] * x[[2]]**params[[2]]
  
  return(u)
}

consumer$validate_c <- function(x){
  assertthat::assert_that(
    is.numeric(x)
    && length(x) == 2,
    msg="alternative must be a vector of 2 numeric values"
  )
  
  if(p_x1*x[[1]]+p_x2*x[[2]] <= I){
    return(x)
  } else {
    return(NA)
  }
}
consumer$params
consumer$preference(c(1,2))
consumer$validate_c(c(1,2))

5.5 Consumer generator

The following function generates consumer who:

  • Faces prices \(p_{x1}=5, p_{x2}=7\) and has income \(I=100\);

  • Have preference as a Cobb-Douglass function, \(x^{\alpha}y^{\beta}\) where \(\alpha, \beta\) can be heterogeneous when a consumer is generated.

p_x1=5; p_x2=7; I=100

Consumer = function(params){
  
  consumer <- new.env()

  consumer$params <- params # replace c(1,1)
  
  consumer$preference <- function(x, params=consumer$params){
    assertthat::assert_that(
      is.numeric(x)
      && length(x) == 2,
      msg="alternative must be a vector of 2 numeric values"
    )
    
    u = x[[1]]**params[[1]] * x[[2]]**params[[2]]
    
    return(u)
  }
  
  consumer$validate_c <- function(x){
    assertthat::assert_that(
      is.numeric(x)
      && length(x) == 2,
      msg="alternative must be a vector of 2 numeric values"
    )
    
    if(p_x1*x[[1]]+p_x2*x[[2]] <= I){
      return(x)
    } else {
      return(NA)
    }
  }
  
  return(consumer)
}
consumer11 <- Consumer(c(1,1)) # preference: x*y
consumer35 <- Consumer(c(3,5)) # preference: x**3 * y**5

consumer11$preference(c(2,5))
consumer35$preference(c(2,5))

5.6 Class

class Consumer:
    def __init__(self, params):
        self.params = params
        
    def preference(self, x):
        assert (
          type(x) is tuple
          and len(x) == 2
          and all([type(x[i]) is int for i in range(len(x))])
        ), "Alternative should be a tuple of two integers"
        
        alpha, beta = self.params
        x1, x2 = x
        u = x1**alpha * x2**beta
        
        return u
    
    @staticmethod
    
    def validate_c(c):
        assert (
          type(c) is tuple # alternative must be a tuple
          and len(c) == 2
          and all([ type(ci) is int or type(ci) is float for ci in c]) # all returns one True/False
        ), "c must be a tuple of two numbers."
        x1, x2 = c
        
        if 5*x1+7*x2 <= 100: # alternative must lie inside the budget constraint
            return c
        else:
            return None

# EOF

The basic three methods:

  • __init__(self, ...): To define instance attributes.
    self represents the instance. Inside the function body, any self._attribute_ = ... will bind ... value to self._attribute_ even no return self at the end.

  • method_name(self, ...): Dynamic instance method.
    Method that requires self content in order to work or is used to update self content.

  • Static instance method:
    Method that does not require or update self content.

@staticmethod
def method_name(...): # no self in input
    ...
consumer = Consumer(params=[1,1])
consumer.params
consumer.preference((2,3))
consumer.validate_c((2,3))

Construct a Consumer class which can generates consumer instance who:

  • Has Cobb-Douglas type of preference \(x^{\alpha}y^{\beta}\) but heterogeneous parameter values of \(\alpha, \beta\).

  • Has different income levels.


Class object is a generator. Any new object generated by it is an instance.

consumer11 = Consumer((1,1)) # preference: x*y
consumer35 = Consumer((3,5)) # preference: x**3 * y**5

All objects when created are an instance of some class.

int0 = 5
float0 = 5.5
char0 = "hello"

int0.__class__
float0.__class__
char0.__class__

From instance-class perspective, to check an object type:

type(int0) is int
type(float0) is float
type(char0) is str

is the same as to check its instance class:

isinstance(int0, int)
isinstance(float0, float)
isinstance(char0, str)

5.7 Instance and class variables

  • All consumers faces the same \(p_{x1}=5, p_{x2}=7\).

  • A consumer is different from the other at:

    • different income \(I\).

    • different preference parameters \(params\).

    in function he will

    • feel .preference() differently.
    • face different choice set .validate_c().
# same prices
prices = (5, 7)

# difference
I = 100
params = (1, 1)

def preference(params, x):
    x1, x2 = x
    alpha, beta = params
    u = x1**alpha * x2**beta
    return u

def validate_c(I, x):
    x1, x2 = x
    p_x1, p_x2 = prices
    if p_x1*x1 + p_x2*x2 <= I:
        return x
    else:
        return None

The key to build a class is:

  • heterogeneous parameters should be defined as self.xxx and accessed via self.xxx.
  • market parameters (homogeneous parameters) should be accessed via {class_name}.xxx.
  • when method requires heterogeneous parameters, it should be a dynamic method with self as its first input.

  • when method requires no heterogeneous parameters, it should be a static method without self input.

Heterogenous parameters are called instance variables (aka instance attributes). Homogeneous parameters are called class variables (aka class attributes).

class Consumer:
    # same prices
    prices = (5, 7)
    
    # difference
    def __init__(self, I, params):
        self.I = I
        self.params = params
    
    def preference(self, x):
        x1, x2 = x
        alpha, beta = self.params # heterogeneous parameters
        u = x1**alpha * x2**beta
        return u
    
    def validate_c(self, x):
        x1, x2 = x
        p_x1, p_x2 = Consumer.prices # homogeneous parameters
        if p_x1*x1 + p_x2*x2 <= self.I:
            return x
        else:
            return None

# EOF
consumer11_100 = Consumer(100, (1,1))
consumer35_250 = Consumer(250, (3,5))
print('homogeneous price: ' + str(consumer11_100.prices))
consumer11_100.I
consumer11_100.params

print('homogeneous price: ' + str(consumer35_250.prices))
consumer35_250.I
consumer35_250.params
Consumer.prices
consumer11_100.prices
consumer35_250.prices
  • Market prices change to \((3,3)\)
    prices are faced by all consumer instances, called class variables. I and params are different across consumer instances, called instance variables.
Consumer.prices = (3,3)
consumer11_100.prices
consumer35_250.prices
  • Changing class variable values through Class changes all instances’ facing such values.
# change an instance's class variable
consumer11_100.prices = (5, 5)
consumer11_100.prices
# WILL NOT change the other instance's class variable
consumer35_250.prices
  • Change class variable value through Instance only creates a counterpart instance variable. To see the instance’s class varible of the same name:
consumer11_100.prices # the instance variable **prices**
consumer11_100.__class__.prices # the class variable **prices**
consumer35_250.prices
  • will prompt Python to look up prices as an instance variable within consumer35_250 instance.

  • However, there is no such an instance variable.

  • The scoping rule of Python will continue its search in class variables.

Instance ( ) and class () variables

5.8 綜合練習

1. Aggregate demand

Suppose in an economy individual demands are like: \[q(p)=\alpha - \beta*p,\] where \(p>0\), and \(\alpha\) and \(\beta\) are numbers that guarantee \(q \geq 0\) and the demand rule holds (i.e. \(\beta > 0\)).

1.1

Construct an individual demand function that has \(\alpha=10\) and \(\beta=2\).

1.2

Construct a Demand class generator that

params = (10, 2)
demand = Demand(params) # generate a demand instance

where

demand.q(2) 

returns the quantity demanded of an individual demand function \(10-2p\) at \(p=2\).


Generate random numbers

In R, there are four functions associated with random variables:

  • d{distribution}: the density function of {distribution} (the proportion of population that has height = 172cm)
dunif(0.3, 0, 0.5) # density at 0.3 for uniform(0, 0.5)
  • p{distribution}: the (cummulative) distribution function of {distribution} (the proportion of population that has height less or equal to 172 cm)
punif(0.3, 0, 0.5) # cdf at 0.3 for uniform(0, 0.5)
  • q{distribution}: the quantile of {distribution} (if the quantile of 70% is 172cm meaning that 70% of population has height less or equal to 172cm )
qunif(0.5, 0, 0.5) # quantile of 0.5 (ie. median) of uniform(0, 0.5)
  • r{distribution}: random draws from {distribution}
# no seed
rns_noSeed = runif(n=5, 0, 0.5)
print(rns_noSeed)
# with seed
set.seed(2020)
rns_seed = runif(n=5, 0, 0.5)
print(rns_seed)

numpy.random is a popular module to deal with distributions. It encapsulates all distribution tools as methods inside an instance of various generators. Among them, to generate random variables, default_rng is commonly used.

from numpy.random import default_rng
rng_noSeed = default_rng() # no seed
rns_noSeed = rng_noSeed.uniform(50, 100, size=5)
print(rns_noSeed)
rng_seed = default_rng(2038) # with seed 2038
rns_seed = rng_seed.uniform(50, 100, size=5)
print(rns_seed)

1.3

Generate 500 individual demands with \(\alpha\)’s drawn from uniform(50,100), and \(\beta\)’s drawn from uniform(2, 30). Save them in a list, ind_demands.

1.4

Construct an aggregate demand function:

aggregate_demand(2, ind_demands)

returns an aggregated quantity demanded at the price of 2.

2. Intertemporal Consumption

A consumer lives for two periods, choosing consumption today and consumption tomorrow. Assume no inflation (or for simplicity price level is fixed at one), with interest rate 5% decreasing $1 of consumption today will increase saving $1 today and yield $1+5% additional of consumption tomorrow. Given that the consumer has a present discounted value of life wealth $100 (ie the maximal \(c_0\) he can get if he spends all his life wealth today), the following consumption alternatives of \((c_0, c_1)\) are feasible: \[[(100,0), (99, 1.05), (98, 2.10), ..., (0, 105)]\] The alternatives above all satisfy: \[c_0+\frac{1}{1.05}c_1 = 100\] Any spending on the LHS less than $100 is also feasible. In other words, the consumer faces the following intertemporal budget constraint: \[c_0 + \frac{1}{1.05}c_1 \leq 100\]

2.1

If \(c_0\) has to be integer, but \(c_1\) can be float, construct a list of tuples that represents \([(100,0), (99, 1.05), (98, 2.10), ..., (0, 105)]\).

2.2

Construct a validation function of consumption that when input a tuple of two numbers, it validates if the alternative is feasible – if feasible return the original tuple; otherwise, return None.

2.3

If both \(c_0\) and \(c_1\) have to be integer, construct the consumer’s choice set (as a list containing all tuples that satisfies the intertemporal budget constraint)

2.4

Suppose the consumer has the following preference function: \[U(c_0, c_1)=\log(c_0)+\beta*\log(c_1),\] where \(\log\) is a natural based log function that can be assessed in Python as:

import math
math.log()

Construct the consumer’s preference function.

2.5

Construct Consumer class object that can generate any consumer with user-chosen \(\beta\) and facing the same intertemporal budget constraint as a validation function as stated at the beginning of the question.

3. class Square

Construct a Square class generator such that:

square = Square(width=2, height=10)
square.width # shows 2
square.height # shows 10
square.area() # shows 20 

4. Utility generator

There are two types of utility functions we want to build:

  1. Constant Elasticity of Substitution (CES):
    \[U(x)=[\alpha_1 x_1^{\rho} +\alpha_2 x_2^{\rho}]^{\frac{1}{\rho}}\]

  2. Quasi-linear (QL): \[U(x)=g(x_1) + b*x2\]

All symbols other than \(x_1, x_2\) are preference parameters.

4.1

Build a CES class such that:

alpha1, alpha2, rho = (2, 3, 5)
params = (alpha1, alpha2, rho)
ces = CES(params)
x = (1, 10)
ces.U(x) # show the utility levels of x given alpha1, alpha2, rho preference parameter values

4.2

Build a QL class such that:

When

import math
def q(x1):
    return math.log(x1)

the following code generates a QL utility instance representing: \[U(x)=\log(x_1)+x_2\]

b = 2
ql = QL(q, b)
x = (2, 5)
ql.U(x) # returns the utility levels of x given q(x1) and b

zip

from numpy.random import default_rng
rng1 = default_rng(293)
rng2 = default_rng(283)
rng_zip = zip(rng1.uniform(50,100, size=3), rng2.uniform(2,10, size=3))
list(rng_zip)
p=5
qs = [ alpha - beta*p for alpha, beta in rng_zip]
  • rng_zip is an iterable.

  • An iterable when loops finish will be empty; nothing to iterate afterward.

list(rng_zip)
  • Normally we won’t set aside the zipped object, but zip columns in loop under the hood as:
rng1 = default_rng(293)
rng2 = default_rng(283)
qs = [ alpha - beta*p for alpha, beta in zip(rng1.uniform(50, 100, size=3), rng2.uniform(2, 10, size=3))]
qs

zip() zips columns of vectors into 1 column of tuples.

X = ["a", "b", "c"]
Y = [2, 3, 5]
zip_xy = zip(X, Y)
list(zip_xy)
  • zip_xy is iterable.
iter_xy = iter(zip_xy)
next(iter_xy)
X = ["a", "b", "c"]
Y = [2, 3, 5]
dict_xy = { key: value for (key, value) in zip(X, Y)}
print(dict_xy)