holobench 1.41.0__py3-none-any.whl → 1.43.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. bencher/__init__.py +20 -2
  2. bencher/bench_cfg.py +262 -54
  3. bencher/bench_report.py +2 -2
  4. bencher/bench_runner.py +96 -10
  5. bencher/bencher.py +421 -89
  6. bencher/class_enum.py +70 -7
  7. bencher/example/example_dataframe.py +2 -2
  8. bencher/example/example_levels.py +17 -173
  9. bencher/example/example_pareto.py +107 -31
  10. bencher/example/example_rerun2.py +1 -1
  11. bencher/example/example_simple_bool.py +2 -2
  12. bencher/example/example_simple_float2d.py +6 -1
  13. bencher/example/example_video.py +2 -0
  14. bencher/example/experimental/example_hvplot_explorer.py +2 -2
  15. bencher/example/inputs_0D/example_0_in_1_out.py +25 -15
  16. bencher/example/inputs_0D/example_0_in_2_out.py +12 -3
  17. bencher/example/inputs_0_float/example_0_cat_in_2_out.py +88 -0
  18. bencher/example/inputs_0_float/example_1_cat_in_2_out.py +98 -0
  19. bencher/example/inputs_0_float/example_2_cat_in_2_out.py +107 -0
  20. bencher/example/inputs_0_float/example_3_cat_in_2_out.py +111 -0
  21. bencher/example/inputs_1D/example1d_common.py +48 -12
  22. bencher/example/inputs_1D/example_0_float_1_cat.py +33 -0
  23. bencher/example/inputs_1D/example_1_cat_in_2_out_repeats.py +68 -0
  24. bencher/example/inputs_1D/example_1_float_2_cat_repeats.py +3 -0
  25. bencher/example/inputs_1D/example_1_int_in_1_out.py +98 -0
  26. bencher/example/inputs_1D/example_1_int_in_2_out.py +101 -0
  27. bencher/example/inputs_1D/example_1_int_in_2_out_repeats.py +99 -0
  28. bencher/example/inputs_1_float/example_1_float_0_cat_in_2_out.py +117 -0
  29. bencher/example/inputs_1_float/example_1_float_1_cat_in_2_out.py +124 -0
  30. bencher/example/inputs_1_float/example_1_float_2_cat_in_2_out.py +132 -0
  31. bencher/example/inputs_1_float/example_1_float_3_cat_in_2_out.py +140 -0
  32. bencher/example/inputs_2D/example_2_cat_in_4_out_repeats.py +104 -0
  33. bencher/example/inputs_2_float/example_2_float_0_cat_in_2_out.py +98 -0
  34. bencher/example/inputs_2_float/example_2_float_1_cat_in_2_out.py +112 -0
  35. bencher/example/inputs_2_float/example_2_float_2_cat_in_2_out.py +122 -0
  36. bencher/example/inputs_2_float/example_2_float_3_cat_in_2_out.py +138 -0
  37. bencher/example/inputs_3_float/example_3_float_0_cat_in_2_out.py +111 -0
  38. bencher/example/inputs_3_float/example_3_float_1_cat_in_2_out.py +117 -0
  39. bencher/example/inputs_3_float/example_3_float_2_cat_in_2_out.py +124 -0
  40. bencher/example/inputs_3_float/example_3_float_3_cat_in_2_out.py +129 -0
  41. bencher/example/meta/generate_examples.py +118 -7
  42. bencher/example/meta/generate_meta.py +88 -40
  43. bencher/job.py +174 -9
  44. bencher/plotting/plot_filter.py +52 -17
  45. bencher/results/bench_result.py +117 -25
  46. bencher/results/bench_result_base.py +117 -8
  47. bencher/results/dataset_result.py +6 -200
  48. bencher/results/explorer_result.py +23 -0
  49. bencher/results/{hvplot_result.py → histogram_result.py} +3 -18
  50. bencher/results/holoview_results/__init__.py +0 -0
  51. bencher/results/holoview_results/bar_result.py +79 -0
  52. bencher/results/holoview_results/curve_result.py +110 -0
  53. bencher/results/holoview_results/distribution_result/__init__.py +0 -0
  54. bencher/results/holoview_results/distribution_result/box_whisker_result.py +73 -0
  55. bencher/results/holoview_results/distribution_result/distribution_result.py +109 -0
  56. bencher/results/holoview_results/distribution_result/scatter_jitter_result.py +92 -0
  57. bencher/results/holoview_results/distribution_result/violin_result.py +70 -0
  58. bencher/results/holoview_results/heatmap_result.py +319 -0
  59. bencher/results/holoview_results/holoview_result.py +346 -0
  60. bencher/results/holoview_results/line_result.py +240 -0
  61. bencher/results/holoview_results/scatter_result.py +107 -0
  62. bencher/results/holoview_results/surface_result.py +158 -0
  63. bencher/results/holoview_results/table_result.py +14 -0
  64. bencher/results/holoview_results/tabulator_result.py +20 -0
  65. bencher/results/optuna_result.py +30 -115
  66. bencher/results/video_controls.py +38 -0
  67. bencher/results/video_result.py +39 -36
  68. bencher/results/video_summary.py +2 -2
  69. bencher/results/{plotly_result.py → volume_result.py} +29 -8
  70. bencher/utils.py +175 -26
  71. bencher/variables/inputs.py +122 -15
  72. bencher/video_writer.py +2 -1
  73. bencher/worker_job.py +31 -3
  74. {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/METADATA +24 -24
  75. holobench-1.43.0.dist-info/RECORD +147 -0
  76. bencher/example/example_levels2.py +0 -37
  77. bencher/example/inputs_1D/example_1_in_1_out.py +0 -62
  78. bencher/example/inputs_1D/example_1_in_2_out.py +0 -63
  79. bencher/example/inputs_1D/example_1_in_2_out_repeats.py +0 -61
  80. bencher/results/holoview_result.py +0 -796
  81. bencher/results/panel_result.py +0 -41
  82. holobench-1.41.0.dist-info/RECORD +0 -114
  83. {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/WHEEL +0 -0
  84. {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,16 @@
1
1
  from typing import Optional
2
+ from functools import partial
3
+
2
4
  import panel as pn
3
5
  from param import Parameter
4
- from bencher.results.bench_result_base import BenchResultBase, ReduceType
5
-
6
-
7
- from functools import partial
8
6
  import holoviews as hv
9
- from bencher.variables.results import (
10
- PANEL_TYPES,
11
- )
7
+
8
+ from bencher.variables.results import PANEL_TYPES
9
+ from bencher.results.bench_result_base import BenchResultBase, ReduceType
12
10
 
13
11
 
14
12
  class DataSetResult(BenchResultBase):
15
- def to_dataset1(
13
+ def to_plot(
16
14
  self,
17
15
  result_var: Parameter = None,
18
16
  hv_dataset=None,
@@ -33,195 +31,3 @@ class DataSetResult(BenchResultBase):
33
31
  result_types=PANEL_TYPES,
34
32
  **kwargs,
35
33
  )
36
-
37
-
38
- # class DataSetResult(BenchResultBase):
39
-
40
- # def to_datatset(
41
- # self,
42
- # result_var: Parameter = None,
43
- # result_types=(ResultDataSet,),
44
- # pane_collection: pn.pane = None,
45
- # time_sequence_dimension=0,
46
- # target_duration: float = None,
47
- # **kwargs,
48
- # ) -> Optional[pn.panel]:
49
- # """Returns the results compiled into a video
50
-
51
- # Args:
52
- # result_var (Parameter, optional): The result var to plot. Defaults to None.
53
- # result_types (tuple, optional): The types of result var to convert to video. Defaults to (ResultDataSet,).
54
- # collection (pn.pane, optional): If there are multiple results, use this collection to stack them. Defaults to pn.Row().
55
-
56
- # Returns:
57
- # Optional[pn.panel]: a panel pane with a video of all results concatenated together
58
- # """
59
- # plot_filter = PlotFilter(
60
- # float_range=VarRange(0, None),
61
- # cat_range=VarRange(0, None),
62
- # panel_range=VarRange(1, None),
63
- # input_range=VarRange(0, None),
64
- # )
65
- # matches_res = plot_filter.matches_result(
66
- # self.plt_cnt_cfg, callable_name(self.to_video_grid_ds)
67
- # )
68
-
69
- # if pane_collection is None:
70
- # pane_collection = pn.Row()
71
-
72
- # if matches_res.overall:
73
- # ds = self.to_dataset(ReduceType.SQUEEZE)
74
- # for rv in self.get_results_var_list(result_var):
75
- # if isinstance(rv, result_types):
76
- # pane_collection.append(
77
- # self.to_video_grid_ds(
78
- # ds,
79
- # rv,
80
- # time_sequence_dimension=time_sequence_dimension,
81
- # target_duration=target_duration,
82
- # **kwargs,
83
- # )
84
- # )
85
- # return pane_collection
86
- # return matches_res.to_panel()
87
-
88
- # def to_video_grid_ds(
89
- # self,
90
- # dataset: xr.Dataset,
91
- # result_var: Parameter,
92
- # reverse=True,
93
- # time_sequence_dimension=0,
94
- # video_controls: VideoControls = None,
95
- # target_duration: float = None,
96
- # **kwargs,
97
- # ):
98
- # cvc = self._to_video_panes_ds(
99
- # dataset,
100
- # self.plot_cb,
101
- # target_dimension=0,
102
- # horizontal=True,
103
- # compose_method=ComposeType.right,
104
- # time_sequence_dimension=time_sequence_dimension,
105
- # result_var=result_var,
106
- # final=True,
107
- # reverse=reverse,
108
- # target_duration=target_duration,
109
- # **kwargs,
110
- # )
111
-
112
- # filename = VideoWriter().write_video_raw(cvc)
113
-
114
- # if filename is not None:
115
- # if video_controls is None:
116
- # video_controls = VideoControls()
117
- # return video_controls.video_container(
118
- # filename, width=kwargs.get("width", None), height=kwargs.get("height", None)
119
- # )
120
- # return None
121
-
122
- # def plot_cb(self, dataset, result_var, **kwargs):
123
- # val = self.ds_to_container(dataset, result_var, container=None, **kwargs)
124
- # return val
125
-
126
- # def dataset_to_compose_list(
127
- # self,
128
- # dataset: xr.Dataset,
129
- # first_compose_method: ComposeType = ComposeType.down,
130
- # time_sequence_dimension: int = 0,
131
- # ) -> List[ComposeType]:
132
- # """ "Given a dataset, chose an order for composing the results. By default will flip between right and down and the last dimension will be a time sequence.
133
-
134
- # Args:
135
- # dataset (xr.Dataset): the dataset to render
136
- # first_compose_method (ComposeType, optional): the direction of the first composition method. Defaults to ComposeType.right.
137
- # time_sequence_dimension (int, optional): The dimension to start time sequencing instead of composing in space. Defaults to 0.
138
-
139
- # Returns:
140
- # List[ComposeType]: A list of composition methods for composing the dataset result
141
- # """
142
-
143
- # num_dims = len(dataset.sizes)
144
- # if time_sequence_dimension == -1: # use time sequence for everything
145
- # compose_method_list = [ComposeType.sequence] * (num_dims + 1)
146
- # else:
147
- # compose_method_list = [first_compose_method]
148
- # compose_method_list.extend(
149
- # ComposeType.flip(compose_method_list[-1]) for _ in range(num_dims - 1)
150
- # )
151
- # compose_method_list.append(ComposeType.sequence)
152
-
153
- # for i in range(min(len(compose_method_list), time_sequence_dimension + 1)):
154
- # compose_method_list[i] = ComposeType.sequence
155
-
156
- # return compose_method_list
157
-
158
- # def _to_video_panes_ds(
159
- # self,
160
- # dataset: xr.Dataset,
161
- # plot_callback: callable = None,
162
- # target_dimension=0,
163
- # compose_method=ComposeType.right,
164
- # compose_method_list=None,
165
- # result_var=None,
166
- # time_sequence_dimension=0,
167
- # root_dimensions=None,
168
- # reverse=False,
169
- # target_duration: float = None,
170
- # **kwargs,
171
- # ) -> pn.panel:
172
- # num_dims = len(dataset.sizes)
173
- # dims = list(d for d in dataset.sizes)
174
- # if reverse:
175
- # dims = list(reversed(dims))
176
-
177
- # if root_dimensions is None:
178
- # root_dimensions = num_dims
179
-
180
- # if compose_method_list is None:
181
- # compose_method_list = self.dataset_to_compose_list(
182
- # dataset, compose_method, time_sequence_dimension=time_sequence_dimension
183
- # )
184
-
185
- # # print(compose_method_list)
186
-
187
- # compose_method_list_pop = deepcopy(compose_method_list)
188
- # if len(compose_method_list_pop) > 1:
189
- # compose_method = compose_method_list_pop.pop()
190
-
191
- # if num_dims > (target_dimension) and num_dims != 0:
192
- # selected_dim = dims[-1]
193
- # outer_container = ComposableContainerVideo()
194
- # for i in range(dataset.sizes[selected_dim]):
195
- # sliced = dataset.isel({selected_dim: i})
196
- # label_val = sliced.coords[selected_dim].values.item()
197
- # inner_container = ComposableContainerVideo()
198
-
199
- # panes = self._to_video_panes_ds(
200
- # sliced,
201
- # plot_callback=plot_callback,
202
- # target_dimension=target_dimension,
203
- # compose_method_list=compose_method_list_pop,
204
- # result_var=result_var,
205
- # root_dimensions=root_dimensions,
206
- # time_sequence_dimension=time_sequence_dimension,
207
- # )
208
- # inner_container.append(panes)
209
-
210
- # rendered = inner_container.render(
211
- # RenderCfg(
212
- # var_name=selected_dim,
213
- # var_value=label_val,
214
- # compose_method=compose_method,
215
- # duration=target_duration,
216
- # )
217
- # )
218
- # outer_container.append(rendered)
219
- # return outer_container.render(
220
- # RenderCfg(
221
- # compose_method=compose_method,
222
- # duration=target_duration,
223
- # background_col=color_tuple_to_255(int_to_col(num_dims - 2, 0.05, 1.0)),
224
- # # background_col= (255,0,0),
225
- # )
226
- # )
227
- # return plot_callback(dataset=dataset, result_var=result_var, **kwargs)
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+ import panel as pn
3
+ import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
4
+ import hvplot.pandas # noqa pylint: disable=duplicate-code,unused-import
5
+
6
+ from bencher.results.video_result import VideoResult
7
+
8
+
9
+ class ExplorerResult(VideoResult):
10
+ def to_plot(self, **kwargs) -> pn.pane.Pane: # noqa pylint: disable=unused-argument
11
+ """Produces a hvplot explorer instance to explore the generated dataset
12
+ see: https://hvplot.holoviz.org/getting_started/explorer.html
13
+
14
+ Returns:
15
+ pn.pane.Pane: A dynamic pane for exploring a dataset
16
+ """
17
+
18
+ if len(self.bench_cfg.input_vars) > 0:
19
+ return self.to_xarray().hvplot.explorer()
20
+
21
+ # For some reason hvplot doesn't like 1D datasets in xarray, so convert to pandas which it has no problem with
22
+ # TODO look into why this is, its probably due to how I am setting up the indexing in xarray.
23
+ return self.to_pandas().hvplot.explorer()
@@ -6,30 +6,15 @@ import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
6
6
  import hvplot.pandas # noqa pylint: disable=duplicate-code,unused-import
7
7
  import xarray as xr
8
8
 
9
- from bencher.results.panel_result import PanelResult
9
+ from bencher.results.video_result import VideoResult
10
10
  from bencher.results.bench_result_base import ReduceType
11
11
 
12
12
  from bencher.plotting.plot_filter import VarRange
13
13
  from bencher.variables.results import ResultVar
14
14
 
15
15
 
16
- class HvplotResult(PanelResult):
17
- def to_explorer(self) -> pn.pane.Pane:
18
- """Produces a hvplot explorer instance to explore the generated dataset
19
- see: https://hvplot.holoviz.org/getting_started/explorer.html
20
-
21
- Returns:
22
- pn.pane.Pane: A dynamic pane for exploring a dataset
23
- """
24
-
25
- if len(self.bench_cfg.input_vars) > 0:
26
- return self.to_xarray().hvplot.explorer()
27
-
28
- # For some reason hvplot doesn't like 1D datasets in xarray, so convert to pandas which it has no problem with
29
- # TODO look into why this is, its probably due to how I am setting up the indexing in xarray.
30
- return self.to_pandas().hvplot.explorer()
31
-
32
- def to_histogram(self, result_var: Parameter = None, **kwargs) -> Optional[pn.pane.Pane]:
16
+ class HistogramResult(VideoResult):
17
+ def to_plot(self, result_var: Parameter = None, **kwargs) -> Optional[pn.pane.Pane]:
33
18
  return self.filter(
34
19
  self.to_histogram_ds,
35
20
  float_range=VarRange(0, 0),
File without changes
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+ from typing import Optional
3
+ import panel as pn
4
+ from param import Parameter
5
+ import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
6
+ import xarray as xr
7
+
8
+ from bencher.results.bench_result_base import ReduceType
9
+ from bencher.plotting.plot_filter import VarRange
10
+ from bencher.variables.results import ResultVar
11
+ from bencher.results.holoview_results.holoview_result import HoloviewResult
12
+
13
+
14
+ class BarResult(HoloviewResult):
15
+ """A class for creating bar chart visualizations from benchmark results.
16
+
17
+ Bar charts are effective for comparing values across categorical variables or
18
+ discrete data points. This class provides methods to generate bar charts that
19
+ display benchmark results, particularly useful for comparing performance metrics
20
+ between different configurations or categories.
21
+ """
22
+
23
+ def to_plot(
24
+ self, result_var: Parameter = None, override: bool = True, **kwargs
25
+ ) -> Optional[pn.panel]:
26
+ return self.to_bar(result_var, override, **kwargs)
27
+
28
+ def to_bar(
29
+ self, result_var: Parameter = None, override: bool = True, **kwargs
30
+ ) -> Optional[pn.panel]:
31
+ """Generates a bar chart from benchmark data.
32
+
33
+ This method applies filters to ensure the data is appropriate for a bar chart
34
+ and then passes the filtered data to to_bar_ds for rendering.
35
+
36
+ Args:
37
+ result_var (Parameter, optional): The result variable to plot. If None, uses the default.
38
+ override (bool, optional): Whether to override filter restrictions. Defaults to True.
39
+ **kwargs: Additional keyword arguments passed to the plot rendering.
40
+
41
+ Returns:
42
+ Optional[pn.panel]: A panel containing the bar chart if data is appropriate,
43
+ otherwise returns filter match results.
44
+ """
45
+ return self.filter(
46
+ self.to_bar_ds,
47
+ float_range=VarRange(0, 0),
48
+ cat_range=VarRange(0, None),
49
+ repeats_range=VarRange(1, 1),
50
+ panel_range=VarRange(0, None),
51
+ reduce=ReduceType.SQUEEZE,
52
+ target_dimension=2,
53
+ result_var=result_var,
54
+ result_types=(ResultVar),
55
+ override=override,
56
+ **kwargs,
57
+ )
58
+
59
+ def to_bar_ds(self, dataset: xr.Dataset, result_var: Parameter = None, **kwargs):
60
+ """Creates a bar chart from the provided dataset.
61
+
62
+ Given a filtered dataset, this method generates a bar chart visualization showing
63
+ values of the result variable, potentially grouped by categorical variables.
64
+
65
+ Args:
66
+ dataset (xr.Dataset): The dataset containing benchmark results.
67
+ result_var (Parameter, optional): The result variable to plot. If None, uses the default.
68
+ **kwargs: Additional keyword arguments passed to the bar chart options.
69
+
70
+ Returns:
71
+ hvplot.element.Bars: A bar chart visualization of the benchmark data.
72
+ """
73
+ by = None
74
+ if self.plt_cnt_cfg.cat_cnt >= 2:
75
+ by = self.plt_cnt_cfg.cat_vars[1].name
76
+ da_plot = dataset[result_var.name]
77
+ title = self.title_from_ds(da_plot, result_var, **kwargs)
78
+ time_widget_args = self.time_widget(title)
79
+ return da_plot.hvplot.bar(by=by, **time_widget_args, **kwargs)
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+ from typing import Optional
3
+ import holoviews as hv
4
+ from param import Parameter
5
+ import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
6
+ import xarray as xr
7
+
8
+ from bencher.results.bench_result_base import ReduceType
9
+ from bencher.plotting.plot_filter import VarRange
10
+ from bencher.variables.results import ResultVar
11
+ from bencher.results.holoview_results.holoview_result import HoloviewResult
12
+
13
+
14
+ class CurveResult(HoloviewResult):
15
+ """A class for creating curve plots from benchmark results.
16
+
17
+ Curve plots are useful for visualizing the relationship between a continuous
18
+ input variable and a result variable. This class provides methods to generate
19
+ line plots that can also display standard deviation bounds when benchmark runs
20
+ include multiple repetitions.
21
+ """
22
+
23
+ def to_plot(
24
+ self, result_var: Parameter = None, override: bool = True, **kwargs
25
+ ) -> Optional[hv.Curve]:
26
+ """Generates a curve plot from benchmark data.
27
+
28
+ This is a convenience method that calls to_curve() with the same parameters.
29
+
30
+ Args:
31
+ result_var (Parameter, optional): The result variable to plot. If None, uses the default.
32
+ override (bool, optional): Whether to override filter restrictions. Defaults to True.
33
+ **kwargs: Additional keyword arguments passed to the plot rendering.
34
+
35
+ Returns:
36
+ Optional[hv.Curve]: A curve plot if data is appropriate,
37
+ otherwise returns filter match results.
38
+ """
39
+ return self.to_curve(result_var=result_var, override=override, **kwargs)
40
+
41
+ def to_curve(self, result_var: Parameter = None, override: bool = True, **kwargs):
42
+ """Generates a curve plot from benchmark data.
43
+
44
+ This method applies filters to ensure the data is appropriate for a curve plot
45
+ and then passes the filtered data to to_curve_ds for rendering.
46
+
47
+ Args:
48
+ result_var (Parameter, optional): The result variable to plot. If None, uses the default.
49
+ override (bool, optional): Whether to override filter restrictions. Defaults to True.
50
+ **kwargs: Additional keyword arguments passed to the plot rendering.
51
+
52
+ Returns:
53
+ Optional[hv.Curve]: A curve plot if data is appropriate,
54
+ otherwise returns filter match results.
55
+ """
56
+ return self.filter(
57
+ self.to_curve_ds,
58
+ float_range=VarRange(1, 1),
59
+ cat_range=VarRange(0, None),
60
+ repeats_range=VarRange(2, None),
61
+ reduce=ReduceType.REDUCE,
62
+ # reduce=ReduceType.MINMAX,
63
+ target_dimension=2,
64
+ result_var=result_var,
65
+ result_types=(ResultVar),
66
+ override=override,
67
+ **kwargs,
68
+ )
69
+
70
+ def to_curve_ds(
71
+ self, dataset: xr.Dataset, result_var: Parameter, **kwargs
72
+ ) -> Optional[hv.Curve]:
73
+ """Creates a curve plot from the provided dataset.
74
+
75
+ Given a filtered dataset, this method generates a curve visualization showing
76
+ the relationship between a continuous input variable and the result variable.
77
+ When multiple benchmark repetitions are available, standard deviation bounds
78
+ can also be displayed using a spread plot.
79
+
80
+ Args:
81
+ dataset (xr.Dataset): The dataset containing benchmark results.
82
+ result_var (Parameter): The result variable to plot.
83
+ **kwargs: Additional keyword arguments passed to the curve plot options.
84
+
85
+ Returns:
86
+ Optional[hv.Curve]: A curve plot with optional standard deviation spread.
87
+ """
88
+ hvds = hv.Dataset(dataset)
89
+ title = self.title_from_ds(dataset, result_var, **kwargs)
90
+ # print(result_var.name)
91
+ # print( dataset)
92
+ pt = hv.Overlay()
93
+ # find pairs of {var_name} {var_name}_std to plot the line and their spreads.
94
+ var = result_var.name
95
+ std_var = f"{var}_std"
96
+ pt *= hvds.to(hv.Curve, vdims=var, label=var).opts(title=title, **kwargs)
97
+ # Only create a Spread if the matching _std variable exists
98
+ if std_var in dataset.data_vars:
99
+ pt *= hvds.to(hv.Spread, vdims=[var, std_var])
100
+
101
+ # for var in dataset.data_vars:
102
+ # print(var)
103
+ # if not var.endswith("_std"):
104
+ # std_var = f"{var}_std"
105
+ # pt *= hvds.to(hv.Curve, vdims=var, label=var).opts(title=title, **kwargs)
106
+ # #Only create a Spread if the matching _std variable exists
107
+ # if std_var in dataset.data_vars:
108
+ # pt *= hvds.to(hv.Spread, vdims=[var, std_var])
109
+
110
+ return pt.opts(legend_position="right")
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+ from typing import Optional, Any
3
+ import panel as pn
4
+ import holoviews as hv
5
+ from param import Parameter
6
+ import xarray as xr
7
+
8
+ from bencher.results.holoview_results.distribution_result.distribution_result import (
9
+ DistributionResult,
10
+ )
11
+
12
+
13
+ class BoxWhiskerResult(DistributionResult):
14
+ """A class for creating box and whisker plots from benchmark results.
15
+
16
+ Box and whisker plots are useful for visualizing the distribution of data,
17
+ including the median, quartiles, and potential outliers. This class provides
18
+ methods to generate these plots from benchmark data, particularly useful for
19
+ comparing distributions across different categorical variables or between
20
+ different repetitions of the same benchmark.
21
+
22
+ Box plots show:
23
+ - The median (middle line in the box)
24
+ - The interquartile range (IQR) as a box (25th to 75th percentile)
25
+ - Whiskers extending to the furthest data points within 1.5*IQR
26
+ - Outliers as individual points beyond the whiskers
27
+ """
28
+
29
+ def to_plot(
30
+ self, result_var: Optional[Parameter] = None, override: bool = True, **kwargs: Any
31
+ ) -> Optional[pn.panel]:
32
+ """Generates a box and whisker plot from benchmark data.
33
+
34
+ This method applies filters to ensure the data is appropriate for a box plot
35
+ and then passes the filtered data to to_boxplot_ds for rendering.
36
+
37
+ Args:
38
+ result_var (Optional[Parameter]): The result variable to plot. If None, uses the default.
39
+ override (bool): Whether to override filter restrictions. Defaults to True.
40
+ **kwargs (Any): Additional keyword arguments passed to the plot rendering.
41
+
42
+ Returns:
43
+ Optional[pn.panel]: A panel containing the box plot if data is appropriate,
44
+ otherwise returns filter match results.
45
+ """
46
+ return self.to_distribution_plot(
47
+ self.to_boxplot_ds,
48
+ result_var=result_var,
49
+ override=override,
50
+ **kwargs,
51
+ )
52
+
53
+ def to_boxplot_ds(
54
+ self, dataset: xr.Dataset, result_var: Parameter, **kwargs: Any
55
+ ) -> hv.BoxWhisker:
56
+ """Creates a box and whisker plot from the provided dataset.
57
+
58
+ Given a filtered dataset, this method generates a box and whisker visualization showing
59
+ the distribution of values for a result variable, potentially grouped by a categorical variable.
60
+
61
+ Args:
62
+ dataset (xr.Dataset): The dataset containing benchmark results.
63
+ result_var (Parameter): The result variable to plot.
64
+ **kwargs (Any): Additional keyword arguments for plot customization such as:
65
+ - box_fill_color: Color for the box
66
+ - whisker_color: Color for the whiskers
67
+ - outlier_color: Color for outlier points
68
+ - line_width: Width of lines in the plot
69
+
70
+ Returns:
71
+ hv.BoxWhisker: A HoloViews BoxWhisker plot of the benchmark data.
72
+ """
73
+ return self._plot_distribution(dataset, result_var, hv.BoxWhisker, **kwargs)
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+ from typing import Optional, Callable, Any, Type
3
+ import panel as pn
4
+ import holoviews as hv
5
+ from param import Parameter
6
+ import xarray as xr
7
+
8
+ from bencher.results.bench_result_base import ReduceType
9
+ from bencher.plotting.plot_filter import VarRange
10
+ from bencher.variables.results import ResultVar
11
+ from bencher.results.holoview_results.holoview_result import HoloviewResult
12
+ from bencher.utils import params_to_str
13
+
14
+
15
+ class DistributionResult(HoloviewResult):
16
+ """A base class for creating distribution plots (violin, box-whisker) from benchmark results.
17
+
18
+ This class provides common functionality for various distribution plot types that show
19
+ the distribution shape of the data. Child classes implement specific plot types
20
+ (e.g., violin plots, box and whisker plots) but share filtering and data preparation logic.
21
+
22
+ Distribution plots are particularly useful for visualizing the statistical spread of
23
+ benchmark metrics across different configurations, allowing for better understanding
24
+ of performance variability.
25
+ """
26
+
27
+ def to_distribution_plot(
28
+ self,
29
+ plot_method: Callable[[xr.Dataset, Parameter, Any], hv.Element],
30
+ result_var: Optional[Parameter] = None,
31
+ override: bool = True,
32
+ **kwargs: Any,
33
+ ) -> Optional[pn.panel]:
34
+ """Generates a distribution plot from benchmark data.
35
+
36
+ This method applies filters to ensure the data is appropriate for distribution plots
37
+ and then passes the filtered data to the specified plot method for rendering.
38
+
39
+ Args:
40
+ plot_method: The method to use for creating the specific plot type (e.g., violin, box-whisker)
41
+ result_var: The result variable to plot. If None, uses the default.
42
+ override: Whether to override filter restrictions. Defaults to True.
43
+ **kwargs: Additional keyword arguments passed to the plot rendering.
44
+
45
+ Returns:
46
+ A panel containing the plot if data is appropriate,
47
+ otherwise returns filter match results.
48
+ """
49
+ return self.filter(
50
+ plot_method,
51
+ float_range=VarRange(0, 0),
52
+ cat_range=VarRange(0, None),
53
+ repeats_range=VarRange(2, None),
54
+ reduce=ReduceType.NONE,
55
+ target_dimension=self.plt_cnt_cfg.cat_cnt + 1, # +1 cos we have a repeats dimension
56
+ result_var=result_var,
57
+ result_types=(ResultVar),
58
+ override=override,
59
+ **kwargs,
60
+ )
61
+
62
+ def _plot_distribution(
63
+ self,
64
+ dataset: xr.Dataset,
65
+ result_var: Parameter,
66
+ plot_class: Type[hv.Selection1DExpr],
67
+ **kwargs: Any,
68
+ ) -> hv.Element:
69
+ """Prepares data for distribution plots and creates the plot.
70
+
71
+ This method handles common operations needed for all distribution plot types,
72
+ including converting data formats, setting up dimensions, and configuring
73
+ plot aesthetics.
74
+
75
+ Args:
76
+ dataset: The dataset containing benchmark results.
77
+ result_var: The result variable to plot.
78
+ plot_class: The HoloViews plot class to use (e.g., hv.Violin, hv.BoxWhisker)
79
+ **kwargs: Additional keyword arguments for plot customization.
80
+
81
+ Returns:
82
+ A HoloViews Element representing the distribution plot.
83
+ """
84
+ # Get the name of the result variable (which is the data we want to plot)
85
+ var_name = result_var.name
86
+
87
+ # Create plot title
88
+ title = self.title_from_ds(dataset[var_name], result_var, **kwargs)
89
+
90
+ # Convert dataset to dataframe for HoloViews
91
+ df = dataset[var_name].to_dataframe().reset_index()
92
+ kdims = params_to_str(self.plt_cnt_cfg.cat_vars)
93
+
94
+ if not isinstance(plot_class, list):
95
+ plot_class = [plot_class]
96
+
97
+ overlay = hv.Overlay()
98
+ for plot in plot_class:
99
+ overlay *= plot(
100
+ df,
101
+ kdims=kdims,
102
+ vdims=[var_name],
103
+ ).opts(
104
+ title=title,
105
+ ylabel=f"{var_name} [{result_var.units}]",
106
+ xrotation=30, # Rotate x-axis labels by 30 degrees
107
+ **kwargs,
108
+ )
109
+ return overlay