python-package/lets_plot/bistro/waterfall.py (58 lines of code) (raw):

# Copyright (c) 2024. JetBrains s.r.o. # Use of this source code is governed by the MIT license that can be found in the LICENSE file. from lets_plot.plot.core import PlotSpec, LayerSpec, FeatureSpecArray, aes from lets_plot.plot.util import as_annotated_data __all__ = ['waterfall_plot'] def waterfall_plot(data, x, y, *, measure=None, group=None, color=None, fill=None, size=None, alpha=None, linetype=None, width=None, show_legend=None, relative_tooltips=None, absolute_tooltips=None, sorted_value=None, threshold=None, max_values=None, base=None, calc_total=None, total_title=None, hline=None, hline_ontop=None, connector=None, relative_labels=None, absolute_labels=None, label=None, label_format=None, background_layers=None) -> PlotSpec: """ A waterfall plot shows the cumulative effect of sequentially introduced positive or negative values. Parameters ---------- data : dict or Pandas or Polars ``DataFrame`` The data to be displayed. x : str Name of a variable. y : str Name of a numeric variable. measure : str Kind of a calculation. It takes the name of a data column. The values in the column could be: 'absolute' - the value is shown as is; 'relative' - the value is shown as a difference from the previous value; 'total' - the value is shown as a cumulative sum of all previous values. group : str Grouping variable. Each group calculates its own statistics. color : str Color of the box boundary lines. For more info see `Color and Fill <https://lets-plot.org/python/pages/aesthetics.html#color-and-fill>`__. Use 'flow_type' to color lines by the direction of the flow. Flow type names: "Absolute", "Increase", "Decrease" and "Total". You could use these names to change the default colors with the `scale_color_manual() <https://lets-plot.org/python/pages/api/lets_plot.scale_color_manual.html>`__ function. fill : str Fill color of the boxes. For more info see `Color and Fill <https://lets-plot.org/python/pages/aesthetics.html#color-and-fill>`__. Use 'flow_type' to color boxes by the direction of the flow. Flow type names: "Absolute", "Increase", "Decrease" and "Total". You could use these names to change the default colors with the `scale_fill_manual() <https://lets-plot.org/python/pages/api/lets_plot.scale_fill_manual.html>`__ function. size : float, default=0.0 Line width of the box boundary lines. alpha : float Transparency level of the boxes. Accept values between 0 and 1. linetype : int or str or list Type of the box boundary lines. Accept codes or names (0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'), a hex string (up to 8 digits for dash-gap lengths), or a list pattern [offset, [dash, gap, ...]] / [dash, gap, ...]. For more info see `Line Types <https://lets-plot.org/python/pages/aesthetics.html#line-types>`__. width : float, default=0.9 Width of the boxes. Typically range between 0 and 1. Values that are greater than 1 lead to overlapping of the boxes. show_legend : bool, default=False True - show the legend. relative_tooltips : ``layer_tooltips`` or str Tooltips for boxes with relative values. Result of the call to the `layer_tooltips() <https://lets-plot.org/python/pages/api/lets_plot.layer_tooltips.html>`__ function. Specify appearance, style and content. When 'none', tooltips are not shown. When 'detailed', a more detailed (compared to the default) version of the tooltips is shown. absolute_tooltips : ``layer_tooltips`` or str Tooltips for boxes with absolute values. Result of the call to the `layer_tooltips() <https://lets-plot.org/python/pages/api/lets_plot.layer_tooltips.html>`__ function. Specify appearance, style and content. When 'none', tooltips are not shown. When 'detailed', a more detailed (compared to the default) version of the tooltips is shown. sorted_value : bool, default=False Sorts categories by absolute value of the changes. threshold : float Groups all categories under a certain threshold value into "Other" category. max_values : int Groups all categories with the smallest changes, except the first ``max_values``, into "Other" category. base : float, default=0.0 Values with measure 'absolute' or 'total' are relative to this value. calc_total : bool, default=True Setting the ``calc_total`` to True will put the final cumulative sum into a new separate box. Taken into account only if the 'measure' column isn't provided. total_title : str The header of the last box with the final cumulative sum, if 'measure' column isn't provided. Also used as a title in the legend for columns of type 'total'. hline : str or dict Horizontal line passing through 0. Set 'blank' or result of `element_blank() <https://lets-plot.org/python/pages/api/lets_plot.element_blank.html>`__ to draw nothing. Set `element_line() <https://lets-plot.org/python/pages/api/lets_plot.element_line.html>`__ to specify parameters. hline_ontop : bool, default=True Option to place horizontal line over the other layers. connector : str or dict Line between neighbouring boxes connecting the end of the previous box and the beginning of the next box. Set 'blank' or result of `element_blank() <https://lets-plot.org/python/pages/api/lets_plot.element_blank.html>`__ to draw nothing. Set `element_line() <https://lets-plot.org/python/pages/api/lets_plot.element_line.html>`__ to specify parameters. relative_labels : dict Result of the call to the `layer_labels() <https://lets-plot.org/python/pages/api/lets_plot.layer_labels.html>`__ function. Specify content and formatting of annotation labels on relative change bars. If specified, overrides ``label_format`` for relative bars. absolute_labels : dict Result of the call to the `layer_labels() <https://lets-plot.org/python/pages/api/lets_plot.layer_labels.html>`__ function. Specify content and formatting of annotation labels on absolute value bars. If specified, overrides ``label_format`` for absolute bars. label : str or dict Style configuration for labels on bars. Applied to default labels or to relative/absolute labels when ``relative_labels`` or ``absolute_labels`` are specified. Set 'blank' or result of `element_blank() <https://lets-plot.org/python/pages/api/lets_plot.element_blank.html>`__ to draw nothing. Set `element_text() <https://lets-plot.org/python/pages/api/lets_plot.element_text.html>`__ to specify style parameters. Use ``element_text(color='inherit')`` to make labels inherit the color of bar borders. label_format : str Format string used to transform label values to text. Applied to default labels or to relative/absolute labels when ``relative_labels`` or ``absolute_labels`` are specified. Can be overridden by formatting specified in ``relative_labels`` or ``absolute_labels``. Examples: - '.2f' -> '12.45' - 'Num {}' -> 'Num 12.456789' - 'TTL: {.2f}$' -> 'TTL: 12.45$' For more info see `Formatting <https://lets-plot.org/python/pages/formats.html>`__. background_layers : LayerSpec or FeatureSpecArray Background layers to be added to the plot. Returns ------- ``PlotSpec`` Plot object specification. Notes ----- Computed variables: - ..x.. : category id. - ..xlabel.. : category name. - ..ymin.. : lower value of the change. - ..ymax.. : upper value of the change. - ..measure.. : kind of a calculation: absolute, relative or total. - ..flow_type.. : direction of the flow: increasing, decreasing, or the result (total). - ..initial.. : initial value of the change. - ..value.. : current cumsum (result of the change) or absolute value (depending on the 'measure' column). - ..dy.. : value of the change. Examples -------- .. jupyter-execute:: :linenos: :emphasize-lines: 11 import numpy as np from lets_plot import * from lets_plot.bistro.waterfall import * LetsPlot.setup_html() categories = list("ABCDEF") np.random.seed(42) data = { 'x': categories, 'y': np.random.normal(size=len(categories)) } waterfall_plot(data, 'x', 'y') | .. jupyter-execute:: :linenos: :emphasize-lines: 21-25 import numpy as np from lets_plot import * from lets_plot.bistro.waterfall import * LetsPlot.setup_html() categories = list("ABCDEF") np.random.seed(42) data = { 'x': categories, 'y': np.random.normal(size=len(categories)) } band_data = { 'xmin': [-0.5, 2.5], 'xmax': [2.5, 5.5], 'name': ['Q1', 'Q2'] } text_data = { 'x': [0, 3], 'y': [2.7, 2.7], 'name': ['Q1', 'Q2'] } waterfall_plot(data, 'x', 'y', label_format='.2f', background_layers=geom_band( aes(xmin='xmin', xmax='xmax', fill='name', color='name'), data=band_data, alpha=0.2 )) + \\ geom_text(aes(x='x', y='y', label='name'), data=text_data, size=10) + \\ ggtitle("Waterfall with background layers") | .. jupyter-execute:: :linenos: :emphasize-lines: 12-18 import numpy as np from lets_plot import * from lets_plot.bistro.waterfall import * LetsPlot.setup_html() n, m = 10, 5 categories = list(range(n)) np.random.seed(42) data = { 'x': categories, 'y': np.random.randint(2 * m + 1, size=len(categories)) - m } waterfall_plot(data, 'x', 'y', \\ threshold=2, \\ width=.7, size=1, fill="white", color='flow_type', \\ hline=element_line(linetype='solid'), hline_ontop=False, \\ connector=element_line(linetype='dotted'), \\ label=element_text(color='inherit'), \\ total_title="Result", show_legend=True) | .. jupyter-execute:: :linenos: :emphasize-lines: 11-20 import numpy as np from lets_plot import * from lets_plot.bistro.waterfall import * LetsPlot.setup_html() categories = list("ABCDEFGHIJKLMNOP") np.random.seed(42) data = { 'x': categories, 'y': np.random.uniform(-1, 1, size=len(categories)) } waterfall_plot(data, 'x', 'y', sorted_value=True, max_values=5, calc_total=False, \\ relative_tooltips=layer_tooltips().title("Category: @..xlabel..") .format("@..initial..", ".2~f") .format("@..value..", ".2~f") .format("@..dy..", ".2~f") .line("@{..flow_type..}d from @..initial.. to @..value..") .line("Difference: @..dy..") .disable_splitting(), \\ size=1, alpha=.5, \\ label=element_text(color="black"), label_format=".4f") | .. jupyter-execute:: :linenos: :emphasize-lines: 17-18 from lets_plot import * from lets_plot.bistro.waterfall import * LetsPlot.setup_html() data = { 'company': ["Badgersoft"] * 7 + ["AIlien Co."] * 7, 'accounts': ["initial", "revenue", "costs", "Q1", "revenue", "costs", "Q2"] * 2, 'values': [200, 200, -100, None, 250, -100, None, 150, 50, -100, None, 100, -100, None], 'measure': ['absolute', 'relative', 'relative', 'total', 'relative', 'relative', 'total'] * 2, } colors = { "Absolute": "darkseagreen", "Increase": "palegoldenrod", "Decrease": "paleturquoise", "Total": "palegreen", } waterfall_plot(data, 'accounts', 'values', measure='measure', group='company', size=.75, label=element_text(color="black")) + \\ scale_fill_manual(values=colors) + \\ facet_wrap(facets='company', scales='free_x') """ data, mapping, data_meta = as_annotated_data(data, aes(x=x, y=y)) if background_layers is None: layers = [] elif isinstance(background_layers, LayerSpec): layers = [background_layers] elif isinstance(background_layers, FeatureSpecArray): for sublayer in background_layers.elements(): if not isinstance(sublayer, LayerSpec): raise TypeError("Invalid 'layer' type: {}".format(type(sublayer))) layers = background_layers.elements() else: raise TypeError("Invalid 'layer' type: {}".format(type(background_layers))) return PlotSpec(data=data, mapping=None, scales=[], layers=[], bistro={ 'name': 'waterfall', 'x': x, 'y': y, 'measure': measure, 'group': group, 'color': color, 'fill': fill, 'size': size, 'alpha': alpha, 'linetype': linetype, 'width': width, 'show_legend': show_legend, 'relative_tooltips': relative_tooltips, 'absolute_tooltips': absolute_tooltips, 'sorted_value': sorted_value, 'threshold': threshold, 'max_values': max_values, 'base': base, 'calc_total': calc_total, 'total_title': total_title, 'hline': hline, 'hline_ontop': hline_ontop, 'connector': connector, 'relative_labels': relative_labels, 'absolute_labels': absolute_labels, 'label': label, 'label_format': label_format, 'background_layers': [layer.as_dict() for layer in layers] }, **data_meta)