def test_buchheim(tree, n_nodes): def walk_tree(draw_tree): res = [(draw_tree.x, draw_tree.y)] for child in draw_tree.children: # parents higher than children: assert child.y == draw_tree.y + 1 res.extend(walk_tree(child)) if len(draw_tree.children): # these trees are always binary # parents are centered above children assert draw_tree.x == (draw_tree.children[0].x + draw_tree.children[1].x) / 2 return res layout = buchheim(tree) coordinates = walk_tree(layout) assert len(coordinates) == n_nodes # test that x values are unique per depth / level # we could also do it quicker using defaultdicts.. depth = 0 while True: x_at_this_depth = [node[0] for node in coordinates if node[1] == depth] if not x_at_this_depth: # reached all leafs break assert len(np.unique(x_at_this_depth)) == len(x_at_this_depth) depth += 1
def test_buchheim(tree, n_nodes): def walk_tree(draw_tree): res = [(draw_tree.x, draw_tree.y)] for child in draw_tree.children: # parents higher than children: assert child.y == draw_tree.y + 1 res.extend(walk_tree(child)) if len(draw_tree.children): # these trees are always binary # parents are centered above children assert draw_tree.x == (draw_tree.children[0].x + draw_tree.children[1].x) / 2 return res layout = buchheim(tree) coordinates = walk_tree(layout) assert len(coordinates) == n_nodes # test that x values are unique per depth / level # we could also do it quicker using defaultdicts.. depth = 0 while True: x_at_this_depth = [] for node in coordinates: if coordinates[1] == depth: x_at_this_depth.append(coordinates[0]) if not x_at_this_depth: # reached all leafs break assert len(np.unique(x_at_this_depth)) == len(x_at_this_depth) depth += 1
def export(self, decision_tree, ax=None): import matplotlib.pyplot as plt from matplotlib.text import Annotation if ax is None: ax = plt.gca() ax.clear() ax.set_axis_off() my_tree = self._make_tree(0, decision_tree.tree_) draw_tree = buchheim(my_tree) # important to make sure we're still # inside the axis after drawing the box # this makes sense because the width of a box # is about the same as the distance between boxes max_x, max_y = draw_tree.max_extents() + 1 ax_width = ax.get_window_extent().width ax_height = ax.get_window_extent().height scale_x = ax_width / max_x scale_y = ax_height / max_y self.recurse(draw_tree, decision_tree.tree_, ax, scale_x, scale_y, ax_height) anns = [ ann for ann in ax.get_children() if isinstance(ann, Annotation) ] # update sizes of all bboxes renderer = ax.figure.canvas.get_renderer() for ann in anns: ann.update_bbox_position_size(renderer) if self.fontsize is None: # get figure to data transform # adjust fontsize to avoid overlap # get max box width and height try: extents = [ ann.get_bbox_patch().get_window_extent() for ann in anns ] max_width = max([extent.width for extent in extents]) max_height = max([extent.height for extent in extents]) # width should be around scale_x in axis coordinates size = anns[0].get_fontsize() * min(scale_x / max_width, scale_y / max_height) for ann in anns: ann.set_fontsize(size) except AttributeError: # matplotlib < 1.5 warnings.warn("Automatic scaling of tree plots requires " "matplotlib 1.5 or higher. Please specify " "fontsize.") return anns
def export(self, formula_ast, ax=None): self.filled = False if ax is None: ax = plt.gca() ax.clear() ax.set_axis_off() # my_tree = self._make_tree(0, decision_tree.tree_, # decision_tree.criterion) my_tree = self.make_tree(formula_ast) draw_tree = buchheim(my_tree) # important to make sure we're still # inside the axis after drawing the box # this makes sense because the width of a box # is about the same as the distance between boxes max_x, max_y = draw_tree.max_extents() + 1 ax_width = ax.get_window_extent().width ax_height = ax.get_window_extent().height scale_x = ax_width / max_x scale_y = ax_height / max_y self.recurse(draw_tree, ax, scale_x, scale_y, ax_height) anns = [ann for ann in ax.get_children() if isinstance(ann, Annotation)] # update sizes of all bboxes renderer = ax.figure.canvas.get_renderer() for ann in anns: ann.update_bbox_position_size(renderer) if self.fontsize is None: # get figure to data transform # adjust fontsize to avoid overlap # get max box width and height extents = [ann.get_bbox_patch().get_window_extent() for ann in anns] max_width = max([extent.width for extent in extents]) max_height = max([extent.height for extent in extents]) # width should be around scale_x in axis coordinates size = anns[0].get_fontsize() * min(scale_x / max_width, scale_y / max_height) for ann in anns: ann.set_fontsize(size) return anns