├── tests ├── localxlsxdiff │ ├── __init__.py │ └── compare.py ├── files │ ├── PandasNA.ipynb.xlsx │ ├── ExcelTest.ipynb.xlsx │ ├── ExcelTest4.ipynb.xlsx │ ├── ExcelTest5.ipynb.xlsx │ ├── PandasTables.ipynb.xlsx │ ├── MultipleOutputs.ipynb.xlsx │ ├── NestedMarkdown1.ipynb.xlsx │ ├── MarkdownReprDisplay.ipynb.xlsx │ ├── ExcelTest5.ipynb │ ├── ExcelTest.ipynb │ ├── NestedMarkdown1.ipynb │ ├── MarkdownReprDisplay.ipynb │ ├── PandasNA.ipynb │ ├── MultipleOutputs.ipynb │ └── PandasTables.ipynb └── test_nb2xls.py ├── MANIFEST.in ├── screenshots └── Jupyter2Excel.png ├── requirements.txt ├── nb2xls ├── __init__.py ├── __meta__.py ├── mdxlsstyles.py ├── mdrenderer.py └── exporter.py ├── .gitignore ├── RELEASE.md ├── run_nbconvert_to_xls.py ├── .travis.yml ├── LICENSE ├── Examples ├── ExcelTest5.ipynb ├── ExcelTest.ipynb ├── NestedMarkdown1.ipynb ├── MarkdownReprDisplay.ipynb ├── PandasNA.ipynb ├── ExcelTest2.ipynb ├── MultipleOutputs.ipynb ├── ExcelTest3.ipynb └── PandasTables.ipynb ├── CONTRIBUTING.md ├── setup.py └── README.md /tests/localxlsxdiff/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Misc 2 | include requirements.txt 3 | include LICENSE 4 | global-exclude *.py[co] 5 | -------------------------------------------------------------------------------- /screenshots/Jupyter2Excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/screenshots/Jupyter2Excel.png -------------------------------------------------------------------------------- /tests/files/PandasNA.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/PandasNA.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/ExcelTest.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/ExcelTest.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/ExcelTest4.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/ExcelTest4.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/ExcelTest5.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/ExcelTest5.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/PandasTables.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/PandasTables.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/MultipleOutputs.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/MultipleOutputs.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/NestedMarkdown1.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/NestedMarkdown1.ipynb.xlsx -------------------------------------------------------------------------------- /tests/files/MarkdownReprDisplay.ipynb.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ideonate/nb2xls/HEAD/tests/files/MarkdownReprDisplay.ipynb.xlsx -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | nbconvert>=5.0.0 2 | xlsxwriter>=1.1.0 3 | beautifulsoup4>=4.6.0 4 | pypng>=0.0.18 5 | mistune>=0.8 6 | pandas 7 | numpy 8 | -------------------------------------------------------------------------------- /nb2xls/__init__.py: -------------------------------------------------------------------------------- 1 | from .exporter import XLSExporter 2 | from .__meta__ import __version__ 3 | 4 | __all__ = ['XLSExporter', '__version__'] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.ipynb_checkpoints/ 3 | dist/ 4 | build/ 5 | *.py[cod] 6 | *.egg-info/ 7 | 8 | .vs_code/ 9 | 10 | node_modules/ 11 | 12 | static/ 13 | 14 | .DS_Store 15 | 16 | _build 17 | 18 | *.old* 19 | 20 | **/package-lock.json 21 | 22 | nb/*.html 23 | nb/*.sh 24 | nb/*.js 25 | 26 | .idea 27 | 28 | Examples/*.xlsx 29 | 30 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | - To release a new version of nb2xls on PyPI: 2 | 3 | Update __meta__.py (set release version, remove 'dev') 4 | 5 | Add version to changelog in README.md 6 | 7 | git add the __meta__.py file and git commit 8 | 9 | `git tag -a X.X.X -m 'comment'` 10 | 11 | `git push` 12 | 13 | `git push --tags` 14 | 15 | This should be built and pushed to PyPI automatically through travis-ci 16 | -------------------------------------------------------------------------------- /nb2xls/__meta__.py: -------------------------------------------------------------------------------- 1 | 2 | def _get_version(version_info): 3 | dic = {'alpha': 'a', 4 | 'beta': 'b', 5 | 'candidate': 'rc', 6 | 'dev': 'dev', 7 | 'final': ''} 8 | vi = version_info 9 | specifier = '' if vi[3] == 'final' else dic[vi[3]] + str(vi[4]) 10 | version = '%s.%s.%s%s' % (vi[0], vi[1], vi[2], specifier) 11 | return version 12 | 13 | 14 | # meta data - change alpha/dev to final for release 15 | 16 | version_info = (0, 1, 6, 'final', 0) 17 | __version__ = _get_version(version_info) 18 | 19 | -------------------------------------------------------------------------------- /run_nbconvert_to_xls.py: -------------------------------------------------------------------------------- 1 | ## Run an example nbconvert through Python directly 2 | # This is to make debugging easier 3 | 4 | import nbformat 5 | 6 | fn = "./Examples/ExcelTest.ipynb" 7 | 8 | with open(fn, "rt") as f: 9 | jsontext = f.read() 10 | 11 | json_nb = nbformat.reads(jsontext, as_version=4) 12 | 13 | from nb2xls import XLSExporter 14 | 15 | xlsexporter = XLSExporter() 16 | 17 | xlsexporter.ignore_markdown_errors = False 18 | 19 | body,resources = xlsexporter.from_notebook_node(json_nb) 20 | 21 | 22 | with open(fn+'.xlsx', "wb") as f: 23 | f.write(body) 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '3.5' 4 | - '3.6' 5 | install: 6 | - pip install -e .[test] 7 | script: 8 | - pytest 9 | deploy: 10 | provider: pypi 11 | user: ideonate 12 | password: 13 | secure: VPyYzGc+PNl4Khqd2K27JdKi+F0Skg9OOQd3VSMIGkPhxU1vazB/tvhOdyLiQKAJye4WxwRY90fNJrc4Bfl5/lFCSBQOmFlDHtf1WeaMTUr1dlWjgz7LM7Tqc7chVnLh2U0W7UJS7/bSMYE6pZle/TrFGF1YxwM+UetfPUlV72XA95Z3d+rP2IwARr7tk+xlDFJ1qNYHkM0QNJ9ZbHA335doHthExTRfPxthm1HzDvP/8CPay/6qis0xFjoN2tpEgVz1eaTdDR/gixGHjapmuyxYEzlSUtcLlneVUxr6Zt3ariGI/L8E8aRSJey/052Ukk5E6fLxleLF2GCpz6sww9DrY6n6JF5SnPgjTcC++cMSsFcrotGhH6XAI8v77JS0VEo499QmLu+XGKPxZhAXNDxMcFkpUY50cqIeSEjL5qSe7KCWgwGAZNmaDsJBeiZPLH0dASAAFkyk28Bc9ZhDwov1+pD2YYtixlChRlWCO91xXgigH8aGsMX7XkCxcvd85P8ZzUR6vRCi/s2VoLyN8LfmRO7/S62HTTqMC19ShrpoZgD8XaIzi2o8nzFfgtENV0k4udOSeUXuZZ5m/Awfi/yEpTGKs13u1bj8Uo9NmxA5CKLCV5IfJFgua2xmBdcWYbI6mI60E5Bw08QBkxJf8KuW41zlt7VzOjzjixaOXnE= 14 | on: 15 | tags: true 16 | skip_existing: true 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ideonate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Examples/ExcelTest5.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Markdown Top Title\n", 8 | "\n", 9 | "## Second heading With **some** text\n", 10 | "\n", 11 | "More __text__ is ***here***.\n", 12 | "\n", 13 | "This one [has a link](http://google.com/) somewhere.\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "# With a list\n", 21 | "\n", 22 | "- Item 1\n", 23 | "- Item 2\n", 24 | "- Item 3\n", 25 | "\n", 26 | "No more" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [] 35 | } 36 | ], 37 | "metadata": { 38 | "kernelspec": { 39 | "display_name": "Python 3", 40 | "language": "python", 41 | "name": "python3" 42 | }, 43 | "language_info": { 44 | "codemirror_mode": { 45 | "name": "ipython", 46 | "version": 3 47 | }, 48 | "file_extension": ".py", 49 | "mimetype": "text/x-python", 50 | "name": "python", 51 | "nbconvert_exporter": "python", 52 | "pygments_lexer": "ipython3", 53 | "version": "3.7.3" 54 | } 55 | }, 56 | "nbformat": 4, 57 | "nbformat_minor": 2 58 | } 59 | -------------------------------------------------------------------------------- /tests/files/ExcelTest5.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Markdown Top Title\n", 8 | "\n", 9 | "## Second heading With **some** text\n", 10 | "\n", 11 | "More __text__ is ***here***.\n", 12 | "\n", 13 | "This one [has a link](http://google.com/) somewhere.\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "# With a list\n", 21 | "\n", 22 | "- Item 1\n", 23 | "- Item 2\n", 24 | "- Item 3\n", 25 | "\n", 26 | "No more" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [] 35 | } 36 | ], 37 | "metadata": { 38 | "kernelspec": { 39 | "display_name": "Python 3", 40 | "language": "python", 41 | "name": "python3" 42 | }, 43 | "language_info": { 44 | "codemirror_mode": { 45 | "name": "ipython", 46 | "version": 3 47 | }, 48 | "file_extension": ".py", 49 | "mimetype": "text/x-python", 50 | "name": "python", 51 | "nbconvert_exporter": "python", 52 | "pygments_lexer": "ipython3", 53 | "version": "3.7.3" 54 | } 55 | }, 56 | "nbformat": 4, 57 | "nbformat_minor": 2 58 | } 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Ideonate / ContainDS Projects 2 | ============================================= 3 | 4 | We welcome contributions to our [open source projects on Github](https://github.com/ideonate). 5 | 6 | Issues 7 | ------ 8 | 9 | Feel free to submit issues and enhancement requests via GitHub Issues. 10 | 11 | Please also [get in touch](https://cdsdashboards.readthedocs.io/en/stable/chapters/contact.html) with any questions or feedback, or for help in working with the software. These 12 | actions are valuable contributions in their own right. 13 | 14 | Contributing 15 | ------------ 16 | 17 | In general, the projects follow the "fork and pull request" Git workflow. 18 | 19 | 1. **Fork** the repo on GitHub 20 | 2. **Clone** the project to your own machine 21 | 3. **Commit** changes to your own branch 22 | 4. **Push** your work back up to your fork 23 | 5. Submit a **Pull request** so that we can review your changes 24 | 25 | NB: Make sure you merge the latest from "upstream" before making a pull request! 26 | 27 | Copyright and Licensing 28 | ----------------------- 29 | 30 | Most Ideonate open source projects are licensed under the BSD 3-Clause License, Apache 2.0 license, or MIT license. 31 | 32 | Contributions to the Git repos will be assumed to be on the same copyright basis as existing files in that repo. 33 | -------------------------------------------------------------------------------- /nb2xls/mdxlsstyles.py: -------------------------------------------------------------------------------- 1 | class MdXlsStyleRegistry(object): 2 | 3 | default_formats = { 4 | 'double_emphasis': {'bold': True}, 5 | 'emphasis': {'italic': True}, 6 | 'strikethrough': {'font_strikeout': True}, 7 | 'codespan': {'font_name': 'Courier'}, 8 | 'h1': {'font_size': 30}, 9 | 'h2': {'font_size': 25}, 10 | 'h3': {'font_size': 20}, 11 | 'h4': {'font_size': 15}, 12 | 'h5': {'font_size': 14}, 13 | 'h6': {'font_size': 13}, 14 | } 15 | 16 | def __init__(self, workbook): 17 | self.workbook = workbook 18 | self.stylereg = {} 19 | 20 | def use_style(self, mdnames): 21 | 22 | if not isinstance(mdnames, list): 23 | mdnames = [mdnames] 24 | 25 | mdname = '-'.join(mdnames) 26 | 27 | if not mdname in self.stylereg: 28 | 29 | style = self._create_style(mdnames) 30 | 31 | self.stylereg[mdname] = style 32 | 33 | return self.stylereg[mdname] 34 | 35 | def _create_style(self, mdnames): 36 | 37 | d = {} 38 | 39 | for submdname in mdnames: 40 | 41 | if submdname in self.default_formats: 42 | d = {**d, **self.default_formats[submdname]} 43 | 44 | if len(d) > 0: 45 | return self.workbook.add_format(d) 46 | 47 | return '' 48 | -------------------------------------------------------------------------------- /Examples/ExcelTest.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Markdown cell Title\n", 8 | "\n", 9 | "With some text" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Test\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "print(\"Test\")" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "Now go to File > Download As, and select 'Excel Spreadsheet (.xlsx)' to see the above output cell in a spreadsheet." 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [] 42 | } 43 | ], 44 | "metadata": { 45 | "kernelspec": { 46 | "display_name": "Python 3", 47 | "language": "python", 48 | "name": "python3" 49 | }, 50 | "language_info": { 51 | "codemirror_mode": { 52 | "name": "ipython", 53 | "version": 3 54 | }, 55 | "file_extension": ".py", 56 | "mimetype": "text/x-python", 57 | "name": "python", 58 | "nbconvert_exporter": "python", 59 | "pygments_lexer": "ipython3", 60 | "version": "3.7.3" 61 | } 62 | }, 63 | "nbformat": 4, 64 | "nbformat_minor": 2 65 | } 66 | -------------------------------------------------------------------------------- /tests/files/ExcelTest.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Markdown cell Title\n", 8 | "\n", 9 | "With some text" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Test\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "print(\"Test\")" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "Now go to File > Download As, and select 'Excel Spreadsheet (.xlsx)' to see the above output cell in a spreadsheet." 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [] 42 | } 43 | ], 44 | "metadata": { 45 | "kernelspec": { 46 | "display_name": "Python 3", 47 | "language": "python", 48 | "name": "python3" 49 | }, 50 | "language_info": { 51 | "codemirror_mode": { 52 | "name": "ipython", 53 | "version": 3 54 | }, 55 | "file_extension": ".py", 56 | "mimetype": "text/x-python", 57 | "name": "python", 58 | "nbconvert_exporter": "python", 59 | "pygments_lexer": "ipython3", 60 | "version": "3.7.3" 61 | } 62 | }, 63 | "nbformat": 4, 64 | "nbformat_minor": 2 65 | } 66 | -------------------------------------------------------------------------------- /Examples/NestedMarkdown1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# More markdown\n", 8 | "\n", 9 | "## Here is an ordered list\n", 10 | "\n", 11 | "An ordered list example:\n", 12 | "\n", 13 | "1. Here is *one*\n", 14 | " 1. Sub __one__\n", 15 | " 1. Sub _Two_ end\n", 16 | "2. Here is two\n", 17 | "3. Here is three\n" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "- [9.2.4 - Example: Top level](#8nowhere)\n", 25 | " - Here is [example sub item link](#Shrinkage)\n", 26 | " - Another sub item\n", 27 | "- [9.5.1 - Example: List link](#9nowhere) " 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [] 36 | } 37 | ], 38 | "metadata": { 39 | "hide_input": false, 40 | "kernelspec": { 41 | "display_name": "Python 3", 42 | "language": "python", 43 | "name": "python3" 44 | }, 45 | "language_info": { 46 | "codemirror_mode": { 47 | "name": "ipython", 48 | "version": 3 49 | }, 50 | "file_extension": ".py", 51 | "mimetype": "text/x-python", 52 | "name": "python", 53 | "nbconvert_exporter": "python", 54 | "pygments_lexer": "ipython3", 55 | "version": "3.7.3" 56 | } 57 | }, 58 | "nbformat": 4, 59 | "nbformat_minor": 1 60 | } 61 | -------------------------------------------------------------------------------- /tests/files/NestedMarkdown1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# More markdown\n", 8 | "\n", 9 | "## Here is an ordered list\n", 10 | "\n", 11 | "An ordered list example:\n", 12 | "\n", 13 | "1. Here is *one*\n", 14 | " 1. Sub __one__\n", 15 | " 1. Sub _Two_ end\n", 16 | "2. Here is two\n", 17 | "3. Here is three\n" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "- [9.2.4 - Example: Top level](#8nowhere)\n", 25 | " - Here is [example sub item link](#Shrinkage)\n", 26 | " - Another sub item\n", 27 | "- [9.5.1 - Example: List link](#9nowhere) " 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [] 36 | } 37 | ], 38 | "metadata": { 39 | "hide_input": false, 40 | "kernelspec": { 41 | "display_name": "Python 3", 42 | "language": "python", 43 | "name": "python3" 44 | }, 45 | "language_info": { 46 | "codemirror_mode": { 47 | "name": "ipython", 48 | "version": 3 49 | }, 50 | "file_extension": ".py", 51 | "mimetype": "text/x-python", 52 | "name": "python", 53 | "nbconvert_exporter": "python", 54 | "pygments_lexer": "ipython3", 55 | "version": "3.7.3" 56 | } 57 | }, 58 | "nbformat": 4, 59 | "nbformat_minor": 1 60 | } 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.util import convert_path 2 | from setuptools import setup, find_packages 3 | 4 | module = 'nb2xls' 5 | 6 | # get version from __meta__ 7 | meta_ns = {} 8 | path = convert_path(module+'/__meta__.py') 9 | with open(path) as meta_file: 10 | exec(meta_file.read(), meta_ns) 11 | 12 | # read requirements.txt 13 | with open('requirements.txt', 'r') as f: 14 | content = f.read() 15 | li_req = content.split('\n') 16 | install_requires = [e.strip() for e in li_req if len(e)] 17 | 18 | 19 | name = module 20 | name_url = name.replace('_', '-') 21 | 22 | packages = [module] 23 | version = meta_ns['__version__'] 24 | description = 'Export Jupyter notebook as an Excel xls file.' 25 | long_description = 'Export Jupyter notebook as an Excel xls file.' 26 | author = 'ideonate' 27 | author_email = 'dan@ideonate.com' 28 | # github template 29 | url = 'https://github.com/{}/{}'.format(author, 30 | name_url) 31 | download_url = 'https://github.com/{}/{}/tarball/{}'.format(author, 32 | name_url, 33 | version) 34 | keywords = ['jupyter', 35 | 'nbconvert', 36 | ] 37 | license = 'MIT' 38 | classifiers = ['Development Status :: 4 - Beta', 39 | 'License :: OSI Approved :: MIT License', 40 | 'Programming Language :: Python :: 3.5', 41 | 'Programming Language :: Python :: 3.6', 42 | 'Programming Language :: Python :: 3.7' 43 | ] 44 | include_package_data = True 45 | 46 | zip_safe = False 47 | 48 | extra_requirements = { 49 | 'test': ['pytest', 'testpath', 'openpyxl', 'matplotlib'] 50 | } 51 | 52 | # ref https://packaging.python.org/tutorials/distributing-packages/ 53 | setup( 54 | name=name, 55 | version=version, 56 | packages=packages, 57 | author=author, 58 | author_email=author_email, 59 | description=description, 60 | long_description=long_description, 61 | url=url, 62 | download_url=download_url, 63 | keywords=keywords, 64 | license=license, 65 | classifiers=classifiers, 66 | include_package_data=include_package_data, 67 | install_requires=install_requires, 68 | extras_require=extra_requirements, 69 | zip_safe=zip_safe, 70 | 71 | entry_points = { 72 | 'nbconvert.exporters': [ 73 | 'xls = nb2xls:XLSExporter' 74 | ], 75 | } 76 | ) 77 | 78 | -------------------------------------------------------------------------------- /tests/test_nb2xls.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | 4 | from testpath.tempdir import TemporaryWorkingDirectory 5 | from nb2xls.exporter import XLSExporter 6 | 7 | # This should be discoverable by pytest only 8 | from localxlsxdiff.compare import diff 9 | 10 | 11 | class LocalExportersTestsBase(object): 12 | 13 | """ 14 | Some functions taken from nbconvert.exporters.tests.base 15 | """ 16 | 17 | def _get_notebook(self, nb_name='notebook.ipynb'): 18 | return os.path.join(self._get_files_path(), nb_name) 19 | 20 | def _get_files_path(self): 21 | 22 | #Get the relative path to this module in the IPython directory. 23 | names = self.__module__.split('.')[1:-1] 24 | names.append('files') 25 | 26 | #Build a path using this directory and the relative path we just 27 | #found. 28 | path = os.path.dirname(__file__) 29 | return os.path.join(path, *names) 30 | 31 | def create_temp_cwd(self, copy_filenames=None): 32 | return TemporaryWorkingDirectory() 33 | 34 | 35 | class TestsExcelExporter(LocalExportersTestsBase): 36 | 37 | exporter_class = XLSExporter 38 | 39 | def test_constructor(self): 40 | """ 41 | Can a XLSExporter be constructed? 42 | """ 43 | xls = XLSExporter() 44 | assert xls.export_from_notebook == "Excel Spreadsheet" 45 | assert xls._file_extension_default() == '.xlsx' 46 | 47 | def test_export_basic(self): 48 | """ 49 | Can a XLSExporter export? 50 | """ 51 | (output, resources) = XLSExporter().from_filename(self._get_notebook('ExcelTest5.ipynb')) 52 | assert len(output) > 0 53 | 54 | @pytest.mark.parametrize("ipynb_filename,expected_size", 55 | [ 56 | ("ExcelTest4.ipynb", 42193), 57 | ("NestedMarkdown1.ipynb", 6225), 58 | ("ExcelTest.ipynb", 5455), 59 | ("PandasNA.ipynb", 6038), 60 | ("MarkdownReprDisplay.ipynb", 5574), 61 | ("MultipleOutputs.ipynb", 6630), 62 | ("PandasTables.ipynb", 7711), 63 | ]) 64 | def test_export_compare(self, ipynb_filename, expected_size): 65 | """ 66 | Does a XLSExporter export the same thing as before? 67 | """ 68 | (other, resources) = XLSExporter().from_filename(self._get_notebook(ipynb_filename)) 69 | 70 | if expected_size != -1: 71 | # Check file size is about right 72 | len_other = len(other) 73 | assert len_other >= expected_size-10 and len_other <= expected_size+10 74 | 75 | with self.create_temp_cwd() as temp_cwd: 76 | 77 | other_fn = os.path.join(temp_cwd, ipynb_filename+'.xlsx') 78 | with open(other_fn, "wb") as f: 79 | f.write(other) 80 | 81 | ref_fn = self._get_notebook(ipynb_filename)+'.xlsx' 82 | 83 | wb_diff, sheet_diffs = diff(ref_fn, other_fn) 84 | 85 | assert len(wb_diff) == 0 86 | 87 | assert len(sheet_diffs) == 0 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nb2xls - Jupyter notebooks to Excel Spreadsheets 2 | 3 | Convert Jupyter notebooks to Excel Spreadsheets (xlsx), through a new 'Download As' option or via nbconvert on the 4 | command line. 5 | 6 | Respects tables such as Pandas DataFrames. Also exports image data such as matplotlib output. 7 | 8 | Markdown is supported where possible (some elements still need work). 9 | 10 | Input (code) cells are not included in the spreadsheet. 11 | 12 | This allows you to share your results with non-programmers such that they can still easily play with the data. 13 | 14 | ![Screenshot of Jupyter Notebook exported to Excel spreadsheet](screenshots/Jupyter2Excel.png) 15 | 16 | Please note this is an ALPHA version. Some notebook features may be lost. Please send example ipynb files to me along with 17 | reports of any problems. 18 | 19 | Try it out online through Binder: 20 | 21 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/ideonate/nb2xls/master) 22 | 23 | ## Installation 24 | 25 | Install via pip (recommended) 26 | 27 | ``` 28 | pip install nb2xls 29 | ``` 30 | 31 | Restart Jupyter to pick up the new 'Excel Spreadsheet (.xlsx)' option under 'Download As' in the File menu. 32 | 33 | ## Usage 34 | 35 | In Jupyter Notebook, just select the 'Excel Spreadsheet (.xlsx)' option under 'Download As' in the File menu. 36 | 37 | To run from the command line try: 38 | 39 | ``` 40 | jupyter nbconvert --to xls Examples/ExcelTest.ipynb 41 | ``` 42 | 43 | or 44 | 45 | ``` 46 | jupyter nbconvert --to nb2xls.XLSExporter Examples/ExcelTest.ipynb 47 | ``` 48 | 49 | This should output ExcelTest.xlsx in the same folder as the ipynb file specified. 50 | 51 | ## Development Installation 52 | 53 | If you want to contribute or debug: 54 | 55 | ``` 56 | git clone https://github.com/ideonate/nb2xls 57 | cd nb2xls 58 | pip install -e . 59 | ``` 60 | 61 | To run tests, you will need to install some extra dependencies. Run: 62 | ``` 63 | pip install -e .[test] 64 | ``` 65 | 66 | Then run: 67 | ``` 68 | pytest 69 | ``` 70 | 71 | ## Requirements 72 | 73 | nb2xls requires Python 3 and is tested against recent versions of jupyter and nbconvert. Please let me know if you 74 | find incompatibilities 75 | 76 | ## Contact for Feedback 77 | 78 | Please get in touch with any feedback or questions: [dan@ideonate.com](dan@ideonate.com). It is very helpful to send 79 | example notebooks, especially if you have bug reports or feature suggestions. 80 | 81 | ## License 82 | 83 | This code is released under an MIT license. 84 | 85 | ## Change Log 86 | 87 | 0.1.6 (5 Aug 2019) 88 | - Better layout and formatting of Pandas tables 89 | 90 | 0.1.5 (1 Aug 2019) 91 | - Displays multiple outputs in display_data and execute_data output types 92 | 93 | 0.1.4 (26 Jul 2019) 94 | - Better handling of mimetypes application/json and text/markdown 95 | 96 | 0.1.3 (24 Jul 2019) 97 | - Working with Pandas/NumPy NaN values 98 | 99 | 0.1.2 (11 Jul 2019) 100 | - Minor changes, mainly to deployment mechanism 101 | 102 | 0.1.1 (10 Jul 2019) 103 | - Displays images over multiple rows for better scrolling 104 | - Better markdown parsing especially for nested lists 105 | 106 | 0.0.1 (14 Jun 2019) 107 | - Initial release 108 | -------------------------------------------------------------------------------- /Examples/MarkdownReprDisplay.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/markdown": [ 11 | "\n", 12 | "# Hi Dan\n", 13 | "1. Thanks for fixing the last issue so quickly\n", 14 | "1. This library is really cool\n", 15 | "1. I'll probably start using it for work soon\n" 16 | ], 17 | "text/plain": [ 18 | "" 19 | ] 20 | }, 21 | "metadata": {}, 22 | "output_type": "display_data" 23 | } 24 | ], 25 | "source": [ 26 | "from IPython.display import Markdown, display\n", 27 | "\n", 28 | "display(Markdown('''\n", 29 | "# Hi Dan\n", 30 | "1. Thanks for fixing the last issue so quickly\n", 31 | "1. This library is really cool\n", 32 | "1. I'll probably start using it for work soon\n", 33 | "'''))" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "text/html": [ 44 | "\n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | "
User: mynbiqpmzj
Password: plsgqejeydtzirwztejd
\n", 49 | " " 50 | ], 51 | "text/plain": [ 52 | "<__main__.RandomUser at 0x105050588>" 53 | ] 54 | }, 55 | "execution_count": 2, 56 | "metadata": {}, 57 | "output_type": "execute_result" 58 | } 59 | ], 60 | "source": [ 61 | "from random import choice, seed\n", 62 | "from string import ascii_lowercase\n", 63 | "\n", 64 | "seed(0)\n", 65 | "\n", 66 | "def _random_str(length: int):\n", 67 | " return ''.join(choice(ascii_lowercase) for _ in range(length))\n", 68 | "\n", 69 | "class RandomUser:\n", 70 | " def __init__(self):\n", 71 | " self.user = _random_str(10)\n", 72 | " self.password = _random_str(20)\n", 73 | " \n", 74 | " def _repr_html_(self):\n", 75 | " return f'''\n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | "
User: {self.user}
Password: {self.password}
\n", 80 | " '''\n", 81 | " \n", 82 | "RandomUser()" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "metadata": {}, 89 | "outputs": [ 90 | { 91 | "data": { 92 | "application/json": { 93 | "hello": "world" 94 | }, 95 | "text/plain": [ 96 | "{'hello': 'world'}" 97 | ] 98 | }, 99 | "execution_count": 3, 100 | "metadata": {}, 101 | "output_type": "execute_result" 102 | } 103 | ], 104 | "source": [ 105 | "import json\n", 106 | "\n", 107 | "class DisplayDict(dict):\n", 108 | " def _repr_json_(self):\n", 109 | " return self\n", 110 | " \n", 111 | "DisplayDict({'hello': 'world'})" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [] 120 | } 121 | ], 122 | "metadata": { 123 | "kernelspec": { 124 | "display_name": "Python 3", 125 | "language": "python", 126 | "name": "python3" 127 | }, 128 | "language_info": { 129 | "codemirror_mode": { 130 | "name": "ipython", 131 | "version": 3 132 | }, 133 | "file_extension": ".py", 134 | "mimetype": "text/x-python", 135 | "name": "python", 136 | "nbconvert_exporter": "python", 137 | "pygments_lexer": "ipython3", 138 | "version": "3.7.3" 139 | } 140 | }, 141 | "nbformat": 4, 142 | "nbformat_minor": 2 143 | } 144 | -------------------------------------------------------------------------------- /tests/files/MarkdownReprDisplay.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/markdown": [ 11 | "\n", 12 | "# Hi Dan\n", 13 | "1. Thanks for fixing the last issue so quickly\n", 14 | "1. This library is really cool\n", 15 | "1. I'll probably start using it for work soon\n" 16 | ], 17 | "text/plain": [ 18 | "" 19 | ] 20 | }, 21 | "metadata": {}, 22 | "output_type": "display_data" 23 | } 24 | ], 25 | "source": [ 26 | "from IPython.display import Markdown, display\n", 27 | "\n", 28 | "display(Markdown('''\n", 29 | "# Hi Dan\n", 30 | "1. Thanks for fixing the last issue so quickly\n", 31 | "1. This library is really cool\n", 32 | "1. I'll probably start using it for work soon\n", 33 | "'''))" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "text/html": [ 44 | "\n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | "
User: mynbiqpmzj
Password: plsgqejeydtzirwztejd
\n", 49 | " " 50 | ], 51 | "text/plain": [ 52 | "<__main__.RandomUser at 0x105050588>" 53 | ] 54 | }, 55 | "execution_count": 2, 56 | "metadata": {}, 57 | "output_type": "execute_result" 58 | } 59 | ], 60 | "source": [ 61 | "from random import choice, seed\n", 62 | "from string import ascii_lowercase\n", 63 | "\n", 64 | "seed(0)\n", 65 | "\n", 66 | "def _random_str(length: int):\n", 67 | " return ''.join(choice(ascii_lowercase) for _ in range(length))\n", 68 | "\n", 69 | "class RandomUser:\n", 70 | " def __init__(self):\n", 71 | " self.user = _random_str(10)\n", 72 | " self.password = _random_str(20)\n", 73 | " \n", 74 | " def _repr_html_(self):\n", 75 | " return f'''\n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | "
User: {self.user}
Password: {self.password}
\n", 80 | " '''\n", 81 | " \n", 82 | "RandomUser()" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "metadata": {}, 89 | "outputs": [ 90 | { 91 | "data": { 92 | "application/json": { 93 | "hello": "world" 94 | }, 95 | "text/plain": [ 96 | "{'hello': 'world'}" 97 | ] 98 | }, 99 | "execution_count": 3, 100 | "metadata": {}, 101 | "output_type": "execute_result" 102 | } 103 | ], 104 | "source": [ 105 | "import json\n", 106 | "\n", 107 | "class DisplayDict(dict):\n", 108 | " def _repr_json_(self):\n", 109 | " return self\n", 110 | " \n", 111 | "DisplayDict({'hello': 'world'})" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [] 120 | } 121 | ], 122 | "metadata": { 123 | "kernelspec": { 124 | "display_name": "Python 3", 125 | "language": "python", 126 | "name": "python3" 127 | }, 128 | "language_info": { 129 | "codemirror_mode": { 130 | "name": "ipython", 131 | "version": 3 132 | }, 133 | "file_extension": ".py", 134 | "mimetype": "text/x-python", 135 | "name": "python", 136 | "nbconvert_exporter": "python", 137 | "pygments_lexer": "ipython3", 138 | "version": "3.7.3" 139 | } 140 | }, 141 | "nbformat": 4, 142 | "nbformat_minor": 2 143 | } 144 | -------------------------------------------------------------------------------- /Examples/PandasNA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pandas DataFrame with N/As\n", 8 | "\n", 9 | "Solving GitHub [issue number 3](https://github.com/ideonate/nb2xls/issues/3)" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import pandas as pd" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/html": [ 29 | "
\n", 30 | "\n", 43 | "\n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | "
0
0NaN
\n", 57 | "
" 58 | ], 59 | "text/plain": [ 60 | " 0\n", 61 | "0 NaN" 62 | ] 63 | }, 64 | "execution_count": 2, 65 | "metadata": {}, 66 | "output_type": "execute_result" 67 | } 68 | ], 69 | "source": [ 70 | "pd.DataFrame([[pd.np.nan]])" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "metadata": {}, 77 | "outputs": [ 78 | { 79 | "data": { 80 | "text/html": [ 81 | "
\n", 82 | "\n", 95 | "\n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | "
col1
01.0
1NaN
22.5
3NaN
\n", 121 | "
" 122 | ], 123 | "text/plain": [ 124 | " col1\n", 125 | "0 1.0\n", 126 | "1 NaN\n", 127 | "2 2.5\n", 128 | "3 NaN" 129 | ] 130 | }, 131 | "execution_count": 3, 132 | "metadata": {}, 133 | "output_type": "execute_result" 134 | } 135 | ], 136 | "source": [ 137 | "pd.DataFrame({'col1': [1.0, pd.np.nan, 2.5, float('nan')]})" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [] 146 | } 147 | ], 148 | "metadata": { 149 | "hide_input": false, 150 | "kernelspec": { 151 | "display_name": "Python 3", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.7.3" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /tests/files/PandasNA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pandas DataFrame with N/As\n", 8 | "\n", 9 | "Solving GitHub [issue number 3](https://github.com/ideonate/nb2xls/issues/3)" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import pandas as pd" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/html": [ 29 | "
\n", 30 | "\n", 43 | "\n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | "
0
0NaN
\n", 57 | "
" 58 | ], 59 | "text/plain": [ 60 | " 0\n", 61 | "0 NaN" 62 | ] 63 | }, 64 | "execution_count": 2, 65 | "metadata": {}, 66 | "output_type": "execute_result" 67 | } 68 | ], 69 | "source": [ 70 | "pd.DataFrame([[pd.np.nan]])" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "metadata": {}, 77 | "outputs": [ 78 | { 79 | "data": { 80 | "text/html": [ 81 | "
\n", 82 | "\n", 95 | "\n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | "
col1
01.0
1NaN
22.5
3NaN
\n", 121 | "
" 122 | ], 123 | "text/plain": [ 124 | " col1\n", 125 | "0 1.0\n", 126 | "1 NaN\n", 127 | "2 2.5\n", 128 | "3 NaN" 129 | ] 130 | }, 131 | "execution_count": 3, 132 | "metadata": {}, 133 | "output_type": "execute_result" 134 | } 135 | ], 136 | "source": [ 137 | "pd.DataFrame({'col1': [1.0, pd.np.nan, 2.5, float('nan')]})" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [] 146 | } 147 | ], 148 | "metadata": { 149 | "hide_input": false, 150 | "kernelspec": { 151 | "display_name": "Python 3", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.7.3" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /tests/localxlsxdiff/compare.py: -------------------------------------------------------------------------------- 1 | # Based almost entirely on https://bitbucket.org/adimian/xlsx-diff/src/default/ 2 | # License: MIT License (MIT/Expat) 3 | # Author: Eric Gazoni (eric.gazoni@adimian.com) 4 | 5 | import openpyxl 6 | 7 | 8 | try: 9 | # Python 3 10 | from itertools import zip_longest 11 | except ImportError: 12 | from itertools import izip_longest as zip_longest 13 | 14 | 15 | class Difference(object): 16 | def __init__(self, **kwargs): 17 | for k, v in kwargs.items(): 18 | setattr(self, k, v) 19 | self.__dict__[k] = v 20 | 21 | def __repr__(self): 22 | return u'%s: {%s}' % (self.__class__.__name__, 23 | u', '.join([k + ': ' + repr(self.__dict__[k]) 24 | for k in self.__slots__])) 25 | 26 | 27 | class MissingWorksheet(Difference): 28 | __slots__ = ('worksheet', 'missing_in') 29 | 30 | 31 | class MissingCell(Difference): 32 | __slots__ = ('worksheet', 'coordinate', 'missing_in') 33 | 34 | 35 | class CellDifference(Difference): 36 | __slots__ = ('worksheet', 'coordinate', 'kind', 'expected', 'found') 37 | 38 | 39 | def diff(ref, other, ignores=(), precision=None, typeless=False): 40 | ''' compare a reference workbook with another workbook 41 | @param ignores: list of ignore classes to ignore 42 | @param precision: number of decimal digits to keep when comparing floats 43 | @param typeless: whether we consider number-looking strings as number or not 44 | 45 | @return: (workbook-level differences, dict with per-sheet differences) 46 | ''' 47 | 48 | kwargs = dict(read_only=True, keep_vba=False, data_only=True) 49 | 50 | if precision is not None: 51 | if precision < 0: 52 | raise ValueError('negative precision is meaningless') 53 | else: 54 | precision = 10 ** (-precision) 55 | 56 | wb1 = openpyxl.load_workbook(ref, **kwargs) 57 | wb2 = openpyxl.load_workbook(other, **kwargs) 58 | 59 | wb1_sheets = set(wb1.sheetnames) 60 | wb2_sheets = set(wb2.sheetnames) 61 | 62 | wb_differences = [] 63 | if not MissingWorksheet in ignores: 64 | for sh in (wb1_sheets - wb2_sheets): 65 | wb_differences.append(MissingWorksheet(worksheet=sh, 66 | missing_in='reference')) 67 | 68 | for sh in (wb2_sheets - wb1_sheets): 69 | wb_differences.append(MissingWorksheet(worksheet=sh, 70 | missing_in='other')) 71 | 72 | sheet_differences = {} 73 | for sh in (wb1_sheets & wb2_sheets): 74 | changes = sheet_changes(wb1, wb2, sh, ignores, precision, typeless) 75 | if changes: 76 | sheet_differences[sh] = changes 77 | 78 | return wb_differences, sheet_differences 79 | 80 | 81 | def sheet_changes(wb1, wb2, sheet_name, ignores, precision, typeless): 82 | sheet1 = wb1[sheet_name] 83 | sheet2 = wb2[sheet_name] 84 | 85 | diffs = [] 86 | miss = MissingCell not in ignores 87 | diff = CellDifference not in ignores 88 | for r1, r2 in zip_longest(sheet1.rows, sheet2.rows): 89 | if r1 is None: 90 | if miss: 91 | for cell in r2: 92 | if not empty(cell): 93 | diffs.append(MissingCell(worksheet=sheet_name, 94 | coordinate=cell.coordinate, 95 | missing_in='reference')) 96 | elif r2 is None: 97 | if miss: 98 | for cell in r1: 99 | if not empty(cell): 100 | diffs.append(MissingCell(worksheet=sheet_name, 101 | coordinate=cell.coordinate, 102 | missing_in='other')) 103 | else: 104 | for c1, c2 in zip_longest(r1, r2): 105 | if empty(c1) and empty(c2): 106 | continue 107 | elif empty(c1) and not empty(c2): 108 | if miss: 109 | diffs.append(MissingCell(worksheet=sheet_name, 110 | coordinate=c2.coordinate, 111 | missing_in='reference')) 112 | elif empty(c2) and not empty(c1): 113 | if miss: 114 | diffs.append(MissingCell(worksheet=sheet_name, 115 | coordinate=c1.coordinate, 116 | missing_in='other')) 117 | elif diff: 118 | if c1.value != c2.value: 119 | v1 = c1.value 120 | v2 = c2.value 121 | if typeless: 122 | try: 123 | v1, v2 = float(v1), float(v2) 124 | except: 125 | pass 126 | if isinstance(v1, float) and isinstance(v2, float): 127 | if precision is not None: 128 | if abs(v1 - v2) < precision: 129 | continue 130 | else: 131 | if v1 == v2: 132 | continue 133 | diffs.append(CellDifference(worksheet=sheet_name, 134 | coordinate=c1.coordinate, 135 | kind='value', 136 | expected=v1, 137 | found=v2)) 138 | diffs.sort(key=klass) 139 | return diffs 140 | 141 | 142 | def empty(cell): 143 | if cell and not cell.value is None: 144 | return False 145 | return True 146 | 147 | 148 | def klass(x): 149 | return x.__class__.__name__ 150 | -------------------------------------------------------------------------------- /Examples/ExcelTest2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Markdown cell Title\n", 8 | "\n", 9 | "With **some** text" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Test\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "print(\"Test\")" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "data": { 36 | "text/html": [ 37 | "
\n", 38 | "\n", 51 | "\n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | "
ABCD
2013-01-011.7640520.4001570.9787382.240893
2013-01-021.867558-0.9772780.950088-0.151357
2013-01-03-0.1032190.4105990.1440441.454274
2013-01-040.7610380.1216750.4438630.333674
2013-01-051.494079-0.2051580.313068-0.854096
2013-01-06-2.5529900.6536190.864436-0.742165
\n", 106 | "
" 107 | ], 108 | "text/plain": [ 109 | " A B C D\n", 110 | "2013-01-01 1.764052 0.400157 0.978738 2.240893\n", 111 | "2013-01-02 1.867558 -0.977278 0.950088 -0.151357\n", 112 | "2013-01-03 -0.103219 0.410599 0.144044 1.454274\n", 113 | "2013-01-04 0.761038 0.121675 0.443863 0.333674\n", 114 | "2013-01-05 1.494079 -0.205158 0.313068 -0.854096\n", 115 | "2013-01-06 -2.552990 0.653619 0.864436 -0.742165" 116 | ] 117 | }, 118 | "execution_count": 2, 119 | "metadata": {}, 120 | "output_type": "execute_result" 121 | } 122 | ], 123 | "source": [ 124 | "import numpy as np\n", 125 | "import pandas as pd\n", 126 | "np.random.seed(0)\n", 127 | "\n", 128 | "dates = pd.date_range('20130101',periods=6)\n", 129 | "\n", 130 | "df = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD'))\n", 131 | "\n", 132 | "df" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 3, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',\n", 144 | " '2013-01-05', '2013-01-06'],\n", 145 | " dtype='datetime64[ns]', freq='D')" 146 | ] 147 | }, 148 | "execution_count": 3, 149 | "metadata": {}, 150 | "output_type": "execute_result" 151 | } 152 | ], 153 | "source": [ 154 | "dates" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 4, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | " A B C D\n", 167 | "2013-01-01 1.764052 0.400157 0.978738 2.240893\n", 168 | "2013-01-02 1.867558 -0.977278 0.950088 -0.151357\n", 169 | "2013-01-03 -0.103219 0.410599 0.144044 1.454274\n", 170 | "2013-01-04 0.761038 0.121675 0.443863 0.333674\n", 171 | "2013-01-05 1.494079 -0.205158 0.313068 -0.854096\n", 172 | "2013-01-06 -2.552990 0.653619 0.864436 -0.742165\n" 173 | ] 174 | } 175 | ], 176 | "source": [ 177 | "print(df)" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [] 186 | } 187 | ], 188 | "metadata": { 189 | "kernelspec": { 190 | "display_name": "Python 3", 191 | "language": "python", 192 | "name": "python3" 193 | }, 194 | "language_info": { 195 | "codemirror_mode": { 196 | "name": "ipython", 197 | "version": 3 198 | }, 199 | "file_extension": ".py", 200 | "mimetype": "text/x-python", 201 | "name": "python", 202 | "nbconvert_exporter": "python", 203 | "pygments_lexer": "ipython3", 204 | "version": "3.7.3" 205 | } 206 | }, 207 | "nbformat": 4, 208 | "nbformat_minor": 2 209 | } 210 | -------------------------------------------------------------------------------- /nb2xls/mdrenderer.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from mistune import Renderer, escape, escape_link 4 | 5 | 6 | class MdStyleInstruction(object): 7 | 8 | softnewline = False 9 | 10 | def __init__(self, mdname): 11 | self.mdname = mdname 12 | 13 | def __repr__(self): 14 | params = ['{}={}'.format(a, getattr(self,a)) for a in dir(self) if not a.startswith('__') and not callable(getattr(self,a))] 15 | return '<{} {}>'.format(type(self), ", ".join(params)) 16 | 17 | 18 | class MdStyleInstructionCell(MdStyleInstruction): 19 | pass 20 | 21 | 22 | class MdStyleInstructionText(MdStyleInstruction): 23 | pass 24 | 25 | 26 | class MdStyleInstructionLink(MdStyleInstruction): 27 | 28 | softnewline = True 29 | 30 | def __init__(self, link): 31 | super(MdStyleInstructionLink, self).__init__('link') 32 | self.link = link 33 | 34 | 35 | class MdStyleInstructionListStart(MdStyleInstruction): 36 | 37 | softnewline = True 38 | ordered = True 39 | 40 | def __init__(self, ordered): 41 | super(MdStyleInstructionListStart, self).__init__('link') 42 | self.ordered = ordered 43 | 44 | 45 | class MdStyleInstructionListEnd(MdStyleInstruction): 46 | 47 | softnewline = False 48 | 49 | def __init__(self): 50 | super(MdStyleInstructionListEnd, self).__init__('link') 51 | 52 | 53 | class MdStyleInstructionListItem(MdStyleInstruction): 54 | 55 | softnewline = True 56 | 57 | def __init__(self): 58 | super(MdStyleInstructionListItem, self).__init__('list_item') 59 | 60 | 61 | class MdStyleInstructionLineBreak(MdStyleInstruction): 62 | 63 | softnewline = True 64 | 65 | def __init__(self): 66 | super(MdStyleInstructionLineBreak, self).__init__('linebreak') 67 | 68 | 69 | class Md2XLSRenderer(Renderer): 70 | 71 | def placeholder(self): 72 | """Returns the default, empty output value for the renderer. 73 | All renderer methods use the '+=' operator to append to this value. 74 | Default is a string so rendering HTML can build up a result string with 75 | the rendered Markdown. 76 | Can be overridden by Renderer subclasses to be types like an empty 77 | list, allowing the renderer to create a tree-like structure to 78 | represent the document (which can then be reprocessed later into a 79 | separate format like docx or pdf). 80 | """ 81 | return [] 82 | 83 | ### Block-level functions 84 | 85 | def block_code(self, code, lang=None): 86 | """Rendering block level code. ``pre > code``. 87 | :param code: text content of the code block. 88 | :param lang: language of the given code. 89 | """ 90 | code = code.rstrip('\n') 91 | return [""] + code 92 | 93 | def block_quote(self, text): 94 | """Rendering
with the given text. 95 | :param text: text content of the blockquote. 96 | """ 97 | return ["
"] + text 98 | 99 | def block_html(self, html): 100 | """Rendering block level pure html content. 101 | :param html: text content of the html snippet. 102 | """ 103 | if self.options.get('skip_style') and \ 104 | html.lower().startswith('`` ``

``. 112 | :param text: rendered text content for the header. 113 | :param level: a number for the header level, for example: 1. 114 | :param raw: raw text content of the header. 115 | """ 116 | return [[MdStyleInstructionCell('h{}'.format(level))] + text] 117 | 118 | def hrule(self): 119 | """Rendering method for ``
`` tag.""" 120 | return [MdStyleInstructionCell('hrule')] 121 | 122 | def list(self, body, ordered=True): 123 | """Rendering list tags like ``
    `` and ``
      ``. 124 | :param body: body contents of the list. 125 | :param ordered: whether this list is ordered or not. 126 | """ 127 | return [[MdStyleInstructionListStart(ordered)]] + body + [[MdStyleInstructionListEnd()]] 128 | 129 | def list_item(self, text): 130 | """Rendering list item snippet. Like ``
    1. ``.""" 131 | #return [[MdStyleInstructionList(), *text]] 132 | return [[MdStyleInstructionListItem()] + text] 133 | 134 | def paragraph(self, text): 135 | """Rendering paragraph tags. Like ``

      ``.""" 136 | return [text] 137 | 138 | def table(self, header, body): 139 | """Rendering table element. Wrap header and body in it. 140 | :param header: header part of the table. 141 | :param body: body part of the table. 142 | """ 143 | return header + body 144 | 145 | def table_row(self, content): 146 | """Rendering a table row. Like ````. 147 | :param content: content of current table row. 148 | """ 149 | return ['\n%s\n'] + content 150 | 151 | def table_cell(self, content, **flags): 152 | """Rendering a table cell. Like ```` ````. 153 | :param content: content of current table cell. 154 | :param header: whether this is header or not. 155 | :param align: align of current table cell. 156 | """ 157 | return content 158 | 159 | 160 | ### Span-level functions 161 | 162 | def double_emphasis(self, text): 163 | """Rendering **strong** text. 164 | :param text: text content for emphasis. 165 | """ 166 | return [MdStyleInstructionText('double_emphasis')] + text 167 | 168 | def emphasis(self, text): 169 | """Rendering *emphasis* text. 170 | :param text: text content for emphasis. 171 | """ 172 | return [MdStyleInstructionText('emphasis')] + text 173 | 174 | def codespan(self, text): 175 | """Rendering inline `code` text. 176 | :param text: text content for inline code. 177 | """ 178 | if isinstance(text, str): 179 | text = [' {} '.format(text)] 180 | return [MdStyleInstructionText('codespan')] + text 181 | 182 | def linebreak(self): 183 | """Rendering line break like ``
      ``.""" 184 | 185 | return [MdStyleInstructionLineBreak()] 186 | 187 | def strikethrough(self, text): 188 | """Rendering ~~strikethrough~~ text. 189 | :param text: text content for strikethrough. 190 | """ 191 | return [MdStyleInstructionText('strikethrough')] + text 192 | 193 | def text(self, text): 194 | """Rendering unformatted text. 195 | :param text: text content. 196 | """ 197 | return [text] 198 | 199 | def escape(self, text): 200 | """Rendering escape sequence. 201 | :param text: text content. 202 | """ 203 | return [escape(text)] 204 | 205 | def autolink(self, link, is_email=False): 206 | """Rendering a given link or email address. 207 | :param link: link content or email address. 208 | :param is_email: whether this is an email or not. 209 | """ 210 | text = link = escape_link(link) 211 | if is_email: 212 | link = 'mailto:%s' % link 213 | return [MdStyleInstructionLink(link)] + text 214 | 215 | def link(self, link, title, text): 216 | """Rendering a given link with content and title. 217 | :param link: href link for ```` tag. 218 | :param title: title content for `title` attribute. 219 | :param text: text content for description. 220 | """ 221 | link = escape_link(link) 222 | return [MdStyleInstructionLink(link)] + text 223 | 224 | def image(self, src, title, text): 225 | """Rendering a image with title and text. 226 | :param src: source link of the image. 227 | :param title: title text of the image. 228 | :param text: alt text of the image. 229 | """ 230 | src = escape_link(src) 231 | text = escape(text, quote=True) 232 | if title: 233 | title = escape(title, quote=True) 234 | html = '%s' % html 239 | return '%s>' % html 240 | 241 | def inline_html(self, html): 242 | """Rendering span level pure html content. 243 | :param html: text content of the html snippet. 244 | """ 245 | if self.options.get('escape'): 246 | return [escape(html)] 247 | return [html] 248 | 249 | def newline(self): 250 | """Rendering newline element.""" 251 | return [''] 252 | 253 | def footnote_ref(self, key, index): 254 | """Rendering the ref anchor of a footnote. 255 | :param key: identity key for the footnote. 256 | :param index: the index count of current footnote. 257 | """ 258 | html = ( 259 | '' 260 | '%d' 261 | ) % (escape(key), escape(key), index) 262 | return html 263 | 264 | def footnote_item(self, key, text): 265 | """Rendering a footnote item. 266 | :param key: identity key for the footnote. 267 | :param text: text content of the footnote. 268 | """ 269 | back = ( 270 | '' 271 | ) % escape(key) 272 | text = text.rstrip() 273 | if text.endswith('

      '): 274 | text = re.sub(r'<\/p>$', r'%s

      ' % back, text) 275 | else: 276 | text = '%s

      %s

      ' % (text, back) 277 | html = '
    2. %s
    3. \n' % (escape(key), text) 278 | return html 279 | 280 | def footnotes(self, text): 281 | """Wrapper for all footnotes. 282 | :param text: contents of all footnotes. 283 | """ 284 | html = '
      \n%s
        %s
      \n
      \n' 285 | return html % (self.hrule(), text) 286 | 287 | -------------------------------------------------------------------------------- /nb2xls/exporter.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from io import BytesIO 3 | import re 4 | import base64 5 | from collections.abc import Iterable 6 | from collections import defaultdict 7 | from math import ceil, isnan 8 | 9 | from nbconvert.exporters import Exporter 10 | 11 | from traitlets import Bool 12 | 13 | from bs4 import BeautifulSoup 14 | from bs4.element import NavigableString, Tag 15 | import xlsxwriter 16 | 17 | import mistune 18 | from .mdrenderer import Md2XLSRenderer, \ 19 | MdStyleInstructionCell, MdStyleInstructionText, MdStyleInstructionLink, MdStyleInstructionListItem, \ 20 | MdStyleInstructionLineBreak, MdStyleInstructionListStart, MdStyleInstructionListEnd 21 | 22 | from .mdxlsstyles import MdXlsStyleRegistry 23 | 24 | try: 25 | import cv2 # Prefer Open CV2 but don't put in requirements.txt because it can be difficult to install 26 | import numpy as np 27 | usecv2 = True 28 | except ImportError: 29 | import png # PyPNG is a pure-Python PNG manipulator 30 | usecv2 = False 31 | 32 | 33 | class XLSExporter(Exporter): 34 | """ 35 | XLSX custom exporter 36 | """ 37 | 38 | # If this custom exporter should add an entry to the 39 | # "File -> Download as" menu in the notebook, give it a name here in the 40 | # `export_from_notebook` class member 41 | export_from_notebook = "Excel Spreadsheet" 42 | 43 | ignore_markdown_errors = Bool(True, help=""" 44 | Set ignore_markdown_errors to False in order to throw an exception with any md errors. 45 | From nbconvert command line for example: 46 | jupyter nbconvert --to xls Examples/Test.ipynb --XLSExporter.ignore_markdown_errors=False 47 | """).tag(config=True) 48 | 49 | def __init__(self, config=None, **kw): 50 | """ 51 | Public constructor 52 | 53 | Parameters 54 | ---------- 55 | config : :class:`~traitlets.config.Config` 56 | User configuration instance. 57 | `**kw` 58 | Additional keyword arguments passed to parent __init__ 59 | 60 | """ 61 | 62 | super(XLSExporter, self).__init__(config=config, **kw) 63 | 64 | self.msxlsstylereg = None 65 | self.workbook = None 66 | self.row = 0 67 | 68 | def _file_extension_default(self): 69 | """ 70 | The new file extension is `.xlsx` 71 | """ 72 | return '.xlsx' 73 | 74 | def from_notebook_node(self, nb, resources=None, **kw): 75 | """ 76 | Convert a notebook from a notebook node instance. 77 | Parameters 78 | ---------- 79 | nb : :class:`~nbformat.NotebookNode` 80 | Notebook node (dict-like with attr-access) 81 | resources : dict 82 | Additional resources that can be accessed read/write by 83 | preprocessors and filters. 84 | `**kw` 85 | Ignored 86 | """ 87 | nb_copy = copy.deepcopy(nb) 88 | resources = self._init_resources(resources) 89 | 90 | if 'language' in nb['metadata']: 91 | resources['language'] = nb['metadata']['language'].lower() 92 | 93 | # Preprocess 94 | nb_copy, resources = super(XLSExporter, self).from_notebook_node(nb, resources, **kw) 95 | 96 | output = BytesIO() 97 | self.workbook = xlsxwriter.Workbook(output, {'nan_inf_to_errors': True}) 98 | 99 | self.msxlsstylereg = MdXlsStyleRegistry(self.workbook) 100 | 101 | self.worksheet = self.workbook.add_worksheet() 102 | 103 | self.row = 0 104 | for cellno, cell in enumerate(nb_copy.cells): 105 | self.worksheet.write(self.row, 0, str(cellno+1)) 106 | 107 | # Convert depending on nbformat 108 | # https://nbformat.readthedocs.io/en/latest/format_description.html#cell-types 109 | 110 | if cell.cell_type == 'markdown': 111 | self._write_markdown(cell.source) 112 | 113 | elif cell.cell_type == 'code': 114 | self._write_code(cell) 115 | 116 | else: 117 | self._write_textplain('No convertible outputs available for cell: {}'.format(cell.source)) 118 | 119 | self.row += 1 120 | 121 | self.workbook.close() 122 | 123 | xlsx_data = output.getvalue() 124 | 125 | return xlsx_data, resources 126 | 127 | def _write_code(self, cell): 128 | """ 129 | Main handler for code cells 130 | :param celloutputs: 131 | :return: 132 | """ 133 | 134 | for i,o in enumerate(cell.outputs): 135 | 136 | display_data = None 137 | if o.output_type in ('execute_result', 'display_data'): 138 | if 'text/html' in o.data: 139 | self._write_texthtml(o.data['text/html']) 140 | elif 'text/markdown' in o.data: 141 | self._write_markdown(o.data['text/markdown']) 142 | elif 'image/png' in o.data: 143 | width, height = 0, 0 144 | if 'image/png' in o.metadata and set(o.metadata['image/png'].keys()) == {'width', 'height'} : 145 | width, height = o.metadata['image/png']['width'], o.metadata['image/png']['height'] 146 | self._write_image(o.data['image/png'], width, height) 147 | elif 'application/json' in o.data: 148 | self._write_textplain(repr(o.data['application/json'])) 149 | elif 'text/plain' in o.data: 150 | self._write_textplain(o.data['text/plain']) 151 | else: 152 | self._write_textplain('No convertible mimetype available for source (output {}): {}'.format(i, cell.source)) 153 | 154 | elif o.output_type == 'stream': 155 | self._write_textplain(o.text) 156 | 157 | if i < len(cell.outputs)-1: 158 | # Blank row between outputs, but not at the end 159 | self.row += 1 160 | 161 | ### 162 | # Sub-handlers for code cells 163 | 164 | def _write_textplain(self, text): 165 | lines = text.split("\n") 166 | for l in lines: 167 | self.worksheet.write(self.row, 1, l) 168 | self.row += 1 169 | 170 | # HTML functions start here 171 | 172 | def _write_texthtml(self, html): 173 | soup = BeautifulSoup(html, 'html.parser') 174 | self._write_soup(soup) 175 | 176 | def _write_soup(self, soup): 177 | s = '' 178 | for child in soup.children: 179 | 180 | if isinstance(child, NavigableString): 181 | s += child.string 182 | 183 | elif isinstance(child, Tag): 184 | 185 | if len(s) > 0: 186 | # Write accumulated string first 187 | re.sub(r'\s+', ' ', s) 188 | s = s.strip() 189 | if len(s) > 0: 190 | self.worksheet.write(self.row, 1, s.strip()) 191 | self.row += 1 192 | s = '' 193 | 194 | if child.name in ('div', 'body', 'span', 'p'): 195 | self._write_soup(child) 196 | 197 | elif child.name == 'table': 198 | self._write_htmltable(soup) 199 | 200 | def _write_htmltable(self, soup): 201 | double_emphasis_fmt = self.msxlsstylereg.use_style(['double_emphasis']) 202 | rowspans = defaultdict(int) 203 | for tablerow in soup('tr'): 204 | col = 1 205 | for child in tablerow.children: 206 | if isinstance(child, Tag): 207 | if child.name == 'th' or child.name == 'td': 208 | while rowspans[col] > 1: 209 | rowspans[col] -= 1 210 | col += 1 211 | 212 | s = child.get_text() 213 | 214 | fmt = double_emphasis_fmt if child.name == 'th' else None 215 | 216 | try: 217 | f = float(s) 218 | if isnan(f): 219 | self.worksheet.write_formula(self.row, col, '=NA()', fmt) 220 | else: 221 | self.worksheet.write_number(self.row, col, f, fmt) 222 | except ValueError: 223 | self.worksheet.write(self.row, col, s, fmt) 224 | 225 | if 'rowspan' in child.attrs and child.attrs['rowspan'].isdigit(): 226 | rowspans[col] = int(child.attrs['rowspan']) 227 | 228 | if 'colspan' in child.attrs and child.attrs['colspan'].isdigit(): 229 | col += int(child.attrs['colspan'])-1 230 | 231 | col += 1 232 | self.row += 1 233 | 234 | # Image handler 235 | 236 | def _write_image(self, image, want_width, want_height): 237 | 238 | image = base64.b64decode(image) 239 | 240 | image_data = BytesIO(image) 241 | 242 | if usecv2: 243 | nparr = np.frombuffer(image, np.uint8) 244 | img = cv2.imdecode(nparr, cv2.IMREAD_ANYCOLOR) 245 | height, width = img.shape[:2] 246 | else: 247 | img = png.Reader(image_data) 248 | (width, height, _, _) = img.asDirect() 249 | 250 | x_scale, y_scale = 1.0, 1.0 251 | 252 | if want_height > 0 and height > 0: 253 | y_scale = want_height / height 254 | 255 | if want_width > 0 and width > 0: 256 | x_scale = want_width / width 257 | 258 | self.row += 1 259 | 260 | self.worksheet.insert_image(self.row, 1, 'image.png', 261 | {'image_data': image_data, 'x_scale': x_scale, 'y_scale': y_scale}) 262 | 263 | self.row += ceil(height*y_scale / 15) # 15 is default row height in Excel 264 | 265 | # Markdown handler 266 | 267 | def _write_markdown(self, md): 268 | if self.ignore_markdown_errors: 269 | try: 270 | self._write_markdown_core(md) 271 | except Exception as e: 272 | print('Markdown Exception: ', e) 273 | self._write_textplain(md) 274 | else: 275 | self._write_markdown_core(md) 276 | 277 | def _write_markdown_core(self, md): 278 | markdown = mistune.Markdown(renderer=Md2XLSRenderer()) 279 | lines = markdown(md) 280 | 281 | def flatten(l): 282 | """ 283 | Nested lists cause problems due to the way the MD parser works. 284 | :param l: arbitrary-depth nested list of strs and MdStyleInstruction objects 285 | :return: single-depth flattened version of the array containing only the leaves 286 | """ 287 | for el in l: 288 | if isinstance(el, Iterable) and not isinstance(el, str): 289 | for sub in flatten(el): 290 | yield sub 291 | else: 292 | yield el 293 | 294 | list_counters = [] 295 | list_ordered = [] 296 | 297 | all_o = [] 298 | 299 | for l in lines: 300 | in_softnewline = False 301 | is_indented = 0 302 | link_url = '' 303 | already_outputted_text = False 304 | cell_format_mdname = '' 305 | o = [] 306 | mdtextstylenames = [] 307 | for i,s in enumerate(flatten(l)): 308 | if isinstance(s, MdStyleInstructionText): 309 | mdtextstylenames += [s.mdname] 310 | 311 | elif isinstance(s, MdStyleInstructionCell): 312 | cell_format_mdname = s.mdname 313 | 314 | elif isinstance(s, MdStyleInstructionLink): 315 | if already_outputted_text: 316 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 317 | o = [] 318 | already_outputted_text = False 319 | in_softnewline = True 320 | link_url = s.link 321 | 322 | elif isinstance(s, MdStyleInstructionListStart): 323 | if already_outputted_text: 324 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 325 | o = [] 326 | already_outputted_text = False 327 | 328 | is_indented += 1 329 | list_counters.append(1) 330 | list_ordered.append(s.ordered) 331 | 332 | elif isinstance(s, MdStyleInstructionListEnd): 333 | 334 | if already_outputted_text: 335 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 336 | o = [] 337 | already_outputted_text = False 338 | 339 | is_indented -= 1 340 | 341 | list_counters.pop() 342 | list_ordered.pop() 343 | 344 | in_softnewline = True 345 | link_url = '' 346 | 347 | elif isinstance(s, MdStyleInstructionListItem): 348 | if already_outputted_text: 349 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 350 | o = [] 351 | already_outputted_text = False 352 | in_softnewline = True 353 | link_url = '' 354 | 355 | li_count = list_counters[-1] 356 | if list_ordered[-1]: 357 | o = ['{}. '.format(li_count)] 358 | list_counters[-1] += 1 359 | 360 | elif isinstance(s, MdStyleInstructionLineBreak): 361 | if already_outputted_text: 362 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 363 | o = [] 364 | already_outputted_text = False 365 | in_softnewline = True 366 | link_url = '' 367 | 368 | elif len(s) > 0: 369 | if len(mdtextstylenames) > 0 or (cell_format_mdname != '' and i >= 2): 370 | fmt = self.msxlsstylereg.use_style([cell_format_mdname] + mdtextstylenames) 371 | o.append(fmt) 372 | mdtextstylenames = [] 373 | 374 | o.append(s) 375 | already_outputted_text = True 376 | 377 | if in_softnewline and link_url != '': 378 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 379 | o = [] 380 | already_outputted_text = False 381 | in_softnewline = False 382 | link_url = '' 383 | 384 | if len(o) > 0: 385 | all_o.append([o, cell_format_mdname, link_url, is_indented]) 386 | 387 | for o, cell_format_mdname, link_url, is_indented in all_o: 388 | 389 | if cell_format_mdname != '': 390 | o.append(self.msxlsstylereg.use_style(cell_format_mdname)) 391 | 392 | if link_url != '': 393 | if len(o) >= 2: 394 | self.worksheet.write_url(self.row, 1+is_indented, link_url, o[1], o[0]) 395 | elif len(o) == 1: 396 | self.worksheet.write_url(self.row, 1+is_indented, link_url, None, o[0]) 397 | 398 | else: 399 | 400 | if len(o) > 2: 401 | self.worksheet.write_rich_string(self.row, 1+is_indented, *o) 402 | elif len(o) == 2: 403 | if isinstance(o[0], xlsxwriter.format.Format) and not isinstance(o[1], xlsxwriter.format.Format): 404 | self.worksheet.write(self.row, 1+is_indented, o[1], o[0]) 405 | elif not isinstance(o[0], xlsxwriter.format.Format) and not isinstance(o[1], xlsxwriter.format.Format): 406 | self.worksheet.write(self.row, 1+is_indented, o[0] + ' ' + o[1]) 407 | else: 408 | self.worksheet.write(self.row, 1+is_indented, o[0], o[1]) 409 | elif len(o) == 1 and not isinstance(o[0], xlsxwriter.format.Format): 410 | self.worksheet.write(self.row, 1+is_indented, o[0]) 411 | 412 | self.row += 1 413 | 414 | -------------------------------------------------------------------------------- /Examples/MultipleOutputs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/markdown": [ 11 | "# Markdown Loop 0" 12 | ], 13 | "text/plain": [ 14 | "" 15 | ] 16 | }, 17 | "metadata": {}, 18 | "output_type": "display_data" 19 | }, 20 | { 21 | "data": { 22 | "text/html": [ 23 | "
      \n", 24 | "\n", 37 | "\n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | "
      01234
      00.5488140.7151890.6027630.5448830.423655
      10.6458940.4375870.8917730.9636630.383442
      20.7917250.5288950.5680450.9255970.071036
      30.0871290.0202180.8326200.7781570.870012
      40.9786180.7991590.4614790.7805290.118274
      \n", 91 | "
      " 92 | ], 93 | "text/plain": [ 94 | " 0 1 2 3 4\n", 95 | "0 0.548814 0.715189 0.602763 0.544883 0.423655\n", 96 | "1 0.645894 0.437587 0.891773 0.963663 0.383442\n", 97 | "2 0.791725 0.528895 0.568045 0.925597 0.071036\n", 98 | "3 0.087129 0.020218 0.832620 0.778157 0.870012\n", 99 | "4 0.978618 0.799159 0.461479 0.780529 0.118274" 100 | ] 101 | }, 102 | "metadata": {}, 103 | "output_type": "display_data" 104 | }, 105 | { 106 | "data": { 107 | "text/markdown": [ 108 | "# Markdown Loop 1" 109 | ], 110 | "text/plain": [ 111 | "" 112 | ] 113 | }, 114 | "metadata": {}, 115 | "output_type": "display_data" 116 | }, 117 | { 118 | "data": { 119 | "text/html": [ 120 | "
      \n", 121 | "\n", 134 | "\n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | "
      01234
      00.6399210.1433530.9446690.5218480.414662
      10.2645560.7742340.4561500.5684340.018790
      20.6176350.6120960.6169340.9437480.681820
      30.3595080.4370320.6976310.0602250.666767
      40.6706380.2103830.1289260.3154280.363711
      \n", 188 | "
      " 189 | ], 190 | "text/plain": [ 191 | " 0 1 2 3 4\n", 192 | "0 0.639921 0.143353 0.944669 0.521848 0.414662\n", 193 | "1 0.264556 0.774234 0.456150 0.568434 0.018790\n", 194 | "2 0.617635 0.612096 0.616934 0.943748 0.681820\n", 195 | "3 0.359508 0.437032 0.697631 0.060225 0.666767\n", 196 | "4 0.670638 0.210383 0.128926 0.315428 0.363711" 197 | ] 198 | }, 199 | "metadata": {}, 200 | "output_type": "display_data" 201 | }, 202 | { 203 | "data": { 204 | "text/markdown": [ 205 | "# Markdown Loop 2" 206 | ], 207 | "text/plain": [ 208 | "" 209 | ] 210 | }, 211 | "metadata": {}, 212 | "output_type": "display_data" 213 | }, 214 | { 215 | "data": { 216 | "text/html": [ 217 | "
      \n", 218 | "\n", 231 | "\n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | "
      01234
      00.5701970.4386020.9883740.1020450.208877
      10.1613100.6531080.2532920.4663110.244426
      20.1589700.1103750.6563300.1381830.196582
      30.3687250.8209930.0971010.8379450.096098
      40.9764590.4686510.9767610.6048460.739264
      \n", 285 | "
      " 286 | ], 287 | "text/plain": [ 288 | " 0 1 2 3 4\n", 289 | "0 0.570197 0.438602 0.988374 0.102045 0.208877\n", 290 | "1 0.161310 0.653108 0.253292 0.466311 0.244426\n", 291 | "2 0.158970 0.110375 0.656330 0.138183 0.196582\n", 292 | "3 0.368725 0.820993 0.097101 0.837945 0.096098\n", 293 | "4 0.976459 0.468651 0.976761 0.604846 0.739264" 294 | ] 295 | }, 296 | "metadata": {}, 297 | "output_type": "display_data" 298 | }, 299 | { 300 | "data": { 301 | "text/markdown": [ 302 | "# Markdown Loop 3" 303 | ], 304 | "text/plain": [ 305 | "" 306 | ] 307 | }, 308 | "metadata": {}, 309 | "output_type": "display_data" 310 | }, 311 | { 312 | "data": { 313 | "text/html": [ 314 | "
      \n", 315 | "\n", 328 | "\n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | "
      01234
      00.0391880.2828070.1201970.2961400.118728
      10.3179830.4142630.0641470.6924720.566601
      20.2653890.5232480.0939410.5759460.929296
      30.3185690.6674100.1317980.7163270.289406
      40.1831910.5865130.0201080.8289400.004695
      \n", 382 | "
      " 383 | ], 384 | "text/plain": [ 385 | " 0 1 2 3 4\n", 386 | "0 0.039188 0.282807 0.120197 0.296140 0.118728\n", 387 | "1 0.317983 0.414263 0.064147 0.692472 0.566601\n", 388 | "2 0.265389 0.523248 0.093941 0.575946 0.929296\n", 389 | "3 0.318569 0.667410 0.131798 0.716327 0.289406\n", 390 | "4 0.183191 0.586513 0.020108 0.828940 0.004695" 391 | ] 392 | }, 393 | "metadata": {}, 394 | "output_type": "display_data" 395 | }, 396 | { 397 | "data": { 398 | "text/markdown": [ 399 | "# Markdown Loop 4" 400 | ], 401 | "text/plain": [ 402 | "" 403 | ] 404 | }, 405 | "metadata": {}, 406 | "output_type": "display_data" 407 | }, 408 | { 409 | "data": { 410 | "text/html": [ 411 | "
      \n", 412 | "\n", 425 | "\n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | "
      01234
      00.6778170.2700080.7351940.9621890.248753
      10.5761570.5920420.5722520.2230820.952749
      20.4471250.8464090.6994790.2974370.813798
      30.3965060.8811030.5812730.8817350.692532
      40.7252540.5013240.9560840.6439900.423855
      \n", 479 | "
      " 480 | ], 481 | "text/plain": [ 482 | " 0 1 2 3 4\n", 483 | "0 0.677817 0.270008 0.735194 0.962189 0.248753\n", 484 | "1 0.576157 0.592042 0.572252 0.223082 0.952749\n", 485 | "2 0.447125 0.846409 0.699479 0.297437 0.813798\n", 486 | "3 0.396506 0.881103 0.581273 0.881735 0.692532\n", 487 | "4 0.725254 0.501324 0.956084 0.643990 0.423855" 488 | ] 489 | }, 490 | "metadata": {}, 491 | "output_type": "display_data" 492 | } 493 | ], 494 | "source": [ 495 | "import pandas as pd\n", 496 | "import numpy as np\n", 497 | "np.random.seed(0)\n", 498 | "from IPython.display import *\n", 499 | "\n", 500 | "for i in range(5):\n", 501 | " display(Markdown(f'# Markdown Loop {i}'))\n", 502 | " display(pd.DataFrame(pd.np.random.random((5,5))))\n" 503 | ] 504 | }, 505 | { 506 | "cell_type": "code", 507 | "execution_count": null, 508 | "metadata": {}, 509 | "outputs": [], 510 | "source": [] 511 | } 512 | ], 513 | "metadata": { 514 | "kernelspec": { 515 | "display_name": "Python 3", 516 | "language": "python", 517 | "name": "python3" 518 | }, 519 | "language_info": { 520 | "codemirror_mode": { 521 | "name": "ipython", 522 | "version": 3 523 | }, 524 | "file_extension": ".py", 525 | "mimetype": "text/x-python", 526 | "name": "python", 527 | "nbconvert_exporter": "python", 528 | "pygments_lexer": "ipython3", 529 | "version": "3.7.3" 530 | } 531 | }, 532 | "nbformat": 4, 533 | "nbformat_minor": 2 534 | } 535 | -------------------------------------------------------------------------------- /tests/files/MultipleOutputs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/markdown": [ 11 | "# Markdown Loop 0" 12 | ], 13 | "text/plain": [ 14 | "" 15 | ] 16 | }, 17 | "metadata": {}, 18 | "output_type": "display_data" 19 | }, 20 | { 21 | "data": { 22 | "text/html": [ 23 | "
      \n", 24 | "\n", 37 | "\n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | "
      01234
      00.5488140.7151890.6027630.5448830.423655
      10.6458940.4375870.8917730.9636630.383442
      20.7917250.5288950.5680450.9255970.071036
      30.0871290.0202180.8326200.7781570.870012
      40.9786180.7991590.4614790.7805290.118274
      \n", 91 | "
      " 92 | ], 93 | "text/plain": [ 94 | " 0 1 2 3 4\n", 95 | "0 0.548814 0.715189 0.602763 0.544883 0.423655\n", 96 | "1 0.645894 0.437587 0.891773 0.963663 0.383442\n", 97 | "2 0.791725 0.528895 0.568045 0.925597 0.071036\n", 98 | "3 0.087129 0.020218 0.832620 0.778157 0.870012\n", 99 | "4 0.978618 0.799159 0.461479 0.780529 0.118274" 100 | ] 101 | }, 102 | "metadata": {}, 103 | "output_type": "display_data" 104 | }, 105 | { 106 | "data": { 107 | "text/markdown": [ 108 | "# Markdown Loop 1" 109 | ], 110 | "text/plain": [ 111 | "" 112 | ] 113 | }, 114 | "metadata": {}, 115 | "output_type": "display_data" 116 | }, 117 | { 118 | "data": { 119 | "text/html": [ 120 | "
      \n", 121 | "\n", 134 | "\n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | "
      01234
      00.6399210.1433530.9446690.5218480.414662
      10.2645560.7742340.4561500.5684340.018790
      20.6176350.6120960.6169340.9437480.681820
      30.3595080.4370320.6976310.0602250.666767
      40.6706380.2103830.1289260.3154280.363711
      \n", 188 | "
      " 189 | ], 190 | "text/plain": [ 191 | " 0 1 2 3 4\n", 192 | "0 0.639921 0.143353 0.944669 0.521848 0.414662\n", 193 | "1 0.264556 0.774234 0.456150 0.568434 0.018790\n", 194 | "2 0.617635 0.612096 0.616934 0.943748 0.681820\n", 195 | "3 0.359508 0.437032 0.697631 0.060225 0.666767\n", 196 | "4 0.670638 0.210383 0.128926 0.315428 0.363711" 197 | ] 198 | }, 199 | "metadata": {}, 200 | "output_type": "display_data" 201 | }, 202 | { 203 | "data": { 204 | "text/markdown": [ 205 | "# Markdown Loop 2" 206 | ], 207 | "text/plain": [ 208 | "" 209 | ] 210 | }, 211 | "metadata": {}, 212 | "output_type": "display_data" 213 | }, 214 | { 215 | "data": { 216 | "text/html": [ 217 | "
      \n", 218 | "\n", 231 | "\n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | "
      01234
      00.5701970.4386020.9883740.1020450.208877
      10.1613100.6531080.2532920.4663110.244426
      20.1589700.1103750.6563300.1381830.196582
      30.3687250.8209930.0971010.8379450.096098
      40.9764590.4686510.9767610.6048460.739264
      \n", 285 | "
      " 286 | ], 287 | "text/plain": [ 288 | " 0 1 2 3 4\n", 289 | "0 0.570197 0.438602 0.988374 0.102045 0.208877\n", 290 | "1 0.161310 0.653108 0.253292 0.466311 0.244426\n", 291 | "2 0.158970 0.110375 0.656330 0.138183 0.196582\n", 292 | "3 0.368725 0.820993 0.097101 0.837945 0.096098\n", 293 | "4 0.976459 0.468651 0.976761 0.604846 0.739264" 294 | ] 295 | }, 296 | "metadata": {}, 297 | "output_type": "display_data" 298 | }, 299 | { 300 | "data": { 301 | "text/markdown": [ 302 | "# Markdown Loop 3" 303 | ], 304 | "text/plain": [ 305 | "" 306 | ] 307 | }, 308 | "metadata": {}, 309 | "output_type": "display_data" 310 | }, 311 | { 312 | "data": { 313 | "text/html": [ 314 | "
      \n", 315 | "\n", 328 | "\n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | "
      01234
      00.0391880.2828070.1201970.2961400.118728
      10.3179830.4142630.0641470.6924720.566601
      20.2653890.5232480.0939410.5759460.929296
      30.3185690.6674100.1317980.7163270.289406
      40.1831910.5865130.0201080.8289400.004695
      \n", 382 | "
      " 383 | ], 384 | "text/plain": [ 385 | " 0 1 2 3 4\n", 386 | "0 0.039188 0.282807 0.120197 0.296140 0.118728\n", 387 | "1 0.317983 0.414263 0.064147 0.692472 0.566601\n", 388 | "2 0.265389 0.523248 0.093941 0.575946 0.929296\n", 389 | "3 0.318569 0.667410 0.131798 0.716327 0.289406\n", 390 | "4 0.183191 0.586513 0.020108 0.828940 0.004695" 391 | ] 392 | }, 393 | "metadata": {}, 394 | "output_type": "display_data" 395 | }, 396 | { 397 | "data": { 398 | "text/markdown": [ 399 | "# Markdown Loop 4" 400 | ], 401 | "text/plain": [ 402 | "" 403 | ] 404 | }, 405 | "metadata": {}, 406 | "output_type": "display_data" 407 | }, 408 | { 409 | "data": { 410 | "text/html": [ 411 | "
      \n", 412 | "\n", 425 | "\n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | "
      01234
      00.6778170.2700080.7351940.9621890.248753
      10.5761570.5920420.5722520.2230820.952749
      20.4471250.8464090.6994790.2974370.813798
      30.3965060.8811030.5812730.8817350.692532
      40.7252540.5013240.9560840.6439900.423855
      \n", 479 | "
      " 480 | ], 481 | "text/plain": [ 482 | " 0 1 2 3 4\n", 483 | "0 0.677817 0.270008 0.735194 0.962189 0.248753\n", 484 | "1 0.576157 0.592042 0.572252 0.223082 0.952749\n", 485 | "2 0.447125 0.846409 0.699479 0.297437 0.813798\n", 486 | "3 0.396506 0.881103 0.581273 0.881735 0.692532\n", 487 | "4 0.725254 0.501324 0.956084 0.643990 0.423855" 488 | ] 489 | }, 490 | "metadata": {}, 491 | "output_type": "display_data" 492 | } 493 | ], 494 | "source": [ 495 | "import pandas as pd\n", 496 | "import numpy as np\n", 497 | "np.random.seed(0)\n", 498 | "from IPython.display import *\n", 499 | "\n", 500 | "for i in range(5):\n", 501 | " display(Markdown(f'# Markdown Loop {i}'))\n", 502 | " display(pd.DataFrame(pd.np.random.random((5,5))))\n" 503 | ] 504 | }, 505 | { 506 | "cell_type": "code", 507 | "execution_count": null, 508 | "metadata": {}, 509 | "outputs": [], 510 | "source": [] 511 | } 512 | ], 513 | "metadata": { 514 | "kernelspec": { 515 | "display_name": "Python 3", 516 | "language": "python", 517 | "name": "python3" 518 | }, 519 | "language_info": { 520 | "codemirror_mode": { 521 | "name": "ipython", 522 | "version": 3 523 | }, 524 | "file_extension": ".py", 525 | "mimetype": "text/x-python", 526 | "name": "python", 527 | "nbconvert_exporter": "python", 528 | "pygments_lexer": "ipython3", 529 | "version": "3.7.3" 530 | } 531 | }, 532 | "nbformat": 4, 533 | "nbformat_minor": 2 534 | } 535 | -------------------------------------------------------------------------------- /Examples/ExcelTest3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Markdown cell Title\n", 8 | "\n", 9 | "With **some** text" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Test\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "print(\"Test\")" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "data": { 36 | "text/html": [ 37 | "
      \n", 38 | "\n", 51 | "\n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | "
      ABCD
      2013-01-011.7640520.4001570.9787382.240893
      2013-01-021.867558-0.9772780.950088-0.151357
      2013-01-03-0.1032190.4105990.1440441.454274
      2013-01-040.7610380.1216750.4438630.333674
      2013-01-051.494079-0.2051580.313068-0.854096
      2013-01-06-2.5529900.6536190.864436-0.742165
      \n", 106 | "
      " 107 | ], 108 | "text/plain": [ 109 | " A B C D\n", 110 | "2013-01-01 1.764052 0.400157 0.978738 2.240893\n", 111 | "2013-01-02 1.867558 -0.977278 0.950088 -0.151357\n", 112 | "2013-01-03 -0.103219 0.410599 0.144044 1.454274\n", 113 | "2013-01-04 0.761038 0.121675 0.443863 0.333674\n", 114 | "2013-01-05 1.494079 -0.205158 0.313068 -0.854096\n", 115 | "2013-01-06 -2.552990 0.653619 0.864436 -0.742165" 116 | ] 117 | }, 118 | "execution_count": 2, 119 | "metadata": {}, 120 | "output_type": "execute_result" 121 | } 122 | ], 123 | "source": [ 124 | "import numpy as np\n", 125 | "import pandas as pd\n", 126 | "np.random.seed(0)\n", 127 | "\n", 128 | "dates = pd.date_range('20130101',periods=6)\n", 129 | "\n", 130 | "df = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD'))\n", 131 | "\n", 132 | "df" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 3, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',\n", 144 | " '2013-01-05', '2013-01-06'],\n", 145 | " dtype='datetime64[ns]', freq='D')" 146 | ] 147 | }, 148 | "execution_count": 3, 149 | "metadata": {}, 150 | "output_type": "execute_result" 151 | } 152 | ], 153 | "source": [ 154 | "dates" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 5, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "data": { 164 | "image/png": "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W7RoEW+//TadOnUC4JNPPqFTp068/fbbEUco1eHEE0+kXr16vP/++yxdupQzzjiDLVu2sGzZMpo2bUr37t2ZP38+q1ev5vXXX2fbtm307t2b/v37A/D666+zYcMGmjdvTseOHfnOd77D8uXLuf3227nzzju57bbbuPzyy5k0aRL9+vXj/fff55xzzmHDhg0AvPnmmyxatIhdu3ZxyimnMHHiROrXr/+5PlNa70GZWV1gJdAJuNvdXzWzicC1ZvZz4AVgqrsf8es9M5sATABo165dOsOUUGUfUf3hO9srvV2U45PVJkVFRdSvX59mzZqV9Kq7+uqr+fDDD0vWadSoUVqSU2XOh6qcQ6DzKJm+ffuydOlSli5dypVXXsmWLVtYunQpTZs25cwzz+Tll19m1KhR1K1bl1atWvHVr36V1157jSZNmtC7d++Spt+TTjqJs88+G4Du3buzaNEiABYsWMD69etLjvfxxx+XDK/09a9/nQYNGtCgQQNOOOEEtm7dSnZ29uf6PGlNUO5+EMg1s2bA42bWDfgR8CFwDHAvcDXwizK2vTdcTq9evVTzEqmEZL3q5OhWfB/qjTfeoFu3brRt25abb76ZJk2a8O1vf7sk0ZQl8XEbderUKZmuU6dOyaM3Dh06xCuvvELDhg3L3b66HteRkd9BuftOYBEwxN0LPbAP+F8gLxMxiNQmxb3q1qxZw9q1a/n5z39+xDplDSwqNVvfvn2ZP38+zZs3p27dujRv3pydO3eybNky+vbty1lnncXcuXM5ePAgRUVFvPjii+TlpX4JPvvss7nzzjtLplevTm8XgrTVoMysJbDf3Xea2bHAYOAGM8ty90ILRpL8JrA2XTGIiESmgm7h6dC9e3e2bdvGxRdffNi83bt306JFC8477zyWLVvGaaedhpnx61//mtatW/Pmm2+mtP877riDSy+9lB49enDgwAH69++f1t+2pbOJLwuYHd6HqgM84u7zzWxhmLwMWA1cksYYRERqjbp16/Lxxx8fNm/WrFkl782MG2+8kRtvvPGwdUqP6JH4/LXEZS1atCjp+Zdo+vTph02vXVs99Y509uJbA/QsY/7AdB1TaodkD8cbPXo0K1asoH79+uTl5TFz5szP3YtIRKKjkSSkxkk2jM/o0aN58MEHAbj44ou57777mDhxYqSxVumJwJUdgWDTnqptF0ET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\n", 165 | "text/plain": [ 166 | "
      " 167 | ] 168 | }, 169 | "metadata": { 170 | "needs_background": "light" 171 | }, 172 | "output_type": "display_data" 173 | } 174 | ], 175 | "source": [ 176 | "import matplotlib\n", 177 | "import matplotlib.pyplot as plt\n", 178 | "\n", 179 | "\n", 180 | "men_means, men_std = (20, 35, 30, 35, 27), (2, 3, 4, 1, 2)\n", 181 | "women_means, women_std = (25, 32, 34, 20, 25), (3, 5, 2, 3, 3)\n", 182 | "\n", 183 | "ind = np.arange(len(men_means)) # the x locations for the groups\n", 184 | "width = 0.35 # the width of the bars\n", 185 | "\n", 186 | "fig, ax = plt.subplots()\n", 187 | "rects1 = ax.bar(ind - width/2, men_means, width, yerr=men_std,\n", 188 | " label='Men')\n", 189 | "rects2 = ax.bar(ind + width/2, women_means, width, yerr=women_std,\n", 190 | " label='Women')\n", 191 | "\n", 192 | "# Add some text for labels, title and custom x-axis tick labels, etc.\n", 193 | "ax.set_ylabel('Scores')\n", 194 | "ax.set_title('Scores by group and gender')\n", 195 | "ax.set_xticks(ind)\n", 196 | "ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))\n", 197 | "ax.legend()\n", 198 | "\n", 199 | "\n", 200 | "def autolabel(rects, xpos='center'):\n", 201 | " \"\"\"\n", 202 | " Attach a text label above each bar in *rects*, displaying its height.\n", 203 | "\n", 204 | " *xpos* indicates which side to place the text w.r.t. the center of\n", 205 | " the bar. It can be one of the following {'center', 'right', 'left'}.\n", 206 | " \"\"\"\n", 207 | "\n", 208 | " ha = {'center': 'center', 'right': 'left', 'left': 'right'}\n", 209 | " offset = {'center': 0, 'right': 1, 'left': -1}\n", 210 | "\n", 211 | " for rect in rects:\n", 212 | " height = rect.get_height()\n", 213 | " ax.annotate('{}'.format(height),\n", 214 | " xy=(rect.get_x() + rect.get_width() / 2, height),\n", 215 | " xytext=(offset[xpos]*3, 3), # use 3 points offset\n", 216 | " textcoords=\"offset points\", # in both directions\n", 217 | " ha=ha[xpos], va='bottom')\n", 218 | "\n", 219 | "\n", 220 | "autolabel(rects1, \"left\")\n", 221 | "autolabel(rects2, \"right\")\n", 222 | "\n", 223 | "fig.tight_layout()\n", 224 | "\n", 225 | "plt.show()\n" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "This is the end of the notebook." 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": null, 238 | "metadata": {}, 239 | "outputs": [], 240 | "source": [] 241 | } 242 | ], 243 | "metadata": { 244 | "kernelspec": { 245 | "display_name": "Python 3", 246 | "language": "python", 247 | "name": "python3" 248 | }, 249 | "language_info": { 250 | "codemirror_mode": { 251 | "name": "ipython", 252 | "version": 3 253 | }, 254 | "file_extension": ".py", 255 | "mimetype": "text/x-python", 256 | "name": "python", 257 | "nbconvert_exporter": "python", 258 | "pygments_lexer": "ipython3", 259 | "version": "3.7.3" 260 | } 261 | }, 262 | "nbformat": 4, 263 | "nbformat_minor": 2 264 | } 265 | -------------------------------------------------------------------------------- /Examples/PandasTables.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pandas DataFrame with N/As\n", 8 | "\n", 9 | "Solving GitHub [issue number 6](https://github.com/ideonate/nb2xls/issues/6)\n", 10 | "\n", 11 | "[Source](https://pandas.pydata.org/pandas-docs/stable/user_guide/cookbook.html#cookbook-multi-index)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "data": { 21 | "text/html": [ 22 | "
      \n", 23 | "\n", 36 | "\n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | "
      2
      01
      a1123
      2345
      b1678
      a1345
      \n", 71 | "
      " 72 | ], 73 | "text/plain": [ 74 | " 2\n", 75 | "0 1 \n", 76 | "a 1 123\n", 77 | " 2 345\n", 78 | "b 1 678\n", 79 | "a 1 345" 80 | ] 81 | }, 82 | "execution_count": 1, 83 | "metadata": {}, 84 | "output_type": "execute_result" 85 | } 86 | ], 87 | "source": [ 88 | "import pandas as pd\n", 89 | "import numpy as np\n", 90 | "np.random.seed(0)\n", 91 | "\n", 92 | "df = pd.DataFrame([['a',1,123], ['a',2,345], ['b',1,678], ['a',1,345]]).set_index([0,1])\n", 93 | "df" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 2, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "data": { 103 | "text/html": [ 104 | "
      \n", 105 | "\n", 118 | "\n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | "
      rowOne_XOne_YTwo_XTwo_Y
      001.11.21.111.22
      111.11.21.111.22
      221.11.21.111.22
      \n", 156 | "
      " 157 | ], 158 | "text/plain": [ 159 | " row One_X One_Y Two_X Two_Y\n", 160 | "0 0 1.1 1.2 1.11 1.22\n", 161 | "1 1 1.1 1.2 1.11 1.22\n", 162 | "2 2 1.1 1.2 1.11 1.22" 163 | ] 164 | }, 165 | "execution_count": 2, 166 | "metadata": {}, 167 | "output_type": "execute_result" 168 | } 169 | ], 170 | "source": [ 171 | "df = pd.DataFrame({'row': [0, 1, 2],\n", 172 | " 'One_X': [1.1, 1.1, 1.1],\n", 173 | " 'One_Y': [1.2, 1.2, 1.2],\n", 174 | " 'Two_X': [1.11, 1.11, 1.11],\n", 175 | " 'Two_Y': [1.22, 1.22, 1.22]})\n", 176 | "df" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 3, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/html": [ 187 | "
      \n", 188 | "\n", 201 | "\n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | "
      One_XOne_YTwo_XTwo_Y
      row
      01.11.21.111.22
      11.11.21.111.22
      21.11.21.111.22
      \n", 242 | "
      " 243 | ], 244 | "text/plain": [ 245 | " One_X One_Y Two_X Two_Y\n", 246 | "row \n", 247 | "0 1.1 1.2 1.11 1.22\n", 248 | "1 1.1 1.2 1.11 1.22\n", 249 | "2 1.1 1.2 1.11 1.22" 250 | ] 251 | }, 252 | "execution_count": 3, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "df = df.set_index('row'); df" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 4, 264 | "metadata": {}, 265 | "outputs": [ 266 | { 267 | "data": { 268 | "text/html": [ 269 | "
      \n", 270 | "\n", 287 | "\n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | "
      OneTwo
      XYXY
      row
      01.11.21.111.22
      11.11.21.111.22
      21.11.21.111.22
      \n", 333 | "
      " 334 | ], 335 | "text/plain": [ 336 | " One Two \n", 337 | " X Y X Y\n", 338 | "row \n", 339 | "0 1.1 1.2 1.11 1.22\n", 340 | "1 1.1 1.2 1.11 1.22\n", 341 | "2 1.1 1.2 1.11 1.22" 342 | ] 343 | }, 344 | "execution_count": 4, 345 | "metadata": {}, 346 | "output_type": "execute_result" 347 | } 348 | ], 349 | "source": [ 350 | "df.columns = pd.MultiIndex.from_tuples([tuple(c.split('_'))\n", 351 | " for c in df.columns])\n", 352 | "\n", 353 | "df " 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": 5, 359 | "metadata": {}, 360 | "outputs": [ 361 | { 362 | "data": { 363 | "text/html": [ 364 | "
      \n", 365 | "\n", 378 | "\n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | "
      level_1XY
      row
      0One1.101.20
      0Two1.111.22
      1One1.101.20
      1Two1.111.22
      2One1.101.20
      2Two1.111.22
      \n", 432 | "
      " 433 | ], 434 | "text/plain": [ 435 | " level_1 X Y\n", 436 | "row \n", 437 | "0 One 1.10 1.20\n", 438 | "0 Two 1.11 1.22\n", 439 | "1 One 1.10 1.20\n", 440 | "1 Two 1.11 1.22\n", 441 | "2 One 1.10 1.20\n", 442 | "2 Two 1.11 1.22" 443 | ] 444 | }, 445 | "execution_count": 5, 446 | "metadata": {}, 447 | "output_type": "execute_result" 448 | } 449 | ], 450 | "source": [ 451 | "df = df.stack(0).reset_index(1); df" 452 | ] 453 | }, 454 | { 455 | "cell_type": "code", 456 | "execution_count": 6, 457 | "metadata": {}, 458 | "outputs": [ 459 | { 460 | "data": { 461 | "text/html": [ 462 | "
      \n", 463 | "\n", 476 | "\n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | "
      SampleAll_XAll_Y
      row
      0One1.101.20
      0Two1.111.22
      1One1.101.20
      1Two1.111.22
      2One1.101.20
      2Two1.111.22
      \n", 530 | "
      " 531 | ], 532 | "text/plain": [ 533 | " Sample All_X All_Y\n", 534 | "row \n", 535 | "0 One 1.10 1.20\n", 536 | "0 Two 1.11 1.22\n", 537 | "1 One 1.10 1.20\n", 538 | "1 Two 1.11 1.22\n", 539 | "2 One 1.10 1.20\n", 540 | "2 Two 1.11 1.22" 541 | ] 542 | }, 543 | "execution_count": 6, 544 | "metadata": {}, 545 | "output_type": "execute_result" 546 | } 547 | ], 548 | "source": [ 549 | "df.columns = ['Sample', 'All_X', 'All_Y']; df" 550 | ] 551 | }, 552 | { 553 | "cell_type": "code", 554 | "execution_count": 7, 555 | "metadata": {}, 556 | "outputs": [ 557 | { 558 | "data": { 559 | "text/html": [ 560 | "
      \n", 561 | "\n", 574 | "\n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | "
      ABC
      OIOIOI
      n1.7640520.4001570.9787382.2408931.867558-0.977278
      m0.950088-0.151357-0.1032190.4105990.1440441.454274
      \n", 613 | "
      " 614 | ], 615 | "text/plain": [ 616 | " A B C \n", 617 | " O I O I O I\n", 618 | "n 1.764052 0.400157 0.978738 2.240893 1.867558 -0.977278\n", 619 | "m 0.950088 -0.151357 -0.103219 0.410599 0.144044 1.454274" 620 | ] 621 | }, 622 | "execution_count": 7, 623 | "metadata": {}, 624 | "output_type": "execute_result" 625 | } 626 | ], 627 | "source": [ 628 | "cols = pd.MultiIndex.from_tuples([(x, y) for x in ['A', 'B', 'C']\n", 629 | " for y in ['O', 'I']])\n", 630 | "\n", 631 | "df = pd.DataFrame(np.random.randn(2, 6), index=['n', 'm'], columns=cols)\n", 632 | "df" 633 | ] 634 | }, 635 | { 636 | "cell_type": "code", 637 | "execution_count": 8, 638 | "metadata": {}, 639 | "outputs": [ 640 | { 641 | "data": { 642 | "text/html": [ 643 | "
      \n", 644 | "\n", 657 | "\n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | "
      ABC
      OIOIOI
      n0.944577-0.4094610.524074-2.2929951.01.0
      m6.595840-0.104078-0.7165810.2823391.01.0
      \n", 696 | "
      " 697 | ], 698 | "text/plain": [ 699 | " A B C \n", 700 | " O I O I O I\n", 701 | "n 0.944577 -0.409461 0.524074 -2.292995 1.0 1.0\n", 702 | "m 6.595840 -0.104078 -0.716581 0.282339 1.0 1.0" 703 | ] 704 | }, 705 | "execution_count": 8, 706 | "metadata": {}, 707 | "output_type": "execute_result" 708 | } 709 | ], 710 | "source": [ 711 | "df = df.div(df['C'], level=1); df" 712 | ] 713 | }, 714 | { 715 | "cell_type": "code", 716 | "execution_count": 9, 717 | "metadata": {}, 718 | "outputs": [ 719 | { 720 | "data": { 721 | "text/html": [ 722 | "
      \n", 723 | "\n", 736 | "\n", 737 | " \n", 738 | " \n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | "
      MyData
      AAone11
      six22
      BBone33
      two44
      six55
      \n", 769 | "
      " 770 | ], 771 | "text/plain": [ 772 | " MyData\n", 773 | "AA one 11\n", 774 | " six 22\n", 775 | "BB one 33\n", 776 | " two 44\n", 777 | " six 55" 778 | ] 779 | }, 780 | "execution_count": 9, 781 | "metadata": {}, 782 | "output_type": "execute_result" 783 | } 784 | ], 785 | "source": [ 786 | "coords = [('AA', 'one'), ('AA', 'six'), ('BB', 'one'), ('BB', 'two'),\n", 787 | " ('BB', 'six')]\n", 788 | "\n", 789 | "index = pd.MultiIndex.from_tuples(coords)\n", 790 | "\n", 791 | "df = pd.DataFrame([11, 22, 33, 44, 55], index, ['MyData']); df" 792 | ] 793 | }, 794 | { 795 | "cell_type": "code", 796 | "execution_count": 10, 797 | "metadata": {}, 798 | "outputs": [ 799 | { 800 | "data": { 801 | "text/html": [ 802 | "
      \n", 803 | "\n", 820 | "\n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | "
      ExamsLabs
      IIIIII
      StudentCourse
      AdaComp70717273
      Math71737574
      Sci72757575
      QuinnComp73747576
      Math74767877
      Sci75787878
      VioletComp76777879
      Math77798180
      Sci78818181
      \n", 914 | "
      " 915 | ], 916 | "text/plain": [ 917 | " Exams Labs \n", 918 | " I II I II\n", 919 | "Student Course \n", 920 | "Ada Comp 70 71 72 73\n", 921 | " Math 71 73 75 74\n", 922 | " Sci 72 75 75 75\n", 923 | "Quinn Comp 73 74 75 76\n", 924 | " Math 74 76 78 77\n", 925 | " Sci 75 78 78 78\n", 926 | "Violet Comp 76 77 78 79\n", 927 | " Math 77 79 81 80\n", 928 | " Sci 78 81 81 81" 929 | ] 930 | }, 931 | "execution_count": 10, 932 | "metadata": {}, 933 | "output_type": "execute_result" 934 | } 935 | ], 936 | "source": [ 937 | "import itertools\n", 938 | "\n", 939 | "index = list(itertools.product(['Ada', 'Quinn', 'Violet'],\n", 940 | " ['Comp', 'Math', 'Sci']))\n", 941 | "\n", 942 | "headr = list(itertools.product(['Exams', 'Labs'], ['I', 'II']))\n", 943 | "\n", 944 | "indx = pd.MultiIndex.from_tuples(index, names=['Student', 'Course'])\n", 945 | "\n", 946 | "cols = pd.MultiIndex.from_tuples(headr) # Notice these are un-named\n", 947 | "\n", 948 | "data = [[70 + x + y + (x * y) % 3 for x in range(4)] for y in range(9)]\n", 949 | "\n", 950 | "df = pd.DataFrame(data, indx, cols); df\n" 951 | ] 952 | }, 953 | { 954 | "cell_type": "code", 955 | "execution_count": null, 956 | "metadata": {}, 957 | "outputs": [], 958 | "source": [] 959 | } 960 | ], 961 | "metadata": { 962 | "hide_input": false, 963 | "kernelspec": { 964 | "display_name": "Python 3", 965 | "language": "python", 966 | "name": "python3" 967 | }, 968 | "language_info": { 969 | "codemirror_mode": { 970 | "name": "ipython", 971 | "version": 3 972 | }, 973 | "file_extension": ".py", 974 | "mimetype": "text/x-python", 975 | "name": "python", 976 | "nbconvert_exporter": "python", 977 | "pygments_lexer": "ipython3", 978 | "version": "3.7.3" 979 | } 980 | }, 981 | "nbformat": 4, 982 | "nbformat_minor": 2 983 | } 984 | -------------------------------------------------------------------------------- /tests/files/PandasTables.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pandas DataFrame with N/As\n", 8 | "\n", 9 | "Solving GitHub [issue number 6](https://github.com/ideonate/nb2xls/issues/6)\n", 10 | "\n", 11 | "[Source](https://pandas.pydata.org/pandas-docs/stable/user_guide/cookbook.html#cookbook-multi-index)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "data": { 21 | "text/html": [ 22 | "
      \n", 23 | "\n", 36 | "\n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | "
      2
      01
      a1123
      2345
      b1678
      a1345
      \n", 71 | "
      " 72 | ], 73 | "text/plain": [ 74 | " 2\n", 75 | "0 1 \n", 76 | "a 1 123\n", 77 | " 2 345\n", 78 | "b 1 678\n", 79 | "a 1 345" 80 | ] 81 | }, 82 | "execution_count": 1, 83 | "metadata": {}, 84 | "output_type": "execute_result" 85 | } 86 | ], 87 | "source": [ 88 | "import pandas as pd\n", 89 | "import numpy as np\n", 90 | "np.random.seed(0)\n", 91 | "\n", 92 | "df = pd.DataFrame([['a',1,123], ['a',2,345], ['b',1,678], ['a',1,345]]).set_index([0,1])\n", 93 | "df" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 2, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "data": { 103 | "text/html": [ 104 | "
      \n", 105 | "\n", 118 | "\n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | "
      rowOne_XOne_YTwo_XTwo_Y
      001.11.21.111.22
      111.11.21.111.22
      221.11.21.111.22
      \n", 156 | "
      " 157 | ], 158 | "text/plain": [ 159 | " row One_X One_Y Two_X Two_Y\n", 160 | "0 0 1.1 1.2 1.11 1.22\n", 161 | "1 1 1.1 1.2 1.11 1.22\n", 162 | "2 2 1.1 1.2 1.11 1.22" 163 | ] 164 | }, 165 | "execution_count": 2, 166 | "metadata": {}, 167 | "output_type": "execute_result" 168 | } 169 | ], 170 | "source": [ 171 | "df = pd.DataFrame({'row': [0, 1, 2],\n", 172 | " 'One_X': [1.1, 1.1, 1.1],\n", 173 | " 'One_Y': [1.2, 1.2, 1.2],\n", 174 | " 'Two_X': [1.11, 1.11, 1.11],\n", 175 | " 'Two_Y': [1.22, 1.22, 1.22]})\n", 176 | "df" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 3, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/html": [ 187 | "
      \n", 188 | "\n", 201 | "\n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | "
      One_XOne_YTwo_XTwo_Y
      row
      01.11.21.111.22
      11.11.21.111.22
      21.11.21.111.22
      \n", 242 | "
      " 243 | ], 244 | "text/plain": [ 245 | " One_X One_Y Two_X Two_Y\n", 246 | "row \n", 247 | "0 1.1 1.2 1.11 1.22\n", 248 | "1 1.1 1.2 1.11 1.22\n", 249 | "2 1.1 1.2 1.11 1.22" 250 | ] 251 | }, 252 | "execution_count": 3, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "df = df.set_index('row'); df" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 4, 264 | "metadata": {}, 265 | "outputs": [ 266 | { 267 | "data": { 268 | "text/html": [ 269 | "
      \n", 270 | "\n", 287 | "\n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | "
      OneTwo
      XYXY
      row
      01.11.21.111.22
      11.11.21.111.22
      21.11.21.111.22
      \n", 333 | "
      " 334 | ], 335 | "text/plain": [ 336 | " One Two \n", 337 | " X Y X Y\n", 338 | "row \n", 339 | "0 1.1 1.2 1.11 1.22\n", 340 | "1 1.1 1.2 1.11 1.22\n", 341 | "2 1.1 1.2 1.11 1.22" 342 | ] 343 | }, 344 | "execution_count": 4, 345 | "metadata": {}, 346 | "output_type": "execute_result" 347 | } 348 | ], 349 | "source": [ 350 | "df.columns = pd.MultiIndex.from_tuples([tuple(c.split('_'))\n", 351 | " for c in df.columns])\n", 352 | "\n", 353 | "df " 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": 5, 359 | "metadata": {}, 360 | "outputs": [ 361 | { 362 | "data": { 363 | "text/html": [ 364 | "
      \n", 365 | "\n", 378 | "\n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | "
      level_1XY
      row
      0One1.101.20
      0Two1.111.22
      1One1.101.20
      1Two1.111.22
      2One1.101.20
      2Two1.111.22
      \n", 432 | "
      " 433 | ], 434 | "text/plain": [ 435 | " level_1 X Y\n", 436 | "row \n", 437 | "0 One 1.10 1.20\n", 438 | "0 Two 1.11 1.22\n", 439 | "1 One 1.10 1.20\n", 440 | "1 Two 1.11 1.22\n", 441 | "2 One 1.10 1.20\n", 442 | "2 Two 1.11 1.22" 443 | ] 444 | }, 445 | "execution_count": 5, 446 | "metadata": {}, 447 | "output_type": "execute_result" 448 | } 449 | ], 450 | "source": [ 451 | "df = df.stack(0).reset_index(1); df" 452 | ] 453 | }, 454 | { 455 | "cell_type": "code", 456 | "execution_count": 6, 457 | "metadata": {}, 458 | "outputs": [ 459 | { 460 | "data": { 461 | "text/html": [ 462 | "
      \n", 463 | "\n", 476 | "\n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | "
      SampleAll_XAll_Y
      row
      0One1.101.20
      0Two1.111.22
      1One1.101.20
      1Two1.111.22
      2One1.101.20
      2Two1.111.22
      \n", 530 | "
      " 531 | ], 532 | "text/plain": [ 533 | " Sample All_X All_Y\n", 534 | "row \n", 535 | "0 One 1.10 1.20\n", 536 | "0 Two 1.11 1.22\n", 537 | "1 One 1.10 1.20\n", 538 | "1 Two 1.11 1.22\n", 539 | "2 One 1.10 1.20\n", 540 | "2 Two 1.11 1.22" 541 | ] 542 | }, 543 | "execution_count": 6, 544 | "metadata": {}, 545 | "output_type": "execute_result" 546 | } 547 | ], 548 | "source": [ 549 | "df.columns = ['Sample', 'All_X', 'All_Y']; df" 550 | ] 551 | }, 552 | { 553 | "cell_type": "code", 554 | "execution_count": 7, 555 | "metadata": {}, 556 | "outputs": [ 557 | { 558 | "data": { 559 | "text/html": [ 560 | "
      \n", 561 | "\n", 574 | "\n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | "
      ABC
      OIOIOI
      n1.7640520.4001570.9787382.2408931.867558-0.977278
      m0.950088-0.151357-0.1032190.4105990.1440441.454274
      \n", 613 | "
      " 614 | ], 615 | "text/plain": [ 616 | " A B C \n", 617 | " O I O I O I\n", 618 | "n 1.764052 0.400157 0.978738 2.240893 1.867558 -0.977278\n", 619 | "m 0.950088 -0.151357 -0.103219 0.410599 0.144044 1.454274" 620 | ] 621 | }, 622 | "execution_count": 7, 623 | "metadata": {}, 624 | "output_type": "execute_result" 625 | } 626 | ], 627 | "source": [ 628 | "cols = pd.MultiIndex.from_tuples([(x, y) for x in ['A', 'B', 'C']\n", 629 | " for y in ['O', 'I']])\n", 630 | "\n", 631 | "df = pd.DataFrame(np.random.randn(2, 6), index=['n', 'm'], columns=cols)\n", 632 | "df" 633 | ] 634 | }, 635 | { 636 | "cell_type": "code", 637 | "execution_count": 8, 638 | "metadata": {}, 639 | "outputs": [ 640 | { 641 | "data": { 642 | "text/html": [ 643 | "
      \n", 644 | "\n", 657 | "\n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | "
      ABC
      OIOIOI
      n0.944577-0.4094610.524074-2.2929951.01.0
      m6.595840-0.104078-0.7165810.2823391.01.0
      \n", 696 | "
      " 697 | ], 698 | "text/plain": [ 699 | " A B C \n", 700 | " O I O I O I\n", 701 | "n 0.944577 -0.409461 0.524074 -2.292995 1.0 1.0\n", 702 | "m 6.595840 -0.104078 -0.716581 0.282339 1.0 1.0" 703 | ] 704 | }, 705 | "execution_count": 8, 706 | "metadata": {}, 707 | "output_type": "execute_result" 708 | } 709 | ], 710 | "source": [ 711 | "df = df.div(df['C'], level=1); df" 712 | ] 713 | }, 714 | { 715 | "cell_type": "code", 716 | "execution_count": 9, 717 | "metadata": {}, 718 | "outputs": [ 719 | { 720 | "data": { 721 | "text/html": [ 722 | "
      \n", 723 | "\n", 736 | "\n", 737 | " \n", 738 | " \n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | "
      MyData
      AAone11
      six22
      BBone33
      two44
      six55
      \n", 769 | "
      " 770 | ], 771 | "text/plain": [ 772 | " MyData\n", 773 | "AA one 11\n", 774 | " six 22\n", 775 | "BB one 33\n", 776 | " two 44\n", 777 | " six 55" 778 | ] 779 | }, 780 | "execution_count": 9, 781 | "metadata": {}, 782 | "output_type": "execute_result" 783 | } 784 | ], 785 | "source": [ 786 | "coords = [('AA', 'one'), ('AA', 'six'), ('BB', 'one'), ('BB', 'two'),\n", 787 | " ('BB', 'six')]\n", 788 | "\n", 789 | "index = pd.MultiIndex.from_tuples(coords)\n", 790 | "\n", 791 | "df = pd.DataFrame([11, 22, 33, 44, 55], index, ['MyData']); df" 792 | ] 793 | }, 794 | { 795 | "cell_type": "code", 796 | "execution_count": 10, 797 | "metadata": {}, 798 | "outputs": [ 799 | { 800 | "data": { 801 | "text/html": [ 802 | "
      \n", 803 | "\n", 820 | "\n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | "
      ExamsLabs
      IIIIII
      StudentCourse
      AdaComp70717273
      Math71737574
      Sci72757575
      QuinnComp73747576
      Math74767877
      Sci75787878
      VioletComp76777879
      Math77798180
      Sci78818181
      \n", 914 | "
      " 915 | ], 916 | "text/plain": [ 917 | " Exams Labs \n", 918 | " I II I II\n", 919 | "Student Course \n", 920 | "Ada Comp 70 71 72 73\n", 921 | " Math 71 73 75 74\n", 922 | " Sci 72 75 75 75\n", 923 | "Quinn Comp 73 74 75 76\n", 924 | " Math 74 76 78 77\n", 925 | " Sci 75 78 78 78\n", 926 | "Violet Comp 76 77 78 79\n", 927 | " Math 77 79 81 80\n", 928 | " Sci 78 81 81 81" 929 | ] 930 | }, 931 | "execution_count": 10, 932 | "metadata": {}, 933 | "output_type": "execute_result" 934 | } 935 | ], 936 | "source": [ 937 | "import itertools\n", 938 | "\n", 939 | "index = list(itertools.product(['Ada', 'Quinn', 'Violet'],\n", 940 | " ['Comp', 'Math', 'Sci']))\n", 941 | "\n", 942 | "headr = list(itertools.product(['Exams', 'Labs'], ['I', 'II']))\n", 943 | "\n", 944 | "indx = pd.MultiIndex.from_tuples(index, names=['Student', 'Course'])\n", 945 | "\n", 946 | "cols = pd.MultiIndex.from_tuples(headr) # Notice these are un-named\n", 947 | "\n", 948 | "data = [[70 + x + y + (x * y) % 3 for x in range(4)] for y in range(9)]\n", 949 | "\n", 950 | "df = pd.DataFrame(data, indx, cols); df\n" 951 | ] 952 | }, 953 | { 954 | "cell_type": "code", 955 | "execution_count": null, 956 | "metadata": {}, 957 | "outputs": [], 958 | "source": [] 959 | } 960 | ], 961 | "metadata": { 962 | "hide_input": false, 963 | "kernelspec": { 964 | "display_name": "Python 3", 965 | "language": "python", 966 | "name": "python3" 967 | }, 968 | "language_info": { 969 | "codemirror_mode": { 970 | "name": "ipython", 971 | "version": 3 972 | }, 973 | "file_extension": ".py", 974 | "mimetype": "text/x-python", 975 | "name": "python", 976 | "nbconvert_exporter": "python", 977 | "pygments_lexer": "ipython3", 978 | "version": "3.7.3" 979 | } 980 | }, 981 | "nbformat": 4, 982 | "nbformat_minor": 2 983 | } 984 | --------------------------------------------------------------------------------