As far as board games go, Monopoly® may be considered an all-time classic. Owning property to generate revenue in attempt to gain financial and proprietary dominance over opponents is easier said than done in real life, however Monopoly® offers a make-belief alternative to emulate the sense of power and competition. That being said, growing up I was interested in numbers more than anything - probability to be specific. Whenever I played with my family, whilst their focus was on fighting over Mayfair and Park Lane, I always wanted to know things like where players were most likely to end up on the board - referred to as ‘hot spots’. There was a general understanding that Bow Street, Malborough Street and Vine Street (the orange properties) were potential hot spots as they were 6, 8 and 9 spaces after jail respectively. However, I wanted to know more. ‘Are Mayfair and Park Lane really worth it?’ ‘What about the utilities?’
After developing my understanding of stochastic processes it was immediately apparent that the game of Monopoly® could be modelled as a discrete time Markov chain (DTMC), which is the focus of this report.
I first decided to establish the state space of the DTMC. The intuitive answer is that the state space is simply the 41 available spaces on the board - perhaps with the exception of ‘Go to Jail’ as it is impossible for a player to remain there after their turn is over. This is where I encountered the first hurdle, as carrying on like this would have resulted in the DTMC failling to satisfy the Markov property. The reason is that the probability distribution would depend on what had happened previously. In particular, notice that a player is more likely to end up in Jail if they have previously thrown two consecutive doubles than if it is their first roll of the dice - since throwing three consecutive doubles lands a player in Jail.
If I wishied to continue with the DTMC approach I realised that the state space would have to consist of three states per space on the board - the first given that no doubles were rolled to reach the space, the second given that one double was rolled to reach the space, and the third given that two doubles were rolled to reach the space. In turn, this meant that there would have to be three different states for when a payer was at Go, Old Kent Road, etc.
The second hurdle I approached was the fact that players can choose to pay to escape Jail or they can attempt to roll a double. This lead me to make the desicion to have two seperate DTMCs:
It is well known that staying in Jail for as long as possible towards the end of the game is a favoured tactic as it reduces the chances of landing on opponents’ properties and paying them rent. However, this tactic is not favourable near the beginning of a game as the aim is to buy as many properties as possible. When considering long run probabilities the first strategy seems more appropriate, however I decided to conduct the analysis accounting for both strategies.
When considering the remain strategy I also realised that two new states needed to be accounted for. The first being that a player is in Jail and they have had one unsuccessful attempt at rolling a double to escape. The second being that a player is in Jail and they have had two unsuccessful attempts at rolling a double to escape.
This code chunk illustrates how the problem was set up. In summary, the following were defined:
rows later on.# define all the spaces on the board (excluding 'go to jail' since a player cant end up there)
state_space_simple <- c("go","old_kent_road","community_chest1","whitechapel_road","income_tax",
"king's_cross_station","the_angel,_islington","chance1","euston_road","pentonville_road",
"visiting_jail","pall_mall","electric_company","whitehall","northumberland_avenue",
"marylebone_station","bow_street","community_chest2","marlborough_street","vine_street",
"free_parking","strand","chance2","fleet_street","trafalgar_square",
"fenchurch_st._station","leicester_square","coventry_street","water_works","piccadilly","in_jail",
"regent_street","oxford_street","community_chest3","bond_street","liverpool_st._station",
"chance3","park_lane","super_tax","mayfair")
# define each state for number of doubles previously rolled.
# state_space_x defines each state given x doubles have been rolled prior
state_space0 <- paste0("0",state_space_simple)
state_space1 <- paste0("1",state_space_simple)
state_space2 <- paste0("2",state_space_simple)
# entire state space
# in_jailx denotes the state space in which there have been x unsuccessful attempts to escape jail
state_space <- c(state_space0,state_space1,state_space2,"3in_jail","in_jail1","in_jail2")
# transition probabilities after landing on community chest
community_chest_transition1 <-community_chest_transition2 <- community_chest_transition3 <- rep(0,40)
names(community_chest_transition1) <- names(community_chest_transition2) <- names(community_chest_transition3) <- state_space_simple
# probabilities based on the cards in each deck
community_chest_transition1[c("go","in_jail","community_chest1")] <- c(1/16,1/16,14/16)
community_chest_transition2[c("go","in_jail","community_chest2")] <- c(1/16,1/16,14/16)
community_chest_transition3[c("go","in_jail","community_chest3")] <- c(1/16,1/16,14/16)
# transition probabilities after landing on chance
chance_transition1 <- chance_transition2 <- chance_transition3 <- rep(0,40)
names(chance_transition1) <- names(chance_transition2) <- names(chance_transition3) <- state_space_simple
# probabilities based on the cards in each deck
chance_transition1[c("go","income_tax","electric_company","in_jail","marylebone_station",
"pall_mall","trafalgar_square","king's_cross_station","mayfair","chance1")] <- c(1/16,1/16,1/16,1/16,2/16,1/16,1/16,1/16,1/16,6/16)
chance_transition2[c("go","vine_street","water_works","in_jail","fenchurch_st._station",
"pall_mall","trafalgar_square","king's_cross_station","mayfair","chance2")] <- c(1/16,1/16,1/16,1/16,2/16,1/16,1/16,1/16,1/16,6/16)
chance_transition3[c("go","community_chest3","electric_company","in_jail",
"king's_cross_station","pall_mall","trafalgar_square","mayfair","chance3")] <- c(1/16,1/16,1/16,1/16,3/16,1/16,1/16,1/16,6/16)
# initialise empty named vector
empty <- rep(0,123)
names(empty) <- state_space
# define probabilities of dice outcomes (4 = 1+3 or 3+1, 4d = 2+2)
rolls <- c(p_3 = 2/36,
p_4 = 2/36,
p_5 = 4/36,
p_6 = 4/36,
p_7 = 6/36,
p_8 = 4/36,
p_9 = 4/36,
p_10 = 2/36,
p_11 = 2/36,
p_2d = 1/36,
p_4d = 1/36,
p_6d = 1/36,
p_8d = 1/36,
p_10d= 1/36,
p_12d= 1/36)
# extended simple state space used for the main function 'rows'
state_space_rep <- c("go","old_kent_road","community_chest1","whitechapel_road","income_tax",
"king's_cross_station","the_angel,_islington","chance1","euston_road","pentonville_road",
"visiting_jail","pall_mall","electric_company","whitehall","northumberland_avenue",
"marylebone_station","bow_street","community_chest2","marlborough_street","vine_street",
"free_parking","strand","chance2","fleet_street","trafalgar_square",
"fenchurch_st._station","leicester_square","coventry_street","water_works","piccadilly",
"in_jail","regent_street","oxford_street","community_chest3","bond_street","liverpool_st._station",
"chance3","park_lane","super_tax","mayfair",
"go","old_kent_road","community_chest1","whitechapel_road","income_tax",
"king's_cross_station","the_angel,_islington","chance1","euston_road","pentonville_road",
"visiting_jail","pall_mall")
# defining the colour of each space
colour_simple <- c("tan","brown","aquamarine","brown","brown1","black","skyblue2","gold4","skyblue2","skyblue2",
"grey","purple","bisque3","purple","purple","black","orange","gold4","orange","orange",
"lightpink","red","aquamarine","red","red","black","yellow","yellow","bisque3","yellow",
"grey","green","green","aquamarine","green","black","gold4","blue","brown1","blue")
colour_label <- c("Brown","Light blue","Pink/purple","Orange","Red","Yellow","Green","Dark blue")
As expected, the state description of the DTMC is where the player is on the board, accounting for the number of doubles that have been rolled to reach that space. The state space is defined in the code chunk in Setting up the Problem in R.
I decided to create a function which would calculate the transition rows. The function is quite neat, however it comes at a slight cost of incorrectly calculating the Jail rows. I decided that I would use the function and simply modify the Jail rows after. This matrix assumes that the ‘escape’ strategy is being used, as I realised it would be relatively easy to modify it to fit the ‘remain’ strategy later.
# this function takes input s, the starting position of the piece (0 = go, 1 = old kent road, etc.)
# it returns a list containing three transition rows
# the first row assumes that no doubles have been thrown, the second assumes one double has just been thrown and the third assumes two doubles have just been thrown
# for example, rows(1)[[2]] returns the transition row when starting at old kent road given that exactly one double has been previously thrown
rows <- function(s) {
# initialise empty vector to be filled in
total <- empty
# the first loop accumulates the probabilities for throwing non-doubles
for (i in 1:9) {
temp <- empty
if (str_detect(state_space_rep[i+3+s],"community_chest1")) {
temp[1:40] <- community_chest_transition1*rolls[i]
}else if (str_detect(state_space_rep[i+3+s],"community_chest2")) {
temp[1:40] <- community_chest_transition2*rolls[i]
}else if (str_detect(state_space_rep[i+3+s],"community_chest3")) {
temp[1:40] <- community_chest_transition3*rolls[i]
}else if (str_detect(state_space_rep[i+3+s],"chance1")) {
temp[1:40] <- chance_transition1*rolls[i]
}else if (str_detect(state_space_rep[i+3+s],"chance2")) {
temp[1:40] <- chance_transition2*rolls[i]
}else if (str_detect(state_space_rep[i+3+s],"chance3")) {
temp[1:40] <- chance_transition3*rolls[i]
}else {
temp[paste0("0",state_space_rep[i+3+s])] <- rolls[i]
}
total <- total + temp
}
# the second loop accumulates the probabilities for throwing doubles
for (i in 10:15) {
temp <- empty
if (str_detect(state_space_rep[2*i-17+s],"community_chest1")) {
temp[41:80] <- community_chest_transition1*rolls[i]
}else if (str_detect(state_space_rep[2*i-17+s],"community_chest2")) {
temp[41:80] <- community_chest_transition2*rolls[i]
}else if (str_detect(state_space_rep[2*i-17+s],"community_chest3")) {
temp[41:80] <- community_chest_transition3*rolls[i]
}else if (str_detect(state_space_rep[2*i-17+s],"chance1")) {
temp[41:80] <- chance_transition1*rolls[i]
}else if (str_detect(state_space_rep[2*i-17+s],"chance2")) {
temp[41:80] <- chance_transition2*rolls[i]
}else if (str_detect(state_space_rep[2*i-17+s],"chance3")) {
temp[41:80] <- chance_transition3*rolls[i]
}else {
temp[paste0("1",state_space_rep[2*i-17+s])] <- rolls[i]
}
total <- total + temp
}
# 'total' now contains the transition rows given that no doubles were previously thrown
# 'total1' and 'total2' will account for one and two doubles having been thrown previously respectively
total1 <- empty
total1[1:40] <- total[1:40]
total1[81:120] <- total[41:80]
total2 <- empty
total2[1:40] <- total[1:40]
total2["3in_jail"] <- 1/6 # 3 consecutive doubles lands a player in jail
list(total,total1,total2) # return three rows as a list
}
The function rows assumes that if a player lands on Go to Jail they stay there and carry on their next turn as normal. This is obviously not the case as the Go to Jail space instructs a player to immetiately move to Jail. The code chunk below illustrates the steps taken to construct the correct Jail rows.
# this script handles the intricacies involved when dealing with jail
# store all transition rows in an array
all_rows <- sapply(0:39,rows)
# an empty matrix which will later store the transition probabilities
transition_remain <- matrix(rep(0,123^2),ncol=123,dimnames=list(state_space,state_space))
# filling out the transition matrix
for (j in 1:3) {
for (i in 1:40) {
transition_remain[i+(j-1)*40,] <- all_rows[j,i][[1]]
}
}
# initialise transition row for when starting at visiting jail (which is the same as leaving jail)
vj0 <- transition_remain["0visiting_jail",]
# 2d
ij_2d <- empty
ij_2d["0electric_company"] <- rolls["p_2d"]
# 4d
ij_4d <- empty
ij_4d["0northumberland_avenue"] <- rolls["p_4d"]
# 6d
ij_6d <- empty
ij_6d["0bow_street"] <- rolls["p_6d"]
# 8d
ij_8d <- empty
ij_8d["0marlborough_street"] <- rolls["p_8d"]
# 10d
ij_10d <- empty
ij_10d["0free_parking"] <- rolls["p_10d"]
# 12d
ij_12d <- empty
ij_12d[1:40] <-chance_transition2*rolls["p_12d"]
# odd
ij_odd <- empty
ij_odd["in_jail1"] <- 5/6
# constructing the transition row for first throw in jail (regardless of number of prior doubles)
ij0 <- ij_2d+ij_4d+ij_6d+ij_8d+ij_10d+ij_12d+ij_odd
# transition row for second throw in jail
ij1 <- empty
ij1[1:40] <- ij0[1:40]
ij1["in_jail2"] <- 5/6
# transition row for third throw in jail
ij2 <- empty
ij2[1:40] <- vj0[1:40]+vj0[41:80] # throwing a double to escape jail does not permit another throw
I then needed to modify the transition matrix so that it contained the correct Jail rows. Furthermore, I needed to construct a matrix for each strategy. I first constructed the ‘remain’ matrix and then made a copy wich I modified to fit the ‘escape’ strategy.
The incorrect jail rows were replaced with the correctly calculated rows from 2.1.2. The code below shows how the final transition matrix was constructed.
# as it stands, the transition rows starting as the jail spaces are actually incorrect and need to be manually adjusted
transition_remain["0in_jail",] <- transition_remain["1in_jail",] <- transition_remain["2in_jail",] <- transition_remain["3in_jail",] <- ij0
transition_remain["in_jail1",] <- ij1
transition_remain["in_jail2",] <- ij2
# displaying the transition matrix
# kable(data.frame(transition_remain),col.names = gsub("[.]", " ", colnames(transition_remain)),caption="Transition matrix when adopting the remain strategy") %>% kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>% scroll_box(width = "100%", height = "500px",fixed_thead = T)
The matrix required for when adopting the escape strategy was similar to the remain matrix however the jail rows needed to be modified and the final two states needed to be removed. The code below illustrates how the final transition matrix was constructed.
# start by replicating the previous transition matrix
transition_escape <- transition_remain
# again, manually adjust the transition rows for the jail spaces
transition_escape["0in_jail",] <- transition_escape["1in_jail",] <- transition_escape["2in_jail",] <- transition_escape["3in_jail",] <- transition_remain["0visiting_jail",]
# exclude the last two states as when adopting the 'escape' strategy a player never stays in jail
transition_escape <- transition_escape[-122:-123,-122:-123]
# displaying the transition matrix
# kable(data.frame(transition_escape),col.names = gsub("[.]", " ", colnames(transition_escape)),caption="Transition matrix when adopting the escape strategy") %>% kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>% scroll_box(width = "100%", height = "500px",fixed_thead = T)
This section answers the question ‘what are the hot spots in a game of Monopoly®?’ The long run probability distribution (stationary distribution) of each transition matrix will highlight where players are most likely to spend their time as the game progresses. Before the results are calculated and displayed, a tidy version of the state names is constructed - the code is shown below.
# function to capitalise first letter of each word
simpleCap <- function(x) {
s <- strsplit(x, " ")[[1]]
paste(toupper(substring(s, 1,1)), substring(s, 2),
sep="", collapse=" ")
}
# replace underscores with spaces
state_space_proper <- str_replace_all(state_space_simple,"_"," ")
# use simpleCap function on state space
for (i in 1:length(state_space_proper)) {
state_space_proper[i] <- simpleCap(state_space_proper[i])
}
# add space between states and numbers
state_space_proper <- str_replace(state_space_proper,"([a-z]*)(\\d)","\\1 \\2")
The stationary distribution can be calculated manually by solving the equations \(\pi=P\pi\) and \(\sum\limits_{\pi_i}=1\) where \(P\) is the transition matrix and \(\pi\) is the stationary distribution. However, with such large transition matrices it would be impractical do this by hand. I decided to use the ‘markovchain’ package to calculate the stationary distribution.
# initialise the markov chain
transition_matrix_remain <- new("markovchain", transitionMatrix=transition_remain)
# create a data frame with the stationary distribution
large_ss_remain <- steadyStates(transition_matrix_remain) %>% t() %>% data.frame()
# include the state names with some basic string processing to remove the numbers indicating the amount of previously thrown doubles
tidy_ish_ss_remain <- large_ss_remain %>% mutate(state=str_replace(str_replace(rownames(large_ss_remain),"^\\d?",""),"in_jail\\d?","in_jail"))
# the final stationary distribution
tidy_ss_remain <- tapply(tidy_ish_ss_remain$., tidy_ish_ss_remain$state, FUN=sum)[state_space_simple]
# rename with tidy labels
names(tidy_ss_remain) <- state_space_proper
| Space | Proportion of Time Spent |
|---|---|
| Go | 0.0291259 |
| Old Kent Road | 0.0201247 |
| Community Chest 1 | 0.0177928 |
| Whitechapel Road | 0.0204139 |
| Income Tax | 0.0219792 |
| King’s Cross Station | 0.0280595 |
| The Angel, Islington | 0.0213506 |
| Chance 1 | 0.0081651 |
| Euston Road | 0.0219102 |
| Pentonville Road | 0.0217195 |
| Visiting Jail | 0.0214308 |
| Pall Mall | 0.0256041 |
| Electric Company | 0.0261581 |
| Whitehall | 0.0217655 |
| Northumberland Avenue | 0.0242510 |
| Marylebone Station | 0.0263664 |
| Bow Street | 0.0267829 |
| Community Chest 2 | 0.0229476 |
| Marlborough Street | 0.0281892 |
| Vine Street | 0.0281124 |
| Free Parking | 0.0282411 |
| Strand | 0.0261395 |
| Chance 2 | 0.0104470 |
| Fleet Street | 0.0256704 |
| Trafalgar Square | 0.0299517 |
| Fenchurch St. Station | 0.0289235 |
| Leicester Square | 0.0253966 |
| Coventry Street | 0.0251877 |
| Water Works | 0.0265430 |
| Piccadilly | 0.0243834 |
| In Jail | 0.0936366 |
| Regent Street | 0.0252454 |
| Oxford Street | 0.0247655 |
| Community Chest 3 | 0.0224593 |
| Bond Street | 0.0235727 |
| Liverpool St. Station | 0.0229258 |
| Chance 3 | 0.0081767 |
| Park Lane | 0.0206272 |
| Super Tax | 0.0205745 |
| Mayfair | 0.0248828 |
The table illustrates the proportions of time spent at each space on the board. It is important to note that the Chance and Community Chest proportions are quite low as players are often moved to other spaces as a result of landing on these spaces.
The graph below highlights the proportions of time spent at each coloured set. As expected, the brown and dark blue sets are quite low since there are only two properties in each of these sets. This graph also confirms the suspicion that the orange properties are hotspots as their proportions have the highest total. To my surprise, the light blue set has a relatively low proportion. Because of cards forcing players to Go I assumed that the light blue set was desirable - perhaps I was wrong.
# initialise the markov chain
transition_matrix_escape <- new("markovchain", transitionMatrix=transition_escape)
# create a data frame with the stationary distribution
large_ss_escape <- steadyStates(transition_matrix_escape) %>% t() %>% data.frame()
# include the state names with some basic string processing to remove the numbers indicating the amount of previously thrown doubles
tidy_ish_ss_escape <- large_ss_escape %>% mutate(state=str_replace(str_replace(rownames(large_ss_escape),"^\\d?",""),"in_jail\\d?","in_jail"))
# the final stationary distribution
tidy_ss_escape <- tapply(tidy_ish_ss_escape$., tidy_ish_ss_escape$state, FUN=sum)[state_space_simple]
# rename with tidy labels
names(tidy_ss_escape) <- state_space_proper
| Space | Proportion of Time Spent |
|---|---|
| Go | 0.0308967 |
| Old Kent Road | 0.0213367 |
| Community Chest 1 | 0.0188650 |
| Whitechapel Road | 0.0216382 |
| Income Tax | 0.0232960 |
| King’s Cross Station | 0.0296376 |
| The Angel, Islington | 0.0226234 |
| Chance 1 | 0.0086509 |
| Euston Road | 0.0232135 |
| Pentonville Road | 0.0230084 |
| Visiting Jail | 0.0227013 |
| Pall Mall | 0.0270222 |
| Electric Company | 0.0260441 |
| Whitehall | 0.0237219 |
| Northumberland Avenue | 0.0246461 |
| Marylebone Station | 0.0291934 |
| Bow Street | 0.0279147 |
| Community Chest 2 | 0.0259333 |
| Marlborough Street | 0.0293449 |
| Vine Street | 0.0308415 |
| Free Parking | 0.0288273 |
| Strand | 0.0283502 |
| Chance 2 | 0.0104775 |
| Fleet Street | 0.0273501 |
| Trafalgar Square | 0.0318496 |
| Fenchurch St. Station | 0.0306495 |
| Leicester Square | 0.0270634 |
| Coventry Street | 0.0267801 |
| Water Works | 0.0280657 |
| Piccadilly | 0.0258528 |
| In Jail | 0.0394022 |
| Regent Street | 0.0267660 |
| Oxford Street | 0.0262441 |
| Community Chest 3 | 0.0238347 |
| Bond Street | 0.0249988 |
| Liverpool St. Station | 0.0243240 |
| Chance 3 | 0.0086699 |
| Park Lane | 0.0218725 |
| Super Tax | 0.0218126 |
| Mayfair | 0.0262791 |
The table illustrates the proportions of time spent at each space on the board. It is important to note that the Chance and Community Chest proportions are quite low as players are often moved to other spaces as a result of landing on these spaces.
The graph below highlights the proportions of time spent at each coloured set. As expected, the brown and dark blue sets are quite low since there are only two properties in each of these sets. This graph also confirms the suspicion that the orange properties are hotspots as their proportions have the highest total. To my surprise, the light blue set has a relatively low proportion. Because of cards forcing players to Go I assumed that the light blue set was desirable - perhaps I was wrong.
It has been made apparent that the orange and red sets are most commonly landed on. Is this to say that they are necessarily the most favourable options for seeing a return on a player’s investment? Houses for the orange set only cost 100 whereas houses for the red set cost 150. However, more rent is received when opponents land on the red properties. There are several things that could be investigated, including the expected time to see a return on:
All are relatively simple to calculate with the stationary distributions, however for now I will focus on building houses - when I come back to finish this report I will investigate all of the listed points. Furthermore, for simplicity I will only consider the ‘remain’ strategy. The reason for this is that at the stage in which players are building houses it is likely that there are not many properties left and, if given the chance, players would choose to remain in jail for as long as possible. Again, when I return to finish this report I will also consider the ‘escape’ strategy.
This section illustrates the expected number of rolls it takes to see a return on purchasing a single house for each property. This includes going from having one house to two, two houses to three, etc. The table below illustrates the expected time to see a return on a given investment for each property. For example, the entry in the “Second House” column for “Pentonville Road” describes the expected number of opponent rolls it takes to see a return on the 50 it cost to build the house. In this case, it is arouond 38 rolls.
# website containing data on rent, cost etc.
url <- "http://www.jdawiseman.com/papers/trivia/monopoly-rents.html" # accessed 13/06/2020
h <- read_html(url)
tab <- h %>% html_nodes("table")
tab <- html_table(tab[[2]]) # extract desired table
# tidy the table
names(tab) <- tab[1,]
tab <- tab[-1,]
# detect properties and not stations/utilities
ind <- str_detect(tab$Site,"^\\d*$")
tab_properties <- tab[ind,]
colnames(tab_properties) <- c("Property","Cost","Mortgage","Single","One_house","Two_houses","Three_houses","Four_houses","Hotel")
tab_properties <- tab_properties %>% mutate(Set=as.numeric(Single)*2,
Cost=as.numeric(Cost),
Mortgage=as.numeric(Mortgage),
Single=as.numeric(Single),
One_house=as.numeric(One_house),
Two_houses=as.numeric(Two_houses),
Three_houses=as.numeric(Three_houses),
Four_houses=as.numeric(Four_houses),
Hotel=as.numeric(Hotel)) %>% select(c(1:4,10,5:9))
# alter names that differ slightly
tab_properties$Property[3] <- "The Angel, Islington"
tab_properties$Property[12] <- "Strand"
# define ling run proabilities for properties
long_run_properties <- tidy_ss_remain[names(tidy_ss_remain)%in%tab_properties$Property]
# expected income per roll for properties
ipr_properties <- cbind(Property=tab_properties$Property,tab_properties[-1:-3]*tidy_ss_remain[names(tidy_ss_remain)%in%tab_properties$Property])
# defining cost for houses on each property
building_cost <- c(rep(50,5),rep(100,6),rep(150,6),rep(200,5))
names(building_cost) <- tab_properties$Property
# initialise empty data frame
return_inv <- data.frame("First_House"=rep(0,22),"Second_House"=rep(0,22),"Third_House"=rep(0,22),"Fourth_House"=rep(0,22),"Hotel"=rep(0,22))
# return_inv will contain the expected number of rolls to see return on investment for each property in each state
for (j in 1:22) {
for (i in 1:5) {
return_inv[j,i] <- building_cost[j]/(ipr_properties[j,i+3]-ipr_properties[j,i+2])
}
}
return_inv <- cbind(Property=tab_properties$Property,return_inv)
| Property | First House | Second House | Third House | Fourth House | Hotel |
|---|---|---|---|---|---|
| Old Kent Road | 414.08401 | 124.22520 | 41.408401 | 35.49292 | 27.60560 |
| Whitechapel Road | 204.10974 | 61.23292 | 20.410974 | 17.49512 | 18.84090 |
| The Angel, Islington | 130.10298 | 39.03089 | 13.010298 | 18.01426 | 15.61236 |
| Euston Road | 126.78019 | 38.03406 | 12.678019 | 17.55418 | 15.21362 |
| Pentonville Road | 95.91973 | 38.36789 | 11.510368 | 15.34716 | 15.34716 |
| Pall Mall | 130.18733 | 39.05620 | 13.018733 | 22.31783 | 31.24496 |
| Whitehall | 153.14736 | 45.94421 | 15.314736 | 26.25383 | 36.75537 |
| Northumberland Avenue | 114.54301 | 34.36290 | 12.886089 | 20.61774 | 20.61774 |
| Bow Street | 88.89827 | 28.72098 | 10.667793 | 18.66864 | 18.66864 |
| Marlborough Street | 84.46333 | 27.28815 | 10.135600 | 17.73730 | 17.73730 |
| Vine Street | 74.10734 | 25.40823 | 9.360928 | 17.78576 | 17.78576 |
| Strand | 106.26725 | 35.86520 | 12.752069 | 32.79104 | 32.79104 |
| Fleet Street | 108.20949 | 36.52070 | 12.985138 | 33.39036 | 33.39036 |
| Trafalgar Square | 83.46761 | 25.04028 | 11.129015 | 28.61747 | 28.61747 |
| Leicester Square | 89.48944 | 26.84683 | 12.566602 | 33.75030 | 33.75030 |
| Coventry Street | 90.23152 | 27.06946 | 12.670810 | 34.03017 | 34.03017 |
| Piccadilly | 80.94379 | 25.63220 | 12.554546 | 35.15273 | 35.15273 |
| Regent Street | 101.56702 | 30.47011 | 15.533780 | 39.61114 | 45.26987 |
| Oxford Street | 103.53539 | 31.06062 | 15.834824 | 40.37880 | 46.14720 |
| Bond Street | 90.25958 | 28.28134 | 15.426183 | 42.42200 | 42.42200 |
| Park Lane | 92.34244 | 29.83371 | 16.159928 | 48.47978 | 48.47978 |
| Mayfair | 80.37682 | 20.09421 | 10.047103 | 26.79227 | 26.79227 |
The behaviour of the results is more apparent when observing the graph below. Interestingly, the quickest expected return on investments occurs when the third house is purchased. This means that if the property owner wishes to regain their investment ASAP then building three houses per property might be the optimal choice. However, although this strategy predicts that the property owner would see a quicker return on their investment it is worth noting that building a fourth house or a hotel would still be of interest as it aids in bankrupting other players. A property owner with a lot of cash would most likely want to build more than three houses, however a property owner short on cash (or who wishes to spend money building houses on other properties) would maybe prefer to stick with the optimal choice of three houses.
It is also worth noting that the orange properties (Bow Street, Marlborough Street and and Vine Street) tend to result in a quick return on investment, particuarly when building a third house. Does this mean that players should always build on the orange properties when possible? Not necessarily, as if a player’s opponent is on Free Parking then the favourable option would be to build on the yellow or green properties if possible. It is vital to remember that these are long run probabilities and decision making in a real game should really be made on a case by case basis. That being said, this information is still useful as the analysis so far suggests that the orange properties are more desirable than the pink/purple ones for example. In this case, a player may wish to trade early on in attempt to receive orange properties in exchange for purple ones. Giving up Whitehall and 150 in exchange for Bow Street may initially seem like a poor decision, however it may be worthwhile in the long run.
Note: I’m sure some of the data visualisation maestros reading this might have a thing or two to say about using a line graph with categorical variables across the x-axis. However, I’ll quickly argue my case for doing so. The first reason is that I am treating the spaces as ordinal data as there is a sense of order to going round the board. Quite a weak point I’ll admit, however the second (and more convincing point) is that it makes for a far better visualisation. I don’t even want to look at a clustered bar chart with twenty two clusters with five bars in each cluster. With the line graph it is immediately apparent which properties see quicker returns on investments. It is also easy to tell which stage of building houses is optimal for seeing a return on an investment - building the third house.
My experience of playing Monopoly® has demonstrated that players will often simultaneously build houses on all properties in a colour set. Whether it be tactics, superstition or OCD it appears to happen so I decided to investigate it. This analysis is much the same as the previous section, however properties of the same colour are grouped together and treated as single assets.
The conclusions drawn from this analysis are much the same as before, however the graph below makes it easier to view the expected return times by colour set.
# define colors of each property
col_set <- c(rep("Brown",2),rep("Light blue",3),rep("Pink/purple",3),rep("Orange",3),rep("Red",3),rep("Yellow",3),rep("Green",3),rep("Dark blue",2))
# income per roll for each colour set, treated as single assets
ipr_set <- ipr_properties %>% mutate(Colour=factor(col_set,levels=unique(col_set))) %>% group_by(Colour)%>% summarise(Single=sum(Single),
Set=sum(Set),
One_house=sum(One_house),
Two_houses=sum(Two_houses),
Three_houses=sum(Three_houses),
Four_houses=sum(Four_houses),
Hotel=sum(Hotel))
# cost for building on all properties in a colour set
building_cost_set <- c(100,150,300,300,450,450,600,400)
# initalise empty data frame
return_inv_set <- data.frame("First_House"=rep(0,8),"Second_House"=rep(0,8),"Third_House"=rep(0,8),"Fourth_House"=rep(0,8),"Hotel"=rep(0,8))
# return_inv_set will contain the expected number of rolls to see a return on investment when building on each set
for (j in 1:8) {
for (i in 1:5) {
return_inv_set[j,i] <- building_cost_set[j]/(ipr_set[j,i+3]-ipr_set[j,i+2])
}
}
return_inv_set <- cbind(Colour=unique(col_set),return_inv_set)
| Colour | First House | Second House | Third House | Fourth House | Hotel |
|---|---|---|---|---|---|
| Brown | 273.43719 | 82.03116 | 27.34372 | 23.43747 | 22.39628 |
| Light blue | 115.38785 | 38.47317 | 12.36516 | 16.88840 | 15.38927 |
| Pink/purple | 130.76885 | 39.23066 | 13.65424 | 22.83126 | 27.85154 |
| Orange | 82.00708 | 27.07068 | 10.02576 | 18.05392 | 18.05392 |
| Red | 97.93595 | 31.51275 | 12.23067 | 31.45030 | 31.45030 |
| Yellow | 86.67675 | 26.50088 | 12.59710 | 34.30048 | 34.30048 |
| Green | 98.09241 | 29.88846 | 15.59636 | 40.76997 | 44.55520 |
| Dark blue | 85.94516 | 24.01401 | 12.39060 | 34.51171 | 34.51171 |