Source code for postreise.plot.plot_utilization_map

# This plotting module has a corresponding demo notebook in
#   PostREISE/postreise/plot/demo: utilization_map_demo.ipynb

import numpy as np
import pandas as pd
from bokeh.models import ColorBar, ColumnDataSource, HoverTool
from bokeh.transform import linear_cmap

from postreise.analyze.transmission.utilization import (
    generate_cong_stats,
    get_utilization,
)
from postreise.plot.canvas import create_map_canvas
from postreise.plot.check import _check_func_kwargs
from postreise.plot.colors import traffic_palette
from postreise.plot.plot_states import add_state_borders
from postreise.plot.projection_helpers import project_branch


[docs]def map_risk_bind( risk_or_bind, scenario=None, congestion_stats=None, branch=None, us_states_dat=None, vmin=None, vmax=None, color_bar_width=500, palette=None, all_branch_scale_factor=0.5, all_branch_min_width=0.2, select_branch_scale_factor=1, select_branch_min_width=2, figsize=(1400, 800), show_color_bar=True, state_borders_kwargs=None, ): """Make map showing risk or binding incidents on US states map. Either ``scenario`` XOR (``congestion_stats`` AND ``branch``) must be specified. :param str risk_or_bind: specify plotting "risk" or "bind". :param powersimdata.scenario.scenario.Scenario scenario: scenario to analyze. :param pandas.DataFrame congestion_stats: data frame as returned by :func:`postreise.analyze.transmission.utilization.generate_cong_stats`. :param pandas.DataFrame branch: branch data frame. :param int/float vmin: minimum value for color range. If None, use data minimum. :param int/float vmax: maximum value for color range. If None, use data maximum. :param int color_bar_width: width of color bar (pixels). :param iterable palette: sequence of colors used for color range, passed as `palette` kwarg to :func:`bokeh.transform.linear_cmap`. If None, default to `postreise.plot.colors.traffic_palette`. :param int/float all_branch_scale_factor: scale factor for unhighlighted branches (pixels/GW). :param int/float all_branch_min_width: minimum width for unhighlighted branches (pixels). :param int/float select_branch_scale_factor: scale factor for highlighted branches (pixels/GW). :param int/float select_branch_min_width: minimum width for highlighted branches (pixels). :param tuple(int, int) figsize: size of the bokeh figure (in pixels). :param bool show_color_bar: whether to render the color bar on the figure. :param dict state_borders_kwargs: keyword arguments to be passed to :func:`postreise.plot.plot_states.add_state_borders`. :raises ValueError: if (``scenario`` XOR (``congestion_stats`` AND ``branch``)) are not properly specified. :return: (*bokeh.plotting.figure*) -- map of lines with risk and bind incidents color coded. """ unit_labels = {"risk": "Risk (MWH)", "bind": "Binding incidents"} if risk_or_bind not in unit_labels: raise ValueError("risk_or_bind must be either 'risk' or 'bind'") risk_or_bind_units = unit_labels[risk_or_bind] # Check that we've appropriately specified: # `scenario` XOR (`congestion_stats` AND `branch`) if scenario is not None: branch = scenario.get_grid().branch congestion_stats = generate_cong_stats(scenario.get_pf(), branch) elif congestion_stats is not None and branch is not None: pass else: raise ValueError( "Either scenario XOR (congestion_stats AND branch) must be specified" ) if palette is None: palette = list(traffic_palette) # projection steps for mapping branch_congestion = pd.concat( [branch.loc[congestion_stats.index], congestion_stats], axis=1 ) branch_map_all = project_branch(branch) branch_congestion = branch_congestion[branch_congestion[risk_or_bind] > 0] branch_congestion.sort_values(by=[risk_or_bind]) branch_map = project_branch(branch_congestion) min_val = branch_congestion[risk_or_bind].min() if vmin is None else vmin max_val = branch_congestion[risk_or_bind].max() if vmax is None else vmax mapper = linear_cmap( field_name=risk_or_bind, palette=palette, low=min_val, high=max_val ) multi_line_source = ColumnDataSource( { "xs": branch_map[["from_x", "to_x"]].values.tolist(), "ys": branch_map[["from_y", "to_y"]].values.tolist(), risk_or_bind: branch_map[risk_or_bind], "value": branch_map[risk_or_bind].round(), "cap": ( branch_map["capacity"] * select_branch_scale_factor / 1000 + select_branch_min_width ), "capacity": branch_map.rateA.round(), } ) # Create canvas canvas = create_map_canvas(figsize=figsize) # Add state borders default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": True} all_state_borders_kwargs = ( {**default_state_borders_kwargs, **state_borders_kwargs} if state_borders_kwargs is not None else default_state_borders_kwargs ) _check_func_kwargs( add_state_borders, set(all_state_borders_kwargs), "state_borders_kwargs" ) canvas = add_state_borders(canvas, **all_state_borders_kwargs) # Add color bar if show_color_bar: color_bar = ColorBar( color_mapper=mapper["transform"], width=color_bar_width, height=5, location=(0, 0), title=risk_or_bind_units, orientation="horizontal", padding=5, ) canvas.add_layout(color_bar, "center") canvas.multi_line( branch_map_all[["from_x", "to_x"]].to_numpy().tolist(), branch_map_all[["from_y", "to_y"]].to_numpy().tolist(), color="gray", line_width=( branch_map_all["rateA"] * all_branch_scale_factor / 1000 + all_branch_min_width ), alpha=0.5, ) lines = canvas.multi_line( "xs", "ys", color=mapper, line_width="cap", source=multi_line_source ) hover = HoverTool( tooltips=[ ("Capacity MW", "@capacity"), (risk_or_bind_units, "@value"), ], renderers=[lines], ) canvas.add_tools(hover) return canvas
[docs]def map_utilization( scenario=None, utilization_df=None, branch=None, vmin=None, vmax=None, color_bar_width=500, palette=None, branch_scale_factor=0.5, branch_min_width=0.2, figsize=(1400, 800), show_color_bar=True, state_borders_kwargs=None, ): """Make map showing utilization. Utilization input can either be medians only, or can be normalized utilization dataframe. Either ``scenario`` XOR (``utilization_df`` AND ``branch``) must be specified. :param powersimdata.scenario.scenario.Scenario scenario: scenario to analyze. :param pandas.DataFrame utilization_df: utilization returned by :func:`postreise.analyze.transmission.utilization.get_utilization` :param pandas.DataFrame branch: branch data frame. :param int/float vmin: minimum value for color range. If None, use data minimum. :param int/float vmax: maximum value for color range. If None, use data maximum. :param int color_bar_width: width of color bar (pixels). :param iterable palette: sequence of colors used for color range, passed as `palette` kwarg to :func:`bokeh.transform.linear_cmap`. If None, default to `postreise.plot.colors.traffic_palette`. :param int/float branch_scale_factor: scale factor for branches (pixels/GW). :param int/float branch_min_width: minimum width for branches (pixels). :param tuple(int, int) figsize: size of the bokeh figure (in pixels). :param dict state_borders_kwargs: keyword arguments to be passed to :func:`postreise.plot.plot_states.add_state_borders`. :raises ValueError: if (``scenario`` XOR (``utilization_df`` AND ``branch``)) are not properly specified. :return: (*bokeh.plotting.figure*) -- map of lines with median utilization color coded. """ # Check that we've appropriately specified: # `scenario` XOR (`utilization_df` AND `branch`) if scenario is not None: branch = scenario.get_grid().branch utilization_df = get_utilization(branch, scenario.get_pf(), median=True) elif utilization_df is not None and branch is not None: pass else: raise ValueError( "Either scenario XOR (utilization_df AND branch) must be specified" ) if palette is None: palette = list(traffic_palette) branch_mask = branch.rateA != 0 median_util = utilization_df[branch.loc[branch_mask].index].median() branch_utilization = pd.concat( [branch.loc[branch_mask], median_util.rename("median_utilization")], axis=1 ) lines = branch_utilization.loc[(branch_utilization["branch_device_type"] == "Line")] min_val = lines["median_utilization"].min() if vmin is None else vmin max_val = lines["median_utilization"].max() if vmax is None else vmax mapper = linear_cmap( field_name="median_utilization", palette=palette, low=min_val, high=max_val, ) branch_map = project_branch(branch_utilization) branch_map = branch_map.sort_values(by=["median_utilization"]) branch_map = branch_map[~branch_map.isin([np.nan, np.inf, -np.inf]).any(1)] multi_line_source = ColumnDataSource( { "xs": branch_map[["from_x", "to_x"]].values.tolist(), "ys": branch_map[["from_y", "to_y"]].values.tolist(), "median_utilization": branch_map.median_utilization, "width": branch_map.rateA * branch_scale_factor / 1000 + branch_min_width, "util": branch_map.median_utilization.round(2), "capacity": branch_map.rateA.round(), } ) # Create canvas canvas = create_map_canvas(figsize=figsize) # Add state borders default_state_borders_kwargs = {"fill_alpha": 0.0, "line_width": 2} all_state_borders_kwargs = ( {**default_state_borders_kwargs, **state_borders_kwargs} if state_borders_kwargs is not None else default_state_borders_kwargs ) _check_func_kwargs( add_state_borders, set(all_state_borders_kwargs), "state_borders_kwargs" ) canvas = add_state_borders(canvas, **all_state_borders_kwargs) # Add color bar if show_color_bar: color_bar = ColorBar( color_mapper=mapper["transform"], width=color_bar_width, height=5, location=(0, 0), title="median utilization", orientation="horizontal", padding=5, ) canvas.add_layout(color_bar, "center") lines = canvas.multi_line( "xs", "ys", color=mapper, line_width="width", source=multi_line_source ) hover = HoverTool( tooltips=[ ("Capacity MW", "@capacity"), ("Utilization", "@util{f0.00}"), ], renderers=[lines], ) canvas.add_tools(hover) return canvas