From 99ef028b37de61add55c8851a9ff3114de233474 Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Wed, 23 Oct 2024 15:17:50 -0500 Subject: [PATCH 1/4] wip --- plugins/plotly-express/docs/bar.md | 15 ++ .../src/deephaven/plot/express/__init__.py | 1 - .../deephaven/plot/express/plots/__init__.py | 2 +- .../src/deephaven/plot/express/plots/bar.py | 105 +-------- .../deephaven/plot/express/plots/test_bar.py | 217 ++++++++++++++++++ 5 files changed, 241 insertions(+), 99 deletions(-) create mode 100644 plugins/plotly-express/test/deephaven/plot/express/plots/test_bar.py diff --git a/plugins/plotly-express/docs/bar.md b/plugins/plotly-express/docs/bar.md index 37503beaf..6d2431b36 100644 --- a/plugins/plotly-express/docs/bar.md +++ b/plugins/plotly-express/docs/bar.md @@ -50,6 +50,21 @@ bar_plot_smoke = dx.bar(sorted_tips, x="Day", y="TotalBill", by="Smoker") bar_plot_sex = dx.bar(sorted_tips, x="Day", y="TotalBill", by="Sex") ``` +### Frequency of categories + +Visualize the frequency of categories in a column by passing to either the `x` or `y` argument. + +```python +import deephaven.plot.express as dx +tips = dx.data.tips() + +# count the number of occurrences of each day with a vertical bar plot +bar_plot_vertical = dx.bar(tips, x="Day") + +# count the number of occurrences of each day with a horizontal bar plot +bar_plot_horizontal = dx.bar(tips, y="Day") +``` + ## API Reference ```{eval-rst} .. dhautofunction:: deephaven.plot.express.bar diff --git a/plugins/plotly-express/src/deephaven/plot/express/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/__init__.py index 6a4694442..7ece24c5c 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/__init__.py @@ -12,7 +12,6 @@ from .plots import ( area, bar, - frequency_bar, timeline, histogram, box, diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py index f87c84086..8bd067327 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py @@ -1,7 +1,7 @@ from .scatter import scatter, scatter_3d, scatter_polar, scatter_ternary from .line import line, line_3d, line_polar, line_ternary from .area import area -from .bar import bar, frequency_bar, timeline +from .bar import bar, timeline from .distribution import histogram, violin, strip, box from .financial import candlestick, ohlc from .hierarchial import treemap, icicle, sunburst, funnel, funnel_area diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py index ed67460f7..f3a9c885e 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py @@ -55,7 +55,9 @@ def bar( Args: table: A table to pull data from. x: A column or list of columns that contain x-axis values. + If only x is specified, the y-axis values are the count of each unique x value. y: A column or list of columns that contain y-axis values. + If only y is specified, the x-axis values are the count of each unique y value. by: A column or list of columns that contain values to plot the figure traces by. All values or combination of values map to a unique design. The variable by_vars specifies which design elements are used. @@ -144,7 +146,12 @@ def bar( """ args = locals() - return process_args(args, {"bar", "supports_lists"}, px_func=px.bar) + groups = {"bar", "supports_lists"} + + if not x or not y: + groups.add("preprocess_freq") + + return process_args(args, groups, px_func=px.bar) def _bar_polar( @@ -299,99 +306,3 @@ def timeline( args = locals() return process_args(args, {"bar", "preprocess_time"}, px_func=px.timeline) - - -def frequency_bar( - table: Table | None = None, - x: str | list[str] | None = None, - y: str | list[str] | None = None, - by: str | list[str] | None = None, - by_vars: str | list[str] = "color", - labels: dict[str, str] | None = None, - color: str | list[str] | None = None, - pattern_shape: str | list[str] | None = None, - color_discrete_sequence: list[str] | None = None, - color_discrete_map: dict[str | tuple[str], str] | None = None, - pattern_shape_sequence: list[str] | None = None, - pattern_shape_map: dict[str | tuple[str], str] | None = None, - opacity: float | None = None, - barmode: str = "relative", - log_x: bool = False, - log_y: bool = False, - range_x: list[int] | None = None, - range_y: list[int] | None = None, - text_auto: bool | str = False, - title: str | None = None, - template: str | None = None, - unsafe_update_figure: Callable = default_callback, -) -> DeephavenFigure: - """Returns a bar chart that contains the counts of the specified columns - - Args: - table: A table to pull data from. - x: A column or list of columns that contain x-axis values. - Only one of x or y can be specified. If x is specified, the bars - are drawn vertically. - y: A column or list of columns that contain y-axis values. - Only one of x or y can be specified. If y is specified, the bars - are drawn horizontally. - by: A column or list of columns that contain values to plot the figure traces by. - All values or combination of values map to a unique design. The variable - by_vars specifies which design elements are used. - This is overriden if any specialized design variables such as color are specified - by_vars: A string or list of string that contain design elements to plot by. - Can contain color and pattern_shape. - If associated maps or sequences are specified, they are used to map by column values - to designs. Otherwise, default values are used. - color: A column or list of columns that contain color values. - The value is used for a plot by on color. - See color_discrete_map for additional behaviors. - pattern_shape: A column or list of columns that contain pattern shape values. - The value is used for a plot by on pattern shape. - See pattern_shape_map for additional behaviors. - labels: A dictionary of labels mapping columns to new labels. - color_discrete_sequence: A list of colors to sequentially apply to - the series. The colors loop, so if there are more series than colors, - colors will be reused. - color_discrete_map: If dict, the keys should be strings of the column values (or a tuple - of combinations of column values) which map to colors. - pattern_shape_sequence: A list of patterns to sequentially apply - to the series. The patterns loop, so if there are more series than - patterns, patterns will be reused. - pattern_shape_map: If dict, the keys should be strings of the column values (or a tuple - of combinations of column values) which map to patterns. - opacity: Opacity to apply to all markers. 0 is completely transparent - and 1 is completely opaque. - barmode: If 'relative', bars are stacked. If 'overlay', bars are drawn on top - of each other. If 'group', bars are drawn next to each other. - log_x: A boolean that specifies if the corresponding axis is a log - axis or not. - log_y: A boolean that specifies if the corresponding axis is a log - axis or not. - range_x: A list of two numbers that specify the range of the x-axis. - range_y: A list of two numbers that specify the range of the y-axis. - text_auto: If True, display the value at each bar. - If a string, specifies a plotly texttemplate. - title: The title of the chart - template: The template for the chart. - unsafe_update_figure: An update function that takes a plotly figure - as an argument and optionally returns a plotly figure. If a figure is - not returned, the plotly figure passed will be assumed to be the return - value. Used to add any custom changes to the underlying plotly figure. - Note that the existing data traces should not be removed. This may lead - to unexpected behavior if traces are modified in a way that break data - mappings. - - Returns: - DeephavenFigure: A DeephavenFigure that contains the bar chart - - """ - - if x and y: - raise ValueError("Cannot specify both x and y") - - args = locals() - - return process_args( - args, {"bar", "preprocess_freq", "supports_lists"}, px_func=px.bar - ) diff --git a/plugins/plotly-express/test/deephaven/plot/express/plots/test_bar.py b/plugins/plotly-express/test/deephaven/plot/express/plots/test_bar.py new file mode 100644 index 000000000..481b4e23b --- /dev/null +++ b/plugins/plotly-express/test/deephaven/plot/express/plots/test_bar.py @@ -0,0 +1,217 @@ +import unittest + +from ..BaseTest import BaseTestCase + + +class BarTestCase(BaseTestCase): + def setUp(self) -> None: + from deephaven import new_table + from deephaven.column import int_col + + self.source = new_table( + [ + int_col("X", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("X2", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("Y", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("Y2", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("size", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("text", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("hover_name", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("category", [1, 2, 1, 2, 1, 2, 1, 2, 1]), + ] + ) + + def test_basic_bar_x(self): + import src.deephaven.plot.express as dx + from deephaven.constants import NULL_LONG, NULL_INT + + chart = dx.bar(self.source, x="X").to_dict(self.exporter) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "alignmentgroup": "True", + "hovertemplate": "X=%{x}
count=%{y}", + "legendgroup": "", + "marker": {"color": "#636efa", "pattern": {"shape": ""}}, + "name": "", + "offsetgroup": "", + "orientation": "v", + "showlegend": False, + "textposition": "auto", + "type": "bar", + "x": [NULL_INT], + "xaxis": "x", + "y": [NULL_LONG], + "yaxis": "y", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "barmode": "relative", + "legend": {"tracegroupgap": 0}, + "margin": {"t": 60}, + "xaxis": { + "anchor": "y", + "domain": [0.0, 1.0], + "side": "bottom", + "title": {"text": "X"}, + }, + "yaxis": { + "anchor": "x", + "domain": [0.0, 1.0], + "side": "left", + "title": {"text": "count"}, + }, + } + + self.assertEqual(plotly["layout"], expected_layout) + + expected_mappings = [ + { + "data_columns": { + "X": ["/plotly/data/0/x"], + "count": ["/plotly/data/0/y"], + }, + "table": 0, + } + ] + + self.assertEqual(deephaven["mappings"], expected_mappings) + + self.assertEqual(deephaven["is_user_set_template"], False) + self.assertEqual(deephaven["is_user_set_color"], False) + + def test_basic_bar_y(self): + import src.deephaven.plot.express as dx + from deephaven.constants import NULL_LONG, NULL_INT + + chart = dx.bar(self.source, y="Y").to_dict(self.exporter) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "alignmentgroup": "True", + "hovertemplate": "count=%{x}
Y=%{y}", + "legendgroup": "", + "marker": {"color": "#636efa", "pattern": {"shape": ""}}, + "name": "", + "offsetgroup": "", + "orientation": "h", + "showlegend": False, + "textposition": "auto", + "type": "bar", + "x": [NULL_LONG], + "xaxis": "x", + "y": [NULL_INT], + "yaxis": "y", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "barmode": "relative", + "legend": {"tracegroupgap": 0}, + "margin": {"t": 60}, + "xaxis": { + "anchor": "y", + "domain": [0.0, 1.0], + "side": "bottom", + "title": {"text": "count"}, + }, + "yaxis": { + "anchor": "x", + "domain": [0.0, 1.0], + "side": "left", + "title": {"text": "Y"}, + }, + } + + self.assertEqual(plotly["layout"], expected_layout) + + expected_mappings = [ + { + "data_columns": { + "Y": ["/plotly/data/0/y"], + "count": ["/plotly/data/0/x"], + }, + "table": 0, + } + ] + + self.assertEqual(deephaven["mappings"], expected_mappings) + + self.assertEqual(deephaven["is_user_set_template"], False) + self.assertEqual(deephaven["is_user_set_color"], False) + + def test_basic_bar_x_y(self): + import src.deephaven.plot.express as dx + from deephaven.constants import NULL_INT + + chart = dx.bar(self.source, x="X", y="Y").to_dict(self.exporter) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "alignmentgroup": "True", + "hovertemplate": "X=%{x}
Y=%{y}", + "legendgroup": "", + "marker": {"color": "#636efa", "pattern": {"shape": ""}}, + "name": "", + "offsetgroup": "", + "orientation": "v", + "showlegend": False, + "textposition": "auto", + "type": "bar", + "x": [NULL_INT], + "xaxis": "x", + "y": [NULL_INT], + "yaxis": "y", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "barmode": "relative", + "legend": {"tracegroupgap": 0}, + "margin": {"t": 60}, + "xaxis": { + "anchor": "y", + "domain": [0.0, 1.0], + "side": "bottom", + "title": {"text": "X"}, + }, + "yaxis": { + "anchor": "x", + "domain": [0.0, 1.0], + "side": "left", + "title": {"text": "Y"}, + }, + } + + self.assertEqual(plotly["layout"], expected_layout) + + expected_mappings = [ + { + "data_columns": {"X": ["/plotly/data/0/x"], "Y": ["/plotly/data/0/y"]}, + "table": 0, + } + ] + + self.assertEqual(deephaven["mappings"], expected_mappings) + + self.assertEqual(deephaven["is_user_set_template"], False) + self.assertEqual(deephaven["is_user_set_color"], False) From 1a45a363a92e4d713c07cd3cc964efc6e598a57d Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Thu, 24 Oct 2024 11:28:21 -0500 Subject: [PATCH 2/4] wip --- .../src/deephaven/plot/express/__init__.py | 1 + .../deephaven/plot/express/plots/__init__.py | 2 +- .../src/deephaven/plot/express/plots/bar.py | 99 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/plugins/plotly-express/src/deephaven/plot/express/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/__init__.py index 7ece24c5c..6a4694442 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/__init__.py @@ -12,6 +12,7 @@ from .plots import ( area, bar, + frequency_bar, timeline, histogram, box, diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py index 8bd067327..f87c84086 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py @@ -1,7 +1,7 @@ from .scatter import scatter, scatter_3d, scatter_polar, scatter_ternary from .line import line, line_3d, line_polar, line_ternary from .area import area -from .bar import bar, timeline +from .bar import bar, frequency_bar, timeline from .distribution import histogram, violin, strip, box from .financial import candlestick, ohlc from .hierarchial import treemap, icicle, sunburst, funnel, funnel_area diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py index f3a9c885e..e2ab5e91e 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Callable +import warnings from plotly import express as px @@ -306,3 +307,101 @@ def timeline( args = locals() return process_args(args, {"bar", "preprocess_time"}, px_func=px.timeline) + + +def frequency_bar( + table: Table | None = None, + x: str | list[str] | None = None, + y: str | list[str] | None = None, + by: str | list[str] | None = None, + by_vars: str | list[str] = "color", + labels: dict[str, str] | None = None, + color: str | list[str] | None = None, + pattern_shape: str | list[str] | None = None, + color_discrete_sequence: list[str] | None = None, + color_discrete_map: dict[str | tuple[str], str] | None = None, + pattern_shape_sequence: list[str] | None = None, + pattern_shape_map: dict[str | tuple[str], str] | None = None, + opacity: float | None = None, + barmode: str = "relative", + log_x: bool = False, + log_y: bool = False, + range_x: list[int] | None = None, + range_y: list[int] | None = None, + text_auto: bool | str = False, + title: str | None = None, + template: str | None = None, + unsafe_update_figure: Callable = default_callback, +) -> DeephavenFigure: + """Returns a bar chart that contains the counts of the specified columns + Args: + table: A table to pull data from. + x: A column or list of columns that contain x-axis values. + Only one of x or y can be specified. If x is specified, the bars + are drawn vertically. + y: A column or list of columns that contain y-axis values. + Only one of x or y can be specified. If y is specified, the bars + are drawn horizontally. + by: A column or list of columns that contain values to plot the figure traces by. + All values or combination of values map to a unique design. The variable + by_vars specifies which design elements are used. + This is overriden if any specialized design variables such as color are specified + by_vars: A string or list of string that contain design elements to plot by. + Can contain color and pattern_shape. + If associated maps or sequences are specified, they are used to map by column values + to designs. Otherwise, default values are used. + color: A column or list of columns that contain color values. + The value is used for a plot by on color. + See color_discrete_map for additional behaviors. + pattern_shape: A column or list of columns that contain pattern shape values. + The value is used for a plot by on pattern shape. + See pattern_shape_map for additional behaviors. + labels: A dictionary of labels mapping columns to new labels. + color_discrete_sequence: A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors will be reused. + color_discrete_map: If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to colors. + pattern_shape_sequence: A list of patterns to sequentially apply + to the series. The patterns loop, so if there are more series than + patterns, patterns will be reused. + pattern_shape_map: If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to patterns. + opacity: Opacity to apply to all markers. 0 is completely transparent + and 1 is completely opaque. + barmode: If 'relative', bars are stacked. If 'overlay', bars are drawn on top + of each other. If 'group', bars are drawn next to each other. + log_x: A boolean that specifies if the corresponding axis is a log + axis or not. + log_y: A boolean that specifies if the corresponding axis is a log + axis or not. + range_x: A list of two numbers that specify the range of the x-axis. + range_y: A list of two numbers that specify the range of the y-axis. + text_auto: If True, display the value at each bar. + If a string, specifies a plotly texttemplate. + title: The title of the chart + template: The template for the chart. + unsafe_update_figure: An update function that takes a plotly figure + as an argument and optionally returns a plotly figure. If a figure is + not returned, the plotly figure passed will be assumed to be the return + value. Used to add any custom changes to the underlying plotly figure. + Note that the existing data traces should not be removed. This may lead + to unexpected behavior if traces are modified in a way that break data + mappings. + Returns: + DeephavenFigure: A DeephavenFigure that contains the bar chart + """ + warnings.warn( + "This function is deprecated and will be removed in a future release. " + "Use bar with only one of x or y specified instead for identical behavior.", + FutureWarning, + ) + + if x and y: + raise ValueError("Cannot specify both x and y") + + args = locals() + + return process_args( + args, {"bar", "preprocess_freq", "supports_lists"}, px_func=px.bar + ) From ee1cb6905d103f5fc13d7f41e80c76f9c4cede0d Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Thu, 24 Oct 2024 11:32:02 -0500 Subject: [PATCH 3/4] wip --- plugins/plotly-express/src/deephaven/plot/express/plots/bar.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py index e2ab5e91e..9ef160bce 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py @@ -334,6 +334,7 @@ def frequency_bar( unsafe_update_figure: Callable = default_callback, ) -> DeephavenFigure: """Returns a bar chart that contains the counts of the specified columns + Args: table: A table to pull data from. x: A column or list of columns that contain x-axis values. @@ -388,8 +389,10 @@ def frequency_bar( Note that the existing data traces should not be removed. This may lead to unexpected behavior if traces are modified in a way that break data mappings. + Returns: DeephavenFigure: A DeephavenFigure that contains the bar chart + """ warnings.warn( "This function is deprecated and will be removed in a future release. " From 61b654c527845da4b3d5070d28fb4fbecc7780e4 Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Thu, 24 Oct 2024 15:08:21 -0500 Subject: [PATCH 4/4] wip --- plugins/plotly-express/src/deephaven/plot/express/plots/bar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py index 9ef160bce..58b75e59c 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py @@ -397,7 +397,8 @@ def frequency_bar( warnings.warn( "This function is deprecated and will be removed in a future release. " "Use bar with only one of x or y specified instead for identical behavior.", - FutureWarning, + DeprecationWarning, + stacklevel=2, ) if x and y: