logo

Preface

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.

1 Introduction

Exploring the DTMC

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:

  • Escape: Adopting the strategy of paying to escape Jail or using a ‘Get out of Jail Free Card’ (GJFC).
  • Remain: Deciding to try and roll doubles to escape Jail.

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.

Assumptions

  1. The dice being rolled are fair
  2. If a player is in Jail and has had two unsuccessful attemts at escaping, they to not pay or use a GJFC to escape before their third attempt.
  3. When drawing a Chance or Community Chest card, there is an equal chance of drawing any card that exists in each deck.
  4. The problem can be modelled as a DTMC.

Setting up the Problem in R

This code chunk illustrates how the problem was set up. In summary, the following were defined:

  • A simplified state space, containing all spaces on the board.
  • The state space (with three states per space).
  • The probability distributions for having landed on each of the Community Chest and Chance spaces.
  • The probability distribusion of the possible outcomes of the dice.
  • A further variant of the state space, which will be useful when constructing the function rows later on.
  • The chosen colours to represent each space.
# 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")

2 Formulating the DTMC

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.

Calculating the rows of the transition matrix

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

Constructing Both Transition Matrices

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.

Remain

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)

Escape

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)

3 Stationary Distribution

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.

Remain

# 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.

Escape

# 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.

4 Return on Investment

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:

  • purchasing a property without owning the colour set,
  • purchasing a property resulting in ownership of a colour set,
  • building a house on a single property,
  • building houses on all properties in a colour set,
  • purchasing first, second, third and fourth stations.

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.

Building a House on a Single Property

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.

Building a House on Each Property in a Colour Set

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