From af622e4834861e1141d418034cbaceb7afa820c8 Mon Sep 17 00:00:00 2001 From: Marcus Ritter Date: Thu, 14 Sep 2023 15:54:33 -0700 Subject: [PATCH 1/5] support for extra-p model complexity analysis --- hatchet/external/console.py | 131 ++++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 22 deletions(-) diff --git a/hatchet/external/console.py b/hatchet/external/console.py index b63150ff..a0810855 100644 --- a/hatchet/external/console.py +++ b/hatchet/external/console.py @@ -88,7 +88,8 @@ def render(self, roots, dataframe, **kwargs): elif isinstance(self.colormap_annotations, list): self.colors_annotations.colormap = self.colormap_annotations self.colors_annotations_mapping = sorted( - list(dataframe[self.annotation_column].apply(str).unique()) + list(dataframe[self.annotation_column].apply( + str).unique()) ) elif isinstance(self.colormap_annotations, dict): self.colors_annotations_mapping = self.colormap_annotations @@ -133,7 +134,8 @@ def render(self, roots, dataframe, **kwargs): # nan values if "rank" in dataframe.index.names: - metric_series = (dataframe.xs(self.rank, level=1))[self.primary_metric] + metric_series = (dataframe.xs(self.rank, level=1))[ + self.primary_metric] else: metric_series = dataframe[self.primary_metric] isfinite_mask = np.isfinite(metric_series.values) @@ -153,7 +155,7 @@ def render(self, roots, dataframe, **kwargs): result += self.render_frame(root, dataframe) if self.color is True: - result += self.render_legend() + result += self.render_legend(dataframe) if self.unicode: return result @@ -167,14 +169,15 @@ def render_preamble(self): r" / /_ ____ _/ /______/ /_ ___ / /_", r" / __ \/ __ `/ __/ ___/ __ \/ _ \/ __/", r" / / / / /_/ / /_/ /__/ / / / __/ /_ ", - r"/_/ /_/\__,_/\__/\___/_/ /_/\___/\__/ {:>2}".format("v" + __version__), + r"/_/ /_/\__,_/\__/\___/_/ /_/\___/\__/ {:>2}".format( + "v" + __version__), r"", r"", ] return "\n".join(lines) - def render_legend(self): + def render_legend(self, dataframe): def render_label(index, low, high): metric_range = self.max_metric - self.min_metric @@ -207,7 +210,8 @@ def render_label(index, low, high): legend += render_label(4, 0.1, 0.3) legend += render_label(5, 0.0, 0.1) - legend += "\n" + self._ansi_color_for_name("name") + "name" + self.colors.end + legend += "\n" + \ + self._ansi_color_for_name("name") + "name" + self.colors.end legend += " User code " legend += self.colors.left + self.lr_arrows["◀"] + self.colors.end @@ -215,6 +219,26 @@ def render_label(index, low, high): legend += self.colors.right + self.lr_arrows["▶"] + self.colors.end legend += " Only in right graph\n" + if self.annotation_column is not None: + # extra-p model complexity analysis legend customization + if "_complexity" in self.annotation_column: + + # get unique complexity classes from all models + unique_complexity_classes = self.get_unique_complexity_classes( + dataframe) + + # add color coding for complexity classes to data frame + color_map_dict = self.colormap_for_complexity_classes( + unique_complexity_classes) + + legend += "\n\033[4mLegend Complexity Classes" + \ + self.colors.end + + for complexity_class in unique_complexity_classes: + legend += "\n" + color_map_dict[complexity_class] + u"█ " + \ + self.colors.end + str(complexity_class) + legend += "\n" + return legend def render_frame(self, node, dataframe, indent=u"", child_indent=u""): @@ -251,30 +275,52 @@ def render_frame(self, node, dataframe, indent=u"", child_indent=u""): annotation_content = str( dataframe.loc[df_index, self.annotation_column] ) - if self.colormap_annotations: - if isinstance(self.colormap_annotations, dict): - color_annotation = self.colors_annotations_mapping[ - annotation_content - ] - else: - color_annotation = self.colors_annotations.colormap[ - self.colors_annotations_mapping.index(annotation_content) - % len(self.colors_annotations.colormap) - ] - metric_str += " [{}".format(color_annotation) + + # custom visualization for complexity class analysis with extra-p models + if "_complexity" in self.annotation_column: + + # get unique complexity classes from all models + unique_complexity_classes = self.get_unique_complexity_classes( + dataframe) + + # add color coding for complexity classes to data frame + color_map_dict = self.colormap_for_complexity_classes( + unique_complexity_classes) + + metric_str += " [{}".format( + color_map_dict[annotation_content]) metric_str += "{}".format(annotation_content) - metric_str += "{}]".format(self.colors_annotations.end) + metric_str += "{}]".format("\033[0m") + else: - metric_str += " [{}]".format(annotation_content) + if self.colormap_annotations: + if isinstance(self.colormap_annotations, dict): + color_annotation = self.colors_annotations_mapping[ + annotation_content + ] + else: + color_annotation = self.colors_annotations.colormap[ + self.colors_annotations_mapping.index( + annotation_content) + % len(self.colors_annotations.colormap) + ] + metric_str += " [{}".format(color_annotation) + metric_str += "{}".format(annotation_content) + metric_str += "{}]".format(self.colors_annotations.end) + + else: + metric_str += " [{}]".format(annotation_content) node_name = dataframe.loc[df_index, self.name] if self.expand is False: if len(node_name) > 39: node_name = ( - node_name[:18] + "..." + node_name[(len(node_name) - 18) :] + node_name[:18] + "..." + + node_name[(len(node_name) - 18):] ) name_str = ( - self._ansi_color_for_name(node_name) + node_name + self.colors.end + self._ansi_color_for_name( + node_name) + node_name + self.colors.end ) # 0 is "", 1 is "L", and 2 is "R" @@ -298,7 +344,8 @@ def render_frame(self, node, dataframe, indent=u"", child_indent=u""): result += lr_decorator if self.context in dataframe.columns: result += u" {c.faint}{context}{c.end}\n".format( - context=dataframe.loc[df_index, self.context], c=self.colors + context=dataframe.loc[df_index, + self.context], c=self.colors ) else: result += u"\n" @@ -332,6 +379,46 @@ def render_frame(self, node, dataframe, indent=u"", child_indent=u""): return result + def get_unique_complexity_classes(self, dataframe): + unique_complexity_classes = [] + for i in range(len(dataframe[self.annotation_column])): + if str(dataframe[self.annotation_column].iloc[i]) not in unique_complexity_classes: + unique_complexity_classes.append( + str(dataframe[self.annotation_column].iloc[i])) + return unique_complexity_classes + + def colormap_for_complexity_classes(self, unique_complexity_classes): + color_map_dict = {} + range_values = np.arange( + 0, 1, 1 / len(unique_complexity_classes)) + import matplotlib + # chose the color map to take the colors from dynamically + if self.colormap_annotations: + if isinstance(self.colormap_annotations, str): + colormap = self.colormap_annotations + else: + if len(unique_complexity_classes) > 20: + colormap = "brg" + else: + colormap = "tab20b" + cmap = matplotlib.cm.get_cmap(colormap) + for i in range(len(range_values)): + red = int(cmap(range_values[i])[0] / (1 / 255)) + green = int(cmap(range_values[i])[1] / (1 / 255)) + blue = int(cmap(range_values[i])[2] / (1 / 255)) + ansi_color_str = ( + "\033[38;2;" + + str(red) + + ";" + + str(green) + + ";" + + str(blue) + + "m" + ) + color_map_dict[unique_complexity_classes[i] + ] = ansi_color_str + return color_map_dict + def _ansi_color_for_metric(self, metric): metric_range = self.max_metric - self.min_metric From 81d05f764571c3747d673f295889eb00ea1ed8d7 Mon Sep 17 00:00:00 2001 From: Marcus Ritter Date: Tue, 19 Sep 2023 15:23:20 -0700 Subject: [PATCH 2/5] added legend for model parameters, no color output for complexity annotation column --- hatchet/external/console.py | 62 +++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/hatchet/external/console.py b/hatchet/external/console.py index a0810855..41d89818 100644 --- a/hatchet/external/console.py +++ b/hatchet/external/console.py @@ -239,6 +239,21 @@ def render_label(index, low, high): self.colors.end + str(complexity_class) legend += "\n" + # create a legend for the model parameters + legend += "\n\033[4mLegend Model Parameters" + \ + self.colors.end + column_headers = list(dataframe.columns.values) + column_name = None + for column in column_headers: + if "_extrap-model" in column: + column_name = column + break + model_wrapper_object = dataframe[column_name].iloc[0] + for i in range(len(model_wrapper_object.parameters)): + legend += "\n" + \ + str(model_wrapper_object.default_param_names[i]) + " -> " + \ + str(model_wrapper_object.parameters[i]) + return legend def render_frame(self, node, dataframe, indent=u"", child_indent=u""): @@ -401,22 +416,37 @@ def colormap_for_complexity_classes(self, unique_complexity_classes): colormap = "brg" else: colormap = "tab20b" - cmap = matplotlib.cm.get_cmap(colormap) - for i in range(len(range_values)): - red = int(cmap(range_values[i])[0] / (1 / 255)) - green = int(cmap(range_values[i])[1] / (1 / 255)) - blue = int(cmap(range_values[i])[2] / (1 / 255)) - ansi_color_str = ( - "\033[38;2;" - + str(red) - + ";" - + str(green) - + ";" - + str(blue) - + "m" - ) - color_map_dict[unique_complexity_classes[i] - ] = ansi_color_str + if colormap != "black": + cmap = matplotlib.cm.get_cmap(colormap) + for i in range(len(range_values)): + red = int(cmap(range_values[i])[0] / (1 / 255)) + green = int(cmap(range_values[i])[1] / (1 / 255)) + blue = int(cmap(range_values[i])[2] / (1 / 255)) + ansi_color_str = ( + "\033[38;2;" + + str(red) + + ";" + + str(green) + + ";" + + str(blue) + + "m" + ) + color_map_dict[unique_complexity_classes[i] + ] = ansi_color_str + else: + for i in range(len(range_values)): + ansi_color_str = ( + "\033[38;2;" + + str(0) + + ";" + + str(0) + + ";" + + str(0) + + "m" + ) + color_map_dict[unique_complexity_classes[i] + ] = ansi_color_str + return color_map_dict def _ansi_color_for_metric(self, metric): From 6a5a25bbc10a7623e6f1e7c72ced716d5e3dfb8f Mon Sep 17 00:00:00 2001 From: Marcus Ritter Date: Wed, 11 Oct 2023 11:40:19 -0700 Subject: [PATCH 3/5] added exception for the case a node does not exist --- hatchet/external/console.py | 296 ++++++++++++++++++------------------ 1 file changed, 152 insertions(+), 144 deletions(-) diff --git a/hatchet/external/console.py b/hatchet/external/console.py index a2807c39..1d0bf36d 100644 --- a/hatchet/external/console.py +++ b/hatchet/external/console.py @@ -300,92 +300,78 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): else: df_index = node - node_metric = dataframe.loc[df_index, self.primary_metric] - - metric_precision = "{:." + str(self.precision) + "f}" - metric_str = ( - self._ansi_color_for_metric(node_metric) - + metric_precision.format(node_metric) - + self.colors.end - ) - - if self.second_metric is not None: - metric_str += " {c.faint}{second_metric:.{precision}f}{c.end}".format( - second_metric=dataframe.loc[df_index, self.second_metric], - precision=self.precision, - c=self.colors, + try: + node_metric = dataframe.loc[df_index, self.primary_metric] + + metric_precision = "{:." + str(self.precision) + "f}" + metric_str = ( + self._ansi_color_for_metric(node_metric) + + metric_precision.format(node_metric) + + self.colors.end ) - if self.annotation_column is not None: - annotation_content = str( - dataframe.loc[df_index, self.annotation_column] - ) + if self.second_metric is not None: + metric_str += " {c.faint}{second_metric:.{precision}f}{c.end}".format( + second_metric=dataframe.loc[df_index, + self.second_metric], + precision=self.precision, + c=self.colors, + ) - # custom visualization for complexity class analysis with extra-p models - if "_complexity" in self.annotation_column: - - # get unique complexity classes from all models - unique_complexity_classes = self.get_unique_complexity_classes( - dataframe) - - # add color coding for complexity classes to data frame - color_map_dict = self.colormap_for_complexity_classes( - unique_complexity_classes) - - metric_str += " [{}".format( - color_map_dict[annotation_content]) - - # custom visualization for temporal pattern metrics if it is the annotation column - if "_pattern" in self.annotation_column: - self.temporal_symbols = { - "none": "", - "constant": "\U00002192", - "phased": "\U00002933", - "dynamic": "\U000021DD", - "sporadic": "\U0000219D", - } - pattern_metric = dataframe.loc[df_index, - self.annotation_column] - annotation_content = self.temporal_symbols[pattern_metric] - if self.colormap_annotations: - self.colors_annotations_mapping = list( - dataframe[self.annotation_column].apply( - str).unique() - ) - coloring_content = pattern_metric - if coloring_content != "none": - color_annotation = self.colors_annotations.colormap[ - self.colors_annotations_mapping.index( - coloring_content) - % len(self.colors_annotations.colormap) - ] - metric_str += " {}".format(color_annotation) - metric_str += "{}".format(annotation_content) - metric_str += "{}".format( - self.colors_annotations.end) - else: - metric_str += "{}".format(annotation_content) - else: # no colormap passed in - metric_str += " {}".format(annotation_content) - - # no pattern column - elif self.colormap_annotations: - if isinstance(self.colormap_annotations, dict): - color_annotation = self.colors_annotations_mapping[ - annotation_content - ] - else: - color_annotation = self.colors_annotations.colormap[ - self.colors_annotations_mapping.index( - annotation_content) - % len(self.colors_annotations.colormap) - ] - metric_str += " [{}".format(color_annotation) - metric_str += "{}".format(annotation_content) - metric_str += "{}]".format("\033[0m") + if self.annotation_column is not None: + annotation_content = str( + dataframe.loc[df_index, self.annotation_column] + ) - else: - if self.colormap_annotations: + # custom visualization for complexity class analysis with extra-p models + if "_complexity" in self.annotation_column: + + # get unique complexity classes from all models + unique_complexity_classes = self.get_unique_complexity_classes( + dataframe) + + # add color coding for complexity classes to data frame + color_map_dict = self.colormap_for_complexity_classes( + unique_complexity_classes) + + metric_str += "{}".format( + color_map_dict[annotation_content]) + + # custom visualization for temporal pattern metrics if it is the annotation column + if "_pattern" in self.annotation_column: + self.temporal_symbols = { + "none": "", + "constant": "\U00002192", + "phased": "\U00002933", + "dynamic": "\U000021DD", + "sporadic": "\U0000219D", + } + pattern_metric = dataframe.loc[df_index, + self.annotation_column] + annotation_content = self.temporal_symbols[pattern_metric] + if self.colormap_annotations: + self.colors_annotations_mapping = list( + dataframe[self.annotation_column].apply( + str).unique() + ) + coloring_content = pattern_metric + if coloring_content != "none": + color_annotation = self.colors_annotations.colormap[ + self.colors_annotations_mapping.index( + coloring_content) + % len(self.colors_annotations.colormap) + ] + metric_str += " {}".format(color_annotation) + metric_str += "{}".format(annotation_content) + metric_str += "{}".format( + self.colors_annotations.end) + else: + metric_str += "{}".format(annotation_content) + else: # no colormap passed in + metric_str += " {}".format(annotation_content) + + # no pattern column + elif self.colormap_annotations: if isinstance(self.colormap_annotations, dict): color_annotation = self.colors_annotations_mapping[ annotation_content @@ -398,74 +384,96 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): ] metric_str += " [{}".format(color_annotation) metric_str += "{}".format(annotation_content) - metric_str += "{}]".format(self.colors_annotations.end) + metric_str += "{}]".format("\033[0m") else: - metric_str += " [{}]".format(annotation_content) - - node_name = dataframe.loc[df_index, self.name] - if self.expand is False: - if len(node_name) > 39: - node_name = ( - node_name[:18] + "..." + - node_name[(len(node_name) - 18):] - ) - name_str = ( - self._ansi_color_for_name( - node_name) + node_name + self.colors.end - ) - - # 0 is "", 1 is "L", and 2 is "R" - if "_missing_node" in dataframe.columns: - left_or_right = dataframe.loc[df_index, "_missing_node"] - if left_or_right == 0: - lr_decorator = "" - elif left_or_right == 1: - lr_decorator = " {c.left}{decorator}{c.end}".format( - decorator=self.lr_arrows["◀"], c=self.colors - ) - elif left_or_right == 2: - lr_decorator = " {c.right}{decorator}{c.end}".format( - decorator=self.lr_arrows["▶"], c=self.colors - ) + if self.colormap_annotations: + if isinstance(self.colormap_annotations, dict): + color_annotation = self.colors_annotations_mapping[ + annotation_content + ] + else: + color_annotation = self.colors_annotations.colormap[ + self.colors_annotations_mapping.index( + annotation_content) + % len(self.colors_annotations.colormap) + ] + metric_str += " [{}".format(color_annotation) + metric_str += "{}".format(annotation_content) + metric_str += "{}]".format( + self.colors_annotations.end) - result = "{indent}{metric_str} {name_str}".format( - indent=indent, metric_str=metric_str, name_str=name_str - ) - if "_missing_node" in dataframe.columns: - result += lr_decorator - if self.context in dataframe.columns: - result += u" {c.faint}{context}{c.end}\n".format( - context=dataframe.loc[df_index, - self.context], c=self.colors + else: + metric_str += " [{}]".format(annotation_content) + + node_name = dataframe.loc[df_index, self.name] + if self.expand is False: + if len(node_name) > 39: + node_name = ( + node_name[:18] + "..." + + node_name[(len(node_name) - 18):] + ) + name_str = ( + self._ansi_color_for_name( + node_name) + node_name + self.colors.end ) - else: - result += "\n" - if self.unicode: - indents = {"├": "├─ ", "│": "│ ", "└": "└─ ", " ": " "} - else: - indents = {"├": "|- ", "│": "| ", "└": "`- ", " ": " "} - - # ensures that we never revisit nodes in the case of - # large complex graphs - if node not in self.visited: - self.visited.append(node) - sorted_children = sorted( - node.children, key=lambda n: n._hatchet_nid) - if sorted_children: - last_child = sorted_children[-1] - - for child in sorted_children: - if child is not last_child: - c_indent = child_indent + indents["├"] - cc_indent = child_indent + indents["│"] - else: - c_indent = child_indent + indents["└"] - cc_indent = child_indent + indents[" "] - result += self.render_frame( - child, dataframe, indent=c_indent, child_indent=cc_indent + # 0 is "", 1 is "L", and 2 is "R" + if "_missing_node" in dataframe.columns: + left_or_right = dataframe.loc[df_index, "_missing_node"] + if left_or_right == 0: + lr_decorator = "" + elif left_or_right == 1: + lr_decorator = " {c.left}{decorator}{c.end}".format( + decorator=self.lr_arrows["◀"], c=self.colors + ) + elif left_or_right == 2: + lr_decorator = " {c.right}{decorator}{c.end}".format( + decorator=self.lr_arrows["▶"], c=self.colors + ) + + result = "{indent}{metric_str} {name_str}".format( + indent=indent, metric_str=metric_str, name_str=name_str + ) + if "_missing_node" in dataframe.columns: + result += lr_decorator + if self.context in dataframe.columns: + result += u" {c.faint}{context}{c.end}\n".format( + context=dataframe.loc[df_index, + self.context], c=self.colors ) + else: + result += "\n" + + if self.unicode: + indents = {"├": "├─ ", "│": "│ ", "└": "└─ ", " ": " "} + else: + indents = {"├": "|- ", "│": "| ", "└": "`- ", " ": " "} + + # ensures that we never revisit nodes in the case of + # large complex graphs + if node not in self.visited: + self.visited.append(node) + sorted_children = sorted( + node.children, key=lambda n: n._hatchet_nid) + if sorted_children: + last_child = sorted_children[-1] + + for child in sorted_children: + if child is not last_child: + c_indent = child_indent + indents["├"] + cc_indent = child_indent + indents["│"] + else: + c_indent = child_indent + indents["└"] + cc_indent = child_indent + indents[" "] + result += self.render_frame( + child, dataframe, indent=c_indent, child_indent=cc_indent + ) + + except KeyError: + result = "" + indents = {"├": "", "│": "", "└": "", " ": ""} + else: result = "" indents = {"├": "", "│": "", "└": "", " ": ""} From 6eb79eadc7a0f14df8759a0133b607791bb2d993 Mon Sep 17 00:00:00 2001 From: Marcus Ritter Date: Wed, 29 Nov 2023 15:01:03 -0800 Subject: [PATCH 4/5] fix for modeler configuration support in thicket dataframes --- hatchet/external/console.py | 70 ++++++++++++++++++++++++++++--------- hatchet/graphframe.py | 66 +++++++++++++++++++++++----------- 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/hatchet/external/console.py b/hatchet/external/console.py index 1d0bf36d..2886fd86 100644 --- a/hatchet/external/console.py +++ b/hatchet/external/console.py @@ -70,6 +70,10 @@ def render(self, roots, dataframe, **kwargs): self.colormap_annotations = kwargs["colormap_annotations"] self.min_value = kwargs["min_value"] self.max_value = kwargs["max_value"] + try: + self.modeler_config = kwargs["modeler_config"] + except KeyError: + self.modeler_config = None if self.color: self.colors = self.colors_enabled @@ -88,7 +92,7 @@ def render(self, roots, dataframe, **kwargs): elif isinstance(self.colormap_annotations, list): self.colors_annotations.colormap = self.colormap_annotations self.colors_annotations_mapping = sorted( - list(dataframe[self.annotation_column].apply( + list(dataframe[self.modeler_config][self.annotation_column].apply( str).unique()) ) elif isinstance(self.colormap_annotations, dict): @@ -114,7 +118,7 @@ def render(self, roots, dataframe, **kwargs): self.primary_metric = self.metric_columns[0] self.second_metric = None - if self.primary_metric not in dataframe.columns: + if self.primary_metric not in dataframe.columns and self.primary_metric not in dataframe[self.modeler_config].columns: raise KeyError( "metric_column={} does not exist in the dataframe, please select a valid column. See a list of the available metrics with GraphFrame.show_metric_columns().".format( self.primary_metric @@ -137,7 +141,10 @@ def render(self, roots, dataframe, **kwargs): metric_series = (dataframe.xs(self.rank, level=1))[ self.primary_metric] else: - metric_series = dataframe[self.primary_metric] + if self.modeler_config is not None and self.primary_metric not in dataframe.columns: + metric_series = dataframe[self.modeler_config][self.primary_metric] + else: + metric_series = dataframe[self.primary_metric] isfinite_mask = np.isfinite(metric_series.values) filtered_series = pd.Series( metric_series.values[isfinite_mask], metric_series.index[isfinite_mask] @@ -243,13 +250,21 @@ def render_label(index, low, high): # create a legend for the model parameters legend += "\n\033[4mLegend Model Parameters" + \ self.colors.end - column_headers = list(dataframe.columns.values) + if self.modeler_config is None: + column_headers = list(dataframe.columns.values) + else: + column_headers = list( + dataframe[self.modeler_config].columns.values) column_name = None for column in column_headers: - if "_extrap-model" in column: + if "_extrap-model" in column and "AR2" not in column and "RE" not in column and "RSS" not in column and "SMAPE" not in column and "coefficient" not in column and "complexity" not in column and "growth_rank" not in column: column_name = column break - model_wrapper_object = dataframe[column_name].iloc[0] + if self.modeler_config is None: + model_wrapper_object = dataframe[column_name].iloc[0] + else: + model_wrapper_object = dataframe[self.modeler_config][column_name].iloc[0] + # Avg time/rank_extrap-model for i in range(len(model_wrapper_object.parameters)): legend += "\n" + \ str(model_wrapper_object.default_param_names[i]) + " -> " + \ @@ -301,7 +316,14 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): df_index = node try: - node_metric = dataframe.loc[df_index, self.primary_metric] + if self.modeler_config is not None and self.primary_metric not in dataframe.columns: + node_metric = dataframe[self.modeler_config].loc[df_index, + self.primary_metric] + else: + node_metric = dataframe.loc[df_index, + self.primary_metric] + if self.modeler_config is not None: + node_metric = float(node_metric) metric_precision = "{:." + str(self.precision) + "f}" metric_str = ( @@ -319,9 +341,15 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): ) if self.annotation_column is not None: - annotation_content = str( - dataframe.loc[df_index, self.annotation_column] - ) + if self.modeler_config is None: + annotation_content = str( + dataframe.loc[df_index, self.annotation_column] + ) + else: + annotation_content = str( + dataframe[self.modeler_config].loc[df_index, + self.annotation_column] + ) # custom visualization for complexity class analysis with extra-p models if "_complexity" in self.annotation_column: @@ -406,7 +434,10 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): else: metric_str += " [{}]".format(annotation_content) - node_name = dataframe.loc[df_index, self.name] + if self.modeler_config is None: + node_name = dataframe.loc[df_index, self.name] + else: + node_name = dataframe[self.modeler_config].loc[df_index, self.name] if self.expand is False: if len(node_name) > 39: node_name = ( @@ -470,7 +501,8 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): child, dataframe, indent=c_indent, child_indent=cc_indent ) - except KeyError: + except KeyError as e: + raise (e) result = "" indents = {"├": "", "│": "", "└": "", " ": ""} @@ -482,10 +514,16 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): def get_unique_complexity_classes(self, dataframe): unique_complexity_classes = [] - for i in range(len(dataframe[self.annotation_column])): - if str(dataframe[self.annotation_column].iloc[i]) not in unique_complexity_classes: - unique_complexity_classes.append( - str(dataframe[self.annotation_column].iloc[i])) + if self.modeler_config is None: + for i in range(len(dataframe[self.annotation_column])): + if str(dataframe[self.annotation_column].iloc[i]) not in unique_complexity_classes: + unique_complexity_classes.append( + str(dataframe[self.annotation_column].iloc[i])) + else: + for i in range(len(dataframe[self.modeler_config][self.annotation_column])): + if str(dataframe[self.modeler_config][self.annotation_column].iloc[i]) not in unique_complexity_classes: + unique_complexity_classes.append( + str(dataframe[self.modeler_config][self.annotation_column].iloc[i])) return unique_complexity_classes def colormap_for_complexity_classes(self, unique_complexity_classes): diff --git a/hatchet/graphframe.py b/hatchet/graphframe.py index 822720a8..784a880c 100644 --- a/hatchet/graphframe.py +++ b/hatchet/graphframe.py @@ -974,6 +974,7 @@ def tree( render_header=True, min_value=None, max_value=None, + modeler_config=None ): """Visualize the Hatchet graphframe as a tree @@ -994,6 +995,7 @@ def tree( render_header (bool, optional): Shows the Preamble. Defaults to True. min_value (int, optional): Overwrites the min value for the coloring legend. Defaults to None. max_value (int, optional): Overwrites the max value for the coloring legend. Defaults to None. + modeler_config (str, optional): Used when using Extra-P modeler configurations in the thicket dataframe to access the correct multi-column index. Returns: str: String representation of the tree, ready to print @@ -1019,26 +1021,50 @@ def tree( elif sys.version_info.major == 3: unicode = True - return ConsoleRenderer(unicode=unicode, color=color).render( - self.graph.roots, - self.dataframe, - metric_column=metric_column, - annotation_column=annotation_column, - precision=precision, - name_column=name_column, - expand_name=expand_name, - context_column=context_column, - rank=rank, - thread=thread, - depth=depth, - highlight_name=highlight_name, - colormap=colormap, - invert_colormap=invert_colormap, - colormap_annotations=colormap_annotations, - render_header=render_header, - min_value=min_value, - max_value=max_value, - ) + if modeler_config is None: + return ConsoleRenderer(unicode=unicode, color=color).render( + self.graph.roots, + self.dataframe, + metric_column=metric_column, + annotation_column=annotation_column, + precision=precision, + name_column=name_column, + expand_name=expand_name, + context_column=context_column, + rank=rank, + thread=thread, + depth=depth, + highlight_name=highlight_name, + colormap=colormap, + invert_colormap=invert_colormap, + colormap_annotations=colormap_annotations, + render_header=render_header, + min_value=min_value, + max_value=max_value, + ) + else: + return ConsoleRenderer(unicode=unicode, color=color).render( + self.graph.roots, + self.dataframe, + metric_column=metric_column, + annotation_column=annotation_column, + precision=precision, + name_column=name_column, + expand_name=expand_name, + context_column=context_column, + rank=rank, + thread=thread, + depth=depth, + highlight_name=highlight_name, + colormap=colormap, + invert_colormap=invert_colormap, + colormap_annotations=colormap_annotations, + render_header=render_header, + min_value=min_value, + max_value=max_value, + modeler_config=modeler_config, + ) + def to_dot(self, metric=None, name="name", rank=0, thread=0, threshold=0.0): """Write the graph in the graphviz dot format: From 7950c78c05db412eb21570de1480ef3d0cf17f89 Mon Sep 17 00:00:00 2001 From: Marcus Ritter Date: Wed, 29 Nov 2023 15:01:53 -0800 Subject: [PATCH 5/5] fix for thicket dataframe extrap modeler configuration support --- hatchet/external/console.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hatchet/external/console.py b/hatchet/external/console.py index 2886fd86..74fb41e1 100644 --- a/hatchet/external/console.py +++ b/hatchet/external/console.py @@ -501,8 +501,7 @@ def render_frame(self, node, dataframe, indent="", child_indent=""): child, dataframe, indent=c_indent, child_indent=cc_indent ) - except KeyError as e: - raise (e) + except KeyError: result = "" indents = {"├": "", "│": "", "└": "", " ": ""}