예제 #1
0
def test_change_time_zone_argument_value():
    arg = (
        (ts_as_data_frame, "US/Japan"),
        (ts_as_series.resample("D").sum(), "GMT+8"),
    )
    for a in arg:
        with pytest.raises(ValueError):
            change_time_zone(a[0], a[1])
예제 #2
0
def test_change_time_zone_argument_type():
    arg = (
        (ts_as_series, 1),
        (ts_as_data_frame, {1, 2, 3}),
        (ts_as_series.to_dict(), "US/Eastern"),
    )
    for a in arg:
        with pytest.raises(TypeError):
            change_time_zone(a[0], a[1])
예제 #3
0
def test_change_time_zone():
    arg = (ts_as_data_frame, ts_as_series)
    for a in arg:
        ts_idx = change_time_zone(a, "US/Pacific").index
        assert ts_idx.tz != pytz.timezone("UTC")
        assert ts_idx.tz == pytz.timezone("US/Pacific")
        assert set(ts_idx.year.unique()) == {2015, 2016}

        # assign time zone before calling function
        ts_idx = change_time_zone(a.tz_localize("UTC"), "US/Pacific").index
        assert ts_idx.tz != pytz.timezone("UTC")
        assert ts_idx.tz == pytz.timezone("US/Pacific")
        assert set(ts_idx.year.unique()) == {2015, 2016}
예제 #4
0
def sum_generation_by_type_zone(
    scenario: Scenario, time_range=None, time_zone=None
) -> pd.DataFrame:
    """Get total generation for each generator type and load zone combination.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
    :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 time_zone: new time zone.
    :return: (*pandas.DataFrame*) -- total generation, indexed by {type, zone}.
    """
    _check_scenario_is_in_analyze_state(scenario)

    pg = scenario.state.get_pg()
    if time_zone:
        pg = change_time_zone(pg, time_zone)
    if time_range:
        pg = slice_time_series(pg, time_range[0], time_range[1])
    grid = scenario.state.get_grid()
    plant = grid.plant

    summed_gen_series = pg.sum().groupby([plant.type, plant.zone_id]).sum()
    summed_gen_dataframe = summed_gen_series.unstack().fillna(value=0)

    return summed_gen_dataframe
예제 #5
0
def plot_generation_time_series_stack(
    scenario,
    area,
    resources,
    area_type=None,
    time_range=None,
    time_zone="utc",
    time_freq="H",
    show_demand=True,
    show_net_demand=True,
    normalize=False,
    t2c=None,
    t2l=None,
    t2hc=None,
    title=None,
    label_fontsize=20,
    title_fontsize=22,
    tick_fontsize=15,
    legend_fontsize=18,
    save=False,
    filename=None,
    filepath=None,
):
    """Generate time series generation stack plot in a certain area of a scenario.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario instance
    :param str area: one of *loadzone*, *state*, *state abbreviation*,
        *interconnect*, *'all'*
    :param str/list resources: one or a list of resources. *'solar_curtailment'*,
        *'wind_curtailment'*, *'wind_offshore_curtailment'* are valid entries together
        with all available generator types in the area. The order of the resources
        determines the stack order in the figure.
    :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*,
        *'interconnect'*
    :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 time_zone: new time zone.
    :param str time_freq: frequency. Either *'D'* (day), *'W'* (week), *'M'* (month).
    :param bool show_demand: show demand line in the plot or not, default is True.
    :param bool show_net_demand: show net demand line in the plot or not, default is
        True.
    :param bool normalize: normalize the generation based on capacity or not,
        default is False.
    :param dict t2c: user specified color of resource type to overwrite type2color
        default dict. key: resource type, value: color code.
    :param dict t2l: user specified label of resource type to overwrite type2label
        default dict. key: resource type, value: label.
    :param dict t2hc: user specified color of curtailable resource hatches to overwrite
        type2hatchcolor default dict. key: resource type, valid keys are
        *'wind_curtailment'*, *'solar_curtailment'*, *'wind_offshore_curtailment'*,
        value: color code.
    :param str title: user specified title of the figure, default is set to be area.
    :param float label_fontsize: user specified label fontsize, default is 20.
    :param float title_fontsize: user specified title fontsize, default is 22.
    :param float tick_fontsize: user specified ticks of axes fontsize, default is 15.
    :param float legend_fontsize: user specified legend fontsize, default is 18.
    :param bool save: save the generated figure or not, default is False.
    :param str filename: if save is True, user specified filename, use area if None.
    :param str filepath: if save is True, user specified filepath, use current
        directory if None.
    """
    _check_scenario_is_in_analyze_state(scenario)

    mi = ModelImmutables(scenario.info["grid_model"])
    type2color = mi.plants["type2color"]
    type2label = mi.plants["type2label"]
    type2hatchcolor = mi.plants["type2hatchcolor"]
    if t2c:
        type2color.update(t2c)
    if t2l:
        type2label.update(t2l)
    if t2hc:
        type2hatchcolor.update(t2hc)

    pg_stack = get_generation_time_series_by_resources(scenario,
                                                       area,
                                                       resources,
                                                       area_type=area_type)
    capacity = get_capacity_by_resources(scenario,
                                         area,
                                         resources,
                                         area_type=area_type)
    demand = get_demand_time_series(scenario, area, area_type=area_type)
    net_demand = get_net_demand_time_series(scenario,
                                            area,
                                            area_type=area_type)
    capacity_ts = pd.Series(capacity.sum(), index=pg_stack.index)

    curtailable_resources = {
        "solar_curtailment",
        "wind_curtailment",
        "wind_offshore_curtailment",
    }
    if curtailable_resources & set(resources):
        curtailment = get_curtailment_time_series(scenario,
                                                  area,
                                                  area_type=area_type)
        for r in curtailable_resources:
            if r in resources and r in curtailment.columns:
                pg_stack[r] = curtailment[r]

    if time_zone != "utc":
        pg_stack = change_time_zone(pg_stack, time_zone)
        demand = change_time_zone(demand, time_zone)
        net_demand = change_time_zone(net_demand, time_zone)
        capacity_ts = change_time_zone(capacity_ts, time_zone)
    if not time_range:
        time_range = (
            pd.Timestamp(scenario.info["start_date"]),
            pd.Timestamp(scenario.info["end_date"]),
        )
    pg_stack = slice_time_series(pg_stack, time_range[0], time_range[1])
    demand = slice_time_series(demand, time_range[0], time_range[1])
    net_demand = slice_time_series(net_demand, time_range[0], time_range[1])
    capacity_ts = slice_time_series(capacity_ts, time_range[0], time_range[1])
    if time_freq != "H":
        pg_stack = resample_time_series(pg_stack, time_freq)
        demand = resample_time_series(demand, time_freq)
        net_demand = resample_time_series(net_demand, time_freq)
        capacity_ts = resample_time_series(capacity_ts, time_freq)

    if "storage" in resources:
        pg_storage = get_storage_time_series(scenario,
                                             area,
                                             area_type=area_type)
        capacity_storage = get_storage_capacity(scenario,
                                                area,
                                                area_type=area_type)
        capacity_storage_ts = pd.Series(capacity_storage,
                                        index=pg_storage.index)

        if time_zone != "utc":
            pg_storage = change_time_zone(pg_storage, time_zone)
            capacity_storage_ts = change_time_zone(capacity_storage_ts,
                                                   time_zone)
        if time_range != ("2016-01-01 00:00:00", "2016-12-31 23:00:00"):
            pg_storage = slice_time_series(pg_storage, time_range[0],
                                           time_range[1])
            capacity_storage_ts = slice_time_series(capacity_storage_ts,
                                                    time_range[0],
                                                    time_range[1])
        if time_freq != "H":
            pg_storage = resample_time_series(pg_storage, time_freq)
            capacity_storage_ts = resample_time_series(capacity_storage_ts,
                                                       time_freq)

        pg_stack["storage"] = pg_storage.clip(lower=0)
        capacity_ts += capacity_storage_ts

        fig, (ax, ax_storage) = plt.subplots(
            2,
            1,
            figsize=(20, 15),
            sharex="row",
            gridspec_kw={
                "height_ratios": [3, 1],
                "hspace": 0.02
            },
        )
        plt.subplots_adjust(wspace=0)
        if normalize:
            pg_storage = pg_storage.divide(capacity_storage_ts, axis="index")
            ax_storage.set_ylabel("Normalized Storage",
                                  fontsize=label_fontsize)
        else:
            ax_storage.set_ylabel("Energy Storage (MW)",
                                  fontsize=label_fontsize)

        ax_storage = pg_storage.plot(color=type2color["storage"],
                                     lw=4,
                                     ax=ax_storage)
        ax_storage.fill_between(
            pg_storage.index.values,
            0,
            pg_storage.values,
            color=type2color["storage"],
            alpha=0.5,
        )

        # Erase year in xticklabels
        xt_with_year = list(ax_storage.__dict__["date_axis_info"][0])
        xt_with_year[-1] = b"%b"
        ax_storage.__dict__["date_axis_info"][0] = tuple(xt_with_year)

        ax_storage.tick_params(axis="both",
                               which="both",
                               labelsize=tick_fontsize)
        ax_storage.set_xlabel("")
        for a in fig.get_axes():
            a.label_outer()
    else:
        fig = plt.figure(figsize=(20, 10))
        ax = fig.gca()

    if normalize:
        pg_stack = pg_stack.divide(capacity_ts, axis="index")
        demand = demand.divide(capacity_ts, axis="index")
        net_demand = net_demand.divide(capacity_ts, axis="index")
        ax.set_ylabel("Normalized Generation", fontsize=label_fontsize)
    else:
        pg_stack = pg_stack.divide(1e6, axis="index")
        demand = demand.divide(1e6, axis="index")
        net_demand = net_demand.divide(1e6, axis="index")
        ax.set_ylabel("Daily Energy TWh", fontsize=label_fontsize)

    available_resources = [r for r in resources if r in pg_stack.columns]
    pg_stack[available_resources].clip(0, None).plot.area(color=type2color,
                                                          linewidth=0,
                                                          alpha=0.7,
                                                          ax=ax,
                                                          sharex="row")

    if show_demand:
        demand.plot(color="red", lw=4, ax=ax)
    if show_net_demand:
        net_demand.plot(color="red", ls="--", lw=2, ax=ax)

    if not title:
        title = area
    ax.set_title("%s" % title, fontsize=title_fontsize)
    ax.grid(color="black", axis="y")

    if "storage" not in resources:
        # Erase year in xticklabels
        xt_with_year = list(ax.__dict__["date_axis_info"][0])
        xt_with_year[-1] = b"%b"
        ax.__dict__["date_axis_info"][0] = tuple(xt_with_year)
        ax.set_xlabel("")

    ax.tick_params(which="both", labelsize=tick_fontsize)
    ax.set_ylim([
        min(0, 1.1 * net_demand.min()),
        max(ax.get_ylim()[1], 1.1 * demand.max()),
    ])

    handles, labels = ax.get_legend_handles_labels()
    if show_demand:
        labels[0] = "Demand"
    if show_net_demand:
        labels[1] = "Net Demand"
    label_offset = show_demand + show_net_demand
    labels = [type2label[l] if l in type2label else l for l in labels]

    # Add hatches
    for r in curtailable_resources:
        if r in available_resources:
            ind = available_resources.index(r)
            ax.fill_between(
                pg_stack[available_resources].index.values,
                pg_stack[available_resources].iloc[:, :ind + 1].sum(axis=1),
                pg_stack[available_resources].iloc[:, :ind].sum(axis=1),
                color="none",
                hatch="//",
                edgecolor=type2hatchcolor[r],
                linewidth=0.0,
            )
            handles[ind + label_offset] = mpatches.Patch(
                facecolor=type2color[r],
                hatch="//",
                edgecolor=type2hatchcolor[r],
                linewidth=0.0,
            )

    ax.legend(
        handles[::-1],
        labels[::-1],
        frameon=2,
        prop={"size": legend_fontsize},
        loc="upper left",
        bbox_to_anchor=(1, 1),
    )

    if save:
        if not filename:
            filename = area
        if not filepath:
            filepath = os.path.join(os.getcwd(), filename)
        plt.savefig(f"{filepath}.pdf", bbox_inches="tight", pad_inches=0)
예제 #6
0
def plot_curtailment_time_series(
    scenario,
    area,
    resources,
    area_type=None,
    time_range=None,
    time_zone="utc",
    time_freq="H",
    show_demand=True,
    percentage=True,
    t2c=None,
    t2l=None,
    title=None,
    label_fontsize=20,
    title_fontsize=22,
    tick_fontsize=15,
    legend_fontsize=18,
    save=False,
    filename=None,
    filepath=None,
):
    """Generate time series curtailment plot of each specified resource in a certain
    area of a scenario.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario instance
    :param str area: one of *loadzone*, *state*, *state abbreviation*,
        *interconnect*, *'all'*
    :param str/list resources: one or a list of resources.
    :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*,
        *'interconnect'*
    :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 time_zone: new time zone.
    :param str time_freq: frequency. Either *'D'* (day), *'W'* (week), *'M'* (month).
    :param bool show_demand: show demand line in the plot or not, default is True.
    :param bool percentage: plot the curtailment in terms of percentage or not,
        default is True.
    :param dict t2c: user specified color of resource type to overwrite type2color
        default dict. key: resource type, value: color code.
    :param dict t2l: user specified label of resource type to overwrite type2label
        default dict. key: resource type, value: label.
    :param str title: user specified title of the figure.
    :param float label_fontsize: user specified label fontsize, default is 20.
    :param float title_fontsize: user specified title fontsize, default is 22.
    :param float tick_fontsize: user specified ticks of axes fontsize, default is 15.
    :param float legend_fontsize: user specified legend fontsize, default is 18.
    :param bool save: save the generated figure or not, default is False.
    :param str filename: if save is True, user specified filename, use area if None.
    :param str filepath: if save is True, user specified filepath, use current
        directory if None.
    """
    _check_scenario_is_in_analyze_state(scenario)
    resources = _check_resources_and_format(
        resources, grid_model=scenario.info["grid_model"])

    mi = ModelImmutables(scenario.info["grid_model"])
    type2color = mi.plants["type2color"]
    type2label = mi.plants["type2label"]
    if t2c:
        type2color.update(t2c)
    if t2l:
        type2label.update(t2l)

    resource_pg = get_generation_time_series_by_resources(scenario,
                                                          area,
                                                          resources,
                                                          area_type=area_type)
    demand = get_demand_time_series(scenario, area, area_type=area_type)
    curtailment = get_curtailment_time_series(scenario,
                                              area,
                                              area_type=area_type)

    resource_pg = change_time_zone(resource_pg, time_zone)
    demand = change_time_zone(demand, time_zone)
    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"),
        )
    resource_pg = slice_time_series(resource_pg, time_range[0], time_range[1])
    demand = slice_time_series(demand, time_range[0], time_range[1])
    curtailment = slice_time_series(curtailment, time_range[0], time_range[1])
    if time_freq != "H":
        resource_pg = resample_time_series(resource_pg, time_freq)
        demand = resample_time_series(demand, time_freq)
        curtailment = resample_time_series(curtailment, time_freq)

    for r in resource_pg.columns:
        curtailment[r + "_curtailment" +
                    "_mean"] = curtailment[r + "_curtailment"].mean()
        curtailment[
            r +
            "_available"] = curtailment[r + "_curtailment"] + resource_pg[r]
        curtailment[r + "_curtailment" +
                    "_percentage"] = (curtailment[r + "_curtailment"] /
                                      curtailment[r + "_available"] * 100)
        curtailment[r + "_curtailment" + "_percentage" +
                    "_mean"] = curtailment[r + "_curtailment" +
                                           "_percentage"].mean()

    for r in resources:
        if r not in resource_pg.columns:
            raise ValueError(f"{r} is invalid in {area}!")
        fig = plt.figure(figsize=(20, 10))
        ax = fig.gca()

        title_text = f"{area} {r.capitalize()}" if not title else title
        plt.title(title_text, fontsize=title_fontsize)

        cr = r + "_curtailment"
        if percentage:
            key1, key2 = f"{cr}_percentage", f"{cr}_percentage_mean"
        else:
            key1, key2 = cr, f"{cr}_mean"
        curtailment[key1].plot(
            ax=ax,
            lw=4,
            alpha=0.7,
            color=type2color[cr],
            label=type2label[cr],
        )
        curtailment[key2].plot(
            ax=ax,
            ls="--",
            lw=4,
            alpha=0.7,
            color=type2color[cr],
            label=type2label[cr] + " Mean",
        )

        ax_twin = ax.twinx()
        curtailment[r + "_available"].plot(
            ax=ax_twin,
            lw=4,
            alpha=0.7,
            color=type2color[r],
            label=f"{type2label[r]} Energy Available",
        )

        if show_demand:
            demand.plot(ax=ax_twin,
                        lw=4,
                        alpha=0.7,
                        color="red",
                        label="Demand")

        # Erase year in xticklabels
        xt_with_year = list(ax_twin.__dict__["date_axis_info"][0])
        xt_with_year[-1] = b"%b"
        ax_twin.__dict__["date_axis_info"][0] = tuple(xt_with_year)
        ax_twin.set_xlabel("")
        ax_twin.tick_params(which="both", labelsize=tick_fontsize)
        ax_twin.yaxis.get_offset_text().set_fontsize(tick_fontsize)
        ax_twin.set_ylabel("MWh", fontsize=label_fontsize)
        ax_twin.legend(loc="upper right", prop={"size": legend_fontsize})

        ax.tick_params(which="both", labelsize=tick_fontsize)
        ax.yaxis.get_offset_text().set_fontsize(tick_fontsize)
        ax.grid(color="black", axis="y")
        ax.set_xlabel("")
        if percentage:
            ax.set_ylabel("Curtailment [%]", fontsize=label_fontsize)
        else:
            ax.set_ylabel("Curtailment", fontsize=label_fontsize)
        ax.legend(loc="upper left", prop={"size": legend_fontsize})

        if save:
            if not filename:
                filename = f"{area.lower()}_{r}_curtailment"
            if not filepath:
                filepath = os.path.join(os.getcwd(), filename)
            plt.savefig(f"{filepath}.pdf", bbox_inches="tight", pad_inches=0)
예제 #7
0
def plot_heatmap(
        series,
        time_zone=None,
        time_zone_label=None,
        title=None,
        cmap="PiYG",
        scale=None,
        save_filename=None,
        origin="upper",
        vmin=None,
        vmax=None,
        cbar_format=None,
        cbar_tick_values=None,
        cbar_label=None,
        cbar_tick_labels=None,
        contour_levels=None,
        figsize=(16, 8),
):
    """Show time-series values via an imshow where each column is one color-coded day.

    :param pandas.Series series: a time-series of values to be color-coded.
    :param str time_zone: a time zone to be passed as `tz` kwarg to
        :func:`postreise.analyze.time.change_time_zone`.
    :param str time_zone_label: a time zone label to be added to the y axis label.
    :param str title: a title to be added to the figure.
    :param str/matplotlib.colors.Colormap cmap: colormap specification to be passed
        as `cmap` kwarg to :func:`matplotlib.pyplot.imshow`.
    :param int/float scale: a scaling factor to be applied to the series values.
    :param str save_filename: a path to save the figure to.
    :param str origin: the vertical location of the origin, either "upper" or "lower".
    :param int/float vmin: Minimum value for coloring, to be passed as `vmin` kwarg to
        :func:`matplotlib.pyplot.imshow`.
    :param int/float vmax: Maximum value for coloring, to be passed as `vmax` kwarg to
        :func:`matplotlib.pyplot.imshow`.
    :param str/matplotlib.ticker.Formatter cbar_format: a formatter for colorbar labels,
        to be passed as `format` kwarg to :func:`matplotlib.pyplot.colorbar`.
    :param iterable cbar_tick_values: colorbar tick locations, to be passed as
        `ticks` kwarg to :func:`matplotlib.pyplot.colorbar`.
    :param str cbar_label: axis label for colorbar.
    :param iterable cbar_tick_labels: colorbar tick labels.
    :param iterable contour_levels: values at which to draw contours, passed as `levels`
        kwarg to :func:`matplotlib.pyplot.contour`.
    :param tuple(int/float, int/float) figsize: size of figure.
    """
    _check_time_series(series, "series")
    df = series.to_frame(name="values").asfreq("H")
    year = df.index[0].year
    if time_zone is not None:
        df = change_time_zone(df, time_zone)
    df["date"] = df.index.date
    df["hour"] = df.index.hour
    df_reshaped = pd.pivot(
        df,
        index="date",
        columns="hour",
        values="values",
    )
    xlims = mdates.date2num([df_reshaped.index[0], df_reshaped.index[-1]])
    ylims = mdates.date2num(
        [dt.datetime(year, 1, 1, 0),
         dt.datetime(year, 1, 1, 23)])

    if scale is not None:
        df_reshaped *= scale

    fig = plt.figure(figsize=figsize)
    ax = fig.add_subplot()

    # if necessary, flip ylims so labels follow data from top to bottom
    extent = [*xlims, *ylims
              ] if origin == "lower" else [*xlims, ylims[1], ylims[0]]
    im = plt.imshow(
        df_reshaped.T,
        cmap=cmap,
        aspect="auto",
        extent=extent,
        origin=origin,
        vmin=vmin,
        vmax=vmax,
    )
    if contour_levels is not None:
        ax.contour(df_reshaped.T,
                   extent=extent,
                   levels=contour_levels,
                   origin=origin)

    date_format = mdates.DateFormatter("%m/%d")
    ax.xaxis_date()
    ax.xaxis.set_major_formatter(date_format)
    ax.set_xlabel("Date")
    time_format = mdates.DateFormatter("%H:%M")
    ax.yaxis_date()
    ax.yaxis.set_major_formatter(time_format)
    y_axis_label = "Time" if time_zone_label is None else f"Time {time_zone_label}"
    ax.set_ylabel(y_axis_label)

    cbar = fig.colorbar(im, format=cbar_format, ticks=cbar_tick_values)
    if cbar_label is not None:
        cbar.set_label(cbar_label)
    if title is not None:
        plt.title(title)
    if cbar_tick_labels is not None:
        cbar.ax.set_yticklabels(cbar_tick_labels)

    if save_filename is not None:
        plt.savefig(save_filename, bbox_inches="tight")
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,
    show_plot=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: one of *loadzone*, *state*, *state abbreviation*,
        *interconnect*, *'all'*
    :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: one of *'loadzone'*, *'state'*, *'state_abbr'*,
        *'interconnect'*
    :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 show_plot: 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 markersize is not an integer or a float and/or
        if fontsize is not an integer or a float and/or
        if title is provided but not in a string format.
    """
    if not isinstance(markersize, (int, float)):
        raise TypeError("markersize should be either an integer or a float")
    if not isinstance(fontsize, (int, float)):
        raise TypeError("fontsize should be either an integer or a float")
    if title is not None and not isinstance(title, str):
        raise TypeError("title should be a string")
    resources = _check_resources_are_renewable_and_format(
        resources, grid_model=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[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.state.get_solar(),
         scenario.state.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.state.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 show_plot:
        plt.show()
    return ax, data_avg