import matplotlib.pyplot as plt
import pandas as pd
from powersimdata.input.check import _check_resources_are_renewable_and_format
from powersimdata.input.helpers import get_plant_id_for_resources_in_area
from powersimdata.network.model import ModelImmutables
from powersimdata.scenario.check import _check_scenario_is_in_analyze_state
from postreise.analyze.generation.capacity import get_capacity_by_resources
from postreise.analyze.generation.curtailment import calculate_curtailment_time_series
from postreise.analyze.time import change_time_zone, slice_time_series
[docs]def plot_scatter_capacity_vs_curtailment(
scenario,
area,
resources,
time_zone="utc",
time_range=None,
area_type=None,
between_time=None,
dayofweek=None,
markersize=50,
fontsize=20,
title=None,
percentage=False,
plot_show=True,
):
"""Generate for a given scenario the scatter plot of the capacity (x-axis) vs
curtailment as a fraction of available resources (y-axis) of generators
located in area and fueled by resources over a time range.
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance
:param str area: name of the area to focus on. Could be a loadzone, a state, a
country, etc. This will depend on the grid model.
:param str/list resources: one or a list of resources.
:param str time_zone: new time zone, default to be *'utc'*.
:param tuple time_range: [start_timestamp, end_timestamp] where each time stamp
is pandas.Timestamp/numpy.datetime64/datetime.datetime. If None, the entire
time range is used for the given scenario.
:param str area_type: area supported by the grid model. For more details, see the
:func:`powersimdata.network.model.area_to_loadzone` function.
:param list between_time: specify the start hour and end hour of each day
inclusively, default to None, which includes every hour of a day. Note that if
the end hour is set before the start hour, the complementary hours of a day are
picked.
:param set dayofweek: specify the interest days of week, which is a subset of
integers in [0, 6] with 0 being Monday and 6 being Sunday, default to None,
which includes every day of a week.
:param int/float markersize: marker size, default to 50.
:param int/float fontsize: font size, default to 20.
:param str title: user specified figure title, default to None.
:param bool percentage: show capacity factor in percentage or not, default to False
:param bool plot_show: show the plot or not, default to True.
:return: (*tuple*) -- the first entry is matplotlib.axes.Axes object of the plot,
the second entry is the capacity weighted average of curtailment over the
selected time range.
:raises TypeError:
if ``area`` is not a str.
if ``resources`` is not a str or a list of str.
if ``time_zone`` is not a str.
if ``markersize`` is not an int or a float.
if ``fontsize`` is not an int or a float.
if ``title`` is provided but not in a string format.
"""
_check_scenario_is_in_analyze_state(scenario)
if not isinstance(area, str):
raise TypeError("area must be a str")
if not isinstance(resources, (str, list)):
raise TypeError("resources must be a list or str")
if isinstance(resources, list) and not all(isinstance(r, str) for r in resources):
raise TypeError("resources must be a list of str")
if not isinstance(markersize, (int, float)):
raise TypeError("markersize must be either an int or float")
if not isinstance(fontsize, (int, float)):
raise TypeError("fontsize must be either an int or float")
if title is not None and not isinstance(title, str):
raise TypeError("title must be a str")
resources = _check_resources_are_renewable_and_format(
resources, mi=ModelImmutables(scenario.info["grid_model"])
)
curtailment = calculate_curtailment_time_series(scenario)
plant_list = get_plant_id_for_resources_in_area(
scenario, area, resources, area_type=area_type
)
curtailment = curtailment[list(set(plant_list) & set(curtailment.columns))]
curtailment = change_time_zone(curtailment, time_zone)
if not time_range:
time_range = (
pd.Timestamp(scenario.info["start_date"], tz="utc"),
pd.Timestamp(scenario.info["end_date"], tz="utc"),
)
curtailment = slice_time_series(
curtailment,
time_range[0],
time_range[1],
between_time=between_time,
dayofweek=dayofweek,
)
profiles = pd.concat([scenario.get_solar(), scenario.get_wind()], axis=1)
curtailment = curtailment.sum() / profiles[curtailment.columns].sum()
if percentage:
curtailment = (curtailment * 100).round(2)
total_cap = get_capacity_by_resources(
scenario, area, resources, area_type=area_type
).sum()
plant_df = scenario.get_grid().plant.loc[plant_list]
if total_cap == 0:
data_avg = 0
else:
data_avg = (plant_df["Pmax"] * curtailment).sum() / total_cap
_, ax = plt.subplots(figsize=[20, 10])
ax.scatter(plant_df["Pmax"], curtailment, s=markersize)
ax.plot(plant_df["Pmax"], [data_avg] * len(plant_df.index), c="red")
ax.grid()
if title is None:
ax.set_title(
f"{area} "
f'{" ".join(resources) if isinstance(resources, (list, set)) else resources}'
)
else:
ax.set_title(title)
ax.set_xlabel("Capacity (MW)")
if percentage:
ax.set_ylabel("Curtailment %")
else:
ax.set_ylabel("Curtailment")
for item in (
[ax.title, ax.xaxis.label, ax.yaxis.label]
+ ax.get_xticklabels()
+ ax.get_yticklabels()
):
item.set_fontsize(fontsize)
if plot_show:
plt.show()
return ax, data_avg