{"action":"created","comment":{"url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments/29920751","id":29920751,"diff_hunk":"@@ -55,32 +100,191 @@ def export_graphviz(decision_tree, out_file=\"tree.dot\", feature_names=None,\n     >>> tree.export_graphviz(clf,\n     ...     out_file='tree.dot')                # doctest: +SKIP\n     \"\"\"\n-    def node_to_str(tree, node_id, criterion):\n-        if not isinstance(criterion, six.string_types):\n-            criterion = \"impurity\"\n \n-        value = tree.value[node_id]\n-        if tree.n_outputs == 1:\n-            value = value[0, :]\n-\n-        if tree.children_left[node_id] == _tree.TREE_LEAF:\n-            return \"%s = %.4f\\\\nsamples = %s\\\\nvalue = %s\" \\\n-                   % (criterion,\n-                      tree.impurity[node_id],\n-                      tree.n_node_samples[node_id],\n-                      value)\n+    def colour_brew(n):\n+        # Generate n colours with equally spaced hues\n+        colour_list = []\n+\n+        # Initialize saturation & value; calculate chroma & value shift\n+        s, v = 0.75, 0.9\n+        c = s * v\n+        m = v - c\n+\n+        for h in np.arange(25, 385, 360./n).astype(int):\n+            # Calculate some intermediate values\n+            h_bar = h / 60.\n+            x = c * (1 - abs((h_bar % 2) - 1))\n+            # Initialize RGB with same hue & chroma as our colour\n+            rgb = [(c, x, 0),\n+                   (x, c, 0),\n+                   (0, c, x),\n+                   (0, x, c),\n+                   (x, 0, c),\n+                   (c, 0, x),\n+                   (c, x, 0)]\n+            r, g, b = rgb[int(h_bar)]\n+            # Shift the initial RGB values to match value and store\n+            rgb = [(int(255 * (r + m))),\n+                   (int(255 * (g + m))),\n+                   (int(255 * (b + m)))]\n+            colour_list.append(rgb)\n+\n+        return colour_list\n+\n+    def init_colours(tree):\n+        # Initialize colours and bounds if required\n+        colours['rgb'] = colour_brew(tree.n_classes[0])\n+        if tree.n_outputs != 1:\n+            # Find max and min impurities for multi-output\n+            colours['bounds'] = (np.min(-tree.impurity),\n+                                 np.max(-tree.impurity))\n+        elif tree.n_classes[0] == 1:\n+            # Find max and min values in leaf nodes for regression\n+            colours['bounds'] = (np.min(tree.value[tree.feature < 0]),\n+                                 np.max(tree.value[tree.feature < 0]))\n+\n+    def get_colour(value):\n+        # Find the appropriate colour & intensity for a node\n+        if colours['bounds'] is None:\n+            # Classification tree\n+            colour = list(colours['rgb'][np.argmax(value)])\n+            sorted_values = sorted(value, reverse=True)\n+            alpha = int(255 * (sorted_values[0] - sorted_values[1]) /\n+                        (1 - sorted_values[1]))\n         else:\n+            # Regression tree or multi-output\n+            colour = list(colours['rgb'][0])\n+            alpha = int(255 * ((value - colours['bounds'][0]) /\n+                               (colours['bounds'][1] - colours['bounds'][0])))\n+\n+        # Return html colour code in #RRGGBBAA format\n+        colour.append(alpha)\n+        hex_codes = [str(i) for i in range(10)]\n+        hex_codes.extend(['a', 'b', 'c', 'd', 'e', 'f'])\n+        colour = [hex_codes[c // 16] + hex_codes[c % 16] for c in colour]\n+\n+        return '#' + ''.join(colour)\n+\n+    def recurse_subtree(tree, node_id):","path":"sklearn/tree/export.py","position":162,"original_position":162,"commit_id":"698cc6ce8258a66f71f1530d140d9d68f3f5c23a","original_commit_id":"698cc6ce8258a66f71f1530d140d9d68f3f5c23a","user":{"login":"glouppe","id":477771,"avatar_url":"https://avatars.githubusercontent.com/u/477771?v=3","gravatar_id":"","url":"https://api.github.com/users/glouppe","html_url":"https://github.com/glouppe","followers_url":"https://api.github.com/users/glouppe/followers","following_url":"https://api.github.com/users/glouppe/following{/other_user}","gists_url":"https://api.github.com/users/glouppe/gists{/gist_id}","starred_url":"https://api.github.com/users/glouppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glouppe/subscriptions","organizations_url":"https://api.github.com/users/glouppe/orgs","repos_url":"https://api.github.com/users/glouppe/repos","events_url":"https://api.github.com/users/glouppe/events{/privacy}","received_events_url":"https://api.github.com/users/glouppe/received_events","type":"User","site_admin":false},"body":"I guess this is no longer needed given 359492536e1a82a26efb57603785c2ba7167e2b9","created_at":"2015-05-08T07:17:38Z","updated_at":"2015-05-08T07:17:38Z","html_url":"https://github.com/scikit-learn/scikit-learn/pull/3735#discussion_r29920751","pull_request_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735","_links":{"self":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments/29920751"},"html":{"href":"https://github.com/scikit-learn/scikit-learn/pull/3735#discussion_r29920751"},"pull_request":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735"}}},"pull_request":{"url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735","id":22240409,"html_url":"https://github.com/scikit-learn/scikit-learn/pull/3735","diff_url":"https://github.com/scikit-learn/scikit-learn/pull/3735.diff","patch_url":"https://github.com/scikit-learn/scikit-learn/pull/3735.patch","issue_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/3735","number":3735,"state":"open","locked":false,"title":"[MRG] Pretty decision trees - tree.export_graphviz()","user":{"login":"trevorstephens","id":5210848,"avatar_url":"https://avatars.githubusercontent.com/u/5210848?v=3","gravatar_id":"","url":"https://api.github.com/users/trevorstephens","html_url":"https://github.com/trevorstephens","followers_url":"https://api.github.com/users/trevorstephens/followers","following_url":"https://api.github.com/users/trevorstephens/following{/other_user}","gists_url":"https://api.github.com/users/trevorstephens/gists{/gist_id}","starred_url":"https://api.github.com/users/trevorstephens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/trevorstephens/subscriptions","organizations_url":"https://api.github.com/users/trevorstephens/orgs","repos_url":"https://api.github.com/users/trevorstephens/repos","events_url":"https://api.github.com/users/trevorstephens/events{/privacy}","received_events_url":"https://api.github.com/users/trevorstephens/received_events","type":"User","site_admin":false},"body":"As mentioned in #3643 this enhancement is intended to make the output of tree.export_graphviz more aesthetically pleasing as inspired by [fancyRpartPlot](http://blog.revolutionanalytics.com/2013/06/plotting-classification-and-regression-trees-with-plotrpart.html) in R. Colour brewing is done manually so the only additional import required is `numpy`, and output is still pure dot format. \r\n\r\nEnhancements include:\r\n\r\n- Drawing leaf nodes at the base of the tree for clarity\r\n- Colours indicating how the node classification is 'leaning', including multi-class\r\n- Percentage samples shown in node instead of raw number of samples\r\n- Percentage per class shown for all nodes, not just leaves\r\n\r\nStandard output, omitting the new `pretty` and `simple` flags, should result in the exact same graphical output from Graphviz as the incumbent version, though the dot code has been altered slightly for simplicity given the new more complicated node representation for `pretty`.\r\n\r\nA few examples using the Titanic survivor dataset:\r\n\r\nno flags:\r\n\r\n![full_new](https://cloud.githubusercontent.com/assets/5210848/4514716/0a8c122a-4b88-11e4-92dd-78f78bbfcee9.png)\r\n\r\n`pretty`:\r\n\r\n![full_pretty](https://cloud.githubusercontent.com/assets/5210848/4514717/154df71e-4b88-11e4-9d09-b763ebe63763.png)\r\n\r\n`pretty` & `simple`:\r\n\r\n![full_pretty_simple](https://cloud.githubusercontent.com/assets/5210848/4514718/20512c9e-4b88-11e4-88f7-6eb78fabec4d.png)\r\n\r\n`pretty` & `max_depth`:\r\n\r\n![cropped_pretty](https://cloud.githubusercontent.com/assets/5210848/4514721/3957a330-4b88-11e4-9544-78f68cab3da5.png)\r\n\r\nIt has been moderately tested on pure `tree.DecisionTreeClassifier` objects, though I am well aware that regression needs work and will fail if attempted with the new flags (though the standard output still appears to be functional).\r\n\r\nTo be discussed:\r\n\r\n- How to represent colours for regression... I'm thinking a diverging colour scheme with white for the median or mean of the two most extreme values in the leaves should work well.\r\n- Should the text and graphical options be broken out further? Something like `show_major_class`, `use_colours`, `use_percentages`, etc., or some perhaps discarded. I based the options off the R package, that's not meant to imply that this is the state of the art, just the best I've stumbled across.\r\n- Should the base implementation even be maintained? The extra sub-tree recursion per node is a little expensive, but it's just a single tree... And seeing what each node is voting towards gives more intuitive information than the error coefficients alone. It even appears that this used to be standard output from the examples [here](http://scikit-learn.org/stable/modules/tree.html#tree)\r\n\r\nOther options that might be useful:\r\n\r\n- Left to right orientation for use in reports?\r\n- Anything else you can think of? Though Graphviz is a little limited in what you can pull off\r\n- Welcome any other suggestions / gotchas","created_at":"2014-10-04T05:41:59Z","updated_at":"2015-05-08T07:17:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2485febcfe4ba57fb3f9296a476afbfd44fb667b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735/commits","review_comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735/comments","review_comment_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments{/number}","comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/3735/comments","statuses_url":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/698cc6ce8258a66f71f1530d140d9d68f3f5c23a","head":{"label":"trevorstephens:pretty-decision-trees","ref":"pretty-decision-trees","sha":"698cc6ce8258a66f71f1530d140d9d68f3f5c23a","user":{"login":"trevorstephens","id":5210848,"avatar_url":"https://avatars.githubusercontent.com/u/5210848?v=3","gravatar_id":"","url":"https://api.github.com/users/trevorstephens","html_url":"https://github.com/trevorstephens","followers_url":"https://api.github.com/users/trevorstephens/followers","following_url":"https://api.github.com/users/trevorstephens/following{/other_user}","gists_url":"https://api.github.com/users/trevorstephens/gists{/gist_id}","starred_url":"https://api.github.com/users/trevorstephens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/trevorstephens/subscriptions","organizations_url":"https://api.github.com/users/trevorstephens/orgs","repos_url":"https://api.github.com/users/trevorstephens/repos","events_url":"https://api.github.com/users/trevorstephens/events{/privacy}","received_events_url":"https://api.github.com/users/trevorstephens/received_events","type":"User","site_admin":false},"repo":{"id":24569671,"name":"scikit-learn","full_name":"trevorstephens/scikit-learn","owner":{"login":"trevorstephens","id":5210848,"avatar_url":"https://avatars.githubusercontent.com/u/5210848?v=3","gravatar_id":"","url":"https://api.github.com/users/trevorstephens","html_url":"https://github.com/trevorstephens","followers_url":"https://api.github.com/users/trevorstephens/followers","following_url":"https://api.github.com/users/trevorstephens/following{/other_user}","gists_url":"https://api.github.com/users/trevorstephens/gists{/gist_id}","starred_url":"https://api.github.com/users/trevorstephens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/trevorstephens/subscriptions","organizations_url":"https://api.github.com/users/trevorstephens/orgs","repos_url":"https://api.github.com/users/trevorstephens/repos","events_url":"https://api.github.com/users/trevorstephens/events{/privacy}","received_events_url":"https://api.github.com/users/trevorstephens/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/trevorstephens/scikit-learn","description":"scikit-learn: machine learning in Python","fork":true,"url":"https://api.github.com/repos/trevorstephens/scikit-learn","forks_url":"https://api.github.com/repos/trevorstephens/scikit-learn/forks","keys_url":"https://api.github.com/repos/trevorstephens/scikit-learn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/trevorstephens/scikit-learn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/trevorstephens/scikit-learn/teams","hooks_url":"https://api.github.com/repos/trevorstephens/scikit-learn/hooks","issue_events_url":"https://api.github.com/repos/trevorstephens/scikit-learn/issues/events{/number}","events_url":"https://api.github.com/repos/trevorstephens/scikit-learn/events","assignees_url":"https://api.github.com/repos/trevorstephens/scikit-learn/assignees{/user}","branches_url":"https://api.github.com/repos/trevorstephens/scikit-learn/branches{/branch}","tags_url":"https://api.github.com/repos/trevorstephens/scikit-learn/tags","blobs_url":"https://api.github.com/repos/trevorstephens/scikit-learn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/trevorstephens/scikit-learn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/trevorstephens/scikit-learn/git/refs{/sha}","trees_url":"https://api.github.com/repos/trevorstephens/scikit-learn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/trevorstephens/scikit-learn/statuses/{sha}","languages_url":"https://api.github.com/repos/trevorstephens/scikit-learn/languages","stargazers_url":"https://api.github.com/repos/trevorstephens/scikit-learn/stargazers","contributors_url":"https://api.github.com/repos/trevorstephens/scikit-learn/contributors","subscribers_url":"https://api.github.com/repos/trevorstephens/scikit-learn/subscribers","subscription_url":"https://api.github.com/repos/trevorstephens/scikit-learn/subscription","commits_url":"https://api.github.com/repos/trevorstephens/scikit-learn/commits{/sha}","git_commits_url":"https://api.github.com/repos/trevorstephens/scikit-learn/git/commits{/sha}","comments_url":"https://api.github.com/repos/trevorstephens/scikit-learn/comments{/number}","issue_comment_url":"https://api.github.com/repos/trevorstephens/scikit-learn/issues/comments{/number}","contents_url":"https://api.github.com/repos/trevorstephens/scikit-learn/contents/{+path}","compare_url":"https://api.github.com/repos/trevorstephens/scikit-learn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/trevorstephens/scikit-learn/merges","archive_url":"https://api.github.com/repos/trevorstephens/scikit-learn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/trevorstephens/scikit-learn/downloads","issues_url":"https://api.github.com/repos/trevorstephens/scikit-learn/issues{/number}","pulls_url":"https://api.github.com/repos/trevorstephens/scikit-learn/pulls{/number}","milestones_url":"https://api.github.com/repos/trevorstephens/scikit-learn/milestones{/number}","notifications_url":"https://api.github.com/repos/trevorstephens/scikit-learn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/trevorstephens/scikit-learn/labels{/name}","releases_url":"https://api.github.com/repos/trevorstephens/scikit-learn/releases{/id}","created_at":"2014-09-28T20:28:06Z","updated_at":"2015-05-07T14:40:49Z","pushed_at":"2015-05-07T14:40:48Z","git_url":"git://github.com/trevorstephens/scikit-learn.git","ssh_url":"git@github.com:trevorstephens/scikit-learn.git","clone_url":"https://github.com/trevorstephens/scikit-learn.git","svn_url":"https://github.com/trevorstephens/scikit-learn","homepage":"http://scikit-learn.org","size":60892,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"scikit-learn:master","ref":"master","sha":"c1a429e46db05954f92ab74f62716579bd357cb4","user":{"login":"scikit-learn","id":365630,"avatar_url":"https://avatars.githubusercontent.com/u/365630?v=3","gravatar_id":"","url":"https://api.github.com/users/scikit-learn","html_url":"https://github.com/scikit-learn","followers_url":"https://api.github.com/users/scikit-learn/followers","following_url":"https://api.github.com/users/scikit-learn/following{/other_user}","gists_url":"https://api.github.com/users/scikit-learn/gists{/gist_id}","starred_url":"https://api.github.com/users/scikit-learn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scikit-learn/subscriptions","organizations_url":"https://api.github.com/users/scikit-learn/orgs","repos_url":"https://api.github.com/users/scikit-learn/repos","events_url":"https://api.github.com/users/scikit-learn/events{/privacy}","received_events_url":"https://api.github.com/users/scikit-learn/received_events","type":"Organization","site_admin":false},"repo":{"id":843222,"name":"scikit-learn","full_name":"scikit-learn/scikit-learn","owner":{"login":"scikit-learn","id":365630,"avatar_url":"https://avatars.githubusercontent.com/u/365630?v=3","gravatar_id":"","url":"https://api.github.com/users/scikit-learn","html_url":"https://github.com/scikit-learn","followers_url":"https://api.github.com/users/scikit-learn/followers","following_url":"https://api.github.com/users/scikit-learn/following{/other_user}","gists_url":"https://api.github.com/users/scikit-learn/gists{/gist_id}","starred_url":"https://api.github.com/users/scikit-learn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scikit-learn/subscriptions","organizations_url":"https://api.github.com/users/scikit-learn/orgs","repos_url":"https://api.github.com/users/scikit-learn/repos","events_url":"https://api.github.com/users/scikit-learn/events{/privacy}","received_events_url":"https://api.github.com/users/scikit-learn/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/scikit-learn/scikit-learn","description":"scikit-learn: machine learning in Python","fork":false,"url":"https://api.github.com/repos/scikit-learn/scikit-learn","forks_url":"https://api.github.com/repos/scikit-learn/scikit-learn/forks","keys_url":"https://api.github.com/repos/scikit-learn/scikit-learn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/scikit-learn/scikit-learn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/scikit-learn/scikit-learn/teams","hooks_url":"https://api.github.com/repos/scikit-learn/scikit-learn/hooks","issue_events_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/events{/number}","events_url":"https://api.github.com/repos/scikit-learn/scikit-learn/events","assignees_url":"https://api.github.com/repos/scikit-learn/scikit-learn/assignees{/user}","branches_url":"https://api.github.com/repos/scikit-learn/scikit-learn/branches{/branch}","tags_url":"https://api.github.com/repos/scikit-learn/scikit-learn/tags","blobs_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/refs{/sha}","trees_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/{sha}","languages_url":"https://api.github.com/repos/scikit-learn/scikit-learn/languages","stargazers_url":"https://api.github.com/repos/scikit-learn/scikit-learn/stargazers","contributors_url":"https://api.github.com/repos/scikit-learn/scikit-learn/contributors","subscribers_url":"https://api.github.com/repos/scikit-learn/scikit-learn/subscribers","subscription_url":"https://api.github.com/repos/scikit-learn/scikit-learn/subscription","commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/commits{/sha}","git_commits_url":"https://api.github.com/repos/scikit-learn/scikit-learn/git/commits{/sha}","comments_url":"https://api.github.com/repos/scikit-learn/scikit-learn/comments{/number}","issue_comment_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/comments{/number}","contents_url":"https://api.github.com/repos/scikit-learn/scikit-learn/contents/{+path}","compare_url":"https://api.github.com/repos/scikit-learn/scikit-learn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/scikit-learn/scikit-learn/merges","archive_url":"https://api.github.com/repos/scikit-learn/scikit-learn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/scikit-learn/scikit-learn/downloads","issues_url":"https://api.github.com/repos/scikit-learn/scikit-learn/issues{/number}","pulls_url":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls{/number}","milestones_url":"https://api.github.com/repos/scikit-learn/scikit-learn/milestones{/number}","notifications_url":"https://api.github.com/repos/scikit-learn/scikit-learn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/scikit-learn/scikit-learn/labels{/name}","releases_url":"https://api.github.com/repos/scikit-learn/scikit-learn/releases{/id}","created_at":"2010-08-17T09:43:38Z","updated_at":"2015-05-08T07:04:39Z","pushed_at":"2015-05-07T18:37:13Z","git_url":"git://github.com/scikit-learn/scikit-learn.git","ssh_url":"git@github.com:scikit-learn/scikit-learn.git","clone_url":"https://github.com/scikit-learn/scikit-learn.git","svn_url":"https://github.com/scikit-learn/scikit-learn","homepage":"http://scikit-learn.org","size":125836,"stargazers_count":5929,"watchers_count":5929,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3544,"mirror_url":null,"open_issues_count":651,"forks":3544,"open_issues":651,"watchers":5929,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735"},"html":{"href":"https://github.com/scikit-learn/scikit-learn/pull/3735"},"issue":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/3735"},"comments":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/issues/3735/comments"},"review_comments":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735/comments"},"review_comment":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/pulls/3735/commits"},"statuses":{"href":"https://api.github.com/repos/scikit-learn/scikit-learn/statuses/698cc6ce8258a66f71f1530d140d9d68f3f5c23a"}}}}