├── .gitignore ├── LICENSE ├── README.md ├── bokeh_roc_slider_embed.ipynb ├── bokeh_roc_slider_embed_data.ipynb ├── roc ├── __init__.py ├── c ├── main.py └── pyroc.py ├── screenshot.png └── screenshot_notebook.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #bokeh_roc_slider 2 | Receiver operating characteristic chart in Bokeh 3 | 4 | This example shows how to create a simple applet in Bokeh, which can 5 | be viewed directly on a bokeh-server. 6 | 7 | Authors: [Brian Ray](mailto:brianhray@gmail.com?subject=boc_roc_slider) @brianray 8 | 9 | 10 | ##Running 11 | 12 | ###Standalone 13 | 14 | ![screen shot](https://github.com/brianray/bokeh_roc_slider/blob/master/screenshot.png "Screenshot") 15 | 16 | To view this app directly from a bokeh server using Bokeh >= 0.11, 17 | run the `bokeh serve` command and point it at the stock example directory: 18 | 19 | bokeh serve --show roc 20 | 21 | A browser tab should open automatically to the following URL: 22 | 23 | http://localhost:5006/roc 24 | 25 | ###In Jupyter (ipython) notebook 26 | 27 | ![screen shot](https://github.com/brianray/bokeh_roc_slider/blob/master/screenshot_notebook.png "Screenshot") 28 | 29 | * [Jupyter Notebook with Demo data](bokeh_roc_slider_embed.ipynb) 30 | * [Jupyter Notebook with your data](bokeh_roc_slider_embed_data.ipynb) 31 | -------------------------------------------------------------------------------- /bokeh_roc_slider_embed.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from src.slider_roc_app import RocPlotNotebook" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": { 18 | "collapsed": false 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "app = RocPlotNotebook()" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "collapsed": false 30 | }, 31 | "outputs": [], 32 | "source": [ 33 | "app.interact_widgets()" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": { 40 | "collapsed": true 41 | }, 42 | "outputs": [], 43 | "source": [] 44 | } 45 | ], 46 | "metadata": { 47 | "kernelspec": { 48 | "display_name": "Python 2", 49 | "language": "python", 50 | "name": "python2" 51 | }, 52 | "language_info": { 53 | "codemirror_mode": { 54 | "name": "ipython", 55 | "version": 2 56 | }, 57 | "file_extension": ".py", 58 | "mimetype": "text/x-python", 59 | "name": "python", 60 | "nbconvert_exporter": "python", 61 | "pygments_lexer": "ipython2", 62 | "version": "2.7.11" 63 | } 64 | }, 65 | "nbformat": 4, 66 | "nbformat_minor": 0 67 | } 68 | -------------------------------------------------------------------------------- /bokeh_roc_slider_embed_data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from src.slider_roc_app import RocPlotNotebook, random_roc_data" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "### Some Data to show" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": { 25 | "collapsed": false 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "data = {'y': [0, 0.006666666666666667, 0.013333333333333334, 0.02, 0.02666666666666667,\n", 30 | "0.03333333333333333, 0.04, 0.04666666666666667, 0.05333333333333334, 0.06, 0.06666666666666667,\n", 31 | "0.07333333333333333, 0.08, 0.08666666666666667, 0.09333333333333334, 0.1, 0.10666666666666667,\n", 32 | "0.11333333333333333, 0.12, 0.12666666666666668, 0.13333333333333333, 0.14, 0.14666666666666667,\n", 33 | "0.15333333333333332, 0.16, 0.16666666666666666, 0.17333333333333334, 0.18, 0.18666666666666668,\n", 34 | "0.19333333333333333, 0.2, 0.20666666666666667, 0.21333333333333335, 0.22, 0.22666666666666666,\n", 35 | "0.23333333333333334, 0.24, 0.24666666666666667, 0.25333333333333335, 0.26, 0.26666666666666666,\n", 36 | "0.2733333333333333, 0.28, 0.2866666666666667, 0.29333333333333333, 0.3, 0.30666666666666664,\n", 37 | "0.31333333333333335, 0.32, 0.32666666666666666, 0.3333333333333333, 0.34, 0.34, 0.3466666666666667,\n", 38 | "0.35333333333333333, 0.36, 0.36666666666666664, 0.37333333333333335, 0.38, 0.38666666666666666,\n", 39 | "0.3933333333333333, 0.4, 0.4066666666666667, 0.41333333333333333, 0.42, 0.42, 0.4266666666666667,\n", 40 | "0.43333333333333335, 0.44, 0.44, 0.44666666666666666, 0.4533333333333333, 0.46, 0.4666666666666667,\n", 41 | "0.47333333333333333, 0.47333333333333333, 0.48, 0.4866666666666667, 0.49333333333333335, 0.5,\n", 42 | "0.5066666666666667, 0.5133333333333333, 0.52, 0.5266666666666666, 0.5333333333333333, 0.54, 0.54,\n", 43 | "0.5466666666666666, 0.5533333333333333, 0.56, 0.5666666666666667, 0.5733333333333334, 0.58,\n", 44 | "0.5866666666666667, 0.5933333333333334, 0.6, 0.6066666666666667, 0.6133333333333333, 0.62,\n", 45 | "0.6266666666666667, 0.6333333333333333, 0.64, 0.6466666666666666, 0.6533333333333333, 0.66, 0.66,\n", 46 | "0.6666666666666666, 0.6733333333333333, 0.68, 0.6866666666666666, 0.6933333333333334, 0.7,\n", 47 | "0.7066666666666667, 0.7133333333333334, 0.72, 0.7266666666666667, 0.7333333333333333, 0.74,\n", 48 | "0.7466666666666667, 0.7466666666666667, 0.7533333333333333, 0.7533333333333333, 0.7533333333333333,\n", 49 | "0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.7666666666666667, 0.7733333333333333, 0.78, 0.78, 0.78,\n", 50 | "0.7866666666666666, 0.7933333333333333, 0.8, 0.8, 0.8066666666666666, 0.8133333333333334,\n", 51 | "0.8133333333333334, 0.82, 0.82, 0.82, 0.82, 0.82, 0.82, 0.8266666666666667, 0.8333333333333334,\n", 52 | "0.8333333333333334, 0.8333333333333334, 0.8333333333333334, 0.84, 0.8466666666666667,\n", 53 | "0.8466666666666667, 0.8533333333333334, 0.8533333333333334, 0.86, 0.8666666666666667,\n", 54 | "0.8733333333333333, 0.88, 0.88, 0.8866666666666667, 0.8866666666666667, 0.8866666666666667,\n", 55 | "0.8866666666666667, 0.8866666666666667, 0.8866666666666667, 0.8866666666666667, 0.8933333333333333,\n", 56 | "0.8933333333333333, 0.8933333333333333, 0.8933333333333333, 0.9, 0.9, 0.9, 0.9, 0.9066666666666666,\n", 57 | "0.9133333333333333, 0.9133333333333333, 0.92, 0.92, 0.9266666666666666, 0.9266666666666666,\n", 58 | "0.9266666666666666, 0.9266666666666666, 0.9266666666666666, 0.9266666666666666, 0.9266666666666666,\n", 59 | "0.9333333333333333, 0.9333333333333333, 0.9333333333333333, 0.9333333333333333, 0.9333333333333333,\n", 60 | "0.9333333333333333, 0.9333333333333333, 0.94, 0.94, 0.94, 0.9466666666666667, 0.9466666666666667,\n", 61 | "0.9466666666666667, 0.9466666666666667, 0.9466666666666667, 0.9533333333333334, 0.96, 0.96, 0.96,\n", 62 | "0.96, 0.9666666666666667, 0.9666666666666667, 0.9666666666666667, 0.9666666666666667,\n", 63 | "0.9733333333333334, 0.9733333333333334, 0.9733333333333334, 0.9733333333333334, 0.9733333333333334,\n", 64 | "0.9733333333333334, 0.9733333333333334, 0.9733333333333334, 0.9733333333333334, 0.9733333333333334,\n", 65 | "0.9733333333333334, 0.9733333333333334, 0.9733333333333334, 0.9733333333333334, 0.9733333333333334,\n", 66 | "0.9733333333333334, 0.9733333333333334, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98,\n", 67 | "0.98, 0.9866666666666667, 0.9866666666666667, 0.9933333333333333, 0.9933333333333333,\n", 68 | "0.9933333333333333, 0.9933333333333333, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n", 69 | "1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n", 70 | "1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n", 71 | "1.0, 1.0], 'x': [0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,\n", 72 | "0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,\n", 73 | "0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.006666666666666667,\n", 74 | "0.006666666666666667, 0.006666666666666667, 0.006666666666666667, 0.006666666666666667,\n", 75 | "0.006666666666666667, 0.006666666666666667, 0.006666666666666667, 0.006666666666666667,\n", 76 | "0.006666666666666667, 0.006666666666666667, 0.006666666666666667, 0.006666666666666667,\n", 77 | "0.013333333333333334, 0.013333333333333334, 0.013333333333333334, 0.013333333333333334, 0.02, 0.02,\n", 78 | "0.02, 0.02, 0.02, 0.02, 0.02666666666666667, 0.02666666666666667, 0.02666666666666667,\n", 79 | "0.02666666666666667, 0.02666666666666667, 0.02666666666666667, 0.02666666666666667,\n", 80 | "0.02666666666666667, 0.02666666666666667, 0.02666666666666667, 0.02666666666666667,\n", 81 | "0.03333333333333333, 0.03333333333333333, 0.03333333333333333, 0.03333333333333333,\n", 82 | "0.03333333333333333, 0.03333333333333333, 0.03333333333333333, 0.03333333333333333,\n", 83 | "0.03333333333333333, 0.03333333333333333, 0.03333333333333333, 0.03333333333333333,\n", 84 | "0.03333333333333333, 0.03333333333333333, 0.03333333333333333, 0.03333333333333333,\n", 85 | "0.03333333333333333, 0.03333333333333333, 0.03333333333333333, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04,\n", 86 | "0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04666666666666667, 0.04666666666666667,\n", 87 | "0.05333333333333334, 0.06, 0.06, 0.06666666666666667, 0.07333333333333333, 0.08,\n", 88 | "0.08666666666666667, 0.09333333333333334, 0.09333333333333334, 0.09333333333333334,\n", 89 | "0.09333333333333334, 0.1, 0.10666666666666667, 0.10666666666666667, 0.10666666666666667,\n", 90 | "0.10666666666666667, 0.11333333333333333, 0.11333333333333333, 0.11333333333333333, 0.12, 0.12,\n", 91 | "0.12666666666666668, 0.13333333333333333, 0.14, 0.14666666666666667, 0.15333333333333332,\n", 92 | "0.15333333333333332, 0.15333333333333332, 0.16, 0.16666666666666666, 0.17333333333333334,\n", 93 | "0.17333333333333334, 0.17333333333333334, 0.18, 0.18, 0.18666666666666668, 0.18666666666666668,\n", 94 | "0.18666666666666668, 0.18666666666666668, 0.18666666666666668, 0.19333333333333333,\n", 95 | "0.19333333333333333, 0.2, 0.20666666666666667, 0.21333333333333335, 0.22, 0.22666666666666666,\n", 96 | "0.23333333333333334, 0.23333333333333334, 0.24, 0.24666666666666667, 0.25333333333333335,\n", 97 | "0.25333333333333335, 0.26, 0.26666666666666666, 0.2733333333333333, 0.2733333333333333,\n", 98 | "0.2733333333333333, 0.28, 0.28, 0.2866666666666667, 0.2866666666666667, 0.29333333333333333, 0.3,\n", 99 | "0.30666666666666664, 0.31333333333333335, 0.32, 0.32666666666666666, 0.32666666666666666,\n", 100 | "0.3333333333333333, 0.34, 0.3466666666666667, 0.35333333333333333, 0.36, 0.36666666666666664,\n", 101 | "0.36666666666666664, 0.37333333333333335, 0.38, 0.38, 0.38666666666666666, 0.3933333333333333, 0.4,\n", 102 | "0.4066666666666667, 0.4066666666666667, 0.4066666666666667, 0.41333333333333333, 0.42,\n", 103 | "0.4266666666666667, 0.4266666666666667, 0.43333333333333335, 0.44, 0.44666666666666666,\n", 104 | "0.44666666666666666, 0.4533333333333333, 0.46, 0.4666666666666667, 0.47333333333333333, 0.48,\n", 105 | "0.4866666666666667, 0.49333333333333335, 0.5, 0.5066666666666667, 0.5133333333333333, 0.52,\n", 106 | "0.5266666666666666, 0.5333333333333333, 0.54, 0.5466666666666666, 0.5533333333333333,\n", 107 | "0.5533333333333333, 0.56, 0.5666666666666667, 0.5733333333333334, 0.58, 0.5866666666666667,\n", 108 | "0.5933333333333334, 0.6, 0.6066666666666667, 0.6133333333333333, 0.62, 0.62, 0.6266666666666667,\n", 109 | "0.6266666666666667, 0.6333333333333333, 0.64, 0.6466666666666666, 0.6466666666666666,\n", 110 | "0.6533333333333333, 0.66, 0.6666666666666666, 0.6733333333333333, 0.68, 0.6866666666666666,\n", 111 | "0.6933333333333334, 0.7, 0.7066666666666667, 0.7133333333333334, 0.72, 0.7266666666666667,\n", 112 | "0.7333333333333333, 0.74, 0.7466666666666667, 0.7533333333333333, 0.76, 0.7666666666666667,\n", 113 | "0.7733333333333333, 0.78, 0.7866666666666666, 0.7933333333333333, 0.8, 0.8066666666666666,\n", 114 | "0.8133333333333334, 0.82, 0.8266666666666667, 0.8333333333333334, 0.84, 0.8466666666666667,\n", 115 | "0.8533333333333334, 0.86, 0.8666666666666667, 0.8733333333333333, 0.88, 0.8866666666666667,\n", 116 | "0.8933333333333333, 0.9, 0.9066666666666666, 0.9133333333333333, 0.92, 0.9266666666666666,\n", 117 | "0.9333333333333333, 0.94, 0.9466666666666667, 0.9533333333333334, 0.96, 0.9666666666666667,\n", 118 | "0.9733333333333334, 0.98, 0.9866666666666667, 0.9933333333333333, 1.0]}\n" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": { 125 | "collapsed": false 126 | }, 127 | "outputs": [], 128 | "source": [ 129 | "app = RocPlotNotebook(data=data)" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "metadata": { 136 | "collapsed": false 137 | }, 138 | "outputs": [ 139 | { 140 | "data": { 141 | "application/javascript": [ 142 | "\n", 143 | " var ds = Bokeh.Collections('ColumnDataSource').get('e9da5deb-b8a6-47c2-ac50-9a57385ea491');\n", 144 | " var data = {\"column_names\": [\"y\", \"x\"], \"tags\": [], \"selected\": {\"2d\": {\"indices\": []}, \"1d\": {\"indices\": []}, \"0d\": {\"indices\": [], \"flag\": false}}, \"callback\": null, \"data\": {\"y\": [0.97], \"x\": [0.44]}, \"id\": \"e9da5deb-b8a6-47c2-ac50-9a57385ea491\"};\n", 145 | " ds.set(data);\n", 146 | " " 147 | ] 148 | }, 149 | "metadata": {}, 150 | "output_type": "display_data" 151 | }, 152 | { 153 | "data": { 154 | "application/javascript": [ 155 | "\n", 156 | " var ds = Bokeh.Collections('ColumnDataSource').get('1944799a-87fa-4c5b-bc1c-f4e4df08dd75');\n", 157 | " var data = {\"column_names\": [\"TN\", \"FP\", \"FN\", \"TP\"], \"tags\": [], \"selected\": {\"2d\": {\"indices\": []}, \"1d\": {\"indices\": []}, \"0d\": {\"indices\": [], \"flag\": false}}, \"callback\": null, \"data\": {\"TN\": [88], \"FP\": [6], \"TP\": [194], \"FN\": [112]}, \"id\": \"1944799a-87fa-4c5b-bc1c-f4e4df08dd75\"};\n", 158 | " ds.set(data);\n", 159 | " " 160 | ] 161 | }, 162 | "metadata": {}, 163 | "output_type": "display_data" 164 | } 165 | ], 166 | "source": [ 167 | "app.interact_widgets()" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": { 174 | "collapsed": true 175 | }, 176 | "outputs": [], 177 | "source": [] 178 | } 179 | ], 180 | "metadata": { 181 | "kernelspec": { 182 | "display_name": "Python 2", 183 | "language": "python", 184 | "name": "python2" 185 | }, 186 | "language_info": { 187 | "codemirror_mode": { 188 | "name": "ipython", 189 | "version": 2 190 | }, 191 | "file_extension": ".py", 192 | "mimetype": "text/x-python", 193 | "name": "python", 194 | "nbconvert_exporter": "python", 195 | "pygments_lexer": "ipython2", 196 | "version": "2.7.11" 197 | } 198 | }, 199 | "nbformat": 4, 200 | "nbformat_minor": 0 201 | } 202 | -------------------------------------------------------------------------------- /roc/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /roc/c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianray/bokeh_roc_slider/0c3878c485e9453badb14f8ff0ed04bb2aba0dce/roc/c -------------------------------------------------------------------------------- /roc/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates a bokeh applet, which can be viewed directly 3 | on a bokeh-server. See the README.md file in this directory for 4 | instructions on running. 5 | 6 | ROC Curve example 7 | ================= 8 | 9 | This example is a ROC curve where the data is generated to show 10 | AUC (Area Under the Curve). The threshold may be set, and the 11 | impact of the resultant confustion matrix will display in the 12 | lower right corner. 13 | 14 | Author(s): Brian Ray , 15 | Bryan Van de Ven 16 | 17 | """ 18 | 19 | # this will not be needed in 0.12 20 | from os.path import dirname 21 | import sys 22 | sys.path.insert(0, dirname(__file__)) 23 | 24 | import logging 25 | 26 | from pyroc import random_mixture_model, ROCData 27 | 28 | from bokeh.io import curdoc 29 | from bokeh.plotting.figure import Figure 30 | from bokeh.models import (ColumnDataSource, 31 | HBox, 32 | Slider, 33 | TextInput, 34 | VBoxForm, 35 | CustomJS) 36 | 37 | HAS_REQUESTS = False 38 | try: 39 | import requests 40 | HAS_REQUESTS = True 41 | except: 42 | logging.warn("install 'requests' package for json fetching") 43 | 44 | CACHED_DATA = {} 45 | 46 | 47 | def random_roc_data(auc=.7, std_dev=.2, size=300): 48 | args = dict( 49 | pos_mu=auc, 50 | pos_sigma=std_dev, 51 | neg_mu=1-auc, 52 | neg_sigma=std_dev, 53 | size=size) 54 | arg_hash = hash(frozenset(args.items())) 55 | if arg_hash in CACHED_DATA: 56 | random_sample = CACHED_DATA[arg_hash] 57 | else: 58 | random_sample = random_mixture_model(**args) 59 | CACHED_DATA[arg_hash] = random_sample 60 | roc = ROCData(random_sample) 61 | roc.auc() 62 | roc_x = [x[0] for x in roc.derived_points] 63 | roc_y = [y[1] for y in roc.derived_points] 64 | return dict(x=roc_x, y=roc_y) 65 | 66 | 67 | def get_collide(): 68 | """Finds the point that collides in the 'x' direction with threshold""" 69 | data = zip(source.data['x'], source.data['y']) 70 | return min(data, key=lambda x: abs(x[0] - float(threshold.value)/100.0)) 71 | 72 | 73 | def conf_matrix(): 74 | """Calculate the confusion Matrix""" 75 | P = len(source.data['x']) / 2.0 76 | N = len(source.data['y']) / 2.0 77 | TPR = point_source.data['y'][0] # y axis sensitivity 78 | TNR = point_source.data['x'][0] # x axis specificity 79 | TP = TPR * P # True Positive 80 | TN = TNR * N # True Negative 81 | FP = P - TP 82 | FN = N - TN 83 | return dict(TP=[int(TP)], FP=[int(FP)], FN=[int(FN)], TN=[int(TN)]) 84 | 85 | 86 | def input_change(attr, old, new): 87 | """Executes whenever the input form changes. 88 | 89 | It is responsible for updating the plot, or anything else you want. 90 | 91 | Args: 92 | attr : the name of the attr that changed 93 | old : old value of attr 94 | new : new value of attr 95 | """ 96 | update_data() 97 | plot.title = text.value 98 | 99 | source_url = ColumnDataSource() 100 | 101 | def dataurl_change(attr, old, new): 102 | if new != "DEMO": 103 | try: 104 | source_url.data = requests.get(new).json() 105 | inputs = VBoxForm(text, threshold, dataurl) 106 | curdoc().remove_root(plot) 107 | curdoc().add_root(HBox(inputs, plot, width=800)) 108 | except: 109 | logging.warn("unable to fetch {}".format(new)) 110 | update_data() 111 | 112 | 113 | def update_data(): 114 | """Called each time that any watched property changes. 115 | 116 | This updates the roc curve with the most recent values of the 117 | sliders. This is stored as two numpy arrays in a dict into the app's 118 | data source property. 119 | """ 120 | size = int(sample_size.value) / 2 121 | if source_url.data == {}: 122 | source.data = random_roc_data(auc=float(auc.value)/100.0, size=size) 123 | else: 124 | source.data = source_url.data 125 | for w in [threshold, text, auc, sample_size]: 126 | w.disabled = True 127 | x, y = get_collide() 128 | point_source.data = dict(x=[x], y=[y]) 129 | conf_source.data = conf_matrix() 130 | 131 | 132 | source = ColumnDataSource(data=random_roc_data(size=200)) 133 | 134 | text = TextInput(title="title", name='title', value='ROC Curve') 135 | sample_size = Slider(title="Sample Size (splits 50/50)", value=400, start=50, end=800, step=2) 136 | threshold = Slider(title="Threshold", value=50.0, start=0.0, end=100.0, step=0.1) 137 | auc = Slider(title="Area Under Curve (AUC)", value=70.0, start=0.0, end=100.0, step=0.1) 138 | dataurl = TextInput(title="Data Url", name='data', value='DEMO') 139 | 140 | # Generate a figure container 141 | plot = Figure(title_text_font_size="12pt", 142 | plot_height=400, 143 | plot_width=400, 144 | tools="crosshair,pan,reset,resize,save,wheel_zoom", 145 | title=text.value, 146 | x_range=[0, 1], 147 | y_range=[0, 1]) 148 | 149 | # Plot the line by the x,y values in the source property 150 | plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) 151 | 152 | # Plot the line by the x,y values in the source property 153 | plot.line('xr', 'yr', source=ColumnDataSource(data=dict(xr=[0, 1], yr=[0, 1])), 154 | line_width=3, line_alpha=0.6, color="red") 155 | 156 | x, y = get_collide() 157 | point_source = ColumnDataSource(data=dict(x=[x], y=[y])) 158 | plot.circle_cross('x', 'y', source=point_source, color="blue") 159 | 160 | conf_source = ColumnDataSource(data=conf_matrix()) 161 | 162 | text_props = {"text_font": "Courier", 163 | "text_align": "left", 164 | "text_baseline": "middle"} 165 | 166 | # the confusion matrix 167 | text_props['text_font_size'] = "5pt" 168 | plot.text(x=0.825, y=0.21, text=["True"], **text_props) 169 | plot.text(x=0.925, y=0.21, text=["False"], **text_props) 170 | plot.text(x=0.725, y=0.15, text=["True"], **text_props) 171 | plot.text(x=0.725, y=0.05, text=["False"], **text_props) 172 | 173 | text_props['text_font_size'] = "8pt" 174 | plot.text(x=0.825, y=0.15, text="TP", source=conf_source, **text_props) 175 | plot.text(x=0.925, y=0.15, text="FP", source=conf_source, **text_props) 176 | plot.text(x=0.825, y=0.05, text="FN", source=conf_source, **text_props) 177 | plot.text(x=0.925, y=0.05, text="TN", source=conf_source, **text_props) 178 | 179 | update_data() 180 | 181 | text.on_change('value', input_change) 182 | dataurl.on_change('value', dataurl_change) 183 | 184 | # There must be a better way: 185 | dataurl.callback = CustomJS(args=dict(auc=auc, 186 | sample_size=sample_size), 187 | code=""" 188 | // $("label[for='"+auc.id+"']").parentNode.remove(); 189 | document.getElementById(auc.id).parentNode.hidden = true; 190 | // $("label[for='"+sample_size.id+"']").parentNode.remove(); 191 | document.getElementById(sample_size.id).parentNode.hidden = true; 192 | """) 193 | 194 | for w in (threshold, text, auc, sample_size): 195 | w.on_change('value', input_change) 196 | 197 | vbox_items = [text, sample_size, threshold, auc] 198 | if HAS_REQUESTS: 199 | vbox_items.append(dataurl) 200 | inputs = VBoxForm(*vbox_items) 201 | 202 | curdoc().add_root(HBox(inputs, plot, width=800)) 203 | -------------------------------------------------------------------------------- /roc/pyroc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | PyRoc.py 5 | 6 | Created by Marcel Caraciolo on 2009-11-16. 7 | Copyright (c) 2009 Federal University of Pernambuco. All rights reserved. 8 | 9 | IMPORTANT: 10 | Based on the original code by Eithon Cadag (http://www.eithoncadag.com/files/pyroc.txt) 11 | 12 | Python Module for calculating the area under the receive operating characteristic curve, given a dataset. 13 | 14 | 0.1 - First Release 15 | 0.2 - Updated the code by adding new metrics for analysis with the confusion matrix. 16 | 17 | """ 18 | from __future__ import print_function 19 | 20 | import random 21 | import math 22 | try: 23 | import pylab 24 | except: 25 | print("error: can't import pylab module, you must install the module:\n") 26 | print(" matplotlib to plot charts!'\n") 27 | 28 | 29 | def random_mixture_model(pos_mu=.6,pos_sigma=.1,neg_mu=.4,neg_sigma=.1,size=200): 30 | pos = [(1,random.gauss(pos_mu,pos_sigma),) for x in list(range(int(size/2)))] 31 | neg = [(0,random.gauss(neg_mu,neg_sigma),) for x in list(range(int(size/2)))] 32 | return pos+neg 33 | 34 | 35 | def plot_multiple_rocs_separate(rocList,title='', labels = None, equal_aspect = True): 36 | """ Plot multiples ROC curves as separate at the same painting area. """ 37 | pylab.clf() 38 | pylab.title(title) 39 | for ix, r in enumerate(rocList): 40 | ax = pylab.subplot(4,4,ix+1) 41 | pylab.ylim((0,1)) 42 | pylab.xlim((0,1)) 43 | ax.set_yticklabels([]) 44 | ax.set_xticklabels([]) 45 | if equal_aspect: 46 | cax = pylab.gca() 47 | cax.set_aspect('equal') 48 | 49 | if not labels: 50 | labels = ['' for x in rocList] 51 | 52 | pylab.text(0.2,0.1,labels[ix],fontsize=8) 53 | pylab.plot([x[0] for x in r.derived_points],[y[1] for y in r.derived_points], 'r-',linewidth=2) 54 | 55 | pylab.show() 56 | 57 | 58 | 59 | def _remove_duplicate_styles(rocList): 60 | """ Checks for duplicate linestyles and replaces duplicates with a random one.""" 61 | pref_styles = ['cx-','mx-','yx-','gx-','bx-','rx-'] 62 | points = 'ov^>+xd' 63 | colors = 'bgrcmy' 64 | lines = ['-','-.',':'] 65 | 66 | rand_ls = [] 67 | 68 | for r in rocList: 69 | if r.linestyle not in rand_ls: 70 | rand_ls.append(r.linestyle) 71 | else: 72 | while True: 73 | if len(pref_styles) > 0: 74 | pstyle = pref_styles.pop() 75 | if pstyle not in rand_ls: 76 | r.linestyle = pstyle 77 | rand_ls.append(pstyle) 78 | break 79 | else: 80 | ls = ''.join(random.sample(colors,1) + random.sample(points,1)+ random.sample(lines,1)) 81 | if ls not in rand_ls: 82 | r.linestyle = ls 83 | rand_ls.append(ls) 84 | break 85 | 86 | 87 | 88 | def plot_multiple_roc(rocList,title='',labels=None, include_baseline=False, equal_aspect=True): 89 | """ Plots multiple ROC curves on the same chart. 90 | Parameters: 91 | rocList: the list of ROCData objects 92 | title: The tile of the chart 93 | labels: The labels of each ROC curve 94 | include_baseline: if it's True include the random baseline 95 | equal_aspect: keep equal aspect for all roc curves 96 | """ 97 | pylab.clf() 98 | pylab.ylim((0,1)) 99 | pylab.xlim((0,1)) 100 | pylab.xticks(pylab.arange(0,1.1,.1)) 101 | pylab.yticks(pylab.arange(0,1.1,.1)) 102 | pylab.grid(True) 103 | if equal_aspect: 104 | cax = pylab.gca() 105 | cax.set_aspect('equal') 106 | pylab.xlabel("1 - Specificity") 107 | pylab.ylabel("Sensitivity") 108 | pylab.title(title) 109 | if not labels: 110 | labels = [ '' for x in rocList] 111 | _remove_duplicate_styles(rocList) 112 | for ix, r in enumerate(rocList): 113 | pylab.plot([x[0] for x in r.derived_points], [y[1] for y in r.derived_points], r.linestyle, linewidth=1, label=labels[ix]) 114 | if include_baseline: 115 | pylab.plot([0.0,1.0], [0.0, 1.0], 'k-', label= 'random') 116 | if labels: 117 | pylab.legend(loc='lower right') 118 | 119 | pylab.show() 120 | 121 | 122 | def load_decision_function(path): 123 | """ Function to load the decision function (DataSet) 124 | Parameters: 125 | path: The dataset file path 126 | Return: 127 | model_data: The data modeled 128 | """ 129 | fileHandler = open(path,'r') 130 | reader = fileHandler.readlines() 131 | reader = [line.strip().split() for line in reader] 132 | model_data = [] 133 | for line in reader: 134 | if len(line) == 0: continue 135 | fClass,fValue = line 136 | model_data.append((int(fClass), float(fValue))) 137 | fileHandler.close() 138 | 139 | return model_data 140 | 141 | 142 | class ROCData(object): 143 | """ Class that generates an ROC Curve for the data. 144 | Data is in the following format: a list l of tutples t 145 | where: 146 | t[0] = 1 for positive class and t[0] = 0 for negative class 147 | t[1] = score 148 | t[2] = label 149 | """ 150 | def __init__(self,data,linestyle='rx-'): 151 | """ Constructor takes the data and the line style for plotting the ROC Curve. 152 | Parameters: 153 | data: The data a listl of tuples t (l = [t_0,t_1,...t_n]) where: 154 | t[0] = 1 for positive class and 0 for negative class 155 | t[1] = a score 156 | t[2] = any label (optional) 157 | lineStyle: THe matplotlib style string for plots. 158 | 159 | Note: The ROCData is still usable w/o matplotlib. The AUC is still available, 160 | but plots cannot be generated. 161 | """ 162 | self.data = sorted(data, lambda x,y: cmp(y[1], x[1])) 163 | self.linestyle = linestyle 164 | self.auc() #Seed initial points with default full ROC 165 | 166 | def auc(self,fpnum=0): 167 | """ Uses the trapezoidal ruel to calculate the area under the curve. If fpnum is supplied, it will 168 | calculate a partial AUC, up to the number of false positives in fpnum (the partial AUC is scaled 169 | to between 0 and 1). 170 | It assumes that the positive class is expected to have the higher of the scores (s(+) < s(-)) 171 | Parameters: 172 | fpnum: The cumulativr FP count (fps) 173 | Return: 174 | 175 | """ 176 | fps_count = 0 177 | relevant_pauc = [] 178 | current_index = 0 179 | max_n = len([x for x in self.data if x[0] == 0]) 180 | if fpnum == 0: 181 | relevant_pauc = [x for x in self.data] 182 | elif fpnum > max_n: 183 | fpnum = max_n 184 | #Find the upper limit of the data that does not exceed n FPs 185 | else: 186 | while fps_count < fpnum: 187 | relevant_pauc.append(self.data[current_index]) 188 | if self.data[current_index][0] == 0: 189 | fps_count += 1 190 | current_index +=1 191 | total_n = len([x for x in relevant_pauc if x[0] == 0]) 192 | total_p = len(relevant_pauc) - total_n 193 | 194 | #Convert to points in a ROC 195 | previous_df = -1000000.0 196 | current_index = 0 197 | points = [] 198 | tp_count, fp_count = 0.0 , 0.0 199 | tpr, fpr = 0, 0 200 | while current_index < len(relevant_pauc): 201 | df = relevant_pauc[current_index][1] 202 | if previous_df != df: 203 | points.append((fpr,tpr,fp_count)) 204 | if relevant_pauc[current_index][0] == 0: 205 | fp_count +=1 206 | elif relevant_pauc[current_index][0] == 1: 207 | tp_count +=1 208 | fpr = fp_count/total_n 209 | tpr = tp_count/total_p 210 | previous_df = df 211 | current_index +=1 212 | points.append((fpr,tpr,fp_count)) #Add last point 213 | points.sort(key=lambda i: (i[0],i[1])) 214 | self.derived_points = points 215 | 216 | return self._trapezoidal_rule(points) 217 | 218 | 219 | def _trapezoidal_rule(self,curve_pts): 220 | """ Method to calculate the area under the ROC curve""" 221 | cum_area = 0.0 222 | for ix,x in enumerate(curve_pts[0:-1]): 223 | cur_pt = x 224 | next_pt = curve_pts[ix+1] 225 | cum_area += ((cur_pt[1]+next_pt[1])/2.0) * (next_pt[0]-cur_pt[0]) 226 | return cum_area 227 | 228 | def calculateStandardError(self,fpnum=0): 229 | """ Returns the standard error associated with the curve. 230 | Parameters: 231 | fpnum: The cumulativr FP count (fps) 232 | Return: 233 | the standard error. 234 | """ 235 | area = self.auc(fpnum) 236 | 237 | #real positive cases 238 | Na = len([ x for x in self.data if x[0] == 1]) 239 | 240 | #real negative cases 241 | Nn = len([ x for x in self.data if x[0] == 0]) 242 | 243 | 244 | Q1 = area / (2.0 - area) 245 | Q2 = 2 * area * area / (1.0 + area) 246 | 247 | return math.sqrt( ( area * (1.0 - area) + (Na - 1.0) * (Q1 - area*area) + 248 | (Nn - 1.0) * (Q2 - area * area)) / (Na * Nn)) 249 | 250 | 251 | def plot(self,title='',include_baseline=False,equal_aspect=True): 252 | """ Method that generates a plot of the ROC curve 253 | Parameters: 254 | title: Title of the chart 255 | include_baseline: Add the baseline plot line if it's True 256 | equal_aspect: Aspects to be equal for all plot 257 | """ 258 | 259 | pylab.clf() 260 | pylab.plot([x[0] for x in self.derived_points], [y[1] for y in self.derived_points], self.linestyle) 261 | if include_baseline: 262 | pylab.plot([0.0,1.0], [0.0,1.0],'k-.') 263 | pylab.ylim((0,1)) 264 | pylab.xlim((0,1)) 265 | pylab.xticks(pylab.arange(0,1.1,.1)) 266 | pylab.yticks(pylab.arange(0,1.1,.1)) 267 | pylab.grid(True) 268 | if equal_aspect: 269 | cax = pylab.gca() 270 | cax.set_aspect('equal') 271 | pylab.xlabel('1 - Specificity') 272 | pylab.ylabel('Sensitivity') 273 | pylab.title(title) 274 | 275 | pylab.show() 276 | 277 | 278 | def confusion_matrix(self,threshold,do_print=False): 279 | """ Returns the confusion matrix (in dictionary form) for a fiven threshold 280 | where all elements > threshold are considered 1 , all else 0. 281 | Parameters: 282 | threshold: threshold to check the decision function 283 | do_print: if it's True show the confusion matrix in the screen 284 | Return: 285 | the dictionary with the TP, FP, FN, TN 286 | """ 287 | pos_points = [x for x in self.data if x[1] >= threshold] 288 | neg_points = [x for x in self.data if x[1] < threshold] 289 | tp,fp,fn,tn = self._calculate_counts(pos_points,neg_points) 290 | if do_print: 291 | print(" Actual class") 292 | print(" +(1) -(0)") 293 | print("+(1) %i %i Predicted" % (tp,fp)) 294 | print("-(0) %i %i class" % (fn,tn)) 295 | return {'TP': tp, 'FP': fp, 'FN': fn, 'TN': tn} 296 | 297 | 298 | 299 | def evaluateMetrics(self,matrix,metric=None,do_print=False): 300 | """ Returns the metrics evaluated from the confusion matrix. 301 | Parameters: 302 | matrix: the confusion matrix 303 | metric: the specific metric of the default value is None (all metrics). 304 | do_print: if it's True show the metrics in the screen 305 | Return: 306 | the dictionary with the Accuracy, Sensitivity, Specificity,Efficiency, 307 | PositivePredictiveValue, NegativePredictiveValue, PhiCoefficient 308 | """ 309 | 310 | accuracy = (matrix['TP'] + matrix['TN'])/ float(sum(matrix.values())) 311 | 312 | sensitivity = (matrix['TP'])/ float(matrix['TP'] + matrix['FN']) 313 | 314 | specificity = (matrix['TN'])/float(matrix['TN'] + matrix['FP']) 315 | 316 | efficiency = (sensitivity + specificity) / 2.0 317 | 318 | positivePredictiveValue = matrix['TP'] / float(matrix['TP'] + matrix['FP']) 319 | 320 | NegativePredictiveValue = matrix['TN'] / float(matrix['TN'] + matrix['FN']) 321 | 322 | PhiCoefficient = (matrix['TP'] * matrix['TN'] - matrix['FP'] * matrix['FN'])/( 323 | math.sqrt( (matrix['TP'] + matrix['FP']) * 324 | (matrix['TP'] + matrix['FN']) * 325 | (matrix['TN'] + matrix['FP']) * 326 | (matrix['TN'] + matrix['FN']))) or 1.0 327 | 328 | if do_print: 329 | print('Sensitivity: ' , sensitivity) 330 | print('Specificity: ' , specificity) 331 | print('Efficiency: ' , efficiency) 332 | print('Accuracy: ' , accuracy) 333 | print('PositivePredictiveValue: ' , positivePredictiveValue) 334 | print('NegativePredictiveValue' , NegativePredictiveValue) 335 | print('PhiCoefficient' , PhiCoefficient) 336 | 337 | 338 | return {'SENS': sensitivity, 'SPEC': specificity, 'ACC': accuracy, 'EFF': efficiency, 339 | 'PPV':positivePredictiveValue, 'NPV':NegativePredictiveValue , 'PHI': PhiCoefficient} 340 | 341 | 342 | def _calculate_counts(self,pos_data,neg_data): 343 | """ Calculates the number of false positives, true positives, false negatives and true negatives """ 344 | tp_count = len([x for x in pos_data if x[0] == 1]) 345 | fp_count = len([x for x in pos_data if x[0] == 0]) 346 | fn_count = len([x for x in neg_data if x[0] == 1]) 347 | tn_count = len([x for x in neg_data if x[0] == 0]) 348 | return tp_count,fp_count,fn_count, tn_count 349 | 350 | 351 | 352 | if __name__ == '__main__': 353 | print("PyRoC - ROC Curve Generator") 354 | print("By Marcel Pinheiro Caraciolo (@marcelcaraciolo)") 355 | print("http://aimotion.bogspot.com\n") 356 | from optparse import OptionParser 357 | 358 | parser = OptionParser() 359 | parser.add_option('-f', '--file', dest='origFile', help="Path to a file with the class and decision function. The first column of each row is the class, and the second the decision score.") 360 | parser.add_option("-n", "--max fp", dest = "fp_n", default=0, help= "Maximum false positives to calculate up to (for partial AUC).") 361 | parser.add_option("-p","--plot", action="store_true",dest='plotFlag', default=False, help="Plot the ROC curve (matplotlib required)") 362 | parser.add_option("-t",'--title', dest= 'ptitle' , default='' , help = 'Title of plot.') 363 | 364 | (options,args) = parser.parse_args() 365 | 366 | 367 | if (not options.origFile): 368 | parser.print_help() 369 | exit() 370 | 371 | df_data = load_decision_function(options.origFile) 372 | roc = ROCData(df_data) 373 | roc_n = int(options.fp_n) 374 | print("ROC AUC: %s" % (str(roc.auc(roc_n)),)) 375 | print('Standard Error: %s' % (str(roc.calculateStandardError(roc_n)),)) 376 | 377 | print('') 378 | for pt in roc.derived_points: 379 | print(pt[0],pt[1]) 380 | 381 | if options.plotFlag: 382 | roc.plot(options.ptitle,True,True) 383 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianray/bokeh_roc_slider/0c3878c485e9453badb14f8ff0ed04bb2aba0dce/screenshot.png -------------------------------------------------------------------------------- /screenshot_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianray/bokeh_roc_slider/0c3878c485e9453badb14f8ff0ed04bb2aba0dce/screenshot_notebook.png --------------------------------------------------------------------------------