Module trase.tools.sei_pcs.plotting

Functions

def bump_chart(df,
period,
entity,
value,
period_order=None,
max_rank=10,
max_entities=12,
palette=('#2a78d6', '#eb6834', '#1baf7a', '#eda100', '#e87ba4', '#008300', '#4a3aa7', '#e34948'),
title_text='',
value_format='{:,.1f}',
value_suffix='',
label_width=24,
marker_size=11,
row_height_px=34,
label_margin_px=230,
width=1000,
dividers=()) ‑> plotly.graph_objs.Figure

Plot how entities change rank from one period to the next (a "bump chart").

Within each period the entities are ranked by value (rank 1 = largest), and each entity's rank is drawn as a line running left to right, so a crossing means one entity overtook another. Entities are labelled directly at both ends of their line, so identity never depends on colour alone.

Here is an ASCII-art of what that looks like::

       2019  2020  2021
  1  ─  A─────A     B
                ╲ ╱
  2  ─  B─────B  ╳  A
                ╱ ╲
  3  ─  C─────C     C

Args

df
a pandas DataFrame in long form, with one row per (period, entity). Values duplicated over that pair are summed.
period
name of the column holding the period (usually a year). Periods are drawn evenly spaced in the order given by period_order.
entity
name of the column holding the entity being ranked.
value
name of the numeric column the ranking is based on.
period_order : optional
the periods to draw, in order. Periods in df but not in this list are dropped, which is how you restrict the chart to a span of years. Default: every period in df, sorted.
max_rank : optional
only entities reaching this rank or better in at least one period are drawn. Default: 10.
max_entities : optional
a hard ceiling on how many lines are drawn, applied after max_rank. Over a long span many more entities touch the top max_rank than are on screen at once, and past a dozen lines the chart stops being readable; the ones kept are those with the best rank, ties broken by total value. Pass None for no ceiling. Default: 12.
palette : optional
the fixed hue order used for the entities, assigned in order of best-ever rank. Default: CATEGORICAL_PALETTE.
title_text : optional
title for the chart. Default: "".
value_format : optional
a str.format spec for the value shown in the hover label and in the end-of-line labels. Default: "{:,.1f}".
value_suffix : optional
appended to the formatted value, e.g. " Mt". Default: "".
label_width : optional
entity labels longer than this are truncated with "…". Default: 24.
marker_size / row_height_px / label_margin_px / width (optional): layout
tuning. label_margin_px is the room reserved either side for the
direct labels.
dividers : optional
an iterable of (period_before, text) pairs. Each draws a labelled vertical rule immediately after that period — for marking, say, a model-version boundary. Default: no rules.

Returns: the plotly Figure.

Raises

ValueError
if no entity reaches max_rank, or if a divider names a period that is not being drawn.
def dumbbell_compare(df_a,
df_b,
value,
group_by,
comparator=<function Compare.absolute_error>,
labels=('Base', 'Comparison'),
colors=('#4e6356', '#ff6a5f'),
title_text='',
xaxis_title='',
yaxis_title='',
legend_title='',
label_width=15,
shorten_end=False,
max_rows=15,
other_label='OTHER',
include_other=True,
row_height_px=40) ‑> plotly.graph_objs.Figure

Compare two dataframes and create a "dumbbell"-style plot.

Here is an ASCII-art of what that looks like:

  BUNGE │   *─────*
CARGILL │          *───*
   ADM  │  *─*
  OTHER │  *
        └────────────────
         0   10   20   30

Args

df_a
base DataFrame.
df_b
comparison DataFrame.
value
the name of the column containing numerical data that will be compared. Any values duplicated over group_by will be summed.
group_by
a list of categorical columns to group by.
comparator : optional
a function which takes two series and returns their comparison. You can also pass in the string name of a method on Compare. See Compare for some examples. Defaults to Compare.absolute_error.
labels : optional
name of the two series that will appear in the legend. Default: ("Base", "Comparison").
title_text : optional
title for the chart. Default ""
xaxis_title : optional
title for the X-axis. Default ""
yaxis_title : optional
title for the Y-axis. Default ""
legend_title : optional
title for the legend. Default ""
label_width : optional
the maximum length of a label before it is shortened with "…". Default: 15
shorten_end : optional
if True labels will be shortened from the end ("LONG LA…"); if False they will be shortened from the start (e.g. "…G LABEL"). Default: False
max_rows : optional
how many rows to show, or None if all rows should be shown. Default: 15
other_label : optional
the name of the "other" category; either a string or a tuple of strings of the same length as group_by. Default: "OTHER"
include_other : optional
whether to include the "OTHER". Default: True

Returns: the plotly Figure

def histogram_compare(df_a,
df_b,
value,
group_by,
comparator=<function Compare.absolute_error>,
title_text='',
scale_by='none',
bins=15,
significant_digits=2,
decimal_points=0,
posinf_label='only in b',
neginf_label='only in a')
def render_label(value: Tuple | str, shorten_end=False, label_width=15) ‑> str

Plotly cannot render pd.MultiIndex, so we convert the tuples to strings. This function works both on MultiIndex (tuples) or Index (strings).

def sankey(df,
value_column,
categorical_columns,
title_text='',
shorten_end=False,
label_width=15) ‑> plotly.graph_objs.Figure

Args

df
the dataframe to be rendered. You do not need to consolidate the dataframe, this will be done for you in the function.
value_column
the column which will be used for the widths of the flows
categorical_columns
the columns which will be used for the columns of the Sankey.
title_text : optional
title for the chart. Default ""
label_width : optional
the maximum length of a label before it is shortened with "…". Default: 15
shorten_end : optional
if True labels will be shortened from the end ("LONG LA…"); if False they will be shortened from the start (e.g. "…G LABEL"). Default: False

Returns: the plotly Figure

def shorten_label(label: str, shorten_end=False, label_width=15) ‑> str

Trims labels that are too long and adds '…'