The world’s leading publication for data science, AI, and ML professionals.

Economics for Tech People – Equilibrium (Part 3)

Learn about the Economics of Equilibrium using R and ggplot2

Photo by rupixen.com on Unsplash
Photo by rupixen.com on Unsplash

Welcome to part 3 of the Economics for Tech People series!

This series is intended to help people better understand fundamental principles of economics while also building up skills with the R language. I have noticed there is quite a bit of misunderstanding and outright misinformation about economics, so the goal here is to clear up these fuzzy areas.

In this article, we will explore questions such as: What is equilibrium? How can I find the equilibrium price and quantity? What happens when the numbers do not work out perfectly? How much revenue can I expect to make?

This article assumes that you have a good understanding of both previous parts, which are Economics for Tech People – Demand (Part 1) and Economics for Tech People – Supply (Part 2). Much of the content here builds on code from those two articles as well as explanations of the concepts. Take a look at those before getting started here if you want the full story.


Introduction

Today we will explore the intersection of the demand and supply data using the R programming language using some realistic data I created in Excel files.

Just like in the previous two articles, we are using 10 hypothetical suppliers and 10 hypothetical demanders of a Software as a Service (SaaS) application that charges a monthly fee for each license. The firms are a good mix of sizes and all have different elasticities.

The goal here is to overlay the curves on each other and find at what price and at what quantity the market will be at equilibrium

For this work, I am using RStudio on a Mac with an R Markdown (RMD) file.

All the data and the finished RMD file can be obtained at my GitHub here.

Note: If you are on a Mac, DO NOT OPEN THESE FILES WITH NUMBERS. It will automatically change the encoding of the file, and R will not be happy with you. If it happens, delete the file you opened and download a fresh copy of the Excel file.

I am also assuming that you can manage to get an R environment setup on your local machine. It is best to put all your files in a single folder to simplify the workflow.

Let’s get started!


The Code

Load Libraries

To work through the project, we will need the readxl and tidyverse packages. If you do not have them installed in your R environment, simply remove the "#" sign before the "install.packages…" lines of code [1]. Once they are installed, you will not need to install them again on your machine.

Here’s the code:

# Load Libraries
# If you do not have readxl or tidyverse installed, uncomment the following lines
# install.packages("readxl")
# install.packages("tidyverse")
require(readxl)
require(tidyverse)

Check Data Import

Sometimes Excel files are temperamental when they interact with R, especially RStudio. Use the code below to import the data and check it looks okay.

Here’s the code:

# Import Data
Supply_Data <- read_excel("Supply_Data.xlsx")
Demand_Data <- read_excel("Demand_Data.xlsx")
# Check data import
head(Supply_Data)
head(Demand_Data)

Here’s the output:

Screenshot of the first six rows of our supply data in RStudio on a Mac
Screenshot of the first six rows of our supply data in RStudio on a Mac
Screenshot of the first six rows of our demand data in RStudio on a Mac
Screenshot of the first six rows of our demand data in RStudio on a Mac

If you import the data and the Price and Qs#_ or Qd#_ are not the column labels and are the first row of data, you will probably see something like X1 X2 X3, etc. as the column headings. If that happens, you can follow the same process that I described in "The Setup" section of the previous article Economics for Tech People – Demand (Part 1). That will show you how to import the data a different way to get caught up to this article.

Get Caught Up

Once the data is in, it is time to copy/paste the relevant parts of the demand and supply code to do the heavy lifting. Please reference those two articles for explanations of what the following code chunks do.

Here’s the code:

# Demand first
# Wrangle the data into a ggplot friendly structure
Wrangled_Data <- data.frame(Price = Demand_Data$Price, stack(Demand_Data[2:11]))
names(Wrangled_Data)[2] <- "Quantity"
names(Wrangled_Data)[3] <- "Qd_num"
# Create market demand
Market_Demand <- data.frame(Price = Demand_Data$Price, Market_Demand = rowSums(Demand_Data[2:11]))
# Add Elasticity zones
# 10-6.5 zone 1
# 6-4 zone 2
# 3.5-2 zone 3
# 1.5-0 zone 4
Market_Demand$Elasticity_Zone <- as.character(c(1,1,1,1,1,1,1,1,2,2,2,2,2,3,3,3,3,4,4,4,4))
# Supply second
# Wrangle the data into a ggplot friendly structure
Wrangled_Supply_Data <- data.frame(Price = Supply_Data$Price, stack(Supply_Data[2:11]))
names(Wrangled_Supply_Data)[2] <- "Quantity"
names(Wrangled_Supply_Data)[3] <- "Qs_num"
# Create market supply
Market_Supply <- data.frame(Price = Supply_Data$Price, Market_Supply = rowSums(Supply_Data[2:11]))
# Add Elasticity zones
# 10-5.5 zone 1
# 5-4 zone 2
# 3.5-0 zone 3
Market_Supply$Elasticity_Zone <- as.character(c(1,1,1,1,1,1,1,1,1,1,2,2,2,3,3,3,3,3,3,3,3))

Plot the Equilibrium

Now that we are caught up, let’s plot the equilibrium.

To do this, we will use ggplot again. What is a little different this time around is that the code will start with just ggplot() as the first line then add lines one at a time to build the graph rather than merging everything together first and trying to do this the hard way.

The _geomline() parts of the code will declare which data we use, the aesthetics, and assign a couple other styling parameters. To make it look nice, this code adds vertical and horizontal lines to outline the axes. I also decided to limit the data range with the xlim() function because we will not really be doing anything with the data too far to the right. Finally, some labels are applied.

Here’s the code:

# Plot the equilibrium
ggplot() +
  geom_line(data = Market_Supply, aes(x = Market_Supply, y = Price), color = "darkred", size = 1) +
  geom_line(data = Market_Demand, aes(x = Market_Demand, y = Price), color = "steelblue", size = 1) +
  geom_vline(xintercept = 0) +
  geom_hline(yintercept = 0) +
  xlim(0, 1150) +
  ggtitle("Market Equlibrium") +
  theme(plot.title = element_text(hjust = 0.5)) + 
  xlab("Quantity") +
  ylab("Price")

Here’s the output:

Screenshot of the equilibrium plot in RStudio on a Mac
Screenshot of the equilibrium plot in RStudio on a Mac

Well, it worked!

In economics markets that function well work at the equilibrium point where the quantity demanded equals the quantity supplied [2]. This is also often called the market clearing price because all the quantity that is produced is demanded [2]. In other words, there is no shortage and there is no surplus [2].

Just eyeballing the plot up there, it looks like we have a market clearing price of about $6 and an equilibrium quantity of about 200 units.

Let’s confirm those numbers and get a little more precise.

Find the Equilibrium Quantity and Market Clearing Price

First, let me say that there are probably at least a dozen ways we could go about doing this. For the sake of simplicity and keeping this article’s scope digestible, we are going to avoid calculus and take a "good enough for who it’s for" approach with this that will yield a virtually identical answer anyway.

Unrelated note: The phrase "good enough for who it’s for" is a quote from a contractor who did work on my house when I was a kid. It was a little concerning, but as far as I know the house is still in one piece!

Either way, we will adopt that mindset here.

We will start by joining the demand and supply data together into a single object. I am a fan of using the _innerjoin() function and joining by Price because of its simplicity. It also makes sure everything lines up properly. Since we have a column in common with the same number of rows in each data set, _innerjoin() basically acts as a "zipper" function to combine the data sets into a clean product [1].

Here’s the code:

# Join data together
Merged_data <- inner_join(Market_Demand, Market_Supply, by = "Price")
head(Merged_data)

Here’s the output:

Screenshot of the _innerjoin() function merging demand and supply data in RStudio on a Mac
Screenshot of the _innerjoin() function merging demand and supply data in RStudio on a Mac

Next, let’s find where the difference between the quantity supplied and the quantity demanded is smallest. We know they intersect because of the graph, so the price that corresponds to the smallest difference should be our market clearing price [2].

Here’s the code:

# Find difference between supply and demand
Merged_data$Diff <- Merged_data$Market_Supply - Merged_data$Market_Demand
Merged_data

Here’s the output:

Screenshot of the merged data with the column of quantity difference in RStudio on a Mac
Screenshot of the merged data with the column of quantity difference in RStudio on a Mac

Just looking at the chart, we can tell we’re looking at a minimum difference of 8 units at a price of $6. However, what happens if we have a lot more data and it is hard to tell visually?

We can use a combination of the which.min() function to find the minimum value we want, which is Diff in this case, and the abs() function to get the absolute value of Diff [1]. The absolute value function is necessary because rather than giving us the minimum difference as the smallest distance from 0 like we want, we will get the biggest negative number which is no what we want [1]. The comma at the end of the function specifies that we want the entire row [1]. For bonus points, try removing it and see what is different.

Here’s the code:

# Find row of minimum difference
min_diff_1 <- Merged_data[which.min(abs(Merged_data$Diff)),]
min_diff_1

Here’s the output:

Screenshot of the row with the smallest difference between demand and supply in RStudio on a Mac
Screenshot of the row with the smallest difference between demand and supply in RStudio on a Mac

Modify the Plot

Next, we are going to modify the plot from earlier in a couple ways. First, let’s change the values in the xlim() function so that we can zoom in closer to the intersection. Second, we can add some lines at the market clearing price and the quantities on top of the plot to better visualize our data. An advantage of creating a dedicated object for the minimum difference row is that we can reference it here rather than having to manually enter numbers or redo the calculation another way.

Here’s the code:

# Add lines to previous graph based on the min_diff_1 numbers
# Plot the equilibrium
ggplot() +
  geom_line(data = Market_Supply, aes(x = Market_Supply, y = Price), color = "darkred", size = 1) +
  geom_line(data = Market_Demand, aes(x = Market_Demand, y = Price), color = "steelblue", size = 1) +
  geom_vline(xintercept = 0) +
  geom_hline(yintercept = 0) +
  xlim(0, 300) + ### ZOOM IN A BIT ###
  ggtitle("Market Equlibrium") +
  theme(plot.title = element_text(hjust = 0.5)) + 
  xlab("Quantity") +
  ylab("Price") +
  ### NEW LINES ###
  geom_hline(yintercept = min_diff_1$Price) +
  geom_vline(xintercept = min_diff_1$Market_Demand) + 
  geom_vline(xintercept = min_diff_1$Market_Supply)

Here’s the output:

Screenshot of the equilibrium plot with the market clearing price and approximate equilibrium quantity lines in RStudio on a Mac
Screenshot of the equilibrium plot with the market clearing price and approximate equilibrium quantity lines in RStudio on a Mac

So, why are there two vertical lines instead of one?

Well, it turns out that the precision of our data leads to a small issue where we do not have an exactly perfect intersection. I chose to put both on the graph to make this obvious.

What this is saying is that we should feel pretty good about saying the market clearing price is $6 and the true equilibrium quantity is in somewhere in the 190s [2].

Total Revenue

One of the most straightforward calculations in economics is total revenue because it is simply the price times the total number of units [2]. When at equilibrium, it is the market clearing price and the quantity demanded at that price [2].

For us, we will again reference the _min_diff1 object because that has our market clearing price along with the quantities. In the calculation, I chose to just average the demand and supply numbers together with the mean() function and multiply by the price. That gets us a total revenue of $1,134 in this market.

Here’s the code:

# Calculate total revenue
total_revenue <- min_diff_1$Price * mean(min_diff_1$Market_Demand, min_diff_1$Market_Supply)
total_revenue

Here’s the output:

Screenshot of the total revenue in our market at the market clearing price in RStudio on a Mac
Screenshot of the total revenue in our market at the market clearing price in RStudio on a Mac

Compare and Contrast Results

Remember from the previous two parts of this series that from the suppliers want to sell more units at higher prices.

For the data we had, the maximum total revenue on the supply side was $9,840 when selling at a price of $10 per unit.

On the demand side, we had a maximum total revenue of $1,850 at a price of $2.50 per unit.

When we combine our data in a real market scenario, the compromise of markets becomes clear. The market clearing price is where suppliers and demanders of goods and services meet in the middle [2]. Sure, neither side is getting their ideal or theoretical maximum, but that is just the nature of the market.

Bonus Material – Predict the Equilibrium with Linear Models

It’s all well and good that we have data that lines up pretty closely, but what happens if we are working with something a bit more theoretical? We know that from this article and the previous two that we have various zones of elasticity. Graphically, we can see that a price pretty close to $6 is right, but what if we only had a couple data points and good guess at the slope of the lines?

We will borrow some code from the first article and create some linear models to see where they intersect. What matters here is that we have our models explaining 99%+ of our dependent variable and the predictive ability is significant because we have very small p-values in both of the following models [1].

For a more complete discussion of what R-squared values and p-values mean, please refer to the previous two articles because I assume here that you know what I mean when I reference those values.

When we look at our _min_diff1 object, we can see that demand falls in elasticity zone 2 and supply is in zone 1. We only need to build linear models with that in mind.

Let’s start with supply.

Here’s the code:

# Create Supply linear model
# Filter Data
supply_lm_data <- Market_Supply %>%
  filter(Elasticity_Zone == 1)
# Create linear model
Supply_Zone_1_lm <- lm(Market_Supply ~ Price, data = supply_lm_data)
# Create and print summary
summary(Supply_Zone_1_lm)

Here’s the output:

Screenshot of the summary statistics for the linear model of the supply data in zone 1 in RStudio on a Mac
Screenshot of the summary statistics for the linear model of the supply data in zone 1 in RStudio on a Mac

Demand is next.

Here’s the code:

# Create Demand linear model
# Filter Data
demand_lm_data <- Market_Demand %>%
  filter(Elasticity_Zone == 2)
# Create linear model
Demand_Zone_2_lm <- lm(Market_Demand ~ Price, data = demand_lm_data)
# Create and print summary
summary(Demand_Zone_2_lm)

Here’s the output:

Screenshot of the summary statistics for the linear model of the demand data in zone 2 in RStudio on a Mac
Screenshot of the summary statistics for the linear model of the demand data in zone 2 in RStudio on a Mac

Next, we will create a quick sequence of numbers to be our new price data. I am going from a price of $5.50 to $6.50 because that range fits neatly into the same elasticity zone for both supply and demand data. We will increment it by $0.01, so we can get much more precise.

Here’s the code:

# new price data
new_price_data <- data.frame(Price = seq(5.5, 6.5, by = 0.01))

After that, we will use the classic algebra formula y=mx + b to put together our new data. That will add the intercept from our linear models plus the slope coefficient times the price into a big data frame. We can also round this to 0 decimal places with the round() function because if you think about it, it does not make a lot of sense to sell a fraction of a SaaS subscription.

Here’s the code:

# Use coefficient and intercept data to fill in the blanks
# Supply first
new_price_data$supply <- round(Supply_Zone_1_lm$coefficients[1] + (Supply_Zone_1_lm$coefficients[2] * new_price_data$Price), 0)
# Demand next
new_price_data$demand <- round(Demand_Zone_2_lm$coefficients[1] + (Demand_Zone_2_lm$coefficients[2] * new_price_data$Price), 0)

Finally, we will do the same basic subtraction to get a Diff column of the difference between demand and supply like we did earlier. We can also check the data to make sure it came out right.

Here’s the code:

# Find difference between supply and demand
new_price_data$Diff <- new_price_data$demand - new_price_data$supply
# Check the data
head(new_price_data)

Here’s the output:

Screenshot of the table of predicted supply, demand, and difference values for each new price in RStudio on a Mac
Screenshot of the table of predicted supply, demand, and difference values for each new price in RStudio on a Mac

To wrap it up, we will find the smallest absolute value difference again to get a price and quantity.

Here’s the code:

# Find row of minimum difference
min_diff_2 <- new_price_data[which.min(abs(new_price_data$Diff)),]
min_diff_2

Here’s the output:

Screenshot of the minimum absolute value difference in our predicted data in RStudio on a Mac
Screenshot of the minimum absolute value difference in our predicted data in RStudio on a Mac

The result here is very similar. The predicted values give a market clearing price of $6.02 and an equilibrium quantity of 184 or 185, which is very close to the real data.

In the reality of business, this is great support to just set the price to $6 and be happy with the result.


Conclusion

After making your way through all three articles, hopefully you have a much better understanding about what supply, demand, and equilibrium mean in an economic sense. Specifically for the equilibrium here, much our work was more graphical than mathematical because we did so much of the number crunching in the previous articles. Fortunately, we have built a solid case for having a market clearing price of $6 and being very likely to sell just under 200 units of the SaaS product. In real life, the numbers can fluctuate a bit either direction, but this is certainly a realistic outcome for an equilibrium analysis starting with raw data from individuals working all the way up to understanding the entire efficiently operating market for a service.


References

[1] R. Kabacoff, R in Action (2nd ed.) (2015), Shelter Island, NY: Manning Publications Co.

[2] F. Mishkin, The Economics of Money, Banking, & Financial Markets (9th ed.) (2010), Boston, MA: Pearson Education, Inc.


Related Articles