import copy
from typing import Tuple, Union
import mocharts as mc
from IPython.display import HTML
[docs]
class ValidationResult:
"""A class to handle validation results and plotting functionality.
This class stores validation data and provides methods to plot and save figures
using the mocharts library.
Attributes
----------
key : str
Name of the validation result
data : str, default=None
Data used in the validation
model : str, default=None
Model used in the validation
inputs : dict, default=None
Input arguments used in the validation
value : any, default=None
Detailed validation result values
table : pd.DataFrame or dict of pd.DataFrame, default=None
Table data associated with the validation
func : any, default=None
Function used in the validation
options : dict, default=None
Plotting options and configurations
"""
def __init__(self,
key,
data=None,
model=None,
inputs=None,
value=None,
table=None,
func=None,
options=None):
self.key = key
self.data = data
self.model = model
self.inputs = inputs
self.value = value
self.table = table
self.func = func
self.options = options
def _flatten_options(self, options, parent_key=None):
"""Flatten nested options dictionary into a list of (key, options) pairs.
This internal method traverses a nested dictionary of plotting options and yields
tuples containing the full path to each chart configuration and its options.
Parameters
----------
options : dict
The nested dictionary of plotting options
parent_key : str, int, float, tuple, optional
The parent key in the nested structure
Yields
------
tuple
A tuple of (key_path, chart_options) where key_path can be None, a single key,
or a tuple of keys representing the path to the chart options
"""
if options is None:
yield None, None
elif 'chart_id' in options:
yield None, options
else:
for key, sub_options in options.items():
if sub_options is None:
continue
if 'chart_id' in sub_options:
if parent_key is None:
yield key, sub_options
elif isinstance(parent_key, (str, int, float)):
yield (parent_key,) + (key,), sub_options
elif isinstance(parent_key, tuple):
yield parent_key + (key,), sub_options
else:
if parent_key is None:
yield from self._flatten_options(sub_options, parent_key=key)
elif isinstance(parent_key, (str, int, float)):
yield from self._flatten_options(sub_options, parent_key=(parent_key,) + (key,))
elif isinstance(parent_key, tuple):
yield from self._flatten_options(sub_options, parent_key=parent_key + (key,), )
[docs]
def plot_save(self, name: Union[str, int, float, Tuple] = None, figsize: Tuple[int] = (6, 6),
file_name: str = None, format: str = 'png'):
"""Generate and save figures based on validation options.
This method visualizes and saves figures created from the validation options
using the mocharts library. The figures can be filtered by name, resized,
and saved to a specified file path in the given format.
Parameters
----------
name : str, int, float, tuple, default=None
The name(s) of the figure(s) to visualize and save. Can be a single name
or a tuple/list of names. To retrieve valid figure names, call `self.get_figure_names()`.
If None, all available figures are processed.
figsize : tuple, default=(6, 6)
The size (width, height) of the figure(s) to save, in inches.
file_name : str, default=None
The base file path and name to save the figure(s). If None, the figure will
be saved in the 'images/' directory using the `self.key` as the filename prefix.
If multiple figures are saved, this will serve as the prefix for each figure's name.
format : str, default='png'
The format to save the figure(s), e.g., 'png', 'jpg', 'svg'.
Raises
------
ValueError
If an invalid name is provided, or if an unsupported key type is encountered.
Notes
-----
- The function automatically creates an 'images/' folder if no file path is specified.
- Figure names are constructed by combining the `file_name` (if provided) and the figure's key.
"""
if self.options is None:
return
options_copy = copy.deepcopy(self.options)
if name is not None:
if name in options_copy:
options_copy = options_copy[name]
elif isinstance(name, (list, tuple)):
for key in name:
options_copy = options_copy[key]
else:
raise ValueError("Invalid name; call result.get_figure_names() to get the feasible names.")
options_list = list(self._flatten_options(options_copy))
for key, options in options_list:
if options is None:
continue
# TODO: improve the logic for checking whether this is a mocharts dict
options['figsize'] = {'width': figsize[0] * 100, 'height': figsize[1] * 100}
if file_name is None:
folder_path = 'images/'
new_file_name = self.key
else:
folder_path = '/'.join(file_name.split('/')[:-1]) + '/'
new_file_name = file_name.split('/')[-1]
if key is None:
key = new_file_name
elif isinstance(key, str):
key = new_file_name + '-' + key
elif isinstance(key, tuple):
key = new_file_name + '-' + "-".join(key)
else:
raise ValueError("Invalid key type")
save_fig_path = folder_path + key
mc.mocharts_save(options, file_name=save_fig_path, format=format)
def _bar_plot_setting(self, options: dict, n_bars: int):
for yaxis in options['yAxis']:
if yaxis['type'] == 'category':
yaxis['data'] = yaxis['data'][-min(n_bars, len(yaxis['data'])):]
return options
[docs]
def plot(self, name: [str, int, float, Tuple] = None, figsize: Tuple[int] = (6, 6),
display: bool = False, n_bars: int = None, popup: bool = True):
"""Show the figure results.
Parameters
----------
name : str, int, float, tuple, default=None
The name of the figure to visualize.
The available figure names can be fetched by func get_figure_names.
If None, then all figures will be displayed.
figsize : tuple, default=(6, 6)
The size of displayed figure(s).
display : bool, default=False
Whether to display the figure.
n_bars: int, default=None
The number of displayed bars (from top).
popup: bool, default=True
Whether to support popup for each plot
Returns
----------
fig : HTML object
The figure object in html format.
"""
if self.options is None:
return
options_copy = copy.deepcopy(self.options)
if name is not None:
if name in options_copy:
options_copy = options_copy[name]
elif isinstance(name, (list, tuple)):
for key in name:
options_copy = options_copy[key]
else:
raise ValueError("Invalid name; call result.get_figure_names() to get the feasible names.")
figure_outputs = ""
options_list = list(self._flatten_options(options_copy))
for key, options in options_list:
if options is None:
continue
# TODO: improve the logic for checking whether this is a mochart dict
if n_bars is not None:
for serie in options['series']:
if serie['type'] == 'bar':
options = self._bar_plot_setting(options=options, n_bars=n_bars)
break
options['figsize'] = {'width': figsize[0] * 100, 'height': figsize[1] * 100}
if not popup:
options['toolbox']['popup'] = False
htmlstr = mc.mocharts_plot(options, return_html=True, silent=not display)
figure_outputs += htmlstr
fig = HTML(figure_outputs)
return fig