Hotelling's Law
Do you have questions or comments about this model? Ask them here! (You'll first need to log in.)
WHAT IS IT?
This model is a representation of Hotelling's law (1929), which examines the optimal placement of stores and pricing of their goods in order to maximize profit. In Hotelling's original paper, the stores were confined to a single dimension. This model replicates and extends Hotelling's law, by allowing the stores to move freely on a plane.
In this model, several stores attempt to maximize their profits by moving and changing their prices. Each consumer chooses their store of preference based on the distance to the store and the price of the goods it offers.
HOW IT WORKS
Each consumer adds up the price and distance from each store, and then chooses to go to the store that offers the lowest sum. In the event of a tie, the consumer chooses randomly. The stores can either be constrained to one dimension, in which case all stores operate on a line, or they can be placed on a plane. Under the normal rule, each store tries to move randomly in the four cardinal directions to see if it can gain a larger market share; if not, it does not move. Then each store checks if it can earn a greater profit by increasing or decreasing the price of their goods; if not, it does not change the price. This decision is made without any knowledge of their competitors' strategies. There are two other conditions under which one can run this model: stores can either only change prices, or only move their location.
HOW TO USE IT
Press SETUP to create the stores and a visualization of their starting market share areas.
Press GO to have the model run continuously.
Press GO-ONCE to have the model run once.
The NUMBER-OF-STORES slider decides how many stores are in the world.
If the LAYOUT chooser is on LINE, then the stores will operate only on one dimension. If it is on PLANE, then the stores will operate in a two dimensional space.
If the RULES chooser is on PRICING-ONLY, then stores can only change their price. If it is on MOVING-ONLY, then the stores can only move. If it is on NORMAL, all stores can change their prices and move.
THINGS TO NOTICE
On the default settings, notice that the two stores end up in very close contact and with minimal prices. This is because each store tries to cut into their competitor's fringe consumers by moving closer and reducing their prices.
Also notice how the shapes of the boundaries end up as perpendicular bisectors or hyperbolic arcs. The distance between the stores and their difference in prices determines the eccentricity of these arcs.
Try increasing the store number to three or more, and notice how the store with the most area is not necessarily the most profitable.
Plots show the prices, areas, and revenues of all stores.
THINGS TO TRY
Try to see how stores behave differently when they are either prohibited from moving or changing their prices.
Try increasing the number of stores. Examine how they behave differently and watch which ones are the most successful. What patterns emerge?
EXTENDING THE MODEL
In this model, information is free, but this is not a realistic assumption. Can you think of a way to add a price when stores try to gain information?
In this model, the stores always seek to increase their profits immediately instead of showing any capacity to plan. They are also incapable of predicting what the competitors might do. Making the stores more intelligent would make this model more realistic.
Maybe one way to make the stores more intelligent would be to have them consider their moving and pricing options in conjunction. Right now, they consider the question "would it be good for me to move North" and "would it be good for me to increase my price" completely separately. But what if they asked "would it be good for me to move North AND increase my price"? Would it make a difference in their decision making?
As of now, the consumers are very static. Is there a way to make the consumers move as well or react in some manner to the competition amongst the stores?
Is there a way to include consumer limitations in their spending power or ability to travel? That is, if all stores charge too much or are too far, the consumer could refuse to go to any store.
In this model, if two or more stores are identical from a consumer's point of view, the consumer will choose to go to one of those at random. If the stores are only slightly different for the consumer, is it possible to have the consumer go to either one?
One can extend this model further by introducing a different layout. How would the patterns change, for example, if the layout of the world were circular? What if we just enable world wrapping?
NETLOGO FEATURES
Each store can move up to four times each tick, as each store takes test steps in cardinal directions when deciding on its moving strategy. However, as this model is tick based, the user only sees one step per store.
Notice, also, how the plot pens are dynamically created in the setup code of each plot. There has to be one pen for each store, but we don't know in advance how many there will be, so we use the
create-temporary-plot-pen
primitive to create the pens we need and assign them the right color upon setup.In the procedures where a store chooses a new price or location, we make use of a subtle property of the
sort-by
primitive: the fact that the order of items in the initial list is preserved in case of ties during sorting. What we do is that we put the "status quo" option at the front of the list possible moves, but shuffle all other possible moves. Because of that, when we sort the moves by potential revenues, the status quo is always preferred in case of equal revenues.What if want to know if all members of a list have a certain property? (This is the equivalent of the "for all" universal quantifier (∀) in predicate logic.) NetLogo doesn't have a primitive to do that directly, but we can easily write it ourselves using the
member?
andmap
primitives. The trick is to first map the list to the predicate we want to test. Let's say we want to test if all members of a list are zeros:map [? = 0] [1 0 0]
will report[false true true]
, andmap [? = 0] [ 0 0 0 ]
will report[true true true]
. All we have to is to test the "for all" condition is to make sure thatfalse
is not a member of that new list:not member? false map [? = 0] [1 0 0]
will reportfalse
, andnot member? false map [? = 0] [0 0 0]
will reporttrue
. We use a variant of this in thenew-price-task
reporter.The procedures for choosing new prices and locations do not actually perform these changes right away. Instead, they report
task
s that will be run later in thego
procedure. Notice how we putself
in a local variable before creating our task: this is because NetLogo tasks "capture" local variables, but not agent context. See the "Tasks" section in the NetLogo Programming guide for more details about this.
RELATED MODELS
- Voronoi
- Voronoi - Emergent
CREDITS AND REFERENCES
Hotelling, Harold. (1929). "Stability in Competition." The Economic Journal 39.153: 41 -57. (Stable URL: http://www.jstor.org/stable/2224214 ).
HOW TO CITE
If you mention this model in a publication, we ask that you include these citations for the model itself and for the NetLogo software:
- Ottino, B., Stonedahl, F. and Wilensky, U. (2009). NetLogo Hotelling's Law model. http://ccl.northwestern.edu/netlogo/models/Hotelling'sLaw. Center for Connected Learning and Computer-Based Modeling, Northwestern Institute on Complex Systems, Northwestern University, Evanston, IL.
- Wilensky, U. (1999). NetLogo. http://ccl.northwestern.edu/netlogo/. Center for Connected Learning and Computer-Based Modeling, Northwestern Institute on Complex Systems, Northwestern University, Evanston, IL.
COPYRIGHT AND LICENSE
Copyright 2009 Uri Wilensky.
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
Commercial licenses are also available. To inquire about commercial licenses, please contact Uri Wilensky at uri@northwestern.edu.
Comments and Questions
turtles-own [ price ; How much each store charges for its product area-count ; The area (market share) that a store held at the end of the last tick ] patches-own [ preferred-store ; The store currently preferred by the consumer ] globals [ consumers ; The patches that will act as consumers, either a vertical line or all patches ] ;;;;;;;;;;;;;;;;;;;;;;;; ;;; setup procedures ;;; ;;;;;;;;;;;;;;;;;;;;;;;; to setup clear-all setup-consumers setup-stores recalculate-area reset-ticks end to setup-consumers ; Store the agentset of patches that are going to be our ; consumers in a global variable for easy reference. set consumers ifelse-value (layout = "line") [ patches with [ pxcor = 0 ] ] [ patches ] end to setup-stores ; We choose as many random colors as the number of stores we want to create foreach n-of number-of-stores base-colors [ ; ...and we create a store of each of these colors on random consumer patches ask one-of consumers [ sprout 1 [ set color ? ; use the color from the list that we are looping through set shape "Circle (2)" set size 2 set price 10 set pen-size 5 ] ] ] end to go ; We accumulate location and price changes as list of tasks to be run later ; in order to simulate simultaneous decision making on the part of the stores let location-changes ifelse-value (rules = "pricing-only") [ (list) ] ; if we are doing "pricing-only", the list of moves is empty [ [ new-location-task ] of turtles ] let price-changes ifelse-value (rules = "moving-only") [ (list) ] ; if we are doing "moving-only", the list of price changes is empty [ [ new-price-task ] of turtles ] foreach location-changes run foreach price-changes run recalculate-area tick end to recalculate-area ; Have each consumer (patch) indicate its preference by ; taking on the color of the store it chooses ask consumers [ set preferred-store choose-store set pcolor ([ color ] of preferred-store + 2) ] ask turtles [ set area-count count consumers with [ preferred-store = myself ] ] end ;;;;;;;;;;;;;;;;;;;;;;;;; ;;; turtle procedures ;;; ;;;;;;;;;;;;;;;;;;;;;;;;; ; Have the store consider the benefits of taking a unit step in each of the four cardinal directions ; and report a task that will allow the chosen location change to be enacted later to-report new-location-task ; we want the neighbors4 in random order, but we want to turn them from an agentset to a list ; and `sort` is the way to do that, hence the weird `shuffle sort` expression let possible-moves shuffle sort (neighbors4 with [ member? self consumers ]) if area-count > 0 [ ; Only consider the status quo if we already have a market share, but if we consider it, ; put it at the front of the list so it is favored in case of ties in sort-by set possible-moves fput patch-here possible-moves ] ; pair the potiental moves with their revenues, and sort these pairs by revenues let moves-with-market-shares sort-by [ last ?1 > last ?2 ] map [ list ? (market-share-if-move-to ?) ] possible-moves ; report the first item of the first pair, i.e., the move with the best revenues let chosen-location first first moves-with-market-shares let store self ; put self in a local variable so that it can be "captured" by the task report task [ ask store [ pen-down move-to chosen-location pen-up ] ] end ; report the market share area the store would have if it moved to destination to-report market-share-if-move-to [ destination ] ; turtle procedure let current-position patch-here move-to destination let market-share-at-destination potential-market-share move-to current-position report market-share-at-destination end to-report potential-market-share report count consumers with [ choose-store = myself ] end ; Have the store consider the revenue from hypothetically increasing or decreasing its price by one unit ; and report a task that will allow the chosen price change to be enacted later to-report new-price-task ; We build a list of candidate prices, keeping the status quo in first, but having -1 and +1 in random ; order after that. This order is going to be preserved by the `sort-by` primitive in case of ties, ; and we always want the status quo to win in this case, but -1 and +1 to have equal chances let possible-prices fput price shuffle list (price - 1) (price + 1) ; pair each potential price change with its potential revenue ; and sort them in decreasing order of revenue let prices-with-revenues sort-by [ last ?1 > last ?2 ] map [ list ? (potential-revenue ?) ] possible-prices let all-zeros? (not member? false map [ last ? = 0 ] prices-with-revenues) let chosen-price ifelse-value (all-zeros? and price > 1) [ price - 1 ] ; if all potential revenues are zero, the store lowers its price as an emergency procedure if it can [ first first prices-with-revenues ] ; in any other case, we pick the price with the best potential revenues let store self ; put self in a local variable so that it can be "captured" by the task report task [ ask store [ set price chosen-price ] ] end to-report potential-revenue [ target-price ] let current-price price set price target-price let new-revenue (potential-market-share * target-price) set price current-price report new-revenue end ;;;;;;;;;;;;;;;;;;;;;;; ;;; patch procedure ;;; ;;;;;;;;;;;;;;;;;;;;;;; ; report the store with the best deal, defined as the smallest sum of price and distance to-report choose-store report min-one-of turtles [ (price) + (distance myself) ] end ; Copyright 2009 Uri Wilensky. ; See Info tab for full copyright and license.
There are 4 versions of this model.
Attached files
File | Type | Description | Last updated | |
---|---|---|---|---|
Hotelling's Law.png | preview | Preview for 'Hotelling's Law' | over 12 years ago, by Uri Wilensky | Download |
Hotelling's Law.png | preview | Preview for 'Hotelling's Law' | over 12 years ago, by Uri Wilensky | Download |
This model does not have any ancestors.
This model does not have any descendants.