This notebook expands upon the ESG Analysis notebook and can be utilized for analysis of rounds in the Electricity Strategy Game. Any of the following code can be changed to create new or different visualizations. Running through the cells will allow you to visualize and calculate the profit that your plants have generated in a given hour. You will also be able to calculate cumulative profits, emissions, and expected emissions for rounds 4-6, incorporating user-specified permit prices.
First on our agenda is to import dependencies -- packages in Python that add to the basic functions in Python -- same as before.
from datascience import *
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import pandas as pd
plt.style.use('fivethirtyeight')
Next, let's select your portfolio. In the cell below, assign section to the section number that corresponds to your own according to the following table.
Number | Section Time |
---|---|
1 | Wednesday 8am |
2 | Friday 2pm |
3 | Wednesday 9am |
4 | Friday 4pm |
Assign YOUR_PORTFOLIO to the name of your portfolio from the following choices:
'Bay Views', 'Beachfront', 'Big Coal', 'Big Gas', 'East Bay', 'Fossil Light', 'Old Timers'
The variable periods_completed should be set to the most recently concluded round.
The variable period should be set to the round that you want to analyze.
The variable hour should be set to the hour that you want to analyze.
The variable permit_price_456 should contain expected (or realized) permit prices in each of rounds [4, 5, 6].
The variable rps_demand_reduction_6 should contain expected demand reductions (as a fraction) in each of hours [1, 2, 3, 4] due to the RPS standard that will be implemented in Round 6.
The variable pab_periods should contain each of the periods for which there was or will be a pay-as-bid auction. This shouldn't change.
The variable bid_own_as_mc should be set to True if you would like to default future bids for your own portfolio to marginal cost (inclusive of permit price for rounds 4, 5, and 6). If setting to False, you would need to go into the respective bids_.CSV file and manually edit other teams' future bids. Note that even if True, the current round and hour's bids can be adjusted manually down below (after the bids_ variables are defined).
The variable bid_others_as_mc should be set to True if you would like to default future bids for other teams' portfolios to marginal cost (inclusive of permit price for rounds 4, 5, and 6). If setting to False, you would need to go into the respective bids_.CSV file and manually edit other teams' future bids.
section = 1
YOUR_PORTFOLIO = "Big Coal"
periods_completed = 3
period = 3
hour = 4
permit_price_456 = [10, 10, 10]
rps_demand_reduction_6 = [.15, .15, .15, .15]
pab_periods = [1]
bid_own_as_mc = True
bid_others_as_mc = True
Next we import the demand realizations (for past rounds) and forecasts (for future rounds) and assign the current demand.
demand_table = Table.read_table('demand.csv')
demand_df = demand_table.to_df()
for hour_i in [1,2,3,4]:
demand_df.loc[(demand_df["round"] == 6) & (demand_df["hour"] == hour_i), "load"] *= (1 - rps_demand_reduction_6[hour_i-1])
demand_table = Table.from_df(demand_df)
demand = demand_table.where("round", period).where("hour", hour)["load"].item()
print(demand)
16522.86029
Next we import our tables, one for each discussion section, and one with individualized information about our plants.
bids_1 = Table.read_table('S1_bids.csv').sort('PORTFOLIO')
bids_2 = Table.read_table('S2_bids.csv').sort('PORTFOLIO')
bids_3 = Table.read_table('S3_bids.csv').sort('PORTFOLIO')
bids_4 = Table.read_table('S4_bids.csv').sort('PORTFOLIO')
ESG = Table.read_table('ESGPorfolios_forcsv.csv')
The following will print a list of plants available at the current portfolio. Creating a vector of the
bids_1.where("PERIOD", period).where("PORTFOLIO", YOUR_PORTFOLIO.replace(" ", "_")).sort("PLANT_ID").to_df()[["PLANT"]]
PLANT | |
---|---|
0 | Four Corners |
1 | ALAMITOS_7 |
2 | HUNTINGTON_BEACH_1-2 |
3 | HUNTINGTON_BEACH_5 |
4 | REDONDO_5-6 |
5 | REDONDO_7-8 |
Now we can decide if we want to change the bids of our plants in the current hour for the current round. To change your team's bids for the current round and hour, set edit_bids = True. Then set new_bids to be a vector of bid prices of plants in the order listed in the above table. For example, if my portfolio has four plants, I should see four rows in the above table, numbered 0 through 3. I should set new_bids equal to a vector of length 4, with the first element refering to the bid for the plant in row 0, the second element refering to the bid for the plant in row 1, etc.., separated by commas so my vector would have:
[plant_0_bid, plant_1_bid, plant_2_bid, plant_3_bid]
edit_bids = False
new_bids = []
The table below should now display the names and bids that your team and others in your discussion section assigned for each individual plant.
bids = globals()['bids_' + str(section)]
bids.where("PERIOD", period).show()
TEAM | TEAM_ID | PORTFOLIO | PORTFOLIO_ID | PLANT | PLANT_ID | PERIOD | PRICE1 | PRICE2 | PRICE3 | PRICE4 |
---|---|---|---|---|---|---|---|---|---|---|
Debreu | 4 | Bay_Views | 3 | MORRO_BAY_1-2 | 31 | 3 | 38.78 | 38.78 | 38.78 | 38.78 |
Debreu | 4 | Bay_Views | 3 | MORRO_BAY_3-4 | 32 | 3 | 36.61 | 36.61 | 36.61 | 36.61 |
Debreu | 4 | Bay_Views | 3 | MOSS_LANDING_6 | 33 | 3 | 32.56 | 32.56 | 32.56 | 32.56 |
Debreu | 4 | Bay_Views | 3 | MOSS_LANDING_7 | 34 | 3 | 32.56 | 32.56 | 32.56 | 32.56 |
Debreu | 4 | Bay_Views | 3 | OAKLAND | 35 | 3 | 61.17 | 61.17 | 61.17 | 61.17 |
Arrow | 1 | Beachfront | 4 | COOLWATER | 41 | 3 | 42.39 | 42.39 | 42.39 | 42.39 |
Arrow | 1 | Beachfront | 4 | ETIWANDA_1-4 | 42 | 3 | 42.67 | 42.67 | 42.67 | 42.67 |
Arrow | 1 | Beachfront | 4 | ETIWANDA_5 | 43 | 3 | 62.89 | 62.89 | 62.89 | 62.89 |
Arrow | 1 | Beachfront | 4 | ELLWOOD | 44 | 3 | 75.61 | 75.61 | 75.61 | 75.61 |
Arrow | 1 | Beachfront | 4 | MANDALAY_1-2 | 45 | 3 | 39.06 | 39.06 | 39.06 | 39.06 |
Arrow | 1 | Beachfront | 4 | MANDALAY_3 | 46 | 3 | 52.06 | 52.06 | 52.06 | 52.06 |
Arrow | 1 | Beachfront | 4 | ORMOND_BEACH_1 | 47 | 3 | 38.06 | 38.06 | 38.06 | 38.06 |
Arrow | 1 | Beachfront | 4 | ORMOND_BEACH_2 | 48 | 3 | 38.06 | 38.06 | 38.06 | 38.06 |
Becker | 2 | Big_Coal | 1 | Four Corners | 11 | 3 | 42.38 | 42.38 | 45.5 | 41.94 |
Becker | 2 | Big_Coal | 1 | ALAMITOS_7 | 12 | 3 | 73.73 | 73.72 | 73.72 | 73.72 |
Becker | 2 | Big_Coal | 1 | HUNTINGTON_BEACH_1-2 | 13 | 3 | 42.38 | 42.38 | 45.5 | 41.94 |
Becker | 2 | Big_Coal | 1 | HUNTINGTON_BEACH_5 | 14 | 3 | 66.5 | 66.5 | 66.5 | 66.5 |
Becker | 2 | Big_Coal | 1 | REDONDO_5-6 | 15 | 3 | 42.38 | 42.38 | 45.5 | 41.94 |
Becker | 2 | Big_Coal | 1 | REDONDO_7-8 | 16 | 3 | 42.38 | 42.38 | 45.5 | 41.94 |
Coase | 3 | Big_Gas | 2 | EL_SEGUNDO_1-2 | 21 | 3 | 44.83 | 44.83 | 44.99 | 44.83 |
Coase | 3 | Big_Gas | 2 | EL_SEGUNDO_3-4 | 22 | 3 | 41.22 | 41.22 | 44.99 | 41.22 |
Coase | 3 | Big_Gas | 2 | LONG_BEACH | 23 | 3 | 52.5 | 52.5 | 52.5 | 52.5 |
Coase | 3 | Big_Gas | 2 | NORTH_ISLAND | 24 | 3 | 65.5 | 65.5 | 65.5 | 65.5 |
Coase | 3 | Big_Gas | 2 | ENCINA | 25 | 3 | 41.67 | 41.67 | 44.99 | 41.67 |
Coase | 3 | Big_Gas | 2 | KEARNY | 26 | 3 | 500 | 500 | 500 | 500 |
Coase | 3 | Big_Gas | 2 | SOUTH_BAY | 27 | 3 | 43.83 | 43.83 | 44.99 | 43.83 |
Friedman | 5 | East_Bay | 5 | PITTSBURGH_1-4 | 51 | 3 | 42.97 | 46.97 | 56.97 | 44.97 |
Friedman | 5 | East_Bay | 5 | PITTSBURGH_5-6 | 52 | 3 | 46.97 | 44.45 | 56 | 42.58 |
Friedman | 5 | East_Bay | 5 | PITTSBURG_7 | 53 | 3 | 60 | 60 | 65 | 60 |
Friedman | 5 | East_Bay | 5 | CONTRA_COSTA_4-5 | 54 | 3 | 58.29 | 58.29 | 58.28 | 58.5 |
Friedman | 5 | East_Bay | 5 | CONTRA_COSTA_6-7 | 55 | 3 | 42.97 | 44.5 | 69.97 | 47.21 |
Friedman | 5 | East_Bay | 5 | POTRERO_HILL | 56 | 3 | 69.9 | 69.9 | 69.9 | 69.9 |
Krugman | 7 | Low_Fossil | 7 | HUMBOLDT | 71 | 3 | 47.44 | 47.44 | 47.44 | 47.44 |
Krugman | 7 | Low_Fossil | 7 | HELMS | 72 | 3 | 0.5 | 0.5 | 0.5 | 0.5 |
Krugman | 7 | Low_Fossil | 7 | HUNTERS_POINT_1-2 | 73 | 3 | 49.17 | 49.17 | 49.17 | 49.17 |
Krugman | 7 | Low_Fossil | 7 | HUNTERS_POINT_4 | 74 | 3 | 75.89 | 75.89 | 75.89 | 75.89 |
Krugman | 7 | Low_Fossil | 7 | DIABLO_CANYON_1 | 75 | 3 | 11.5 | 11.5 | 11.5 | 11.5 |
Heckman | 6 | Old_Timers | 6 | BIG_CREEK | 61 | 3 | 0 | 0 | 0 | 0 |
Heckman | 6 | Old_Timers | 6 | MOHAVE_1 | 62 | 3 | 34.5 | 34.5 | 34.5 | 34.5 |
Heckman | 6 | Old_Timers | 6 | MOHAVE_2 | 63 | 3 | 34.5 | 34.5 | 34.5 | 34.5 |
Heckman | 6 | Old_Timers | 6 | HIGHGROVE | 64 | 3 | 49.61 | 49.61 | 80 | 49.61 |
Heckman | 6 | Old_Timers | 6 | SAN_BERNARDINO | 65 | 3 | 53.94 | 53.94 | 100 | 53.94 |
Recall the table ESG, which we used in the introductory notebook. Let's add in the permit cost per MWH using the permit price. This is just the tons per MWH times the permit price. We can add this to variable cost to get a new total variable cost. The first few rows are shown below.
ESG.show(5)
Group | Group_num | UNIT NAME | Capacity_MW | Heat_Rate_MMBTUperMWh | Fuel_Price_USDperMMBTU | Fuel_Cost_USDperMWH | Var_OandM_USDperMWH | Total_Var_Cost_USDperMWH | Carbon_tonsperMWH | FixedCst_OandM_perDay | Plant_ID |
---|---|---|---|---|---|---|---|---|---|---|---|
Big Coal | 1 | FOUR CORNERS | 1900 | 11.67 | 3 | 35 | 1.5 | 36.5 | 1.1 | 8000 | 11 |
Big Coal | 1 | ALAMITOS 7 | 250 | 16.05 | 4.5 | 72.22 | 1.5 | 73.72 | 0.85 | 0 | 12 |
Big Coal | 1 | HUNTINGTON BEACH 1&2 | 300 | 8.67 | 4.5 | 39 | 1.5 | 40.5 | 0.46 | 2000 | 13 |
Big Coal | 1 | HUNTINGTON BEACH 5 | 150 | 14.44 | 4.5 | 65 | 1.5 | 66.5 | 0.77 | 2000 | 14 |
Big Coal | 1 | REDONDO 5&6 | 350 | 8.99 | 4.5 | 40.44 | 1.5 | 41.94 | 0.48 | 3000 | 15 |
... (37 rows omitted)
We will need both of these tables to generate an analysis of how our round went, so in the following cell we will join the tables based on the column Plant_ID. We will then replace any bids as previously assigned with MC or a custom vector for the current hour.
A table will then print listing the plant's original MC, the plant's MC after accounting for the current round's permit price, and the current round's bids that are currently set. Current_Bids should always be set greater than or equal to MC_with_Permit_Price. To experiment with different values for Current_Bids, use the edit_bids and new_bids variables above.
sorted_joined_table_all = bids.join("PLANT_ID", ESG, "Plant_ID").sort("PLANT_ID")
sorted_joined_df_all = sorted_joined_table_all.to_df()
sorted_joined_df_all["Permit_Cost_USDperMWH"] = 0
for i in range(len(permit_price_456)):
sorted_joined_df_all.loc[sorted_joined_df_all["PERIOD"] == i+4, "Permit_Cost_USDperMWH"] = permit_price_456[i] * sorted_joined_df_all["Carbon_tonsperMWH"]
sorted_joined_df_all["Var_Cost_USDperMWH"] = sorted_joined_df_all["Total_Var_Cost_USDperMWH"] + sorted_joined_df_all["Permit_Cost_USDperMWH"]
for hour_i in [1,2,3,4]:
if bid_own_as_mc:
sorted_joined_df_all.loc[(sorted_joined_df_all["Group"] == YOUR_PORTFOLIO) & (sorted_joined_df_all["PERIOD"] > periods_completed), "PRICE" + str(hour_i)] = sorted_joined_df_all.loc[(sorted_joined_df_all["Group"] == YOUR_PORTFOLIO) & (sorted_joined_df_all["PERIOD"] > periods_completed), "Var_Cost_USDperMWH"]
if bid_others_as_mc:
sorted_joined_df_all.loc[(sorted_joined_df_all["Group"] != YOUR_PORTFOLIO) & (sorted_joined_df_all["PERIOD"] > periods_completed), "PRICE" + str(hour_i)] = sorted_joined_df_all.loc[(sorted_joined_df_all["Group"] != YOUR_PORTFOLIO) & (sorted_joined_df_all["PERIOD"] > periods_completed), "Var_Cost_USDperMWH"]
if edit_bids:
sorted_joined_df_all.loc[(sorted_joined_df_all["Group"] == YOUR_PORTFOLIO) & (sorted_joined_df_all["PERIOD"] == period), "PRICE" + str(hour)] = new_bids
sorted_joined_table_all = Table.from_df(sorted_joined_df_all)
sorted_joined_table_all = sorted_joined_table_all.sort("PRICE" + str(hour), descending = False)
sorted_joined_table = sorted_joined_table_all.where("PERIOD", period)
sorted_joined_table.where("Group", YOUR_PORTFOLIO).to_df().rename(columns={'Total_Var_Cost_USDperMWH':'MC_original', 'Var_Cost_USDperMWH':'MC_with_Permit_Price', 'PRICE' + str(hour):'Current_Bids'})[["PLANT", "MC_original", "MC_with_Permit_Price", "Current_Bids"]]
PLANT | MC_original | MC_with_Permit_Price | Current_Bids | |
---|---|---|---|---|
0 | Four Corners | 36.50 | 36.50 | 41.94 |
1 | HUNTINGTON_BEACH_1-2 | 40.50 | 40.50 | 41.94 |
2 | REDONDO_5-6 | 41.94 | 41.94 | 41.94 |
3 | REDONDO_7-8 | 41.94 | 41.94 | 41.94 |
4 | HUNTINGTON_BEACH_5 | 66.50 | 66.50 | 66.50 |
5 | ALAMITOS_7 | 73.72 | 73.72 | 73.72 |
Great! Now we have a table that has our bids for this round as well as information about the capacity of our plants as well as their marginal cost of production. Now we can continue and make plots similar to those in the introductory notebook, that will allow us to examine how we performed in this given hour. Run the following series of cells to generate a legend as well as a plot of plant capacity versus plant bid price, with bids ordered from least to greatest.
def find_x_pos(widths):
cumulative_widths = [0]
cumulative_widths.extend(np.cumsum(widths))
half_widths = [i/2 for i in widths]
x_pos = []
for i in range(0, len(half_widths)):
x_pos.append(half_widths[i] + cumulative_widths[i])
return x_pos
width = sorted_joined_table.column("Capacity_MW")
width
height = sorted_joined_table.column('PRICE' + str(hour))
height
new_x = find_x_pos(width)
energy_colors_dict = {}
count = 0
colors = ['#EC5F67', '#F29056', '#F9C863', '#99C794', '#5FB3B3', '#6699CC', '#C594C5']
for i in sorted(set(sorted_joined_table['Group'])):
energy_colors_dict[i] = colors[count]
count += 1
colors_mapped = list(pd.Series(sorted_joined_table['Group']).map(energy_colors_dict))
sorted_joined_table = sorted_joined_table.with_column('Color', colors_mapped)
plt.figure(figsize=(5,1))
plt.bar(energy_colors_dict.keys(), 1, color = energy_colors_dict.values())
plt.xticks(rotation=60)
plt.title('Legend')
plt.show()
# Make the plot
plt.figure(figsize=(9,6))
plt.bar(new_x, height, width=width, color=sorted_joined_table['Color'], edgecolor = "black")
#plt.xticks(y_pos, bars)
# Add title and axis names
plt.title('All Energy Sources')
plt.xlabel('Capacity_MW')
plt.ylabel('Variable Cost')
plt.show()
plt.figure(figsize=(5,1))
plt.bar(energy_colors_dict.keys(), 1, color = energy_colors_dict.values())
plt.xticks(rotation=60)
plt.title('Legend')
plt.show()
Now we can examine the plot above to determine our market price using the capacity demanded that we entered at the top of this notebook.
def price_calc(demand, sorted_table, hour):
price = 0
sum_cap = 0
for i in range(0,len(sorted_table['Capacity_MW'])):
if sum_cap + sorted_table['Capacity_MW'][i] > demand:
price = sorted_table['PRICE' + str(hour)][i]
break
else:
sum_cap += sorted_table['Capacity_MW'][i]
price = sorted_table['PRICE' + str(hour)][i]
return price
def price_line_plot(price):
plt.axhline(y=price, color='r', linewidth = 2)
print("Price: " + str(price))
def demand_plot(demand):
plt.axvline(x=demand, color='r', linewidth = 2)
print("Capacity: " + str(demand))
# Make the plot
price = price_calc(demand, sorted_joined_table, hour)
plt.figure(figsize=(9,6))
plt.bar(new_x, height, width=width, color=sorted_joined_table['Color'], edgecolor = "black")
plt.title('All Energy Sources')
plt.xlabel('Capacity_MW')
plt.ylabel('Variable Cost')
print('Hour: ' + str(hour))
price_line_plot(price)
demand_plot(demand)
plt.show()
plt.figure(figsize=(5,1))
plt.bar(energy_colors_dict.keys(), 1, color = energy_colors_dict.values())
plt.xticks(rotation=60)
plt.title('Legend')
plt.show()
Hour: 4 Price: 44.83 Capacity: 16522.86029
Finally, we have a market price. Let us construct the same plot, but this time only considering the plants that are part of our portfolio.
your_source = sorted_joined_table.where("Group", YOUR_PORTFOLIO)
width_yours = your_source.column("Capacity_MW")
height_yours = your_source.column('PRICE' + str(hour))
height_yours_marginal_cost = your_source.column("Var_Cost_USDperMWH")
new_x_yours = find_x_pos(width_yours)
label_yours = your_source.column("PLANT")
# Make the plot
plt.figure(figsize=(9,6))
plt.bar(new_x_yours, height_yours, width=width_yours, color = energy_colors_dict[YOUR_PORTFOLIO], edgecolor = "black")
plt.title(YOUR_PORTFOLIO)
plt.xlabel('Capacity_MW')
plt.ylabel('Price')
for new_x_i, height_i, label_i in zip(new_x_yours, height_yours, label_yours):
plt.text(new_x_i, height_i, label_i,
ha='center', va='bottom', fontsize=8)
price_line_plot(price)
plt.show()
Price: 44.83
Let's reduce our problem further to only include the plants that are actually operational under the new market price. Run the following cell to see the plants that will operate given our market price, and then we can begin the process of calculating our profit.
nonmarg_capacity = sum(sorted_joined_table.where('PRICE' + str(hour), are.below(price))["Capacity_MW"])
marg_capacity = sum(sorted_joined_table.where('PRICE' + str(hour), are.equal_to(price))["Capacity_MW"])
marg_demand = demand - nonmarg_capacity
marg_proportion = marg_demand / marg_capacity
marginal_plants = np.where(height_yours == price)[0]
width_yours2 = width_yours.copy()
width_yours2[marginal_plants] = width_yours2[marginal_plants] * marg_proportion
new_x_yours2 = find_x_pos(width_yours2)
plt.figure(figsize=(9,6))
num_x = len(your_source.where('PRICE' + str(hour), are.below_or_equal_to(price))[1])
plt.bar(new_x_yours2[:num_x], height_yours[:num_x], width=width_yours2[:num_x],
color = energy_colors_dict[YOUR_PORTFOLIO],
edgecolor = "black")
plt.title(YOUR_PORTFOLIO)
plt.xlabel('Capacity_MW')
plt.ylabel('Price')
for new_x_i, height_i, label_i in zip(new_x_yours2[:num_x], height_yours[:num_x], label_yours[:num_x]):
plt.text(new_x_i, height_i, label_i,
ha='center', va='bottom', fontsize=8)
price_line_plot(price)
plt.show()
Price: 44.83
The plot above allows us to see how the prices we have set correspond to the market price. However, in order to look at the profits that we are making, we should look at our marginal cost rather than our bid prices. We do this in the plot below, allowing us to compare the market price of energy in this hour to the marginal costs for running the plants that have a bid price that was below the market price. We should include the permit price in marginal cost: even if we are grandfathered permits, the ability to sell the permits represents an opportunity cost.
plt.figure(figsize=(9,6))
num_x = len(your_source.where('PRICE' + str(hour), are.below_or_equal_to(price))[1])
plt.bar(new_x_yours2[:num_x], height_yours_marginal_cost[:num_x], width=width_yours2[:num_x],
color = energy_colors_dict[YOUR_PORTFOLIO],
edgecolor = "black")
plt.title(YOUR_PORTFOLIO)
plt.xlabel('Capacity_MW')
plt.ylabel('Marginal Cost (total)')
for new_x_i, height_i, label_i in zip(new_x_yours2[:num_x], height_yours_marginal_cost[:num_x], label_yours[:num_x]):
plt.text(new_x_i, height_i, label_i,
ha='center', va='bottom', fontsize=8)
price_line_plot(price)
plt.show()
Price: 44.83
Now we can finally calculate profit. The graphic above should show the market price line along with the marginal costs for operating our plants. Thus we can calculate profit by calculating the area between the red line and the blue boxes, as that will give us the total revenue - total cost. The function defined below is designed to perform that calculation.
def profit(sorted_table, price, marg_prop, hour):
capacity_subset = sum(sorted_table.where('PRICE' + str(hour), are.below(price))["Capacity_MW"])
capacity_subset += sum(sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Capacity_MW"] * marg_prop)
revenue = capacity_subset * price
cost = 0
for i in range(len(sorted_table.where('PRICE' + str(hour), are.below(price))["Var_Cost_USDperMWH"])):
cost += sorted_table.where('PRICE' + str(hour), are.below(price))["Var_Cost_USDperMWH"][i]\
* sorted_table.where('PRICE' + str(hour), are.below(price))["Capacity_MW"][i]
for i in range(len(sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Var_Cost_USDperMWH"])):
cost += sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Var_Cost_USDperMWH"][i]\
* (sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Capacity_MW"][i] * marg_prop)
return revenue - cost
def profit_pab(sorted_table, price, marg_prop, hour):
revenue = 0
for i in range(len(sorted_table.where('PRICE' + str(hour), are.below(price))['PRICE' + str(hour)])):
revenue += sorted_table.where('PRICE' + str(hour), are.below(price))['PRICE' + str(hour)][i]\
* sorted_table.where('PRICE' + str(hour), are.below(price))["Capacity_MW"][i]
for i in range(len(sorted_table.where('PRICE' + str(hour), are.equal_to(price))['PRICE' + str(hour)])):
revenue += sorted_table.where('PRICE' + str(hour), are.equal_to(price))['PRICE' + str(hour)][i]\
* (sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Capacity_MW"][i] * marg_prop)
cost = 0
for i in range(len(sorted_table.where('PRICE' + str(hour), are.below(price))["Var_Cost_USDperMWH"])):
cost += sorted_table.where('PRICE' + str(hour), are.below(price))["Var_Cost_USDperMWH"][i]\
* sorted_table.where('PRICE' + str(hour), are.below(price))["Capacity_MW"][i]
for i in range(len(sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Var_Cost_USDperMWH"])):
cost += sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Var_Cost_USDperMWH"][i]\
* (sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Capacity_MW"][i] * marg_prop)
return revenue - cost
profit_pab(your_source, price, marg_proportion, hour)
10768.0
We can now calculate the amount of emissions in the current hour.
def emissions(sorted_table, price, marg_prop, hour):
emissions = 0
for i in range(len(sorted_table.where('PRICE' + str(hour), are.below(price))["Var_Cost_USDperMWH"])):
emissions += sorted_table.where('PRICE' + str(hour), are.below(price))["Carbon_tonsperMWH"][i]\
* sorted_table.where('PRICE' + str(hour), are.below(price))["Capacity_MW"][i]
for i in range(len(sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Var_Cost_USDperMWH"])):
emissions += sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Carbon_tonsperMWH"][i]\
* (sorted_table.where('PRICE' + str(hour), are.equal_to(price))["Capacity_MW"][i] * marg_prop)
return emissions
emissions(your_source, price, marg_proportion, hour)
2852.0
Let's calculate a few more things. First, calculate total profits (up through and including the current round).
# Load in auction prices.
auction_results = Table.read_table('auction_results.csv').where("world_id", 'S' + str(section))
def team_profit(portfolio, period):
# Merge auction results with sorted joined table
full_table = sorted_joined_table_all.join("TEAM", auction_results, "team")
team_table = full_table.where("Group", portfolio)
team_profit = -team_table.to_df().loc[0, "adjustment"] * 1.05**(period-1)
for period_i in range(1, (period+1)):
period_table = full_table.where("PERIOD", period_i)
period_team_table = team_table.where("PERIOD", period_i)
period_team_profit = -sum(period_team_table["FixedCst_OandM_perDay"])
for hour_i in range(1, 5):
period_table_sort = period_table.sort("PRICE" + str(hour_i), descending = False)
period_team_table_sort = period_team_table.sort("PRICE" + str(hour_i), descending = False)
demand_i = demand_table.where("round", period_i).where("hour", hour_i)["load"].item()
price_i = price_calc(demand_i, period_table_sort, hour_i)
nonmarg_capacity_i = sum(period_table_sort.where('PRICE' + str(hour_i), are.below(price_i))["Capacity_MW"])
marg_capacity_i = sum(period_table_sort.where('PRICE' + str(hour_i), are.equal_to(price_i))["Capacity_MW"])
marg_demand_i = demand_i - nonmarg_capacity_i
marg_proportion_i = marg_demand_i / marg_capacity_i
if period_i in pab_periods:
period_team_profit += profit_pab(period_team_table_sort, price_i, marg_proportion_i, hour_i)
else:
period_team_profit += profit(period_team_table_sort, price_i, marg_proportion_i, hour_i)
team_profit += period_team_profit * 1.05**(period-period_i)
if period_i == period:
team_profit_today = period_team_profit
output_df = pd.DataFrame.from_dict({"Current Round: ":team_profit_today, "Total: ":team_profit}, orient = 'index', columns = [portfolio + ' Profit']).round().astype(int)
return output_df
team_profit(YOUR_PORTFOLIO, period)
Big Coal Profit | |
---|---|
Current Round: | 125867 |
Total: | 1083252 |
def total_emissions(portfolio, period):
full_table = sorted_joined_table_all
team_table = full_table.where("Group", portfolio)
team_emissions_4_6 = 0
team_emissions_4_today = 0
for period_i in range(1, 7):
period_table = full_table.where("PERIOD", period_i)
period_team_table = team_table.where("PERIOD", period_i)
period_team_emissions = 0
for hour_i in range(1, 5):
period_table_sort = period_table.sort("PRICE" + str(hour_i), descending = False)
period_team_table_sort = period_team_table.sort("PRICE" + str(hour_i), descending = False)
demand_i = demand_table.where("round", period_i).where("hour", hour_i)["load"].item()
price_i = price_calc(demand_i, period_table_sort, hour_i)
nonmarg_capacity_i = sum(period_table_sort.where('PRICE' + str(hour_i), are.below(price_i))["Capacity_MW"])
marg_capacity_i = sum(period_table_sort.where('PRICE' + str(hour_i), are.equal_to(price_i))["Capacity_MW"])
marg_demand_i = demand_i - nonmarg_capacity_i
marg_proportion_i = marg_demand_i / marg_capacity_i
period_team_emissions += emissions(period_team_table_sort, price_i, marg_proportion_i, hour_i)
if period_i == period:
team_emissions_today = period_team_emissions
if period_i > 3:
team_emissions_4_6 += period_team_emissions
if (period_i > 3) & (period_i <= period):
team_emissions_4_today += period_team_emissions
output_df = pd.DataFrame.from_dict({"Current Round: ":team_emissions_today, "Rounds 4 - Current: ":team_emissions_4_today, "Expected Rounds 4-6":team_emissions_4_6}, orient = 'index', columns = [portfolio + " Emissions"]).round().astype(int)
return output_df
total_emissions(YOUR_PORTFOLIO, period)
Big Coal Emissions | |
---|---|
Current Round: | 11408 |
Rounds 4 - Current: | 0 |
Expected Rounds 4-6 | 30678 |
We now have the ability to estimate the amount of profit our plants will generate based on a given amount of demand! Again, this does not include considerations of credit or carbon emmissions, but it should give you a general sens of how your plants performed for this hour. Try going back to the start of the notebook and changing the hour you are examining, or add more cells to do a deeper analysis of your performance on your own to make better decisions for your team in the future.
Congratulations! You have completed your Jupyter Notebook for the ESG. If you have questions, please do not hesitate to post them on the dedicated Piazza thread (https://piazza.com/class/jr2lknh4q311x1?cid=41).
Module Developers: Alec Kan, Alma Pineda, Aarish Irfan, Elaine Chien, and Octavian Sima.
Data Science Modules: http://data.berkeley.edu/education/modules