Economic Models, Fall 2019 |
In this lab, we will be exploring a dataset from an electricity strategy game that looks at porfolio of different power plants, companies, and their outputs. We will integrating data science into environmental economics.
from datascience import *
import pandas as pd
import numpy as np
from ipywidgets import interact, Dropdown, IntSlider
import ipywidgets as widgets
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from utils import *
%matplotlib inline
plt.rcParams["figure.figsize"] = [10,6]
In the cell below, we load in the ESG table and sort it by the total variable cost column, Total_Var_Cost_USDperMWH
. The result is stored as ESG_sorted
.
ESG = Table.read_table("ESGPorfolios_forcsv.csv")
ESG_sorted = ESG.sort("Total_Var_Cost_USDperMWH")
ESG_sorted
Now that we have sorted our table by the Total_Var_Cost_USDperMWH
," we want to see the distribution of Capacity_MW
and Variable Cost for a particular group. For a given capacity, what is the variable cost, or cost of producing an additional unit of capacity?
From the ESG_sorted table above, we are grouping by the first column, Group, to look closer at a particular energy source.
Select a particular group in the dropdown below to see a barchart of Variable Cost vs. Capacity_MW.
interact(lambda selection: plot_group(selection, ESG_sorted), selection=Dropdown(options=np.unique(ESG["Group"])));
Suppose we are not concerned with one particular group, and we want to see the distribution of Variable Cost for Capacity_MW across all energy sources. Each color shows a unique group below.
How do Capacity and Variable Cost affect each other? In order to determine the market price of energy in our simulation, demand is an important factor. We don't know exactly how much energy will be demanded in a given frame of time, however we can make predictions based off of estimates that we are given, and use those predictions to calculate the profitability of our plants.
We calculate the maximum variable cost companies can have in order to make profit based on an estimated demand. For now, we will make the assumption that plants are willing to sell at a price equal to their variable cost.
In the graph below, we added the vertical line for demand and horizontal line for variable cost cap. Since we have our power plants in order of lowest variable cost to highest variable cost, we can see that the companies to the left of the vertical demand line will produce energy while the companies to the right of the vertical demand line will choose not to.
Why? The market will purchase energy from the power plants that have the cheapest prices, and we have graphed the cumulative energy production of companies ordered by increasing variable cost of production.
Adjust the slider for demand and notice how the lines, price, and Capacity_MW are changing as a result.
interact(lambda demand: all_groups_with_demand(demand, ESG_sorted),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));
Now that we have seen how Capacity_MV
, Variable Cost
, and price change as demand changes, we want to be able to calculate profit generated by a given power plant.
Run the profit function and the interactive slider in the next two cells below to see how profit changes with demand for each power plant.
def profit(sorted_table, price):
capacity_subset = sum(sorted_table.where("Total_Var_Cost_USDperMWH", are.below(price))["Capacity_MW"])
revenue = capacity_subset * price
cost = 0
for i in range(len(sorted_table.where("Total_Var_Cost_USDperMWH", are.below(price))["Total_Var_Cost_USDperMWH"])):
cost += sorted_table.where("Total_Var_Cost_USDperMWH", are.below(price))["Total_Var_Cost_USDperMWH"][i]\
* sorted_table.where("Total_Var_Cost_USDperMWH", are.below(price))["Capacity_MW"][i]
return revenue - cost
interact(lambda selection, demand: calc_profit(selection, demand, ESG_sorted),
selection=Dropdown(options=np.unique(ESG_sorted["Group"])),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));
Now, we will give the electric plants the ability to set their own prices.
(Note: These bids actually comes from students in EEP 147 who played a simulation of the game)
Below, we load in the bids
table with data on energy price bids for different plants and hours.
bids = Table.read_table("S1_bids_0.csv")
bids
With a new bids table with data on energy price bids across different power plants and hours, we will now calculate and visualize the profit our power plants have generated in a given hour. We will also be able to calculate cumulative profits, emissions, and expected emissions.
We will need both of these tables, bids and ESG_sorted, to generate an analysis, so in the following cell we first join the tables based on the column Plant_ID.
In the visualization below, we see how price varies with Capacity for a given hour for all power plants from above as demand changes.
# make sorted joined table
hour = 2
sorted_joined_table = bids.join("PLANT_ID", ESG_sorted, "Plant_ID").sort("PRICE" + str(hour))
sorted_joined_table
interact(lambda demand, hour: all_group_bids(demand, hour, sorted_joined_table),
demand=IntSlider(value=20000, min=0, max=23000, step=1000), hour=Dropdown(options=[1,2,3,4]));
Now, let's examine a particular power plant for a given hour.
interact(lambda selection, hour, demand: your_portfolio_plot(selection, hour, demand, sorted_joined_table),
selection=Dropdown(options=np.unique(ESG_sorted["Group"])), hour=Dropdown(options=[1,2,3,4]),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));
Given a power plant, hour, and demand, we have the market price. Below, we explore how we compare market price of energy for a given hour to the marginal costs of running the power plant plants that have a bid price that was below the market price.
interact(lambda selection, hour, demand: marginal_cost_plot(selection, hour, demand, sorted_joined_table),
selection=Dropdown(options=np.unique(ESG_sorted["Group"])), hour=Dropdown(options=[1,2,3,4]),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));
interact(lambda selection, hour, demand: calc_profit_by_hour(selection, hour, demand, sorted_joined_table),
selection=Dropdown(options=np.unique(ESG_sorted["Group"])), hour=Dropdown(options=[1,2,3,4]),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));
Below, we have the cumulative capacity for our plants when they were selling at cost.
ESG_sorted.select("Group", "UNIT NAME", "Capacity_MW", "Total_Var_Cost_USDperMWH").with_column(
"Cumulative capacity", np.cumsum(ESG_sorted.column("Capacity_MW"))).where("Cumulative capacity", are.above(12000))
If we order this by the price in the first hour, the cumulative supply looks a bit different. As some producers price their electricity higher, for the same demand we have higher prices. Or for the same price, we have fewer sellers and lesser supply
sorted_by_price = sorted_joined_table.sort("PRICE1").select("Group", "UNIT NAME", "Capacity_MW", "Total_Var_Cost_USDperMWH", "PRICE1")
sorted_by_price.with_column("Cumulative capacity", np.cumsum(sorted_by_price.column("Capacity_MW"))).where("Cumulative capacity", are.above(12000))
Notice the plants REDONDO 5&6 and REDONDO 7&8. When the demand was 15000 and sellers sold at cost, the market price was 41.94 and they supplied electricity.
But when plant owners set their own prices, REDONDO 5&6 and REDONDO 7&8 sold at 50 and 48 respectively. As the same 15000 level of demand, the market price rose to 45. REDONDO 5&6 and REDONDO 7&8 are not able to sell anymore.
It seems that if I bid at marginal cost, I still get full market price and get to sell. But if I bid higher, there may be a chance that I am priced out of the market.
It seems to reason that I will bid at cost then becuase I cannot make a profit if I cant sell. What if I told you that was not the case.
This is what led to the California Electricity Crisis of 2001. Certain plant providers, like Enron, started turning off some of their power plants for 'maintainence' in peak hours. What that did was restrict supply. Done carefully, it can raise prices and boost profits by a lot.
You can read about it here
Let us understand the intuition behind it.
Consider a situation in which the plants REDONDO_7-8 and REDONDO_5-6 of Big Coal portfolio are turned off.
sorted_joined_table.where("PORTFOLIO", 'Big_Coal')
sorted_joined_table_no_coal = sorted_joined_table.where(
"PLANT", are.not_equal_to("REDONDO_7-8")).where(
"PLANT", are.not_equal_to("REDONDO_5-6"))
sorted_joined_table_no_coal.where("PORTFOLIO", 'Big_Coal')
When the plants were present, this is what the market equllibrium looked like.
interact(lambda demand, hour: all_group_bids(demand, hour, sorted_joined_table),
demand=IntSlider(value=20000, min=0, max=23000, step=1000), hour=Dropdown(options=[1,2,3,4]));
Without the two coal plants removed - note the missing bars around 15000 capcity - the market looks like this. The price has increased for the same demand).
interact(lambda demand, hour: all_group_bids(demand, hour, sorted_joined_table_no_coal),
demand=IntSlider(value=20000, min=0, max=23000, step=1000), hour=Dropdown(options=[1,2,3,4]));
This is the old profit, when all plants were operating.
interact(lambda selection, hour, demand: calc_profit_by_hour(selection, hour, demand, sorted_joined_table),
selection=Dropdown(options=np.array(['Big Coal', 'Bay Views', 'Beachfront', 'Big Gas', 'East Bay',
'Fossil Light', 'Old Timers'])), hour=Dropdown(options=[1,2,3,4]),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));
By strategically turning off a plant with high variable cost, the portfolio provider can increase price to an extent that the extra profit generated by lower variable cost plants is much more than the profit lost on the switched off plant.
interact(lambda selection, hour, demand: calc_profit_by_hour(selection, hour, demand, sorted_joined_table_no_coal),
selection=Dropdown(options=np.array(['Big Coal', 'Bay Views', 'Beachfront', 'Big Gas', 'East Bay',
'Fossil Light', 'Old Timers'])), hour=Dropdown(options=[1,2,3,4]),
demand=IntSlider(value=20000, min=0, max=23000, step=1000));