Chapter 4 Choice set
library(reticulate)
use_condaenv("r-reticulate")
library(econR) # 經濟模型程式設計專題用

Decision Maker Problem

We need a storage object that can
represent the choice set X, with
its each element x a possible alternative for DM
In the following exercise, we want to construct object X with each element representing one x.
4.1 pRython major features
There are some major differences to notice for your first time amphibian experience:
Only
=
is used for object-value binding; Python doesn’t take<-
.Only
[.]
is used for element VALUE extraction and replacement in Python, which is equivalent to R’s[[.]]
.Python element index starts from 0 instead of 1 in R
Object naming rule is the same as R, EXCEPT NO use of
.
= c("Hello", "World.")
obj_R 1]
obj_R[
1] = "Hi"
obj_R[print(obj_R)
= ["Hello", "World."]
obj_py 0]
obj_py[
0] = "Hi"
obj_py[print(obj_py)
= list(
obj_R2 first_word="Hello",
second_word="World"
)"first_word"]] obj_R2[[
= {
obj_py2 "first_word": "Hello",
"second_word": "World"
}
R’s
[[.]]
extraction is equivalent to[.]
in Python.R’s
[.]
in list (which is the same as dictionary in Python) is equivalent to conduct (loop) comprehension (a mini-loop operation inside vector setup such as list/dictionary) operation in Python.
\[X=\{\mbox{1 apple}, \mbox{1 banana}\}\]
=
choiceSet_basic0 c(apple=1, banana=1)
# one alternative
1]
choiceSet_basic0[
# the other alternative
2] choiceSet_basic0[
=
choiceSet_basic1 list(apple=1, banana=1)
# one alternative
1]
choiceSet_basic1[# the other alternative
2] choiceSet_basic1[
4.2 Dictionary
dict({ })
- The ONLY type of storage that allows users to give element name, called key.
= {"apple": 1, "banana": 1} choiceSet_basic0
dictionary element CANNOT be checked out by its location:
- elements are not indexed even we input them one-by-one.
# one alternative
0]
choiceSet_basic0[
# the other alternative
1] choiceSet_basic0[
"apple"]
choiceSet_basic0["banana"] choiceSet_basic0[
Python has only
[.]
extraction operator, but no[[.]]
or$
.What
.
can be depends on the type of the object.A Dictionary’s element value can be checked out only via its element key but not on its element position (you check the meaning of a word in a dictionary via the spelling of the word, not the sequence of the word entry.)
However, dictionary key can be numeric.
= {
choiceSet_basic0_numKey 1: {"apple": 1},
2: {"banana": 1}
}
Notice we also make index counting starting from 1 instead of 0 – another way to make it behave like R. (not useful though.)
dictionary can nest inside a dictionary
In fact, all vector types in Python:
allow element values of different types
allow nested structure,
which behaves like R: list
.

4.3 List
list([ ])
list([])
list elements are indexed, but never named:
= ["apple"=1, "banana"=1] choiceSet_basic2_error
= ["apple": 1, "banana": 1] choiceSet_basic2_error
=
choiceSet_basic2 "apple": 1}, {"banana": 1}] [{
list to give the ability of position indexation.
dictionary to give the ability of assigning element key.

4.4 Mutable v.s. Immutable
= c(3, 53)
X = X
Y ::address(X)
pryr::address(Y) pryr
value c(3, 53) is stored at some memory address. Object X holds the memory address like holding the key to the value storage place. The relationship of an object holding the memory address to certain data storage place is called binding.
Both X and Y hold the same address reference.
If we change X[[1]] value, will Y[[1]] change?
1]] = 44
X[[::address(X) pryr
- No.
Y[[1]]
will not be changed since X holds a different address reference now.
Almost all R objects are immutable; unable to change its element value in place.
In R, changing part of the object element value, in almost all cases, changes the address reference of the object – a whole new storage place is located and a new binding is formed. There is no change-in-place happening: the process is like copy old value c(3, 53) and change its first value 3 to 44, then find a new place to store c(44, 53), and give X the new place address reference.
An object stores its value under certain memory address. If that value can be changed without relocating to another memory address (i.e. value changed in place), then the object is mutable.

= [3, 53]
X = X
Y id(X)
id(Y)
0] = 44
X[id(X)
print(X)
print(Y) # Y因X改變也跟著改了。
To duplicate a mutable object, use .copy()
method to obtain a shallow copy. This will break the link between the duplicate and the original objects.
= [3, 53]
X = X.copy()
Y id(X)
id(Y)
0] = 44
X[print(X)
print(Y)
= {"banana": 1, "apple": 3}
X = X.copy()
Y
"banana"]=0
X[print(X)
print(Y)
4.5 Define Function
\[u = \mbox{number_of_banana} * 5 + \mbox{number_of_apple}*3\]
= list(
X list(
banana=0, apple=1
),list(
banana=1, apple=0
) )
Prototyping:
= X[[1]]
x
= {
u $banana *5 + x$apple *3
x }
= function(x){
U ::assert_that(
assertthat%in% X,
x msg="x should be an element in choice set X"
)
= {
u $banana *5 + x$apple *3
x
}
return(u)
}
However, %in%
only works on atomic vector, not on R list. You can use:
library(magrittr)
%>% purrr::has_element(x) X
library(magrittr)
= function(x){
U ::assert_that(
assertthat%>% purrr::has_element(x),
X msg="x should be an element in choice set X"
)
= {
u $banana *5 + x$apple *3
x
}
return(u)
}
= [
X "banana": 1, "apple": 0},
{"banana": 0, "apple": 1}
{ ]
prototyping:
= X[0]
x
= x.get("banana") * 5 + x.get("apple") *3 u
def U(x):
assert x in X, "x should be an element in choice set X"
= x.get("banana") * 5 + x.get("apple") *3
u return u
4.6 Indentation
= 3
A
if(A < 5){
print("I am small.")
else {
} print("I am large.")
}
= 3
A
if A < 5:
print("I am small.")
else:
print("I am large.")
python: indentation
is equivalent toR: { }
programming block.All commands belong to the same level of programming block must start with the same indentation space.
Most Python programmer uses a multiple of FOUR spaces (i.e. two tabs) to define the coverage of programming block. (two spaces, or one tab, are saved to long commands that are better read when broken into separate lines.)
= 300
A
if A < 5:
print("I am small.") # Four spaces
else:
print("I am large.") # Four spaces
if A > 50:
print( # Eight spaces
"I am more than 50.") # TEN spaces (two for line breaking)
Exercise 4.1 Re-write the above codes in R to show your understanding of indentation.
In a language using indentation for block programming, it is a good practice:
- ALWAYS start a NEWLINE with proper indentation at the ULTIMATE END of a programming block – this is especially important for the ending of a first level block which should end with a new line without identation.
BAD:
= 300
A
if A < 5:
print("I am small.")
else:
print("I am large.")
if A > 50:
print(
"I am more than 50.")
GOOD:
= 300
A
if A < 5:
print("I am small.")
else:
print("I am large.")
if A > 50:
print(
"I am more than 50.")
# start a newline to end the 1st level block
Exercise 4.2 Which one of the following will raise an error message? (In R, we need } else
to let computer know the control flow has an else-block following. In Python, it’s proper indentation that informs computer such a thing.)
= 3
A
if A < 5:
print("I am small.")
else:
print("I am large.")
if A > 50:
print(
"I am more than 50.")
# start a newline to end the 1st level block
= 3
A
if A < 5:
print("I am small.")
else:
print("I am large.")
if A > 50:
print(
"I am more than 50.")
# start a newline to end the 1st level block
The visual structure of the code reflects its real structure. This makes it easy to grasp the skeleton of code just by looking at it.
Python coding styles are mostly uniform. In other words, you’re unlikely to go crazy from dealing with someone’s idea of aesthetically pleasing code. Their code will look pretty much like yours. — Quick Python Book, Mannings.
4.7 Comparison
Relational operator python: in
is equivalent to R: %in%
(for atomic vectors). As to negation,
%in% X
x !(x %in% X)
in X
x not in X x
Other relational operator that need more attention is:
= c(9, 10)
A = c(9, 10)
B ::address(A)
pryr::address(B) # has different memory address
pryr== B # elementwise comparison
A != B
A identical(A, B) # object comparision
!identical(A,B)
- R only compare its class and elements, not care about its memory reference
= [9, 10]
A = A.copy()
B id(A)
id(B)
== B # object comparison in element values
A != B
A is B # object comparison in memory address (same address definitely same content)
A is not B A
You can also use:
= x["banana"] * 5 + x["apple"] *3 u
dict.get()
not only gets value from x but also can set value when key is not available:
= x.get("banana") *5 + x.get("apple") *3 + x.get("orange") u
= x.get("banana") *5 + x.get("apple") *3 + x.get("orange", 0)
u print(u)
0]) U(X[
Economists model choice set in a continuous space often such as:
\[P_x*x + P_y*y \leq I\], where, say, \(P_x=10, P_y=25, I=10000\) In this situation, DM (decision maker) chooses \((x,y)\) that fits the restriction; his choice set can be phrased as:
\[\textbf{C}=\{(x,y) : 10x+25y\leq 10000\}\] (Usually there are restrictions of \(x, y \geq 0\) as well. At the moment, for simplicity we ignore that.)
For computer to pick up the above choice set, we can design a validation function which:
- check if \(c\) falls in \(\textbf{c}\); if yes, it returns \(c\). Otherwise, return missing value.
library(magrittr) # in order to use %>% operand
= function(c){
validate_c ::assert_that(
assertthatis(c, "list") &&
%>% purrr::every(is.numeric) &&
c length(c)==2,
msg = "Only numeric vector of length 2 is possible to sit inside the choice set."
)
if(
10*c$x + 25*c$y <= 10000
){return(c)
else {
} return(NA)
} }
<- list(x=10, y=20)
c1 validate_c(c1)
<- list(x=500, y=10000)
c2 validate_c(c2)
<- c(x=50, y=10)
c3 try(validate_c(c3)) # 確保有error也不中斷script執行
For Python:
4.7.1 check type
If you aren’t familiar with Python types yet, initiate a type that you want to check:
= dict({})
dict0 dict0.__class__
- It shows
dict
is the verbatim (語法) that Python symbols dictionary type.
= {"x": 10, "y": 20}
c1
type(c1) is dict
- Use
type(X) is type_verbatim
to check if X is an object of typetype_verbatim
= dict({})
dict0
dict0.__class__
type(dict0) is dict
Boolean operations:
TRUE & FALSE
TRUE && TRUE
TRUE | FALSE
TRUE || FALSE
True and True
True or False
Exercise:
= {"a": 5, "b": 0.7} dict1
Check
if
dict1["a"]
anddict1["b"]
are both numeric type.dict1["a"]
is not a string type.
When a condition is involved of multiple relational operations:
= 15
A
# condition: is numeric
type(A) is int or type(A) is float
to form a flag with other conditions, each condition is better bounded by ( )
to avoid erroneous interpretation.
= 15
A
# condition1: is numeric
type(A) is int or type(A) is float
# condition2: larger than 7
> 7 A
= 15
A # flag True: A is a number larger than 7
(type(A) is int or type(A) is float
and A > 7 )
If there is no ( )
to separate multiple conditional flags, Python deals with and
first, then or
:
False or True and False # return False, since it is equivalent to
False or (True and False)
True or False and False # return True, since it is equivalent to
True or (False and False)
# If you mean (A or B) and C
False or True) and False (
prototyping:
type(c1) is dict
"x"]) and is_numeric(c1["y"])
is_numeric(c1[len(c1) == 2
In Python, there are only integer and float (non-integer) types. To check whether an object value is numeric, you need to either check both integer and float:
= 5.5
num0
type(num0) is int or type(num0) is float
or define an is_numeric
function:
def is_numeric(x):
return type(x) is int or type(x) is float
is_numeric(num0)
def validate_c(c):
assert (
type(c) is dict and
"x"]) and is_numeric(c["y"]) and
is_numeric(c[len(c) == 2
"Only dictionary of length 2 is possible to sit inside the choice set."
),
if 10*c["x"] + 25*c["y"] <= 10000:
return c
else:
return None
# EOF
When dealing with long operations, you can use ( )
to block the entire expression, inside ( )
you are allowed to break line before or after the operand.
Python does not give you as much coding style freedom as R:
= {"x": 10, "y": 20}
c1 = validate_c(c1)
c print(c)
= {"x": 500, "y": 10000}
c2 = validate_c(c2)
c print(c)
is None # check if missing data
c
= [50, 10]
c3 try:
= validate_c(c3)
c except:
None # what to do when Error
# EOF
4.8 Loops
= {"x": 10, "y": 20}
c
type(c["x"]) is int and type(c["y"]) is int
type(c) is int
- Won’t work.
= [10, 20]
c
type(c) is int
Won’t work.
Almost all basic operands in Python can not work on vectors (such as list, dictionary) directly, not like R:
= c(10, 20)
c
is.numeric(c)
This is because in computer language basic operands are designed for atomic objects only.
Atom, defined as the smallest storage concept in a programming language, is vector in R, but is one single value in Python.
In R, the following are atoms:
"Hi"
c("Hi","How Are You")
c(187, 192)
TRUE
c(TRUE, FALSE)
- Be careful,
R: list
is atom only if the element values are of the same type. If not, some coercion of type change will happen which can lead to errors or warnings.
<- 15
numVec0 > 13
numVec0
<- c(10, 20, 0, 15)
numVec1 > 13 numVec1
<- list(10, 20, 0, 15)
numList1 > 13
numList1
<- list(10, "a", FALSE, 15)
numList1 > 13 numList1
In Python, the following are atoms:
"Hi"
"How Are You"
187
192
True
False
= 15
numVec0 > 13 # OK
numVec0
= [10, 20, 0, 15]
numVec1 > 13 # NOT OK numVec1
When working on non-atoms, loops are required.
Prototyping via R:
= c(10, 20, 0, 15)
numVec1 = vector("logical", length(numVec1))
lgl1 for(i in seq_along(numVec1)){
<- numVec1[[i]] > 13
lgl1[[i]]
}
lgl1
= [10, 20, 0, 15]
numVec1
= [False] * len(numVec1) # create a list of 4 False
lgl1 for i in range(len(numVec1)):
= numVec1[i] > 13
lgl1[i]
print(lgl1)
4.8.1 Comprehension: mini-loop
Since Python users must deal with loops over elements inside list and dictionary often, Python equips users with a mini-loop verbatim called list comprehension and dictionary comprehension:
- When for loop result is to be saved as a list or dictionary, comprehension allows users to embed for loop inside list or dictionary to produce the result under the hood.
4.8.2 list comprehension
list comprehension:
= ... # list type storage
lgl1 for i in iterate_generator:
= iteration command that generates i-th result lgl1[i]
= [ iteration command that generates i-th result
lgl1 for i in iterate_generator]
= [10, 20, 0, 15]
numVec1
= [False] * len(numVec1) # create a list of 4 False
lgl1 for i in range(len(numVec1)):
= numVec1[i] > 13
lgl1[i]
print(lgl1)
- No need to initiate a storage object. Just keep the iteration part:
for i in range(len(numVec1)):
= numVec1[i] > 13 lgl1[i]
Corresponding list comprehension:
= [ numVec1[i] > 13 for i in range(len(numVec1))] lgl1
4.8.3 Loop over dictionary:
= {"a": 15, "b": 20, "c": 0, "d": 15}
numDict1
= list(numDict1.keys())
keys = dict({}) # initiate dict storage
dict1 for i in range(len(numDict1)):
dict1.update(> 13}
{ keys[i]: numDict1[keys[i]]
)
print(dict1)
dict.keys()
generates keys of dict object.
= {"a": 15, "b": 20, "c": 0, "d": 15}
numDict1
= dict({}) # initiate dict storage
dict1 for (key_i, value_i) in numDict1.items():
dict1.update({> 13 })
key_i : value_i
print(dict1)
dict.items()
generates (key, value) pair.
4.8.4 dictionary comprehension
Dictionary comprehension
= dict({}) # dict type storage
dict1 for i in iterate_generator:
= iteration command that generates i-th key # key_i expression
key_i -th value}) dict1.update({key_i: iteration command that generates i
= {
dict1 -th value
key_i expression : iteration command that generates ifor i in iterate_generator }
= {"a": 15, "b": 20, "c": 0, "d": 15}
numDict1
= list(numDict1.keys())
keys = dict({}) # initiate dict storage
dict1 for i in range(len(numDict1)):
dict1.update(> 13}
{ keys[i]: numDict1[keys[i]]
)
print(dict1)
- Focus only on iteration part:
for i in range(len(numDict1)):
dict1.update(> 13}
{ keys[i]: numDict1[keys[i]] )
- Dictionary comprehension:
= {"a": 15, "b": 20, "c": 0, "d": 15}
numDict1
= list(numDict1.keys())
keys
= {
dict1 > 13 for i in range(len(numDict1))
keys[i]: numDict1[keys[i]]
}
print(dict1)
Dictionary is also equipped with another possible iteration:
= dict({})
dict1 for key_i, value_i in numDict1.items():
dict1.update(> 13}
{key_i: value_i
)
print(dict1)
dict.items()
generate (key_i, value_i) pairsFocus on iteration part:
for key_i, value_i in numDict1.items():
dict1.update(> 13}
{key_i: value_i )
- dictionary comprehension:
= {"a": 15, "b": 20, "c": 0, "d": 15}
numDict1
= {
dict1 > 13 for key_i, value_i in numDict1.items()
key_i: value_i
}
print(dict1)
4.8.5 Python loop efficiency
Python loop efficiency starts from its iterate generator, which acts like a function that produces iterate only when needed – no vector of iterates that occupied memory in advance.
# iterable
# iterate generator: any object that can make iteration happen -- iterable
numDict1.keys()
# collection of iterates
list(numDict1.keys()) # show you what iterates can it generate as a list
# iterator
= iter(numDict1.keys()) # produce an iterator
iter_numDict1Keys
next(iter_numDict1Keys)
next(iter_numDict1Keys)
next(iter_numDict1Keys)
next(iter_numDict1Keys)
- In R, loops requires collection of iterates which take away memory space.
Several iterables:
- range:
range(len(object))
for k in range(len(object)):
...
For dictionary object, the following are common iterables:
for k in numDict.keys():
...
for k, v in numDict.items():
...
4.9 Help
Python uses class.method, or namespace.function to check out the help menu.
help(objectType.method)
= {"apple":2, "banana":3}
dict0
dict0.__class__
help(dict.clear) # class.method
help(dict.fromkeys)
import math
help(math.acos) # namespace.function
Builtin functions can be checked directly:
help(range)
4.10 綜合練習
1. Foodpanda Sukiya
1.1
# newPromotion1
=
allLevels c(
"新登場",
"推薦套餐",
"豪華雙饗丼",
"牛丼類",
"咖哩類",
"豬肉丼類")
<- list(
newPromotion1 "category" = "新登場",
"item" = "番茄牛丼",
"description"= "新鮮番茄的酸味,讓人食慾大開!",
"price" = 109
)# newPromotion1
1.2
<- list(
recommend1 "category" = "推薦套餐",
"item" = "蔥溫玉牛丼套餐",
"description"= "蔥溫玉牛丼+自選套餐",
"price" = 149
)# recommend1
1.3
<- list(
sukiyaMenu
newPromotion1,
recommend1
)# sukiyaMenu
1.4
<- sukiyaMenu
sukiyaMenu2 1]]$category <-
sukiyaMenu2[[factor(c("牛丼類"), levels=allLevels)
1]]$price <- 120
sukiyaMenu2[[
# sukiyaMenu2
1.5
<- purrr::transpose(sukiyaMenu)
sukiyaMenu3 $category <- unlist(sukiyaMenu3$category)
sukiyaMenu3$item <- unlist(sukiyaMenu3$item)
sukiyaMenu3$description <- unlist(sukiyaMenu3$description)
sukiyaMenu3$price <- unlist(sukiyaMenu3$price)
sukiyaMenu3
sukiyaMenu3
1.6
<- sukiyaMenu
sukiyaMenu4
1]][c("item","description","price")] -> options
sukiyaMenu4[[1]][c("item","description","price")] <- NULL
sukiyaMenu4[[1]]$options <- options
sukiyaMenu4[[
2]][c("item","description","price")] -> options
sukiyaMenu4[[2]][c("item","description","price")] <- NULL
sukiyaMenu4[[2]]$options <- options
sukiyaMenu4[[
sukiyaMenu4# sukiyaMenu4
2. Budget constraint
Consider the budget constraint:
\[\ P_{apple} * x_1 + P_{banana} * x_2 \leq I\]
Suppose \(I=1000, P_{apple}=5, P_{banana}=7\). Suppose \(x_1\) and \(x_2\) must be integer.
2.1
Suppose we decide to use [2,3]
in Python to represent the alternative of 2 apples and 3 bananas. Given \(x1=3\), list all [x1, x2]
alternative that satisfies the budget constraint in a list. That is your answer should look like [[3,0], [3,1], [3,2], ...]
and each element is inside the choice set.
2.2
In Python, a list can append a new value or extend values such as:
= [2, 3, 7]
list0 = list0.copy()
list1
= [5, "b"]
newList
list0.append(newList)print(list0)
list1.extend(newList)print(list1)
Construct a list that contains all the [x1, x2]
pair inside the budget constraint. You may need to use append or extend methods above.
3. Atoms
<- c(5)
par0 <- 3
par1
2**par0 # **: 次方
2**par1
There is something wrong in the following. Fix it.
= [5]
par0 = 3
par1
2**par0
2**par1